(git:f7b9015)
Loading...
Searching...
No Matches
cell_manifold_utils.F
Go to the documentation of this file.
1!--------------------------------------------------------------------------------------------------!
2! CP2K: A general program to perform molecular dynamics simulations !
3! Copyright 2000-2026 CP2K developers group <https://cp2k.org> !
4! !
5! SPDX-License-Identifier: GPL-2.0-or-later !
6!--------------------------------------------------------------------------------------------------!
7
8! **************************************************************************************************
9!> \brief Reduced-coordinate parameterization of cell constraints for CELL_OPT.
10!> \details
11!> This pure-math module depends only on kinds, ieee_arithmetic, and LAPACK.
12!> It has no force_env, cell_type, or input dependencies.
13!>
14!> Canonical cell vector:
15!> CP2K places lattice vector a along x and b in the xy plane:
16!> hmat(:, 1) = (AX, 0, 0)
17!> hmat(:, 2) = (BX, BY, 0)
18!> hmat(:, 3) = (CX, CY, CZ)
19!> The 6 upper-triangular entries form q = (AX, BX, BY, CX, CY, CZ).
20!> The 7 geometric quantities are s(q) = (a, b, c, alpha, beta, gamma, V),
21!> with angles in radians and volume V = AX * BY * CZ.
22!>
23!> Constraint equations and reduced coordinates:
24!> Active constraints define k equations r(q) = 0 with k x 6 Jacobian A(q) = dr/dq:
25!> 1. Fixed cell components: q_i - q0_i = 0.
26!> 2. Fixed geometric quantities: s_m(q) - s_m(q0) = 0.
27!> 3. Equal lengths or angles: s_m(q) - s_n(q) = 0 (for example a = b).
28!> 4. Linear relations: w^T (q - q0) = 0 (for example AX = BY).
29!>
30!> At the initial cell q0, QR factorization splits R^6 into two fixed
31!> orthonormal bases:
32!> - Y (6 x k) spans the constrained directions row(A(q0)).
33!> - Z (6 x ndof, ndof = 6 - k) spans the free directions ker(A(q0)).
34!>
35!> The optimizer works only on the reduced vector u of length ndof:
36!> - Forward map (chart_eval): q(u) = q0 + Z (u - u0) + Y w(u).
37!> Newton's method solves the k x k system r(q(u)) = 0 for w(u).
38!> Linear constraints satisfy r = 0 in zero Newton iterations.
39!> - Inverse map (chart_inv): u = u0 + Z^T (q - q0), because Z^T Y = 0.
40!> - Reduced gradient (chart_pullback_grad): g_u = (dq/du)^T g_q.
41!> Implicit differentiation gives g_u = Z^T (g_q - A(q)^T lam), where
42!> (A(q) Y)^T lam = Y^T g_q.
43!> - Unconstrained case (k = 0): Z = I and u0 = q0, so u = q and g_u = g_q
44!> with zero floating-point roundoff.
45!>
46!> Sign convention: get_dg_dh returns g_q = +d(E + pres_ext*V)/dq.
47!> Routine cell_manifold_proj_pressure maps g_q to pdiff = P_constr - pres_ext.
48! **************************************************************************************************
50
51 USE ieee_arithmetic, ONLY: ieee_is_finite
52 USE kinds, ONLY: default_string_length,&
53 dp
54
55 IMPLICIT NONE
56 PRIVATE
57
58 ! Indices of q = (AX, BX, BY, CX, CY, CZ) and s = (a, b, c, alpha, beta, gamma, V).
59 INTEGER, PARAMETER, PUBLIC :: n_slot = 6, n_geom = 7
60 INTEGER, PARAMETER, PUBLIC :: slot_ax = 1, &
61 slot_bx = 2, &
62 slot_by = 3, &
63 slot_cx = 4, &
64 slot_cy = 5, &
65 slot_cz = 6
66 INTEGER, PARAMETER, PUBLIC :: geom_a = 1, &
67 geom_b = 2, &
68 geom_c = 3, &
69 geom_alpha = 4, &
70 geom_beta = 5, &
71 geom_gamma = 6, &
72 geom_v = 7
73
74 ! Bit flags for fixed canonical components and direction/vector shorthands.
75 INTEGER, PARAMETER, PUBLIC :: comp_ax = 2**0, &
76 comp_bx = 2**1, &
77 comp_by = 2**2, &
78 comp_cx = 2**3, &
79 comp_cy = 2**4, &
80 comp_cz = 2**5
92
93 ! Bit flags that fix s(geom_*) to its initial value s(q0).
94 ! cell_opt_types sets these internal bits for KEEP_ANGLES and KEEP_VOLUME.
95 ! The CONSTRAINT parser does not expose them.
96 INTEGER, PARAMETER, PUBLIC :: gfix_a = 2**6, &
97 gfix_b = 2**7, &
98 gfix_c = 2**8, &
99 gfix_alpha = 2**9, &
100 gfix_beta = 2**10, &
101 gfix_gamma = 2**11, &
102 gfix_v = 2**12
103 INTEGER, PARAMETER, PUBLIC :: gfix_all = gfix_a + gfix_b + gfix_c + gfix_alpha + &
105 INTEGER, PARAMETER, PUBLIC :: n_constraint_bits = n_slot + n_geom
106
107 ! max_cell_coord keeps AX*BY*CZ well below HUGE(1.0_dp) (~1.8E308) so volume
108 ! evaluation cannot trap under -ffpe-trap=overflow.
109 REAL(kind=dp), PARAMETER, PUBLIC :: min_cell_length = 1.0e-8_dp
110 REAL(kind=dp), PARAMETER, PRIVATE :: max_cell_coord = 1.0e90_dp
111 REAL(kind=dp), PARAMETER, PRIVATE :: collinear_cos_margin = 1.0e-8_dp
112 REAL(kind=dp), PARAMETER, PRIVATE :: licq_pivot_tol = 1.0e-6_dp
113 REAL(kind=dp), PARAMETER, PRIVATE :: basis_tie_rel_tol = 1.0e-8_dp
114 REAL(kind=dp), PARAMETER, PRIVATE :: default_chart_tol = 1.0e-12_dp
115 INTEGER, PARAMETER, PRIVATE :: default_chart_max_iter = 100
116 REAL(kind=dp), PARAMETER, PRIVATE :: proj_pressure_rel_tol = 1.0e-12_dp
117 REAL(kind=dp), PARAMETER, PRIVATE :: redundancy_probe_frac = 5.0e-2_dp
118 REAL(kind=dp), PARAMETER, PRIVATE :: redundancy_chart_tol = 1.0e-13_dp
119 REAL(kind=dp), PARAMETER, PRIVATE :: redundancy_rel_tol = 1.0e-9_dp
120 REAL(kind=dp), PARAMETER, PRIVATE :: redundancy_drift_rel_tol = 1.0e-10_dp
121 REAL(kind=dp), PARAMETER, PRIVATE :: redundancy_curv_rel_tol = 5.0e-12_dp
122 REAL(kind=dp), PARAMETER, PRIVATE :: semidef_fd_step = 1.0e-3_dp
123 REAL(kind=dp), PARAMETER, PRIVATE :: semidef_fd_shrink = 0.25_dp
124 INTEGER, PARAMETER, PRIVATE :: semidef_fd_max_tries = 6
125 REAL(kind=dp), PARAMETER, PRIVATE :: semidef_lam_rel_tol = 1.0e-11_dp
126 REAL(kind=dp), PARAMETER, PRIVATE :: semidef_min_curv = 1.0e-8_dp
127 REAL(kind=dp), PARAMETER, PRIVATE :: semidef_eig_rel_tol = 1.0e-5_dp
128 REAL(kind=dp), PARAMETER, PRIVATE :: semidef_vertex_rel_tol = 1.0e-3_dp
129 REAL(kind=dp), PARAMETER, PRIVATE :: semidef_fd_noise_floor = 1.0e-11_dp
130 REAL(kind=dp), PARAMETER, PRIVATE :: semidef_vec_zero_tol = 1.0e-9_dp
131 REAL(kind=dp), PARAMETER, PRIVATE :: semidef_max_vec_tol = 1.0e-4_dp
132
133 INTEGER, PARAMETER, PRIVATE :: comp_flags(n_slot) = [comp_ax, comp_bx, comp_by, comp_cx, &
135
136 ! Status codes (negative values indicate an error).
137 INTEGER, PARAMETER, PUBLIC :: info_ok = 0
138 INTEGER, PARAMETER, PUBLIC :: info_degenerate = -1
139 INTEGER, PARAMETER, PUBLIC :: info_licq = -2
140 INTEGER, PARAMETER, PUBLIC :: info_no_converge = -3
141 INTEGER, PARAMETER, PUBLIC :: info_bad_input = -4
142
143 !> \brief Active constraint set and fixed coordinate bases anchored at q0.
144 TYPE, PUBLIC :: cell_manifold_type
145 INTEGER :: mask = 0
146 REAL(kind=dp) :: q0(n_slot) = 0.0_dp
147 REAL(kind=dp) :: s0(n_geom) = 0.0_dp
148 INTEGER :: k = 0
149 INTEGER :: ndof = n_slot
150 INTEGER :: slot_rows(n_slot) = 0
151 INTEGER :: n_slot_rows = 0
152 INTEGER :: geom_rows(n_geom) = 0
153 INTEGER :: n_geom_rows = 0
154 ! Column i holds the unit normal w_i of the linear constraint w_i^T (q - q0) = 0.
155 REAL(kind=dp) :: lin_w(n_slot, n_slot) = 0.0_dp
156 INTEGER :: n_lin_rows = 0
157 ! Column i holds a pair [m, n] in 1..6 for the equality s_m(q) - s_n(q) = 0.
158 INTEGER :: eq_pair(2, n_geom) = 0
159 INTEGER :: n_eq_rows = 0
160 ! Fixed bases set once at init: Y(:, 1:k) spans row(A(q0)),
161 ! Z(:, 1:ndof) spans ker(A(q0)), and u0(1:ndof) = Z(:, 1:ndof)^T q0.
162 REAL(kind=dp) :: y(n_slot, n_slot) = 0.0_dp
163 REAL(kind=dp) :: z(n_slot, n_slot) = 0.0_dp
164 REAL(kind=dp) :: u0(n_slot) = 0.0_dp
165 REAL(kind=dp) :: tol = default_chart_tol
166 INTEGER :: max_iter = default_chart_max_iter
167 INTEGER :: last_iter = 0
168 REAL(kind=dp) :: last_resid = 0.0_dp
169 END TYPE cell_manifold_type
170
171 PUBLIC :: cell_manifold_init, &
185
186 EXTERNAL :: dgeqrf, dgetrf, dgetrs, dorgqr, dpotrf, dpotrs, dsyev
187
188CONTAINS
189
190! **************************************************************************************************
191!> \brief Record constraint targets at q0, check row independence, and build [Y, Z].
192!> \param m initialized constraint state
193!> \param q0 initial canonical cell components
194!> \param mask bit union of comp_* and gfix_* flags
195!> \param info info_ok, info_degenerate, info_licq, or info_bad_input
196!> \param lin_w optional 6 x n_lin linear coupling weights, one constraint per column
197!> \param eq_pair optional 2 x n_eq index pairs [m, n] in 1..6 for s_m(q) = s_n(q)
198! **************************************************************************************************
199 SUBROUTINE cell_manifold_init(m, q0, mask, info, lin_w, eq_pair)
200 TYPE(cell_manifold_type), INTENT(OUT) :: m
201 REAL(kind=dp), INTENT(IN) :: q0(n_slot)
202 INTEGER, INTENT(IN) :: mask
203 INTEGER, INTENT(OUT) :: info
204 REAL(kind=dp), INTENT(IN), OPTIONAL :: lin_w(:, :)
205 INTEGER, INTENT(IN), OPTIONAL :: eq_pair(:, :)
206
207 INTEGER, PARAMETER :: &
209
210 INTEGER :: i, n_eq, n_lin
211 REAL(kind=dp) :: weight_norm
212
213 m%mask = mask
214 m%q0 = q0
215
216 CALL cell_manifold_geometry(q0, m%s0, info)
217 IF (info /= info_ok) RETURN
218
219 IF (iand(mask, not(mask_all + gfix_all)) /= 0) THEN
220 info = info_bad_input
221 RETURN
222 END IF
223
224 DO i = 1, n_slot
225 IF (iand(mask, comp_flags(i)) /= 0) THEN
226 m%n_slot_rows = m%n_slot_rows + 1
227 m%slot_rows(m%n_slot_rows) = i
228 END IF
229 END DO
230 DO i = 1, n_geom
231 IF (iand(mask, gfix_flags(i)) /= 0) THEN
232 m%n_geom_rows = m%n_geom_rows + 1
233 m%geom_rows(m%n_geom_rows) = i
234 END IF
235 END DO
236
237 IF (PRESENT(lin_w)) THEN
238 n_lin = SIZE(lin_w, 2)
239 IF (SIZE(lin_w, 1) /= n_slot .OR. n_lin > n_slot) THEN
240 info = info_bad_input
241 RETURN
242 END IF
243 DO i = 1, n_lin
244 ! Normalize each weight vector so residual checks do not depend on caller scaling.
245 weight_norm = norm2(lin_w(:, i))
246 IF (weight_norm <= tiny(1.0_dp)) THEN
247 info = info_bad_input
248 RETURN
249 END IF
250 m%lin_w(:, i) = lin_w(:, i)/weight_norm
251 END DO
252 m%n_lin_rows = n_lin
253 END IF
254
255 IF (PRESENT(eq_pair)) THEN
256 n_eq = SIZE(eq_pair, 2)
257 IF (SIZE(eq_pair, 1) /= 2 .OR. n_eq > n_geom - 1) THEN
258 info = info_bad_input
259 RETURN
260 END IF
261 DO i = 1, n_eq
262 IF (any(eq_pair(:, i) < geom_a) .OR. any(eq_pair(:, i) > geom_gamma) .OR. &
263 eq_pair(1, i) == eq_pair(2, i)) THEN
264 info = info_bad_input
265 RETURN
266 END IF
267 m%eq_pair(:, i) = eq_pair(:, i)
268 END DO
269 m%n_eq_rows = n_eq
270 END IF
271
272 m%k = m%n_slot_rows + m%n_geom_rows + m%n_lin_rows + m%n_eq_rows
273 IF (m%k > n_slot) THEN
274 info = info_licq
275 RETURN
276 END IF
277 m%ndof = n_slot - m%k
278
279 CALL build_chart_basis(m, info)
280
281 END SUBROUTINE cell_manifold_init
282
283! **************************************************************************************************
284!> \brief Compute cell lengths, angles in radians, and volume s(q).
285!> \param q canonical cell components (AX, BX, BY, CX, CY, CZ)
286!> \param s geometric quantities (a, b, c, alpha, beta, gamma, V)
287!> \param info info_ok or info_degenerate
288! **************************************************************************************************
289 SUBROUTINE cell_manifold_geometry(q, s, info)
290 REAL(kind=dp), INTENT(IN) :: q(n_slot)
291 REAL(kind=dp), INTENT(OUT) :: s(n_geom)
292 INTEGER, INTENT(OUT) :: info
293
294 REAL(kind=dp) :: ax, bx, by, cos_ang(3), cx, cy, cz, &
295 vec_a(3), vec_b(3), vec_c(3)
296
297 s = 0.0_dp
298 IF (.NOT. all(ieee_is_finite(q))) THEN
299 info = info_degenerate
300 RETURN
301 END IF
302 IF (any(abs(q) > max_cell_coord)) THEN
303 info = info_degenerate
304 RETURN
305 END IF
306
307 ax = q(slot_ax)
308 bx = q(slot_bx)
309 by = q(slot_by)
310 cx = q(slot_cx)
311 cy = q(slot_cy)
312 cz = q(slot_cz)
313
314 ! Positive diagonal entries ensure V = AX*BY*CZ > 0 and right-handed cell vectors.
315 IF (min(ax, by, cz) < min_cell_length) THEN
316 info = info_degenerate
317 RETURN
318 END IF
319
320 vec_a = [ax, 0.0_dp, 0.0_dp]
321 vec_b = [bx, by, 0.0_dp]
322 vec_c = [cx, cy, cz]
323 s(geom_a) = norm2(vec_a)
324 s(geom_b) = norm2(vec_b)
325 s(geom_c) = norm2(vec_c)
326 cos_ang(1) = dot_product(vec_b, vec_c)/(s(geom_b)*s(geom_c))
327 cos_ang(2) = dot_product(vec_a, vec_c)/(s(geom_a)*s(geom_c))
328 cos_ang(3) = dot_product(vec_a, vec_b)/(s(geom_a)*s(geom_b))
329 IF (any(abs(cos_ang) > 1.0_dp - collinear_cos_margin)) THEN
330 info = info_degenerate
331 s = 0.0_dp
332 RETURN
333 END IF
334 s(geom_alpha:geom_gamma) = acos(min(1.0_dp, max(-1.0_dp, cos_ang)))
335 s(geom_v) = ax*by*cz
336 info = info_ok
337
338 END SUBROUTINE cell_manifold_geometry
339
340! **************************************************************************************************
341!> \brief Compute the analytic 7 x 6 Jacobian J = ds/dq of the geometric quantities.
342!> \param q canonical cell components (AX, BX, BY, CX, CY, CZ)
343!> \param J Jacobian matrix ds/dq
344!> \param info info_ok or info_degenerate
345! **************************************************************************************************
346 SUBROUTINE cell_manifold_jacobian(q, J, info)
347 REAL(kind=dp), INTENT(IN) :: q(n_slot)
348 REAL(kind=dp), INTENT(OUT) :: j(n_geom, n_slot)
349 INTEGER, INTENT(OUT) :: info
350
351 REAL(kind=dp) :: ax, bx, by, cos_ang(3), cx, cy, cz, db_dq(n_slot), dc_dq(n_slot), &
352 ddot_dq(n_slot), len_a, len_b, len_c, s(n_geom), sin_ang(3)
353
354 j = 0.0_dp
355 CALL cell_manifold_geometry(q, s, info)
356 IF (info /= info_ok) RETURN
357
358 ax = q(slot_ax)
359 bx = q(slot_bx)
360 by = q(slot_by)
361 cx = q(slot_cx)
362 cy = q(slot_cy)
363 cz = q(slot_cz)
364 len_a = s(geom_a)
365 len_b = s(geom_b)
366 len_c = s(geom_c)
367
368 j(geom_a, slot_ax) = sign(1.0_dp, ax)
369
370 db_dq = [0.0_dp, bx/len_b, by/len_b, 0.0_dp, 0.0_dp, 0.0_dp]
371 j(geom_b, :) = db_dq
372
373 dc_dq = [0.0_dp, 0.0_dp, 0.0_dp, cx/len_c, cy/len_c, cz/len_c]
374 j(geom_c, :) = dc_dq
375
376 cos_ang = [dot_product([bx, by, 0.0_dp], [cx, cy, cz])/(len_b*len_c), &
377 (ax*cx)/(len_a*len_c), &
378 (ax*bx)/(len_a*len_b)]
379 cos_ang = min(1.0_dp, max(-1.0_dp, cos_ang))
380 sin_ang = sin(s(geom_alpha:geom_gamma))
381
382 ! alpha = angle(b, c), with b.c = BX*CX + BY*CY.
383 ddot_dq = [0.0_dp, cx, cy, bx, by, 0.0_dp]
384 j(geom_alpha, :) = angle_grad_row(cos_ang(1), sin_ang(1), ddot_dq, len_b, len_c, db_dq, dc_dq)
385
386 ! Since a = (AX, 0, 0) with AX > 0, cos(beta) = CX/|c| does not depend on AX.
387 ! Force exact zero to avoid floating-point cancellation noise in d(beta)/d(AX).
388 ddot_dq = [cx, 0.0_dp, 0.0_dp, ax, 0.0_dp, 0.0_dp]
389 j(geom_beta, :) = angle_grad_row(cos_ang(2), sin_ang(2), ddot_dq, len_a, len_c, &
390 j(geom_a, :), dc_dq)
391 j(geom_beta, slot_ax) = 0.0_dp
392
393 ! Likewise, cos(gamma) = BX/|b| does not depend on AX when AX > 0.
394 ddot_dq = [bx, ax, 0.0_dp, 0.0_dp, 0.0_dp, 0.0_dp]
395 j(geom_gamma, :) = angle_grad_row(cos_ang(3), sin_ang(3), ddot_dq, len_a, len_b, &
396 j(geom_a, :), db_dq)
397 j(geom_gamma, slot_ax) = 0.0_dp
398
399 j(geom_v, :) = [by*cz, 0.0_dp, ax*cz, 0.0_dp, 0.0_dp, ax*by]
400
401 END SUBROUTINE cell_manifold_jacobian
402
403! **************************************************************************************************
404!> \brief Evaluate the k active constraint residuals r(q).
405!> \param m constraint state
406!> \param q canonical cell vector
407!> \param r residual vector of length m%k (zero when q satisfies all constraints)
408!> \param info info_ok or info_degenerate
409! **************************************************************************************************
410 SUBROUTINE cell_manifold_residual(m, q, r, info)
411 TYPE(cell_manifold_type), INTENT(IN) :: m
412 REAL(kind=dp), INTENT(IN) :: q(n_slot)
413 REAL(kind=dp), INTENT(OUT) :: r(m%k)
414 INTEGER, INTENT(OUT) :: info
415
416 INTEGER :: i, row
417 REAL(kind=dp) :: s(n_geom)
418
419 r = 0.0_dp
420 info = info_ok
421 IF (.NOT. all(ieee_is_finite(q))) THEN
422 info = info_degenerate
423 RETURN
424 END IF
425 IF (m%k == 0) RETURN
426
427 ! Check cell validity even when all active rows are linear so collapsed
428 ! or inverted cells cannot pass unnoticed.
429 CALL cell_manifold_geometry(q, s, info)
430 IF (info /= info_ok) RETURN
431
432 DO i = 1, m%n_slot_rows
433 r(i) = q(m%slot_rows(i)) - m%q0(m%slot_rows(i))
434 END DO
435 DO i = 1, m%n_geom_rows
436 row = m%n_slot_rows + i
437 r(row) = s(m%geom_rows(i)) - m%s0(m%geom_rows(i))
438 END DO
439 DO i = 1, m%n_lin_rows
440 row = m%n_slot_rows + m%n_geom_rows + i
441 r(row) = dot_product(m%lin_w(:, i), q) - dot_product(m%lin_w(:, i), m%q0)
442 END DO
443 DO i = 1, m%n_eq_rows
444 row = m%n_slot_rows + m%n_geom_rows + m%n_lin_rows + i
445 r(row) = s(m%eq_pair(1, i)) - s(m%eq_pair(2, i))
446 END DO
447
448 END SUBROUTINE cell_manifold_residual
449
450! **************************************************************************************************
451!> \brief Assemble the k x 6 Jacobian A(q) = dr/dq of the active constraints.
452!> \param m constraint state
453!> \param q canonical cell vector
454!> \param A active constraint Jacobian in the same row order as cell_manifold_residual
455!> \param info info_ok or info_degenerate
456! **************************************************************************************************
457 SUBROUTINE cell_manifold_active_jacobian(m, q, A, info)
458 TYPE(cell_manifold_type), INTENT(IN) :: m
459 REAL(kind=dp), INTENT(IN) :: q(n_slot)
460 REAL(kind=dp), INTENT(OUT) :: a(m%k, n_slot)
461 INTEGER, INTENT(OUT) :: info
462
463 INTEGER :: i, row
464 REAL(kind=dp) :: j(n_geom, n_slot)
465
466 a = 0.0_dp
467 info = info_ok
468 IF (m%k == 0) RETURN
469
470 CALL cell_manifold_jacobian(q, j, info)
471 IF (info /= info_ok) RETURN
472
473 DO i = 1, m%n_slot_rows
474 a(i, m%slot_rows(i)) = 1.0_dp
475 END DO
476 DO i = 1, m%n_geom_rows
477 row = m%n_slot_rows + i
478 a(row, :) = j(m%geom_rows(i), :)
479 END DO
480 DO i = 1, m%n_lin_rows
481 row = m%n_slot_rows + m%n_geom_rows + i
482 a(row, :) = m%lin_w(:, i)
483 END DO
484 DO i = 1, m%n_eq_rows
485 row = m%n_slot_rows + m%n_geom_rows + m%n_lin_rows + i
486 a(row, :) = j(m%eq_pair(1, i), :) - j(m%eq_pair(2, i), :)
487 END DO
488
489 END SUBROUTINE cell_manifold_active_jacobian
490
491! **************************************************************************************************
492!> \brief Map reduced coordinates u to a canonical cell q(u) that satisfies r(q) = 0.
493!> \param m constraint state. This routine updates only last_iter and last_resid.
494!> \param u reduced coordinates of length m%ndof
495!> \param q canonical cell vector on the constraint surface
496!> \param info info_ok, info_degenerate, info_licq, or info_no_converge
497!> \note For qt = q0 + Z (u - u0), Newton's method solves r(qt + Y w) = 0 for w.
498!> Linear constraints satisfy r(qt) = 0 in zero iterations. When k = 0, q
499!> copies u with zero roundoff.
500! **************************************************************************************************
501 SUBROUTINE cell_manifold_chart_eval(m, u, q, info)
502 TYPE(cell_manifold_type), INTENT(INOUT) :: m
503 REAL(kind=dp), INTENT(IN) :: u(m%ndof)
504 REAL(kind=dp), INTENT(OUT) :: q(n_slot)
505 INTEGER, INTENT(OUT) :: info
506
507 INTEGER :: it, lapack_info
508 REAL(kind=dp) :: qt(n_slot), rn_rel, s_chk(n_geom), &
509 row_scales(m%k), row_norms(m%k), &
510 r(m%k), dw(m%k), b(m%k, m%k), &
511 a(m%k, n_slot)
512 INTEGER :: ipiv(m%k)
513
514 ! Separate checks avoid 0*Inf and NaN traps under -ffpe-trap.
515 IF (.NOT. all(ieee_is_finite(u))) THEN
516 q = m%q0
517 m%last_iter = 0
518 m%last_resid = max_cell_coord
519 info = info_no_converge
520 RETURN
521 END IF
522 IF (any(abs(u) > max_cell_coord)) THEN
523 q = m%q0
524 m%last_iter = 0
525 m%last_resid = max_cell_coord
526 info = info_no_converge
527 RETURN
528 END IF
529
530 m%last_iter = 0
531 m%last_resid = 0.0_dp
532 info = info_ok
533
534 IF (m%ndof == 0) THEN
535 q = m%q0
536 RETURN
537 END IF
538
539 IF (m%k == 0) THEN
540 q = u
541 CALL cell_manifold_geometry(q, s_chk, info)
542 RETURN
543 END IF
544
545 qt = m%q0 + matmul(m%Z(:, 1:m%ndof), u - m%u0(1:m%ndof))
546 row_scales = active_row_scales(m)
547
548 CALL cell_manifold_residual(m, qt, r, info)
549 IF (info /= info_ok) THEN
550 q = qt
551 m%last_resid = max_cell_coord
552 RETURN
553 END IF
554 m%last_resid = norm2(r)
555 rn_rel = norm2(r/row_scales)
556
557 DO it = 1, m%max_iter
558 IF (rn_rel <= m%tol) EXIT
559 CALL normalized_active_jacobian(m, qt, a, row_norms, info)
560 IF (info /= info_ok) EXIT
561 CALL factor_normal_block(m%k, a, m%Y(:, 1:m%k), b, ipiv, info)
562 IF (info /= info_ok) EXIT
563 dw = r/row_norms
564 CALL dgetrs('N', m%k, 1, b, m%k, ipiv, dw, m%k, lapack_info)
565 IF (lapack_info /= 0) THEN
566 info = info_licq
567 EXIT
568 END IF
569 qt = qt - matmul(m%Y(:, 1:m%k), dw)
570 m%last_iter = it
571 CALL cell_manifold_residual(m, qt, r, info)
572 IF (info /= info_ok) THEN
573 m%last_resid = max_cell_coord
574 EXIT
575 END IF
576 m%last_resid = norm2(r)
577 rn_rel = norm2(r/row_scales)
578 IF (.NOT. ieee_is_finite(m%last_resid) .OR. .NOT. ieee_is_finite(rn_rel)) THEN
579 info = info_no_converge
580 EXIT
581 END IF
582 END DO
583 q = qt
584 IF (info == info_ok .AND. rn_rel > m%tol) info = info_no_converge
585
586 END SUBROUTINE cell_manifold_chart_eval
587
588! **************************************************************************************************
589!> \brief Map a canonical cell q back to reduced coordinates u = u0 + Z^T (q - q0).
590!> \param m constraint state
591!> \param q canonical cell vector
592!> \param u reduced coordinates of length m%ndof
593!> \note Because Z^T Z = I and Z^T Y = 0, this affine projection is the exact
594!> inverse of cell_manifold_chart_eval. When k = 0, u is a direct copy of q.
595! **************************************************************************************************
596 SUBROUTINE cell_manifold_chart_inv(m, q, u)
597 TYPE(cell_manifold_type), INTENT(IN) :: m
598 REAL(kind=dp), INTENT(IN) :: q(n_slot)
599 REAL(kind=dp), INTENT(OUT) :: u(m%ndof)
600
601 IF (m%ndof == 0) RETURN
602 IF (m%k == 0) THEN
603 u = q
604 RETURN
605 END IF
606 u = m%u0(1:m%ndof) + matmul(transpose(m%Z(:, 1:m%ndof)), q - m%q0)
607
608 END SUBROUTINE cell_manifold_chart_inv
609
610! **************************************************************************************************
611!> \brief Transform a 6-component cell gradient g_q into the reduced gradient g_u = (dq/du)^T g_q.
612!> \param m constraint state
613!> \param q current canonical cell vector
614!> \param g_q enthalpy gradient +dH/dq in canonical cell components
615!> \param g_u enthalpy gradient +dH/du in reduced coordinates
616!> \param info info_ok, info_degenerate, or info_licq
617!> \note Implicit differentiation of r(q(u)) = 0 gives dq/du = Z - Y (A Y)^{-1} A Z.
618!> Thus g_u = Z^T (g_q - A^T lam), where (A Y)^T lam = Y^T g_q.
619! **************************************************************************************************
620 SUBROUTINE cell_manifold_chart_pullback_grad(m, q, g_q, g_u, info)
621 TYPE(cell_manifold_type), INTENT(IN) :: m
622 REAL(kind=dp), INTENT(IN) :: q(n_slot), g_q(n_slot)
623 REAL(kind=dp), INTENT(OUT) :: g_u(m%ndof)
624 INTEGER, INTENT(OUT) :: info
625
626 INTEGER :: lapack_info
627 REAL(kind=dp) :: row_norms(m%k), lam(m%k), b(m%k, m%k), &
628 a(m%k, n_slot)
629 INTEGER :: ipiv(m%k)
630
631 g_u = 0.0_dp
632 info = info_ok
633 IF (m%ndof == 0) RETURN
634
635 IF (m%k == 0) THEN
636 g_u = g_q
637 RETURN
638 END IF
639
640 CALL normalized_active_jacobian(m, q, a, row_norms, info)
641 IF (info /= info_ok) RETURN
642
643 CALL factor_normal_block(m%k, a, m%Y(:, 1:m%k), b, ipiv, info)
644 IF (info /= info_ok) RETURN
645
646 lam = matmul(transpose(m%Y(:, 1:m%k)), g_q)
647 CALL dgetrs('T', m%k, 1, b, m%k, ipiv, lam, m%k, lapack_info)
648 IF (lapack_info /= 0) THEN
649 info = info_licq
650 RETURN
651 END IF
652 g_u = matmul(transpose(m%Z(:, 1:m%ndof)), g_q - matmul(transpose(a), lam))
653
655
656! **************************************************************************************************
657!> \brief Compute the pressure residual P_constr - pres_ext along unconstrained cell directions.
658!> \param m constraint state
659!> \param q current canonical cell components
660!> \param g_phys enthalpy gradient +d(E + pres_ext*V)/dq from get_dg_dh
661!> \param pdiff residual pressure P_constr - pres_ext along the allowed volume direction
662!> \param projected .TRUE. if free directions change volume, .FALSE. if volume is fixed
663!> \param info info_ok, info_degenerate, or info_licq
664!> \note On curved constraints, ker(A(q)) rotates as q moves from q0. This routine
665!> projects dV/dq onto ker(A(q)) at the current cell q:
666!> vt = dV/dq - A(q)^T (A(q) A(q)^T)^{-1} A(q) (dV/dq).
667!> At any constrained minimum, g_phys lies in row(A(q)) and pdiff vanishes.
668! **************************************************************************************************
669 SUBROUTINE cell_manifold_proj_pressure(m, q, g_phys, pdiff, projected, info)
670 TYPE(cell_manifold_type), INTENT(IN) :: m
671 REAL(kind=dp), INTENT(IN) :: q(n_slot), g_phys(n_slot)
672 REAL(kind=dp), INTENT(OUT) :: pdiff
673 LOGICAL, INTENT(OUT) :: projected
674 INTEGER, INTENT(OUT) :: info
675
676 INTEGER :: lapack_info
677 REAL(kind=dp) :: ax, by, cz, denom, dv_dq(n_slot), dv_norm2, s_chk(n_geom), vt(n_slot), &
678 row_norms(m%k), mu(m%k), chol(m%k, m%k), a(m%k, n_slot)
679
680 pdiff = 0.0_dp
681 projected = .false.
682 info = info_ok
683 IF (m%ndof == 0) RETURN
684
685 CALL cell_manifold_geometry(q, s_chk, info)
686 IF (info /= info_ok) RETURN
687
688 ax = q(slot_ax)
689 by = q(slot_by)
690 cz = q(slot_cz)
691 dv_dq = [by*cz, 0.0_dp, ax*cz, 0.0_dp, 0.0_dp, ax*by]
692 dv_norm2 = dot_product(dv_dq, dv_dq)
693 vt = dv_dq
694
695 IF (m%k > 0) THEN
696 CALL normalized_active_jacobian(m, q, a, row_norms, info)
697 IF (info /= info_ok) RETURN
698 chol = matmul(a, transpose(a))
699 CALL dpotrf('U', m%k, chol, m%k, lapack_info)
700 IF (lapack_info /= 0) THEN
701 info = info_licq
702 RETURN
703 END IF
704 mu = matmul(a, dv_dq)
705 CALL dpotrs('U', m%k, 1, chol, m%k, mu, m%k, lapack_info)
706 IF (lapack_info /= 0) THEN
707 info = info_licq
708 RETURN
709 END IF
710 vt = dv_dq - matmul(transpose(a), mu)
711 END IF
712
713 denom = dot_product(vt, vt)
714 IF (denom <= proj_pressure_rel_tol*dv_norm2) RETURN
715
716 projected = .true.
717 ! For isotropic stress g_phys = -(P_int - pres_ext) * dV/dq, so the minus
718 ! sign yields pdiff = P_constr - pres_ext.
719 pdiff = -dot_product(g_phys, vt)/denom
720
721 END SUBROUTINE cell_manifold_proj_pressure
722
723! **************************************************************************************************
724!> \brief Pack the upper-triangular canonical cell matrix into q(1:6).
725!> \param hmat canonical 3x3 cell matrix (a along x, b in the xy plane)
726!> \return q = (AX, BX, BY, CX, CY, CZ)
727! **************************************************************************************************
728 PURE FUNCTION cell_manifold_pack_hmat(hmat) RESULT(q)
729 REAL(kind=dp), DIMENSION(3, 3), INTENT(IN) :: hmat
730 REAL(kind=dp), DIMENSION(n_slot) :: q
731
732 q = [hmat(1, 1), hmat(1, 2), hmat(2, 2), hmat(1, 3), hmat(2, 3), hmat(3, 3)]
733
734 END FUNCTION cell_manifold_pack_hmat
735
736! **************************************************************************************************
737!> \brief Write q(1:6) into the upper-triangular canonical cell matrix.
738!> \param q canonical cell vector (AX, BX, BY, CX, CY, CZ)
739!> \param hmat canonical 3x3 cell matrix with zeroed lower triangle
740! **************************************************************************************************
741 PURE SUBROUTINE cell_manifold_unpack_hmat(q, hmat)
742 REAL(kind=dp), DIMENSION(n_slot), INTENT(IN) :: q
743 REAL(kind=dp), DIMENSION(3, 3), INTENT(OUT) :: hmat
744
745 hmat = 0.0_dp
746 hmat(1, 1) = q(slot_ax)
747 hmat(1, 2) = q(slot_bx)
748 hmat(2, 2) = q(slot_by)
749 hmat(1, 3) = q(slot_cx)
750 hmat(2, 3) = q(slot_cy)
751 hmat(3, 3) = q(slot_cz)
752
753 END SUBROUTINE cell_manifold_unpack_hmat
754
755! **************************************************************************************************
756!> \brief Return a short description of a cell_manifold_utils status code.
757!> \param info status code from a cell_manifold_* routine
758!> \return diagnostic text
759! **************************************************************************************************
760 PURE FUNCTION cell_manifold_status_label(info) RESULT(label)
761 INTEGER, INTENT(IN) :: info
762 CHARACTER(LEN=default_string_length) :: label
763
764 SELECT CASE (info)
765 CASE (info_ok)
766 label = "ok"
767 CASE (info_degenerate)
768 label = "degenerate or non-positive cell"
769 CASE (info_licq)
770 label = "cell constraints are linearly dependent or ill-conditioned"
771 CASE (info_no_converge)
772 label = "nonlinear cell constraint solver did not converge"
773 CASE (info_bad_input)
774 label = "invalid constraint mask or row specification"
775 CASE DEFAULT
776 label = "unknown cell-constraint status"
777 END SELECT
778
779 END FUNCTION cell_manifold_status_label
780
781! **************************************************************************************************
782!> \brief Test whether one candidate constraint stays zero on manifold m near q0.
783!> \param m active constraint manifold anchored at q0
784!> \param mask candidate comp_* or gfix_* bit, or 0 for a lin_w or eq_pair test
785!> \param lin_w optional 6 x 1 column of candidate linear weights
786!> \param eq_pair optional 2 x 1 index pair for a candidate equality
787!> \return .TRUE. if the candidate residual stays zero along m, or .FALSE. otherwise
788! **************************************************************************************************
789 LOGICAL FUNCTION cell_manifold_is_redundant(m, mask, lin_w, eq_pair) RESULT(is_redundant)
790 TYPE(cell_manifold_type), INTENT(IN) :: m
791 INTEGER, INTENT(IN) :: mask
792 REAL(kind=dp), INTENT(IN), OPTIONAL :: lin_w(:, :)
793 INTEGER, INTENT(IN), OPTIONAL :: eq_pair(:, :)
794
795 REAL(kind=dp), PARAMETER :: inv_sqrt2 = sqrt(0.5_dp)
796
797 INTEGER :: i, i_probe, info, j, n_probes
798 REAL(kind=dp) :: anchor_limit, cand_scales(1), curv_limit, drift_limit, q_probe(n_slot), &
799 r0(1), r_neg(1), r_pos(1), r_snap(1), rel_norm, step_u, z_dir(n_slot)
800 TYPE(cell_manifold_type) :: m_cand, m_work
801 REAL(kind=dp) :: u_probe(m%ndof), &
802 u_dirs(m%ndof, m%ndof*m%ndof)
803
804 is_redundant = .false.
805 CALL cell_manifold_init(m_cand, m%q0, mask, info, lin_w=lin_w, eq_pair=eq_pair)
806 IF (info /= info_ok .OR. m_cand%k /= 1) RETURN
807
808 cand_scales = active_row_scales(m_cand)
809 anchor_limit = redundancy_rel_tol*cand_scales(1)
810 drift_limit = redundancy_drift_rel_tol*cand_scales(1)
811 curv_limit = redundancy_curv_rel_tol*cand_scales(1)
812
813 CALL cell_manifold_residual(m_cand, m%q0, r0, info)
814 IF (info /= info_ok .OR. abs(r0(1)) > anchor_limit) RETURN
815
816 IF (m%ndof == 0) THEN
817 is_redundant = .true.
818 RETURN
819 END IF
820
821 m_work = m
822 m_work%tol = min(m%tol, redundancy_chart_tol)
823
824 ! Project u0 onto m_work first so input roundoff at q0 does not appear as
825 ! drift or curvature along m.
826 CALL cell_manifold_chart_eval(m_work, m%u0(1:m%ndof), q_probe, info)
827 IF (info /= info_ok) RETURN
828 CALL cell_manifold_residual(m_cand, q_probe, r_snap, info)
829 IF (info /= info_ok .OR. abs(r_snap(1)) > anchor_limit) RETURN
830
831 n_probes = m%ndof*m%ndof
832 u_dirs = 0.0_dp
833 i_probe = 0
834 DO i = 1, m%ndof
835 i_probe = i_probe + 1
836 u_dirs(i, i_probe) = 1.0_dp
837 DO j = i + 1, m%ndof
838 i_probe = i_probe + 1
839 u_dirs(i, i_probe) = inv_sqrt2
840 u_dirs(j, i_probe) = inv_sqrt2
841 i_probe = i_probe + 1
842 u_dirs(i, i_probe) = inv_sqrt2
843 u_dirs(j, i_probe) = -inv_sqrt2
844 END DO
845 END DO
846
847 DO i_probe = 1, n_probes
848 z_dir = matmul(m%Z(:, 1:m%ndof), u_dirs(:, i_probe))
849 rel_norm = norm2([z_dir(slot_ax)/m_cand%s0(geom_a), &
850 z_dir(slot_bx)/m_cand%s0(geom_b), &
851 z_dir(slot_by)/m_cand%s0(geom_b), &
852 z_dir(slot_cx)/m_cand%s0(geom_c), &
853 z_dir(slot_cy)/m_cand%s0(geom_c), &
854 z_dir(slot_cz)/m_cand%s0(geom_c)])
855 step_u = redundancy_probe_frac/rel_norm
856 u_probe = m%u0(1:m%ndof) + step_u*u_dirs(:, i_probe)
857 CALL cell_manifold_chart_eval(m_work, u_probe, q_probe, info)
858 IF (info /= info_ok) RETURN
859 CALL cell_manifold_residual(m_cand, q_probe, r_pos, info)
860 IF (info /= info_ok) RETURN
861 u_probe = m%u0(1:m%ndof) - step_u*u_dirs(:, i_probe)
862 CALL cell_manifold_chart_eval(m_work, u_probe, q_probe, info)
863 IF (info /= info_ok) RETURN
864 CALL cell_manifold_residual(m_cand, q_probe, r_neg, info)
865 IF (info /= info_ok) RETURN
866 IF (max(abs(r_pos(1) - r_snap(1)), abs(r_neg(1) - r_snap(1))) > drift_limit) RETURN
867 IF (abs(r_pos(1) + r_neg(1) - 2.0_dp*r_snap(1)) > curv_limit) RETURN
868 END DO
869
870 is_redundant = .true.
871
872 END FUNCTION cell_manifold_is_redundant
873
874! **************************************************************************************************
875!> \brief Pin implied directions when a candidate constraint is semidefinite at q0.
876!> \param m active constraint manifold at q0. This routine updates m when is_pinned is .TRUE.
877!> \param mask candidate comp_* or gfix_* bit, or 0 for a lin_w or eq_pair test
878!> \param is_pinned .TRUE. if this routine pins the candidate constraint
879!> \param lin_w optional 6 x 1 column of candidate linear weights
880!> \param eq_pair optional 2 x 1 index pair for a candidate equality
881!> \note This routine builds the 6 x 6 Lagrangian Hessian at q0 from 4-point
882!> central differences of analytic Jacobians. When the reduced Hessian on
883!> ker(A(q0)) is nonzero and semidefinite, this routine pins the curved
884!> directions. It then verifies that the candidate stays zero.
885! **************************************************************************************************
886 SUBROUTINE cell_manifold_pin_semidefinite(m, mask, is_pinned, lin_w, eq_pair)
887 TYPE(cell_manifold_type), INTENT(INOUT) :: m
888 INTEGER, INTENT(IN) :: mask
889 LOGICAL, INTENT(OUT) :: is_pinned
890 REAL(kind=dp), INTENT(IN), OPTIONAL :: lin_w(:, :)
891 INTEGER, INTENT(IN), OPTIONAL :: eq_pair(:, :)
892
893 INTEGER :: col, i, info, j, lapack_info, lwork, &
894 mask_new, max_slot, n_lin_basis, &
895 n_lin_new, n_pin, ndof, slot
896 LOGICAL :: ok, slot_fixed(n_slot)
897 REAL(kind=dp) :: cand_grad(n_slot), cand_norm, cand_scales(1), eig_thresh, g_tilde(n_slot), &
898 h_tilde(n_slot, n_slot), lag_grad(n_slot), lin_basis(n_slot, n_slot), lin_col(n_slot), &
899 lin_w_new(n_slot, n_slot), max_abs_eig, min_pin_norm, mode_vec(n_slot), pin_col(n_slot), &
900 pin_norm, r0(1), slot_scales(n_slot), vec_tol, w_col(n_slot), w_norm, work_query(1), &
901 z_scaled(n_slot)
902 REAL(kind=dp), ALLOCATABLE :: work(:)
903 TYPE(cell_manifold_type) :: m_cand, m_pinned
904 REAL(kind=dp) :: z_tilde(n_slot, m%ndof), w_pin(n_slot, m%ndof), h_red(m%ndof, m%ndof), &
905 eigvecs(m%ndof, m%ndof), eigvals(m%ndof)
906
907 is_pinned = .false.
908 ndof = m%ndof
909 IF (ndof <= 0) RETURN
910 IF (mask /= 0 .AND. iand(m%mask, mask) /= 0) RETURN
911
912 CALL cell_manifold_init(m_cand, m%q0, mask, info, lin_w=lin_w, eq_pair=eq_pair)
913 IF (info /= info_ok .OR. m_cand%k /= 1) RETURN
914
915 cand_scales = active_row_scales(m_cand)
916 CALL cell_manifold_residual(m_cand, m%q0, r0, info)
917 IF (info /= info_ok .OR. abs(r0(1)) > redundancy_rel_tol*cand_scales(1)) RETURN
918
919 slot_scales = [m_cand%s0(geom_a), m_cand%s0(geom_b), m_cand%s0(geom_b), &
920 m_cand%s0(geom_c), m_cand%s0(geom_c), m_cand%s0(geom_c)]
921 CALL lagrangian_hessian_at_q0(m, m_cand, cand_scales(1), slot_scales, &
922 cand_grad, lag_grad, g_tilde, h_tilde, ok)
923 IF (.NOT. ok) RETURN
924 cand_norm = norm2(cand_grad)
925
926 DO j = 1, ndof
927 z_scaled = m%Z(:, j)/slot_scales
928 z_tilde(:, j) = z_scaled/norm2(z_scaled)
929 END DO
930 h_red = matmul(transpose(z_tilde), matmul(h_tilde, z_tilde))
931 h_red = 0.5_dp*(h_red + transpose(h_red))
932 IF (.NOT. all(ieee_is_finite(h_red))) RETURN
933
934 eigvecs = h_red
935 CALL dsyev('V', 'U', ndof, eigvecs, ndof, eigvals, work_query, -1, lapack_info)
936 IF (lapack_info /= 0) RETURN
937 lwork = int(work_query(1))
938 ALLOCATE (work(max(1, lwork)))
939 CALL dsyev('V', 'U', ndof, eigvecs, ndof, eigvals, work, SIZE(work), lapack_info)
940 DEALLOCATE (work)
941 IF (lapack_info /= 0) RETURN
942
943 max_abs_eig = maxval(abs(eigvals))
944 IF (max_abs_eig <= semidef_min_curv) RETURN
945 eig_thresh = semidef_eig_rel_tol*max_abs_eig
946 IF (maxval(eigvals) > eig_thresh .AND. minval(eigvals) < -eig_thresh) RETURN
947
948 slot_fixed = .false.
949 DO i = 1, m%n_slot_rows
950 slot_fixed(m%slot_rows(i)) = .true.
951 END DO
952 n_lin_basis = 0
953 lin_basis = 0.0_dp
954 DO i = 1, m%n_lin_rows
955 lin_col = m%lin_w(:, i)*slot_scales
956 WHERE (slot_fixed) lin_col = 0.0_dp
957 DO j = 1, n_lin_basis
958 lin_col = lin_col - dot_product(lin_basis(:, j), lin_col)*lin_basis(:, j)
959 END DO
960 w_norm = norm2(lin_col)
961 IF (w_norm > semidef_vec_zero_tol) THEN
962 n_lin_basis = n_lin_basis + 1
963 lin_basis(:, n_lin_basis) = lin_col/w_norm
964 END IF
965 END DO
966
967 ! Project out only fixed slots and linear rows of m so curved active rows
968 ! cannot tilt a pure coordinate pin.
969 n_pin = 0
970 min_pin_norm = huge(1.0_dp)
971 DO j = 1, ndof
972 IF (abs(eigvals(j)) > eig_thresh) THEN
973 n_pin = n_pin + 1
974 mode_vec = matmul(z_tilde, eigvecs(:, j))
975 IF (abs(dot_product(mode_vec, g_tilde)) > semidef_vertex_rel_tol*abs(eigvals(j))) RETURN
976 pin_col = matmul(h_tilde, mode_vec)
977 WHERE (slot_fixed) pin_col = 0.0_dp
978 DO i = 1, n_lin_basis
979 pin_col = pin_col - dot_product(lin_basis(:, i), pin_col)*lin_basis(:, i)
980 END DO
981 pin_norm = norm2(pin_col)
982 IF (pin_norm <= tiny(1.0_dp)) RETURN
983 min_pin_norm = min(min_pin_norm, pin_norm)
984 w_pin(:, n_pin) = pin_col/pin_norm
985 END IF
986 END DO
987 IF (n_pin == 0) RETURN
988 vec_tol = max(semidef_vec_zero_tol, semidef_fd_noise_floor/min_pin_norm)
989 IF (vec_tol > semidef_max_vec_tol) RETURN
990
991 CALL echelon_pin_columns(w_pin(:, 1:n_pin), n_pin, vec_tol, ok)
992 IF (.NOT. ok) RETURN
993
994 mask_new = m%mask
995 lin_w_new = m%lin_w
996 n_lin_new = m%n_lin_rows
997 DO col = 1, n_pin
998 pin_col = w_pin(:, col)
999 WHERE (abs(pin_col) <= vec_tol*maxval(abs(pin_col))) pin_col = 0.0_dp
1000 w_col = pin_col/slot_scales
1001 w_norm = norm2(w_col)
1002 IF (w_norm <= tiny(1.0_dp)) RETURN
1003 w_col = w_col/w_norm
1004 IF (count(w_col /= 0.0_dp) == 1) THEN
1005 slot = maxloc(abs(w_col), dim=1)
1006 mask_new = ior(mask_new, comp_flags(slot))
1007 ELSE
1008 max_slot = maxloc(abs(w_col), dim=1)
1009 IF (w_col(max_slot) < 0.0_dp) w_col = -w_col
1010 n_lin_new = n_lin_new + 1
1011 IF (n_lin_new > n_slot) RETURN
1012 lin_w_new(:, n_lin_new) = w_col
1013 END IF
1014 END DO
1015
1016 CALL cell_manifold_init(m_pinned, m%q0, mask_new, info, &
1017 lin_w=lin_w_new(:, 1:n_lin_new), &
1018 eq_pair=m%eq_pair(:, 1:m%n_eq_rows))
1019 IF (info /= info_ok .OR. m_pinned%ndof /= ndof - n_pin) RETURN
1020
1021 IF (m_pinned%ndof > 0) THEN
1022 IF (norm2(matmul(transpose(m_pinned%Z(:, 1:m_pinned%ndof)), lag_grad)) > &
1023 redundancy_chart_tol*cand_norm) RETURN
1024 END IF
1025
1026 IF (.NOT. cell_manifold_is_redundant(m_pinned, mask, lin_w=lin_w, eq_pair=eq_pair)) RETURN
1027
1028 m = m_pinned
1029 is_pinned = .true.
1030
1031 END SUBROUTINE cell_manifold_pin_semidefinite
1032
1033! **************************************************************************************************
1034!> \brief Return positive reference scales for each active residual component of m.
1035!> \param m initialized constraint state (m%k > 0)
1036!> \return per-row reference magnitudes in the row order of cell_manifold_residual
1037! **************************************************************************************************
1038 PURE FUNCTION active_row_scales(m) RESULT(scales)
1039 TYPE(cell_manifold_type), INTENT(IN) :: m
1040 REAL(kind=dp) :: scales(m%k)
1041
1042 INTEGER :: g, i, p1, p2, row
1043 REAL(kind=dp) :: coord_scale
1044
1045 coord_scale = maxval(abs(m%q0))
1046 DO i = 1, m%n_slot_rows
1047 scales(i) = coord_scale
1048 END DO
1049 DO i = 1, m%n_geom_rows
1050 row = m%n_slot_rows + i
1051 g = m%geom_rows(i)
1052 IF (g >= geom_alpha .AND. g <= geom_gamma) THEN
1053 scales(row) = 1.0_dp
1054 ELSE
1055 scales(row) = abs(m%s0(g))
1056 END IF
1057 END DO
1058 DO i = 1, m%n_lin_rows
1059 row = m%n_slot_rows + m%n_geom_rows + i
1060 scales(row) = coord_scale
1061 END DO
1062 DO i = 1, m%n_eq_rows
1063 row = m%n_slot_rows + m%n_geom_rows + m%n_lin_rows + i
1064 p1 = m%eq_pair(1, i)
1065 p2 = m%eq_pair(2, i)
1066 IF (p1 >= geom_alpha .AND. p2 >= geom_alpha) THEN
1067 scales(row) = 1.0_dp
1068 ELSE
1069 scales(row) = max(abs(m%s0(p1)), abs(m%s0(p2)))
1070 END IF
1071 END DO
1072
1073 END FUNCTION active_row_scales
1074
1075! **************************************************************************************************
1076!> \brief Evaluate the dimensionless Lagrangian gradient and 6 x 6 Hessian at q0.
1077!> \param m active constraint manifold at q0
1078!> \param m_cand single-row manifold for the candidate constraint at q0
1079!> \param cand_scale reference residual scale of m_cand
1080!> \param slot_scales per-slot length scales (a, b, b, c, c, c) at q0
1081!> \param cand_grad candidate row gradient dr_cand/dq at q0
1082!> \param lag_grad Lagrangian stationarity gradient at q0
1083!> \param g_tilde dimensionless Lagrangian gradient at q0
1084!> \param H_tilde dimensionless 6 x 6 symmetric Hessian of the Lagrangian at q0
1085!> \param ok .TRUE. when the candidate is first-order tangent and all evaluations succeed
1086! **************************************************************************************************
1087 SUBROUTINE lagrangian_hessian_at_q0(m, m_cand, cand_scale, slot_scales, &
1088 cand_grad, lag_grad, g_tilde, H_tilde, ok)
1089 TYPE(cell_manifold_type), INTENT(IN) :: m, m_cand
1090 REAL(kind=dp), INTENT(IN) :: cand_scale, slot_scales(n_slot)
1091 REAL(kind=dp), INTENT(OUT) :: cand_grad(n_slot), lag_grad(n_slot), &
1092 g_tilde(n_slot), &
1093 h_tilde(n_slot, n_slot)
1094 LOGICAL, INTENT(OUT) :: ok
1095
1096 REAL(kind=dp), PARAMETER :: richardson_w1 = 8.0_dp/12.0_dp, richardson_w2 = 1.0_dp/12.0_dp, &
1097 stencil_offsets(4) = [1.0_dp, -1.0_dp, 2.0_dp, -2.0_dp]
1098
1099 INTEGER :: i_pt, i_try, info, lapack_info, slot
1100 LOGICAL :: stencil_ok
1101 REAL(kind=dp) :: a_cand(1, n_slot), cand_norm, dg_dq(n_slot), q_pert(n_slot), &
1102 stencil_grad(n_slot, 4), step_q, row_norms(m%k), lam_raw(m%k), lam(m%k), b(m%k, m%k), &
1103 a_raw(m%k, n_slot), a_norm(m%k, n_slot)
1104 INTEGER :: ipiv(m%k)
1105
1106 cand_grad = 0.0_dp
1107 lag_grad = 0.0_dp
1108 g_tilde = 0.0_dp
1109 h_tilde = 0.0_dp
1110 ok = .false.
1111
1112 CALL cell_manifold_active_jacobian(m_cand, m%q0, a_cand, info)
1113 IF (info /= info_ok) RETURN
1114 cand_grad = a_cand(1, :)
1115 cand_norm = norm2(cand_grad)
1116 IF (cand_norm <= tiny(1.0_dp)) RETURN
1117 IF (norm2(matmul(transpose(m%Z(:, 1:m%ndof)), cand_grad)) > licq_pivot_tol*cand_norm) RETURN
1118
1119 lag_grad = cand_grad
1120 IF (m%k > 0) THEN
1121 CALL normalized_active_jacobian(m, m%q0, a_norm, row_norms, info)
1122 IF (info /= info_ok) RETURN
1123 CALL factor_normal_block(m%k, a_norm, m%Y(:, 1:m%k), b, ipiv, info)
1124 IF (info /= info_ok) RETURN
1125 lam = matmul(transpose(m%Y(:, 1:m%k)), cand_grad)
1126 CALL dgetrs('T', m%k, 1, b, m%k, ipiv, lam, m%k, lapack_info)
1127 IF (lapack_info /= 0) RETURN
1128 WHERE (abs(lam) <= semidef_lam_rel_tol*cand_norm) lam = 0.0_dp
1129 lam_raw = lam/row_norms
1130 lag_grad = cand_grad - matmul(transpose(a_norm), lam)
1131 END IF
1132
1133 g_tilde = (slot_scales/cand_scale)*lag_grad
1134
1135 DO slot = 1, n_slot
1136 step_q = semidef_fd_step*slot_scales(slot)
1137 stencil_ok = .false.
1138 DO i_try = 1, semidef_fd_max_tries
1139 stencil_ok = .true.
1140 DO i_pt = 1, SIZE(stencil_offsets)
1141 q_pert = m%q0
1142 q_pert(slot) = q_pert(slot) + stencil_offsets(i_pt)*step_q
1143 CALL cell_manifold_active_jacobian(m_cand, q_pert, a_cand, info)
1144 IF (info /= info_ok) THEN
1145 stencil_ok = .false.
1146 EXIT
1147 END IF
1148 stencil_grad(:, i_pt) = a_cand(1, :)
1149 IF (m%k > 0) THEN
1150 CALL cell_manifold_active_jacobian(m, q_pert, a_raw, info)
1151 IF (info /= info_ok) THEN
1152 stencil_ok = .false.
1153 EXIT
1154 END IF
1155 stencil_grad(:, i_pt) = stencil_grad(:, i_pt) - matmul(transpose(a_raw), lam_raw)
1156 END IF
1157 END DO
1158 IF (stencil_ok) EXIT
1159 step_q = semidef_fd_shrink*step_q
1160 END DO
1161 IF (.NOT. stencil_ok) RETURN
1162 dg_dq = (richardson_w1*(stencil_grad(:, 1) - stencil_grad(:, 2)) - &
1163 richardson_w2*(stencil_grad(:, 3) - stencil_grad(:, 4)))/step_q
1164 h_tilde(:, slot) = (slot_scales*slot_scales(slot)/cand_scale)*dg_dq
1165 END DO
1166
1167 h_tilde = 0.5_dp*(h_tilde + transpose(h_tilde))
1168 IF (.NOT. all(ieee_is_finite(h_tilde))) RETURN
1169 ok = .true.
1170
1171 END SUBROUTINE lagrangian_hessian_at_q0
1172
1173! **************************************************************************************************
1174!> \brief Reduce pin columns to reduced column-echelon form.
1175!> \param W_pin 6 x n_pin matrix of dimensionless pin normals. This routine reduces W_pin in place.
1176!> \param n_pin number of active pin columns
1177!> \param vec_tol minimum pivot magnitude for independent pin columns
1178!> \param ok .TRUE. when all n_pin columns have pivots above vec_tol
1179!> \note Full-pivoted Gauss-Jordan reduction gives a LAPACK-independent basis and turns
1180!> pure coordinate directions in span(W_pin) into single-slot columns.
1181! **************************************************************************************************
1182 PURE SUBROUTINE echelon_pin_columns(W_pin, n_pin, vec_tol, ok)
1183 INTEGER, INTENT(IN) :: n_pin
1184 REAL(kind=dp), INTENT(INOUT) :: w_pin(n_slot, n_pin)
1185 REAL(kind=dp), INTENT(IN) :: vec_tol
1186 LOGICAL, INTENT(OUT) :: ok
1187
1188 INTEGER :: col, i, j, piv_col, piv_row, tmp_row, &
1189 piv_rows(n_pin)
1190 LOGICAL :: row_used(n_slot)
1191 REAL(kind=dp) :: piv_val, tmp_col(n_slot)
1192
1193 ok = .false.
1194 row_used = .false.
1195 DO col = 1, n_pin
1196 piv_val = 0.0_dp
1197 piv_row = 0
1198 piv_col = 0
1199 DO i = 1, n_slot
1200 IF (row_used(i)) cycle
1201 DO j = col, n_pin
1202 IF (abs(w_pin(i, j)) > piv_val) THEN
1203 piv_val = abs(w_pin(i, j))
1204 piv_row = i
1205 piv_col = j
1206 END IF
1207 END DO
1208 END DO
1209 IF (piv_row == 0 .OR. piv_val <= vec_tol) RETURN
1210 row_used(piv_row) = .true.
1211 piv_rows(col) = piv_row
1212 IF (piv_col /= col) THEN
1213 tmp_col = w_pin(:, col)
1214 w_pin(:, col) = w_pin(:, piv_col)
1215 w_pin(:, piv_col) = tmp_col
1216 END IF
1217 w_pin(:, col) = w_pin(:, col)/w_pin(piv_row, col)
1218 DO j = 1, n_pin
1219 IF (j /= col) w_pin(:, j) = w_pin(:, j) - w_pin(piv_row, j)*w_pin(:, col)
1220 END DO
1221 END DO
1222
1223 DO col = 1, n_pin - 1
1224 DO j = col + 1, n_pin
1225 IF (piv_rows(j) < piv_rows(col)) THEN
1226 tmp_row = piv_rows(col)
1227 piv_rows(col) = piv_rows(j)
1228 piv_rows(j) = tmp_row
1229 tmp_col = w_pin(:, col)
1230 w_pin(:, col) = w_pin(:, j)
1231 w_pin(:, j) = tmp_col
1232 END IF
1233 END DO
1234 END DO
1235 ok = .true.
1236
1237 END SUBROUTINE echelon_pin_columns
1238
1239! **************************************************************************************************
1240!> \brief Build the fixed orthonormal split [Y, Z] and reduced anchor u0 at m%q0.
1241!> \param m constraint state
1242!> \param info info_ok, info_degenerate, or info_licq
1243!> \note Cholesky factorization of the row-normalized Gram matrix A A^T rejects both
1244!> exact row dependence and near-collinear rows before QR builds [Y, Z].
1245!> Pivoted Gram-Schmidt over the kernel rows makes Z depend only on ker(A(q0)),
1246!> not on LAPACK reflector signs or nullspace rotations.
1247! **************************************************************************************************
1248 SUBROUTINE build_chart_basis(m, info)
1249 TYPE(cell_manifold_type), INTENT(INOUT) :: m
1250 INTEGER, INTENT(OUT) :: info
1251
1252 INTEGER :: cs, i, info_lapack, j, lwork, piv, pmax
1253 LOGICAL :: slot_used(n_slot)
1254 REAL(kind=dp) :: c_rot(n_slot, n_slot), qfull(n_slot, n_slot), row_norms_slot(n_slot), &
1255 work_query(1), z_rows(n_slot, n_slot)
1256 REAL(kind=dp), ALLOCATABLE :: work(:)
1257 REAL(kind=dp) :: tau(m%k), row_norms(m%k), &
1258 gram(m%k, m%k), gdiag(m%k), &
1259 at(n_slot, m%k), a(m%k, n_slot)
1260
1261 IF (m%k == 0) THEN
1262 m%ndof = n_slot
1263 m%Y = 0.0_dp
1264 m%Z = 0.0_dp
1265 DO i = 1, n_slot
1266 m%Z(i, i) = 1.0_dp
1267 END DO
1268 m%u0 = m%q0
1269 info = info_ok
1270 RETURN
1271 END IF
1272
1273 CALL normalized_active_jacobian(m, m%q0, a, row_norms, info)
1274 IF (info /= info_ok) RETURN
1275
1276 gram = matmul(a, transpose(a))
1277 CALL dpotrf('U', m%k, gram, m%k, info_lapack)
1278 IF (info_lapack /= 0) THEN
1279 info = info_licq
1280 RETURN
1281 END IF
1282 gdiag = [(gram(i, i), i=1, m%k)]
1283 IF (minval(gdiag) <= licq_pivot_tol*maxval(gdiag)) THEN
1284 info = info_licq
1285 RETURN
1286 END IF
1287
1288 at = transpose(a)
1289 CALL dgeqrf(n_slot, m%k, at, n_slot, tau, work_query, -1, info_lapack)
1290 lwork = int(work_query(1))
1291 ! DORGQR regenerates all n_slot columns and needs its own workspace query:
1292 ! DGEQRF's optimal LWORK does not cover it (rejected by MKL for k < n_slot).
1293 CALL dorgqr(n_slot, n_slot, m%k, qfull, n_slot, tau, work_query, -1, info_lapack)
1294 lwork = max(lwork, int(work_query(1)))
1295 ALLOCATE (work(max(1, lwork)))
1296
1297 CALL dgeqrf(n_slot, m%k, at, n_slot, tau, work, SIZE(work), info_lapack)
1298 IF (info_lapack /= 0) THEN
1299 info = info_licq
1300 DEALLOCATE (work)
1301 RETURN
1302 END IF
1303 qfull = 0.0_dp
1304 qfull(1:n_slot, 1:m%k) = at(1:n_slot, 1:m%k)
1305 CALL dorgqr(n_slot, n_slot, m%k, qfull, n_slot, tau, work, SIZE(work), info_lapack)
1306 DEALLOCATE (work)
1307 IF (info_lapack /= 0) THEN
1308 info = info_licq
1309 RETURN
1310 END IF
1311
1312 m%ndof = n_slot - m%k
1313 DO j = 1, m%k
1314 pmax = maxloc(abs(qfull(:, j)), dim=1)
1315 IF (qfull(pmax, j) < 0.0_dp) qfull(:, j) = -qfull(:, j)
1316 END DO
1317 m%Y = 0.0_dp
1318 m%Y(:, 1:m%k) = qfull(:, 1:m%k)
1319
1320 ! Canonical tangent basis: pivoted Gram-Schmidt over the rows of the raw
1321 ! kernel columns. The kernel span is basis-choice invariant, but its QR
1322 ! representation is not (LAPACK builds differ by signs and rotations),
1323 ! and the reduced coordinates must stay reproducible across builds.
1324 m%Z = 0.0_dp
1325 IF (m%ndof > 0) THEN
1326 c_rot = 0.0_dp
1327 z_rows = 0.0_dp
1328 z_rows(:, 1:m%ndof) = qfull(:, m%k + 1:n_slot)
1329 slot_used = .false.
1330 DO cs = 1, m%ndof
1331 row_norms_slot = 0.0_dp
1332 DO i = 1, n_slot
1333 IF (.NOT. slot_used(i)) row_norms_slot(i) = norm2(z_rows(i, 1:m%ndof))
1334 END DO
1335 ! Lowest slot index among near-equal norms, so symmetric ties resolve
1336 ! the same way on every build.
1337 piv = findloc(row_norms_slot >= (1.0_dp - basis_tie_rel_tol)*maxval(row_norms_slot), &
1338 .true., dim=1)
1339 c_rot(1:m%ndof, cs) = z_rows(piv, 1:m%ndof)/row_norms_slot(piv)
1340 slot_used(piv) = .true.
1341 DO i = 1, n_slot
1342 IF (.NOT. slot_used(i)) THEN
1343 z_rows(i, 1:m%ndof) = z_rows(i, 1:m%ndof) - &
1344 dot_product(z_rows(i, 1:m%ndof), c_rot(1:m%ndof, cs))*c_rot(1:m%ndof, cs)
1345 END IF
1346 END DO
1347 END DO
1348 m%Z(:, 1:m%ndof) = matmul(qfull(:, m%k + 1:n_slot), c_rot(1:m%ndof, 1:m%ndof))
1349 END IF
1350 m%u0 = 0.0_dp
1351 m%u0(1:m%ndof) = matmul(transpose(m%Z(:, 1:m%ndof)), m%q0)
1352
1353 END SUBROUTINE build_chart_basis
1354
1355! **************************************************************************************************
1356!> \brief Assemble A(q) and scale each row to unit 2-norm.
1357!> \param m constraint state (with m%k > 0)
1358!> \param q canonical cell vector
1359!> \param A row-normalized active Jacobian
1360!> \param row_norms original Euclidean norm of each row before normalization
1361!> \param info info_ok, info_degenerate, or info_licq
1362!> \note Unit-norm rows keep linear solves independent of unit scales when large
1363!> volume gradients mix with component rows.
1364! **************************************************************************************************
1365 SUBROUTINE normalized_active_jacobian(m, q, A, row_norms, info)
1366 TYPE(cell_manifold_type), INTENT(IN) :: m
1367 REAL(kind=dp), INTENT(IN) :: q(n_slot)
1368 REAL(kind=dp), INTENT(OUT) :: a(m%k, n_slot), row_norms(m%k)
1369 INTEGER, INTENT(OUT) :: info
1370
1371 INTEGER :: i
1372
1373 row_norms = 0.0_dp
1374 CALL cell_manifold_active_jacobian(m, q, a, info)
1375 IF (info /= info_ok) RETURN
1376
1377 DO i = 1, m%k
1378 row_norms(i) = norm2(a(i, :))
1379 IF (row_norms(i) <= tiny(1.0_dp)) THEN
1380 info = info_licq
1381 RETURN
1382 END IF
1383 a(i, :) = a(i, :)/row_norms(i)
1384 END DO
1385
1386 END SUBROUTINE normalized_active_jacobian
1387
1388! **************************************************************************************************
1389!> \brief Form and LU-factor the k x k block B = A(q) * Y.
1390!> \param k number of active constraints (k > 0)
1391!> \param A row-normalized k x 6 active Jacobian at q
1392!> \param Y fixed 6 x k constrained-direction basis from q0
1393!> \param B LU factors of A * Y from DGETRF
1394!> \param ipiv pivot indices from DGETRF
1395!> \param info info_ok or info_licq
1396!> \note After DGETRF, the diagonal of B holds the pivots of U. A tiny pivot ratio
1397!> means ker(A(q)) now intersects span(Y), so q(u) is no longer single-valued
1398!> along Y.
1399! **************************************************************************************************
1400 SUBROUTINE factor_normal_block(k, A, Y, B, ipiv, info)
1401 INTEGER, INTENT(IN) :: k
1402 REAL(kind=dp), INTENT(IN) :: a(k, n_slot), y(n_slot, k)
1403 REAL(kind=dp), INTENT(OUT) :: b(k, k)
1404 INTEGER, INTENT(OUT) :: ipiv(k), info
1405
1406 INTEGER :: i, lapack_info
1407 REAL(kind=dp) :: u_pivots(k)
1408
1409 b = matmul(a, y)
1410 CALL dgetrf(k, k, b, k, ipiv, lapack_info)
1411 u_pivots = [(abs(b(i, i)), i=1, k)]
1412 IF (lapack_info /= 0 .OR. minval(u_pivots) <= licq_pivot_tol*maxval(u_pivots)) THEN
1413 info = info_licq
1414 ELSE
1415 info = info_ok
1416 END IF
1417
1418 END SUBROUTINE factor_normal_block
1419
1420! **************************************************************************************************
1421!> \brief Differentiate angle(u, v) = acos((u.v) / (|u| |v|)) with respect to q.
1422!> \param cos_ang cos(angle)
1423!> \param sin_ang sin(angle)
1424!> \param ddot_dq derivative d(u.v)/dq of the dot product
1425!> \param len_u length |u|
1426!> \param len_v length |v|
1427!> \param dlen_u_dq derivative d|u|/dq
1428!> \param dlen_v_dq derivative d|v|/dq
1429!> \return row vector d(angle)/dq
1430! **************************************************************************************************
1431 PURE FUNCTION angle_grad_row(cos_ang, sin_ang, ddot_dq, len_u, len_v, dlen_u_dq, dlen_v_dq) &
1432 result(d_ang_dq)
1433 REAL(kind=dp), INTENT(IN) :: cos_ang, sin_ang, ddot_dq(n_slot), &
1434 len_u, len_v, dlen_u_dq(n_slot), &
1435 dlen_v_dq(n_slot)
1436 REAL(kind=dp) :: d_ang_dq(n_slot)
1437
1438 REAL(kind=dp) :: dcos_dq(n_slot)
1439
1440 dcos_dq = ddot_dq/(len_u*len_v) - cos_ang*(dlen_u_dq/len_u + dlen_v_dq/len_v)
1441 d_ang_dq = -dcos_dq/sin_ang
1442
1443 END FUNCTION angle_grad_row
1444
1445END MODULE cell_manifold_utils
Reduced-coordinate parameterization of cell constraints for CELL_OPT.
integer, parameter, public info_no_converge
integer, parameter, public comp_ax
subroutine, public cell_manifold_jacobian(q, j, info)
Compute the analytic 7 x 6 Jacobian J = ds/dq of the geometric quantities.
integer, parameter, public comp_cx
integer, parameter, public geom_gamma
integer, parameter, public geom_beta
integer, parameter, public gfix_gamma
subroutine, public cell_manifold_pin_semidefinite(m, mask, is_pinned, lin_w, eq_pair)
Pin implied directions when a candidate constraint is semidefinite at q0.
integer, parameter, public slot_cx
integer, parameter, public n_constraint_bits
integer, parameter, public mask_a
integer, parameter, public slot_by
integer, parameter, public gfix_c
integer, parameter, public gfix_v
integer, parameter, public info_degenerate
subroutine, public cell_manifold_chart_pullback_grad(m, q, g_q, g_u, info)
Transform a 6-component cell gradient g_q into the reduced gradient g_u = (dq/du)^T g_q.
integer, parameter, public info_bad_input
integer, parameter, public n_geom
integer, parameter, public n_slot
integer, parameter, public mask_yz
real(kind=dp), parameter, public min_cell_length
integer, parameter, public gfix_all
integer, parameter, public slot_ax
integer, parameter, public gfix_a
subroutine, public cell_manifold_chart_inv(m, q, u)
Map a canonical cell q back to reduced coordinates u = u0 + Z^T (q - q0).
integer, parameter, public gfix_b
subroutine, public cell_manifold_residual(m, q, r, info)
Evaluate the k active constraint residuals r(q).
subroutine, public cell_manifold_init(m, q0, mask, info, lin_w, eq_pair)
Record constraint targets at q0, check row independence, and build [Y, Z].
integer, parameter, public comp_by
integer, parameter, public mask_y
subroutine, public cell_manifold_proj_pressure(m, q, g_phys, pdiff, projected, info)
Compute the pressure residual P_constr - pres_ext along unconstrained cell directions.
integer, parameter, public geom_a
integer, parameter, public mask_x
subroutine, public cell_manifold_chart_eval(m, u, q, info)
Map reduced coordinates u to a canonical cell q(u) that satisfies r(q) = 0.
integer, parameter, public info_licq
integer, parameter, public slot_cz
integer, parameter, public gfix_beta
integer, parameter, public mask_xy
pure subroutine, public cell_manifold_unpack_hmat(q, hmat)
Write q(1:6) into the upper-triangular canonical cell matrix.
integer, parameter, public mask_c
logical function, public cell_manifold_is_redundant(m, mask, lin_w, eq_pair)
Test whether one candidate constraint stays zero on manifold m near q0.
integer, parameter, public slot_bx
integer, parameter, public mask_b
subroutine, public cell_manifold_geometry(q, s, info)
Compute cell lengths, angles in radians, and volume s(q).
integer, parameter, public mask_all
integer, parameter, public mask_xz
pure character(len=default_string_length) function, public cell_manifold_status_label(info)
Return a short description of a cell_manifold_utils status code.
integer, parameter, public comp_cz
pure real(kind=dp) function, dimension(n_slot), public cell_manifold_pack_hmat(hmat)
Pack the upper-triangular canonical cell matrix into q(1:6).
integer, parameter, public comp_bx
integer, parameter, public slot_cy
integer, parameter, public comp_cy
integer, parameter, public mask_z
integer, parameter, public geom_c
integer, parameter, public geom_v
integer, parameter, public geom_alpha
integer, parameter, public gfix_alpha
integer, parameter, public geom_b
subroutine, public cell_manifold_active_jacobian(m, q, a, info)
Assemble the k x 6 Jacobian A(q) = dr/dq of the active constraints.
integer, parameter, public info_ok
Defines the basic variable types.
Definition kinds.F:23
integer, parameter, public dp
Definition kinds.F:34
integer, parameter, public default_string_length
Definition kinds.F:57
Active constraint set and fixed coordinate bases anchored at q0.