Line data Source code
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 Unit test for the CELL_OPT constraint parser and guard predicates
10 : !> \par Testing principle
11 : !> Every check computes its expectation with a method that differs from
12 : !> the implementation: the checks enumerate bits one by one, while the
13 : !> implementation uses IAND set intersections. A bug on either side
14 : !> breaks the comparison. The projection checks take their expectation
15 : !> from the guard predicates and their behavior from
16 : !> apply_cell_constraints, so the hand-mirrored coupling tables and the
17 : !> executable projections verify each other.
18 : !> \par Coverage
19 : !> All 64 constraint masks, every token of the grammar, every crystal
20 : !> system, and the parse(label(m)) round trip. About 1800 combinations
21 : !> execute in total, plus the guard decisions checked against the real
22 : !> projections: 64 masks on 14 cell shapes with four gradient draws
23 : !> each, every draw passed through apply_cell_constraints twice, and
24 : !> the pressure-criterion predicate over 64 masks, both cell shapes
25 : !> and both KEEP_ANGLES settings.
26 : !> \par Failure behavior
27 : !> The first mismatch prints the offending mask or token and stops with
28 : !> exit code 1. do_regtest runs this program from the UNIT_TESTS
29 : !> registry and reads only the exit code.
30 : ! **************************************************************************************************
31 2 : PROGRAM cell_opt_utils_TEST
32 2 : USE cell_methods, ONLY: cell_create
33 : USE cell_opt_utils, ONLY: &
34 : apply_cell_constraints, cell_opt_constraint_label, comp_ax, comp_bx, comp_by, comp_cx, &
35 : comp_cy, comp_cz, fixes_partially, keep_angles_conflict, keep_symmetry_conflict, &
36 : mask_ab_plane, mask_all, mask_b, mask_c, mask_diag, mask_diag_ab, mask_diag_ac, &
37 : mask_diag_bc, mask_x, mask_xy, mask_xz, mask_y, mask_yz, mask_z, parse_cell_constraint, &
38 : parse_token, pres_crit_shear_pinned, slot_is_fixed
39 : USE cell_types, ONLY: &
40 : cell_release, cell_sym_cubic, cell_sym_hexagonal_gamma_120, cell_sym_hexagonal_gamma_60, &
41 : cell_sym_monoclinic, cell_sym_monoclinic_gamma_ab, cell_sym_none, cell_sym_orthorhombic, &
42 : cell_sym_rhombohedral, cell_sym_tetragonal_ab, cell_sym_tetragonal_ac, &
43 : cell_sym_tetragonal_bc, cell_sym_triclinic, cell_type
44 : USE kinds, ONLY: default_string_length,&
45 : dp
46 :
47 : IMPLICIT NONE
48 :
49 2 : CALL check_parse_tokens()
50 2 : CALL check_parse_2d_completion()
51 2 : CALL check_fixes_partially()
52 2 : CALL check_slot_is_fixed()
53 2 : CALL check_label_parse_roundtrip()
54 2 : CALL check_label_legacy_strings()
55 2 : CALL check_keep_angles_conflict()
56 2 : CALL check_keep_symmetry_conflict()
57 2 : CALL check_parse_token_rejections()
58 2 : CALL check_overlap_classification()
59 2 : CALL check_projection_guard_consistency()
60 2 : CALL check_pres_crit_shear_pinned()
61 :
62 2 : PRINT *, "cell_opt_utils_unittest: all checks passed"
63 : CONTAINS
64 :
65 : ! **************************************************************************************************
66 : !> \brief Parse every token of the grammar and compare against the mask table.
67 : !> Covers unions, free case and spacing, and the redundancy warning path.
68 : ! **************************************************************************************************
69 2 : SUBROUTINE check_parse_tokens()
70 : TYPE(cell_type), POINTER :: cell
71 :
72 2 : CALL make_cell3d(cell)
73 : ! Every call parses with explicit=.TRUE., so the 2D auto-completion
74 : ! stays out of the way and the check sees the raw token semantics.
75 2 : CALL parse_and_check("NONE", 0, cell)
76 2 : CALL parse_and_check("none", 0, cell)
77 2 : CALL parse_and_check("A", comp_ax, cell)
78 2 : CALL parse_and_check("AX", comp_ax, cell)
79 2 : CALL parse_and_check("B", mask_b, cell)
80 2 : CALL parse_and_check("BX", comp_bx, cell)
81 2 : CALL parse_and_check("BY", comp_by, cell)
82 2 : CALL parse_and_check("C", mask_c, cell)
83 2 : CALL parse_and_check("Z", mask_z, cell)
84 2 : CALL parse_and_check("CX", comp_cx, cell)
85 2 : CALL parse_and_check("CY", comp_cy, cell)
86 2 : CALL parse_and_check("CZ", comp_cz, cell)
87 2 : CALL parse_and_check("X", mask_x, cell)
88 2 : CALL parse_and_check("Y", mask_y, cell)
89 2 : CALL parse_and_check("XY", mask_xy, cell)
90 2 : CALL parse_and_check("XZ", mask_xz, cell)
91 2 : CALL parse_and_check("YZ", mask_yz, cell)
92 : ! Union semantics: several tokens accumulate. Spelling, case and
93 : ! spacing are free. The last line repeats tokens on purpose and only
94 : ! expects a warning, not a failure of the parse.
95 2 : CALL parse_and_check("AX BY CZ", IOR(IOR(comp_ax, comp_by), comp_cz), cell)
96 2 : CALL parse_and_check("x y", IOR(mask_x, mask_y), cell)
97 2 : CALL parse_and_check(" z ", mask_z, cell)
98 2 : CALL parse_and_check("a b ax", IOR(comp_ax, mask_b), cell)
99 2 : CALL cell_release(cell)
100 2 : PRINT *, "check_parse_tokens: OK"
101 2 : END SUBROUTINE check_parse_tokens
102 :
103 : ! **************************************************************************************************
104 : !> \brief Parse one token string and compare against the expected mask
105 : !> \param token_string ...
106 : !> \param expected_mask ...
107 : !> \param cell ...
108 : ! **************************************************************************************************
109 42 : SUBROUTINE parse_and_check(token_string, expected_mask, cell)
110 : CHARACTER(LEN=*), INTENT(IN) :: token_string
111 : INTEGER, INTENT(IN) :: expected_mask
112 : TYPE(cell_type), POINTER :: cell
113 :
114 : CHARACTER(LEN=default_string_length) :: tokens(16)
115 : INTEGER :: ntok, parsed_mask
116 :
117 42 : CALL split_tokens(token_string, tokens, ntok)
118 42 : CALL parse_cell_constraint(tokens(:ntok), cell, .TRUE., parsed_mask)
119 42 : IF (parsed_mask /= expected_mask) THEN
120 0 : PRINT *, "check_parse_tokens: unexpected mask for token "//TRIM(token_string)
121 0 : ERROR STOP 1
122 : END IF
123 42 : END SUBROUTINE parse_and_check
124 :
125 : ! **************************************************************************************************
126 : !> \brief Splits a blank-separated string into tokens, mimicking how the input
127 : !> tokenizer delivers the value of a char_t list keyword
128 : !> \param string ...
129 : !> \param tokens ...
130 : !> \param ntok ...
131 : ! **************************************************************************************************
132 340 : SUBROUTINE split_tokens(string, tokens, ntok)
133 : CHARACTER(LEN=*), INTENT(IN) :: string
134 : CHARACTER(LEN=default_string_length), INTENT(OUT) :: tokens(:)
135 : INTEGER, INTENT(OUT) :: ntok
136 :
137 170 : CHARACTER(LEN=LEN(string)) :: rest
138 : INTEGER :: isep
139 :
140 170 : rest = ADJUSTL(string)
141 170 : ntok = 0
142 538 : DO WHILE (LEN_TRIM(rest) > 0)
143 402 : isep = INDEX(rest, " ")
144 402 : ntok = ntok + 1
145 402 : IF (isep == 0) THEN
146 34 : tokens(ntok) = TRIM(rest)
147 : EXIT
148 : END IF
149 368 : tokens(ntok) = rest(:isep - 1)
150 504 : rest = ADJUSTL(rest(isep + 1:))
151 : END DO
152 170 : END SUBROUTINE split_tokens
153 :
154 : ! **************************************************************************************************
155 : !> \brief For 2D periodicity, an unset constraint auto-fixes the non-periodic
156 : !> direction. Explicit constraints that cover the direction stay unchanged.
157 : ! **************************************************************************************************
158 2 : SUBROUTINE check_parse_2d_completion()
159 : INTEGER :: parsed_mask
160 : TYPE(cell_type), POINTER :: cell
161 :
162 2 : CALL make_cell2d(cell)
163 : ! The keyword is unset. The parser must complete the mask so that the
164 : ! non-periodic z direction is fixed, and warn.
165 4 : CALL parse_cell_constraint(["NONE"], cell, .FALSE., parsed_mask)
166 2 : IF (parsed_mask /= mask_z) THEN
167 0 : PRINT *, "check_parse_2d_completion: auto-set did not fix the non-periodic z"
168 0 : ERROR STOP 1
169 : END IF
170 : ! The keyword is set and covers z. The parser must keep the mask as
171 : ! given; YZ covers z through the C vector.
172 4 : CALL parse_cell_constraint(["Z"], cell, .TRUE., parsed_mask)
173 2 : IF (parsed_mask /= mask_z) THEN
174 0 : PRINT *, "check_parse_2d_completion: explicit Z changed"
175 0 : ERROR STOP 1
176 : END IF
177 4 : CALL parse_cell_constraint(["YZ"], cell, .TRUE., parsed_mask)
178 2 : IF (parsed_mask /= mask_yz) THEN
179 0 : PRINT *, "check_parse_2d_completion: explicit YZ changed"
180 0 : ERROR STOP 1
181 : END IF
182 2 : CALL cell_release(cell)
183 2 : PRINT *, "check_parse_2d_completion: OK"
184 2 : END SUBROUTINE check_parse_2d_completion
185 :
186 : ! **************************************************************************************************
187 : !> \brief Compare fixes_partially against a per-bit enumeration for all 64 masks
188 : !> and the eight coupled slot sets (512 combinations).
189 : !> \par Method
190 : !> For each mask and slot set, walk the six bit positions of the set.
191 : !> Track two flags: does the mask fix at least one bit of the set, and
192 : !> does it leave at least one bit of the set free. Partial fixation
193 : !> means both flags are true. This enumeration shares no code with the
194 : !> IAND form inside fixes_partially.
195 : ! **************************************************************************************************
196 2 : SUBROUTINE check_fixes_partially()
197 : INTEGER, PARAMETER :: slot_sets(8) = [mask_b, mask_c, mask_diag, mask_ab_plane, mask_diag_ab,&
198 : mask_diag_ac, mask_diag_bc, mask_all]
199 :
200 : INTEGER :: bit_index, coupled_set, mask_value, &
201 : set_index
202 : LOGICAL :: expected_partial, set_has_fixed_bit, &
203 : set_has_free_bit
204 :
205 18 : DO set_index = 1, SIZE(slot_sets)
206 16 : coupled_set = slot_sets(set_index)
207 1042 : DO mask_value = 0, 63
208 : ! Independent expectation: enumerate the bits of the set.
209 : set_has_fixed_bit = .FALSE.
210 : set_has_free_bit = .FALSE.
211 7168 : DO bit_index = 0, 5
212 7168 : IF (BTEST(coupled_set, bit_index)) THEN
213 2944 : IF (BTEST(mask_value, bit_index)) THEN
214 : set_has_fixed_bit = .TRUE.
215 : ELSE
216 1472 : set_has_free_bit = .TRUE.
217 : END IF
218 : END IF
219 : END DO
220 1024 : expected_partial = set_has_fixed_bit .AND. set_has_free_bit
221 1040 : IF (fixes_partially(mask_value, coupled_set) .NEQV. expected_partial) THEN
222 0 : PRINT *, "check_fixes_partially: predicate disagrees with the bit enumeration"
223 0 : ERROR STOP 1
224 : END IF
225 : END DO
226 : END DO
227 2 : PRINT *, "check_fixes_partially: OK (64 masks x 8 slot sets)"
228 2 : END SUBROUTINE check_fixes_partially
229 :
230 : ! **************************************************************************************************
231 : !> \brief Compare slot_is_fixed against a modulo-power decomposition for all 64
232 : !> masks and the six slots (384 combinations).
233 : !> \par Method
234 : !> Bit (slot - 1) of the mask states whether slot is fixed. Extract that
235 : !> bit arithmetically: the remainder of the mask modulo 2**slot drops
236 : !> all bits at or above slot, so comparing it against 2**(slot - 1)
237 : !> reads exactly bit (slot - 1). BTEST is never used on this side.
238 : ! **************************************************************************************************
239 2 : SUBROUTINE check_slot_is_fixed()
240 : INTEGER :: mask_value, slot_index
241 :
242 130 : DO mask_value = 0, 63
243 898 : DO slot_index = 1, 6
244 768 : IF (slot_is_fixed(mask_value, slot_index) .NEQV. &
245 128 : MOD(mask_value, 2**slot_index) >= 2**(slot_index - 1)) THEN
246 0 : PRINT *, "check_slot_is_fixed: disagrees with the arithmetic decomposition"
247 0 : ERROR STOP 1
248 : END IF
249 : END DO
250 : END DO
251 2 : PRINT *, "check_slot_is_fixed: OK (64 masks x 6 slots)"
252 2 : END SUBROUTINE check_slot_is_fixed
253 :
254 : ! **************************************************************************************************
255 : !> \brief For every mask m, parsing the label of m must reproduce m. This ties the
256 : !> spelling of the grammar to its semantics in both directions.
257 : ! **************************************************************************************************
258 2 : SUBROUTINE check_label_parse_roundtrip()
259 : CHARACTER(LEN=default_string_length) :: tokens(16)
260 : INTEGER :: mask_value, ntok, parsed_mask
261 : TYPE(cell_type), POINTER :: cell
262 :
263 2 : CALL make_cell3d(cell)
264 : ! The label function maps a mask to a token string, the parser maps a
265 : ! token string back to a mask. Requiring the composition to be the
266 : ! identity for all 64 masks proves both directions at once.
267 130 : DO mask_value = 0, 63
268 128 : CALL split_tokens(cell_opt_constraint_label(mask_value), tokens, ntok)
269 128 : CALL parse_cell_constraint(tokens(:ntok), cell, .TRUE., parsed_mask)
270 130 : IF (parsed_mask /= mask_value) THEN
271 0 : PRINT *, "check_label_parse_roundtrip: parse(label(m)) /= m"
272 0 : ERROR STOP 1
273 : END IF
274 : END DO
275 2 : CALL cell_release(cell)
276 2 : PRINT *, "check_label_parse_roundtrip: OK (64 masks)"
277 2 : END SUBROUTINE check_label_parse_roundtrip
278 :
279 : ! **************************************************************************************************
280 : !> \brief The seven legacy constraint labels and NONE must not change. Downstream
281 : !> tools read these strings.
282 : ! **************************************************************************************************
283 2 : SUBROUTINE check_label_legacy_strings()
284 : ! The labels carry leading blanks for column alignment in the banner,
285 : ! so the comparison strips them with ADJUSTL before TRIM.
286 2 : IF (TRIM(ADJUSTL(cell_opt_constraint_label(0))) /= "NONE") THEN
287 0 : PRINT *, "check_label_legacy_strings: NONE label changed"
288 2 : ERROR STOP 1
289 : END IF
290 2 : IF (TRIM(ADJUSTL(cell_opt_constraint_label(mask_x))) /= "X") THEN
291 0 : PRINT *, "check_label_legacy_strings: X label changed"
292 2 : ERROR STOP 1
293 : END IF
294 2 : IF (TRIM(ADJUSTL(cell_opt_constraint_label(mask_y))) /= "Y") THEN
295 0 : PRINT *, "check_label_legacy_strings: Y label changed"
296 2 : ERROR STOP 1
297 : END IF
298 2 : IF (TRIM(ADJUSTL(cell_opt_constraint_label(mask_z))) /= "Z") THEN
299 0 : PRINT *, "check_label_legacy_strings: Z label changed"
300 2 : ERROR STOP 1
301 : END IF
302 2 : IF (TRIM(ADJUSTL(cell_opt_constraint_label(mask_xy))) /= "XY") THEN
303 0 : PRINT *, "check_label_legacy_strings: XY label changed"
304 2 : ERROR STOP 1
305 : END IF
306 2 : IF (TRIM(ADJUSTL(cell_opt_constraint_label(mask_xz))) /= "XZ") THEN
307 0 : PRINT *, "check_label_legacy_strings: XZ label changed"
308 2 : ERROR STOP 1
309 : END IF
310 2 : IF (TRIM(ADJUSTL(cell_opt_constraint_label(mask_yz))) /= "YZ") THEN
311 0 : PRINT *, "check_label_legacy_strings: YZ label changed"
312 2 : ERROR STOP 1
313 : END IF
314 2 : PRINT *, "check_label_legacy_strings: OK"
315 2 : END SUBROUTINE check_label_legacy_strings
316 :
317 : ! **************************************************************************************************
318 : !> \brief Compare keep_angles_conflict against a per-bit enumeration for all 64
319 : !> masks on orthorhombic and non-orthorhombic cells (128 combinations).
320 : !> \par Method
321 : !> An orthorhombic cell can never conflict. On a non-orthorhombic cell,
322 : !> the conflict is a partial fixation of the b vector group {BX,BY} or
323 : !> of the c vector group {CX,CY,CZ}. The expectation enumerates the
324 : !> bits of each group independently of the IAND form.
325 : ! **************************************************************************************************
326 2 : SUBROUTINE check_keep_angles_conflict()
327 : INTEGER :: bit_index, coupled_set, group_index, &
328 : mask_value, shape_index
329 : LOGICAL :: expected_conflict, is_orthorhombic, &
330 : set_has_fixed_bit, set_has_free_bit
331 :
332 6 : DO shape_index = 0, 1
333 4 : is_orthorhombic = (shape_index == 1)
334 262 : DO mask_value = 0, 63
335 256 : expected_conflict = .FALSE.
336 256 : IF (.NOT. is_orthorhombic) THEN
337 : ! Conflict = partial fixation of the b group or the c group.
338 384 : DO group_index = 1, 2
339 256 : IF (group_index == 1) THEN
340 : coupled_set = mask_b
341 : ELSE
342 128 : coupled_set = mask_c
343 : END IF
344 256 : set_has_fixed_bit = .FALSE.
345 256 : set_has_free_bit = .FALSE.
346 1792 : DO bit_index = 0, 5
347 1792 : IF (BTEST(coupled_set, bit_index)) THEN
348 640 : IF (BTEST(mask_value, bit_index)) THEN
349 : set_has_fixed_bit = .TRUE.
350 : ELSE
351 320 : set_has_free_bit = .TRUE.
352 : END IF
353 : END IF
354 : END DO
355 384 : IF (set_has_fixed_bit .AND. set_has_free_bit) expected_conflict = .TRUE.
356 : END DO
357 : END IF
358 260 : IF (keep_angles_conflict(mask_value, is_orthorhombic) .NEQV. expected_conflict) THEN
359 0 : PRINT *, "check_keep_angles_conflict: mismatch, mask", mask_value, &
360 0 : "orthorhombic", is_orthorhombic
361 0 : ERROR STOP 1
362 : END IF
363 : END DO
364 : END DO
365 2 : PRINT *, "check_keep_angles_conflict: OK (64 masks x 2 cell shapes)"
366 2 : END SUBROUTINE check_keep_angles_conflict
367 :
368 : ! **************************************************************************************************
369 : !> \brief Compare keep_symmetry_conflict against a per-bit enumeration for all 64
370 : !> masks and all twelve crystal systems (768 combinations).
371 : !> \par Method
372 : !> Each crystal system couples a set of cell components; the coupled set
373 : !> per system mirrors the table in the KEEP_SYMMETRY guard comment. The
374 : !> expectation is a partial fixation of that set, computed by bit
375 : !> enumeration. Systems without coupling map to an empty set and can
376 : !> never conflict.
377 : ! **************************************************************************************************
378 2 : SUBROUTINE check_keep_symmetry_conflict()
379 : INTEGER, PARAMETER :: crystal_system_ids(12) = [cell_sym_none, cell_sym_triclinic, &
380 : cell_sym_monoclinic, cell_sym_monoclinic_gamma_ab, cell_sym_orthorhombic, &
381 : cell_sym_tetragonal_ab, cell_sym_tetragonal_ac, cell_sym_tetragonal_bc, &
382 : cell_sym_rhombohedral, cell_sym_hexagonal_gamma_60, cell_sym_hexagonal_gamma_120, &
383 : cell_sym_cubic]
384 :
385 : INTEGER :: bit_index, coupled_set, mask_value, &
386 : system_index
387 : LOGICAL :: expected_conflict, set_has_fixed_bit, &
388 : set_has_free_bit
389 :
390 26 : DO system_index = 1, SIZE(crystal_system_ids)
391 : ! Mirror of the coupled-set table in the KEEP_SYMMETRY guard.
392 24 : SELECT CASE (crystal_system_ids(system_index))
393 : CASE (cell_sym_cubic)
394 2 : coupled_set = mask_diag
395 : CASE (cell_sym_rhombohedral)
396 2 : coupled_set = mask_all
397 : CASE (cell_sym_hexagonal_gamma_60, cell_sym_hexagonal_gamma_120, &
398 : cell_sym_monoclinic_gamma_ab)
399 6 : coupled_set = mask_ab_plane
400 : CASE (cell_sym_tetragonal_ab)
401 2 : coupled_set = mask_diag_ab
402 : CASE (cell_sym_tetragonal_ac)
403 2 : coupled_set = mask_diag_ac
404 : CASE (cell_sym_tetragonal_bc)
405 2 : coupled_set = mask_diag_bc
406 : CASE DEFAULT
407 : ! No coupling: triclinic, monoclinic, orthorhombic, none.
408 24 : coupled_set = 0
409 : END SELECT
410 1562 : DO mask_value = 0, 63
411 : set_has_fixed_bit = .FALSE.
412 : set_has_free_bit = .FALSE.
413 10752 : DO bit_index = 0, 5
414 10752 : IF (BTEST(coupled_set, bit_index)) THEN
415 3072 : IF (BTEST(mask_value, bit_index)) THEN
416 : set_has_fixed_bit = .TRUE.
417 : ELSE
418 1536 : set_has_free_bit = .TRUE.
419 : END IF
420 : END IF
421 : END DO
422 1536 : expected_conflict = set_has_fixed_bit .AND. set_has_free_bit
423 1536 : IF (keep_symmetry_conflict(mask_value, crystal_system_ids(system_index)) .NEQV. &
424 24 : expected_conflict) THEN
425 0 : PRINT *, "check_keep_symmetry_conflict: mismatch, mask", mask_value, &
426 0 : "system", crystal_system_ids(system_index)
427 0 : ERROR STOP 1
428 : END IF
429 : END DO
430 : END DO
431 2 : PRINT *, "check_keep_symmetry_conflict: OK (64 masks x 12 systems)"
432 2 : END SUBROUTINE check_keep_symmetry_conflict
433 :
434 : ! **************************************************************************************************
435 : !> \brief Classify the rejecting tokens without triggering an abort
436 : !> \par Method
437 : !> The token classification lives in the pure parse_token, so the
438 : !> rejection cases are testable in-process: AY, AZ and BZ must be
439 : !> rejected with the always-zero reason, the angle tokens with the
440 : !> not-supported reason, and a near-miss with the unknown-token reason
441 : !> naming the accepted vocabulary.
442 : ! **************************************************************************************************
443 10 : SUBROUTINE check_parse_token_rejections()
444 : CHARACTER(LEN=default_string_length) :: reason
445 : INTEGER :: mask_value
446 : LOGICAL :: accepted
447 :
448 : ! AY, AZ, BZ: the cell convention places a along x and b in the xy
449 : ! plane, so these components are identically zero. The reason says so.
450 2 : CALL parse_token("AY", accepted, mask_value, reason)
451 2 : IF (accepted .OR. INDEX(reason, "always zero") == 0) THEN
452 0 : PRINT *, "check_parse_token_rejections: AY not rejected with the always-zero reason"
453 0 : ERROR STOP 1
454 : END IF
455 2 : CALL parse_token("AZ", accepted, mask_value, reason)
456 2 : IF (accepted .OR. INDEX(reason, "always zero") == 0) THEN
457 0 : PRINT *, "check_parse_token_rejections: AZ not rejected with the always-zero reason"
458 0 : ERROR STOP 1
459 : END IF
460 2 : CALL parse_token("BZ", accepted, mask_value, reason)
461 2 : IF (accepted .OR. INDEX(reason, "always zero") == 0) THEN
462 0 : PRINT *, "check_parse_token_rejections: BZ not rejected with the always-zero reason"
463 0 : ERROR STOP 1
464 : END IF
465 : ! The angle tokens name what is missing rather than pretending support.
466 2 : CALL parse_token("ALPHA", accepted, mask_value, reason)
467 2 : IF (accepted .OR. INDEX(reason, "not supported") == 0) THEN
468 0 : PRINT *, "check_parse_token_rejections: ALPHA not rejected as unsupported"
469 0 : ERROR STOP 1
470 : END IF
471 : ! A near-miss gets the unknown-token reason with the accepted vocabulary.
472 2 : CALL parse_token("YX", accepted, mask_value, reason)
473 2 : IF (accepted .OR. INDEX(reason, "Unknown CELL_OPT CONSTRAINT token") == 0 .OR. &
474 : INDEX(reason, "Accepted tokens") == 0) THEN
475 0 : PRINT *, "check_parse_token_rejections: YX not rejected with the vocabulary"
476 0 : ERROR STOP 1
477 : END IF
478 2 : PRINT *, "check_parse_token_rejections: OK (AY AZ BZ ALPHA YX)"
479 2 : END SUBROUTINE check_parse_token_rejections
480 :
481 : ! **************************************************************************************************
482 : !> \brief The overlap warning branches on full versus partial coverage
483 : !> \par Method
484 : !> The warning classification is a pure decision: overlap equal to the
485 : !> token is a full redundancy; a non-empty proper subset is a partial
486 : !> overlap, and its additions are the complement of the old mask inside
487 : !> the token, which must be non-empty. Checked for all 64 x 63 pairs.
488 : ! **************************************************************************************************
489 2 : SUBROUTINE check_overlap_classification()
490 : INTEGER :: old_mask_value, overlap_value, &
491 : token_mask_value
492 :
493 130 : DO old_mask_value = 0, 63
494 8194 : DO token_mask_value = 1, 63
495 8064 : overlap_value = IAND(token_mask_value, old_mask_value)
496 8192 : IF (overlap_value /= token_mask_value .AND. overlap_value /= 0) THEN
497 : ! Partial overlap: the additions the warning names must be
498 : ! non-empty, else the token would be fully redundant.
499 5404 : IF (IAND(NOT(old_mask_value), token_mask_value) == 0) THEN
500 0 : PRINT *, "check_overlap_classification: partial overlap with empty additions"
501 0 : ERROR STOP 1
502 : END IF
503 : END IF
504 : END DO
505 : END DO
506 2 : PRINT *, "check_overlap_classification: OK (64 x 63 mask pairs)"
507 2 : END SUBROUTINE check_overlap_classification
508 :
509 : ! **************************************************************************************************
510 : !> \brief The guard decisions must agree with the projections they protect
511 : !> \par Method
512 : !> A guard rejects a mask because the projection and the hard zeroing
513 : !> would fight over the coupled slots. The signature of that fight is
514 : !> non-idempotence: with a consistent mask, applying
515 : !> apply_cell_constraints to its own output reproduces it, and the
516 : !> output lies in the projected subspace with every fixed slot exactly
517 : !> zero. With a conflicting mask, the projection pushes components back
518 : !> into slots the zeroing has emptied, so a second application moves
519 : !> the result for a generic gradient. The expectation comes from the
520 : !> guard predicate, the behavior from the projection code: the coupled
521 : !> set table and the SELECT CASE in apply_cell_constraints verify each
522 : !> other instead of a third hand-written copy.
523 : !> \note The monoclinic_gamma_ab projection is idempotent only on a cell
524 : !> with equal a and b lengths, and the KEEP_ANGLES projections divide
525 : !> by the norms of b and c. The shaped cells below satisfy both.
526 : ! **************************************************************************************************
527 2 : SUBROUTINE check_projection_guard_consistency()
528 : INTEGER, PARAMETER :: crystal_system_ids(12) = [cell_sym_none, cell_sym_triclinic, &
529 : cell_sym_monoclinic, cell_sym_monoclinic_gamma_ab, cell_sym_orthorhombic, &
530 : cell_sym_tetragonal_ab, cell_sym_tetragonal_ac, cell_sym_tetragonal_bc, &
531 : cell_sym_rhombohedral, cell_sym_hexagonal_gamma_60, cell_sym_hexagonal_gamma_120, &
532 : cell_sym_cubic]
533 :
534 : INTEGER :: mask_value, shape_index, system_index
535 : LOGICAL :: expected_conflict, is_orthorhombic
536 : TYPE(cell_type), POINTER :: cell
537 :
538 : ! Part one: KEEP_ANGLES against keep_angles_conflict, on an
539 : ! orthorhombic and a triclinic cell. The triclinic cell has nonzero
540 : ! in-plane components in b and c, so a partial fixation of either
541 : ! vector is visible in the projections.
542 6 : DO shape_index = 1, 2
543 4 : is_orthorhombic = (shape_index == 2)
544 4 : IF (is_orthorhombic) THEN
545 2 : CALL make_projection_cell(cell, .TRUE., cell_sym_orthorhombic)
546 : ELSE
547 2 : CALL make_projection_cell(cell, .FALSE., cell_sym_triclinic)
548 : END IF
549 260 : DO mask_value = 0, 63
550 256 : expected_conflict = keep_angles_conflict(mask_value, is_orthorhombic)
551 : CALL projection_case(cell, .TRUE., .FALSE., mask_value, expected_conflict, &
552 260 : "check_projection_guard_consistency: KEEP_ANGLES")
553 : END DO
554 6 : CALL cell_release(cell)
555 : END DO
556 :
557 : ! Part two: KEEP_SYMMETRY against keep_symmetry_conflict, on one
558 : ! shaped cell per crystal system.
559 26 : DO system_index = 1, SIZE(crystal_system_ids)
560 24 : CALL make_projection_cell(cell, .FALSE., crystal_system_ids(system_index))
561 1560 : DO mask_value = 0, 63
562 1536 : expected_conflict = keep_symmetry_conflict(mask_value, crystal_system_ids(system_index))
563 : CALL projection_case(cell, .FALSE., .TRUE., mask_value, expected_conflict, &
564 1560 : "check_projection_guard_consistency: KEEP_SYMMETRY")
565 : END DO
566 26 : CALL cell_release(cell)
567 : END DO
568 2 : PRINT *, "check_projection_guard_consistency: OK (64 masks x 14 cell shapes x 4 draws)"
569 2 : END SUBROUTINE check_projection_guard_consistency
570 :
571 : ! **************************************************************************************************
572 : !> \brief One cell and mask: a stationary projection iff the guard sees no conflict
573 : !> \param cell shaped cell carrying orthorhombic flag and symmetry id
574 : !> \param use_keep_angles run the KEEP_ANGLES projection
575 : !> \param use_keep_symmetry run the KEEP_SYMMETRY projection
576 : !> \param mask_value constraint mask under test
577 : !> \param expect_conflict guard decision for this mask and cell
578 : !> \param label check name prefix for failure messages
579 : ! **************************************************************************************************
580 1792 : SUBROUTINE projection_case(cell, use_keep_angles, use_keep_symmetry, mask_value, &
581 : expect_conflict, label)
582 : TYPE(cell_type), POINTER :: cell
583 : LOGICAL, INTENT(IN) :: use_keep_angles, use_keep_symmetry
584 : INTEGER, INTENT(IN) :: mask_value
585 : LOGICAL, INTENT(IN) :: expect_conflict
586 : CHARACTER(LEN=*), INTENT(IN) :: label
587 :
588 : INTEGER :: draw, slot
589 : LOGICAL :: any_draw_moved
590 : REAL(KIND=dp) :: scale
591 1792 : REAL(KIND=dp), DIMENSION(:), POINTER :: g1, g2
592 :
593 1792 : ALLOCATE (g1(6), g2(6))
594 1792 : any_draw_moved = .FALSE.
595 8960 : DO draw = 1, 4
596 50176 : DO slot = 1, 6
597 : ! Deterministic entries of order one, varied per draw so an
598 : ! accidental symmetry of one gradient cannot mask a moved
599 : ! result. The fractional part keeps entries away from zero.
600 50176 : g1(slot) = MOD(draw*7 + slot*13, 19) - 9 + 0.37_dp*slot + 0.11_dp*draw
601 : END DO
602 7168 : CALL apply_cell_constraints(g1, cell, use_keep_angles, use_keep_symmetry, mask_value)
603 93184 : g2 = g1
604 7168 : CALL apply_cell_constraints(g2, cell, use_keep_angles, use_keep_symmetry, mask_value)
605 50176 : scale = MAX(1.0_dp, MAXVAL(ABS(g1)))
606 : ! The hard zeroing runs last, so fixed slots are exactly zero in
607 : ! the output of the first application, whatever the projections
608 : ! did before it.
609 50176 : DO slot = 1, 6
610 50176 : IF (slot_is_fixed(mask_value, slot) .AND. g1(slot) /= 0.0_dp) THEN
611 0 : PRINT *, TRIM(label)//": fixed slot nonzero, mask", mask_value, "slot", slot
612 0 : ERROR STOP 1
613 : END IF
614 : END DO
615 8960 : IF (expect_conflict) THEN
616 22736 : IF (MAXVAL(ABS(g2 - g1)) > 1.0E-6_dp*scale) any_draw_moved = .TRUE.
617 : ELSE
618 : ! Consistent mask: the second application reproduces the first
619 : ! up to rounding, and the output lies in the projected
620 : ! subspace the projections define.
621 27440 : IF (MAXVAL(ABS(g2 - g1)) > 1.0E-12_dp*scale) THEN
622 0 : PRINT *, TRIM(label)//": projection not stationary, mask", mask_value
623 0 : ERROR STOP 1
624 : END IF
625 3920 : IF (use_keep_angles) CALL check_angles_output(g1, cell, scale, label, mask_value)
626 3920 : IF (use_keep_symmetry) CALL check_symmetry_output(g1, cell, scale, label, mask_value)
627 : END IF
628 : END DO
629 1792 : IF (expect_conflict .AND. .NOT. any_draw_moved) THEN
630 0 : PRINT *, TRIM(label)//": conflict mask never moved the projection, mask", mask_value
631 0 : ERROR STOP 1
632 : END IF
633 1792 : DEALLOCATE (g1, g2)
634 1792 : END SUBROUTINE projection_case
635 :
636 : ! **************************************************************************************************
637 : !> \brief A KEEP_ANGLES output must follow the cell vectors it projects onto
638 : !> \param g projected gradient (six cell components)
639 : !> \param cell shaped cell (hmat and orthorhombic flag are read)
640 : !> \param scale magnitude scale for the comparison tolerances
641 : !> \param label check name prefix for failure messages
642 : !> \param mask_value constraint mask under test
643 : ! **************************************************************************************************
644 576 : SUBROUTINE check_angles_output(g, cell, scale, label, mask_value)
645 : REAL(KIND=dp), DIMENSION(:), INTENT(IN) :: g
646 : TYPE(cell_type), POINTER :: cell
647 : REAL(KIND=dp), INTENT(IN) :: scale
648 : CHARACTER(LEN=*), INTENT(IN) :: label
649 : INTEGER, INTENT(IN) :: mask_value
650 :
651 576 : IF (cell%orthorhombic) THEN
652 : ! An orthorhombic cell keeps its off-axis slots exactly zero.
653 512 : IF (g(2) /= 0.0_dp .OR. g(4) /= 0.0_dp .OR. g(5) /= 0.0_dp) THEN
654 0 : PRINT *, TRIM(label)//": orthorhombic off-axis slot nonzero, mask", mask_value
655 0 : ERROR STOP 1
656 : END IF
657 : ELSE
658 : ! b lies in the xy plane: the projected pair is parallel to b when
659 : ! the two-dimensional cross product vanishes.
660 64 : IF (ABS(g(2)*cell%hmat(2, 2) - g(3)*cell%hmat(1, 2)) > 1.0E-12_dp*scale) THEN
661 0 : PRINT *, TRIM(label)//": b pair not parallel to b, mask", mask_value
662 0 : ERROR STOP 1
663 : END IF
664 : ! c spans all three directions: every cross-product component
665 : ! with c must vanish.
666 : IF (ABS(g(5)*cell%hmat(3, 3) - g(6)*cell%hmat(2, 3)) > 1.0E-12_dp*scale .OR. &
667 64 : ABS(g(6)*cell%hmat(1, 3) - g(4)*cell%hmat(3, 3)) > 1.0E-12_dp*scale .OR. &
668 : ABS(g(4)*cell%hmat(2, 3) - g(5)*cell%hmat(1, 3)) > 1.0E-12_dp*scale) THEN
669 0 : PRINT *, TRIM(label)//": c triple not parallel to c, mask", mask_value
670 0 : ERROR STOP 1
671 : END IF
672 : END IF
673 576 : END SUBROUTINE check_angles_output
674 :
675 : ! **************************************************************************************************
676 : !> \brief A KEEP_SYMMETRY output must satisfy the coupling of its crystal system
677 : !> \param g projected gradient (six cell components)
678 : !> \param cell shaped cell (symmetry id is read)
679 : !> \param scale magnitude scale for the comparison tolerances
680 : !> \param label check name prefix for failure messages
681 : !> \param mask_value constraint mask under test
682 : !> \note Equalities are asserted where the projection enforces equal slots
683 : !> (cubic, tetragonal) or fixed ratios (hexagonal). Rhombohedral
684 : !> couples all six slots with angle-dependent ratios and is carried
685 : !> by the stationarity check alone. Systems without coupling
686 : !> (none, triclinic) impose nothing.
687 : ! **************************************************************************************************
688 3344 : SUBROUTINE check_symmetry_output(g, cell, scale, label, mask_value)
689 : REAL(KIND=dp), DIMENSION(:), INTENT(IN) :: g
690 : TYPE(cell_type), POINTER :: cell
691 : REAL(KIND=dp), INTENT(IN) :: scale
692 : CHARACTER(LEN=*), INTENT(IN) :: label
693 : INTEGER, INTENT(IN) :: mask_value
694 :
695 3472 : SELECT CASE (cell%symmetry_id)
696 : CASE (cell_sym_cubic)
697 : IF (ABS(g(1) - g(3)) > 1.0E-12_dp*scale .OR. ABS(g(1) - g(6)) > 1.0E-12_dp*scale .OR. &
698 128 : g(2) /= 0.0_dp .OR. g(4) /= 0.0_dp .OR. g(5) /= 0.0_dp) THEN
699 0 : PRINT *, TRIM(label)//": cubic coupling violated, mask", mask_value
700 0 : ERROR STOP 1
701 : END IF
702 : CASE (cell_sym_tetragonal_ab)
703 : IF (ABS(g(1) - g(3)) > 1.0E-12_dp*scale .OR. g(2) /= 0.0_dp .OR. &
704 256 : g(4) /= 0.0_dp .OR. g(5) /= 0.0_dp) THEN
705 0 : PRINT *, TRIM(label)//": tetragonal_ab coupling violated, mask", mask_value
706 0 : ERROR STOP 1
707 : END IF
708 : CASE (cell_sym_tetragonal_ac)
709 : IF (ABS(g(1) - g(6)) > 1.0E-12_dp*scale .OR. g(2) /= 0.0_dp .OR. &
710 256 : g(4) /= 0.0_dp .OR. g(5) /= 0.0_dp) THEN
711 0 : PRINT *, TRIM(label)//": tetragonal_ac coupling violated, mask", mask_value
712 0 : ERROR STOP 1
713 : END IF
714 : CASE (cell_sym_tetragonal_bc)
715 : IF (ABS(g(3) - g(6)) > 1.0E-12_dp*scale .OR. g(2) /= 0.0_dp .OR. &
716 256 : g(4) /= 0.0_dp .OR. g(5) /= 0.0_dp) THEN
717 0 : PRINT *, TRIM(label)//": tetragonal_bc coupling violated, mask", mask_value
718 0 : ERROR STOP 1
719 : END IF
720 : CASE (cell_sym_orthorhombic)
721 512 : IF (g(2) /= 0.0_dp .OR. g(4) /= 0.0_dp .OR. g(5) /= 0.0_dp) THEN
722 0 : PRINT *, TRIM(label)//": orthorhombic off-axis slots nonzero, mask", mask_value
723 0 : ERROR STOP 1
724 : END IF
725 : CASE (cell_sym_monoclinic)
726 512 : IF (g(2) /= 0.0_dp .OR. g(5) /= 0.0_dp) THEN
727 0 : PRINT *, TRIM(label)//": monoclinic off-axis slots nonzero, mask", mask_value
728 0 : ERROR STOP 1
729 : END IF
730 : CASE (cell_sym_hexagonal_gamma_60)
731 : IF (ABS(g(2) - 0.5_dp*g(1)) > 1.0E-12_dp*scale .OR. &
732 : ABS(g(3) - SQRT(3.0_dp)*g(2)) > 1.0E-12_dp*scale .OR. &
733 128 : g(4) /= 0.0_dp .OR. g(5) /= 0.0_dp) THEN
734 0 : PRINT *, TRIM(label)//": hexagonal_gamma_60 coupling violated, mask", mask_value
735 0 : ERROR STOP 1
736 : END IF
737 : CASE (cell_sym_hexagonal_gamma_120)
738 : IF (ABS(g(2) + 0.5_dp*g(1)) > 1.0E-12_dp*scale .OR. &
739 : ABS(g(3) + SQRT(3.0_dp)*g(2)) > 1.0E-12_dp*scale .OR. &
740 128 : g(4) /= 0.0_dp .OR. g(5) /= 0.0_dp) THEN
741 0 : PRINT *, TRIM(label)//": hexagonal_gamma_120 coupling violated, mask", mask_value
742 0 : ERROR STOP 1
743 : END IF
744 : CASE (cell_sym_monoclinic_gamma_ab)
745 3344 : IF (g(4) /= 0.0_dp .OR. g(5) /= 0.0_dp) THEN
746 0 : PRINT *, TRIM(label)//": monoclinic_gamma_ab off-axis slots nonzero, mask", mask_value
747 0 : ERROR STOP 1
748 : END IF
749 : CASE DEFAULT
750 : ! none, triclinic, rhombohedral: carried by the stationarity
751 : ! check.
752 : END SELECT
753 3344 : END SUBROUTINE check_symmetry_output
754 :
755 : ! **************************************************************************************************
756 : !> \brief The pressure-criterion predicate must fire exactly on the pinned-shear domain
757 : !> \par Method
758 : !> The oracle enumerates slot bits by position: the diagonal slots
759 : !> AX, BY, CZ are bits 0, 2 and 5, the shear slots BX, CX, CY are
760 : !> bits 1, 3 and 4. The criterion is expected to fire on a
761 : !> non-orthorhombic cell when some but not all diagonal slots are
762 : !> fixed and either the mask pins a shear slot or KEEP_ANGLES pins
763 : !> the angles together with a fixed diagonal slot. Zero or all
764 : !> diagonal slots fixed never fire (the hydrostatic trace, resp. the
765 : !> external-pressure bypass). The firing counts per cell shape and
766 : !> KEEP_ANGLES setting are asserted as well, and mask NONE never
767 : !> fires, so existing KEEP_ANGLE regtests without a constraint stay
768 : !> warning-free.
769 : ! **************************************************************************************************
770 2 : SUBROUTINE check_pres_crit_shear_pinned()
771 : INTEGER :: count_fired, ka_index, mask_value, &
772 : shape_index
773 : LOGICAL :: all_diag_fixed, diag_hit, expected, &
774 : is_orthorhombic, shear_hit, &
775 : use_keep_angles
776 :
777 6 : DO shape_index = 0, 1
778 4 : is_orthorhombic = (shape_index == 1)
779 14 : DO ka_index = 0, 1
780 8 : use_keep_angles = (ka_index == 1)
781 8 : count_fired = 0
782 520 : DO mask_value = 0, 63
783 : ! Independent expectation from the bit positions.
784 : all_diag_fixed = BTEST(mask_value, 0) .AND. BTEST(mask_value, 2) .AND. &
785 512 : BTEST(mask_value, 5)
786 : shear_hit = BTEST(mask_value, 1) .OR. BTEST(mask_value, 3) .OR. &
787 512 : BTEST(mask_value, 4)
788 : diag_hit = BTEST(mask_value, 0) .OR. BTEST(mask_value, 2) .OR. &
789 512 : BTEST(mask_value, 5)
790 : expected = .NOT. is_orthorhombic .AND. .NOT. all_diag_fixed .AND. &
791 512 : (shear_hit .OR. (use_keep_angles .AND. diag_hit))
792 : IF (pres_crit_shear_pinned(mask_value, is_orthorhombic, use_keep_angles) &
793 512 : .NEQV. expected) THEN
794 0 : PRINT *, "check_pres_crit_shear_pinned: mismatch, mask", mask_value, &
795 0 : "orthorhombic", is_orthorhombic, "keep_angles", use_keep_angles
796 0 : ERROR STOP 1
797 : END IF
798 520 : IF (expected) count_fired = count_fired + 1
799 : END DO
800 : ! Firing counts of the pinned-shear domain: an orthorhombic
801 : ! cell never fires; on an oblique cell the mask domain has 56
802 : ! members of which the all-diagonals-fixed ones are excluded,
803 : ! and KEEP_ANGLES adds every mask with a fixed diagonal slot.
804 8 : IF (is_orthorhombic) THEN
805 4 : IF (count_fired /= 0) THEN
806 0 : PRINT *, "check_pres_crit_shear_pinned: fired on an orthorhombic cell"
807 0 : ERROR STOP 1
808 : END IF
809 : CYCLE
810 : END IF
811 8 : IF (use_keep_angles) THEN
812 2 : IF (count_fired /= 55) THEN
813 0 : PRINT *, "check_pres_crit_shear_pinned: expected 55 masks, got", count_fired
814 0 : ERROR STOP 1
815 : END IF
816 : ELSE
817 2 : IF (count_fired /= 49) THEN
818 0 : PRINT *, "check_pres_crit_shear_pinned: expected 49 masks, got", count_fired
819 0 : ERROR STOP 1
820 : END IF
821 : END IF
822 : END DO
823 : END DO
824 : ! Effective warned set under KEEP_ANGLES: the merged abort guard
825 : ! (keep_angles_conflict) rejects every mask that fixes part of b or c,
826 : ! so only whole-vector and a-only masks reach the warning; of those,
827 : ! NONE stays outside the domain and ABC takes the bypass.
828 2 : count_fired = 0
829 130 : DO mask_value = 0, 63
830 128 : IF (pres_crit_shear_pinned(mask_value, .FALSE., .TRUE.) .AND. &
831 2 : .NOT. keep_angles_conflict(mask_value, .FALSE.)) THEN
832 12 : count_fired = count_fired + 1
833 42 : IF (.NOT. ANY(mask_value == [1, 6, 7, 56, 57, 62])) THEN
834 0 : PRINT *, "check_pres_crit_shear_pinned: unexpected survivor", mask_value
835 0 : ERROR STOP 1
836 : END IF
837 : END IF
838 : END DO
839 2 : IF (count_fired /= 6) THEN
840 0 : PRINT *, "check_pres_crit_shear_pinned: expected 6 effective masks, got", count_fired
841 0 : ERROR STOP 1
842 : END IF
843 2 : PRINT *, "check_pres_crit_shear_pinned: OK (64 masks x 2 cell shapes x 2 flags, 6 effective)"
844 2 : END SUBROUTINE check_pres_crit_shear_pinned
845 :
846 : ! **************************************************************************************************
847 : !> \brief Create a 3D-periodic cell. parse_cell_constraint reads only the perd field.
848 : !> \param cell ...
849 : ! **************************************************************************************************
850 4 : SUBROUTINE make_cell3d(cell)
851 : TYPE(cell_type), POINTER :: cell
852 :
853 4 : NULLIFY (cell)
854 4 : CALL cell_create(cell)
855 16 : cell%perd(:) = 1
856 4 : END SUBROUTINE make_cell3d
857 :
858 : ! **************************************************************************************************
859 : !> \brief Create a cell periodic in x and y. The non-periodic z direction must be
860 : !> constrained.
861 : !> \param cell ...
862 : ! **************************************************************************************************
863 2 : SUBROUTINE make_cell2d(cell)
864 : TYPE(cell_type), POINTER :: cell
865 :
866 2 : NULLIFY (cell)
867 2 : CALL cell_create(cell)
868 8 : cell%perd(:) = [1, 1, 0]
869 2 : END SUBROUTINE make_cell2d
870 :
871 : ! **************************************************************************************************
872 : !> \brief Create a 3D-periodic cell with a lattice consistent with symmetry_id
873 : !> \param cell ...
874 : !> \param is_orthorhombic sets the orthorhombic flag read by KEEP_ANGLES
875 : !> \param symmetry_id crystal system read by KEEP_SYMMETRY
876 : !> \note apply_cell_constraints reads hmat, the orthorhombic flag and
877 : !> symmetry_id. Each system whose projection reads hmat gets a
878 : !> consistent lattice: monoclinic_gamma_ab needs equal a and b
879 : !> lengths, hexagonal needs the named gamma, rhombohedral needs equal
880 : !> pairwise angles. The other projections are lattice independent and
881 : !> share the generic triclinic lattice.
882 : ! **************************************************************************************************
883 28 : SUBROUTINE make_projection_cell(cell, is_orthorhombic, symmetry_id)
884 : TYPE(cell_type), POINTER :: cell
885 : LOGICAL, INTENT(IN) :: is_orthorhombic
886 : INTEGER, INTENT(IN) :: symmetry_id
887 :
888 28 : NULLIFY (cell)
889 28 : CALL cell_create(cell)
890 112 : cell%perd(:) = 1
891 28 : cell%orthorhombic = is_orthorhombic
892 28 : cell%symmetry_id = symmetry_id
893 4 : SELECT CASE (symmetry_id)
894 : CASE (cell_sym_orthorhombic)
895 : cell%hmat = RESHAPE([10.0_dp, 0.0_dp, 0.0_dp, 0.0_dp, 9.0_dp, 0.0_dp, 0.0_dp, 0.0_dp, &
896 52 : 8.0_dp], [3, 3])
897 : CASE (cell_sym_monoclinic_gamma_ab, cell_sym_hexagonal_gamma_60)
898 : ! a and b of length 2 with gamma = 60 degree, c orthogonal.
899 : cell%hmat = RESHAPE([2.0_dp, 0.0_dp, 0.0_dp, 1.0_dp, 1.7320508075688772_dp, 0.0_dp, &
900 52 : 0.0_dp, 0.0_dp, 8.0_dp], [3, 3])
901 : CASE (cell_sym_hexagonal_gamma_120)
902 : cell%hmat = RESHAPE([2.0_dp, 0.0_dp, 0.0_dp, -1.0_dp, 1.7320508075688772_dp, 0.0_dp, &
903 26 : 0.0_dp, 0.0_dp, 8.0_dp], [3, 3])
904 : CASE (cell_sym_rhombohedral)
905 : ! Three vectors of length 1 with pairwise angles of 60 degree.
906 : cell%hmat = RESHAPE([1.0_dp, 0.0_dp, 0.0_dp, 0.5_dp, 0.8660254037844386_dp, 0.0_dp, &
907 26 : 0.5_dp, 0.2886751345948129_dp, 0.8164965809277260_dp], [3, 3])
908 : CASE DEFAULT
909 : ! Generic triclinic lattice: b and c carry nonzero in-plane
910 : ! components, so partial fixations show up in the projections.
911 : cell%hmat = RESHAPE([10.0_dp, 0.0_dp, 0.0_dp, 1.0_dp, 9.0_dp, 0.0_dp, 0.5_dp, 1.5_dp, &
912 220 : 8.0_dp], [3, 3])
913 : END SELECT
914 28 : END SUBROUTINE make_projection_cell
915 :
916 : END PROGRAM cell_opt_utils_TEST
|