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 2 : PROGRAM qs_ot_minimizer_unittest
9 2 : USE ieee_arithmetic, ONLY: ieee_positive_inf,&
10 : ieee_quiet_nan,&
11 : ieee_value
12 : USE kinds, ONLY: dp
13 : USE qs_ot_minimizer, ONLY: &
14 : broyden_history_restart_required, cg_history_restart_required, &
15 : lbfgs_curvature_damping_shift, lbfgs_history_restart_required, &
16 : lbfgs_response_secant_parameters, lbfgs_step_restart_required, ot_mermin_response_assess, &
17 : ot_mermin_response_candidate_preferred, ot_mermin_response_compare, &
18 : ot_mermin_response_preparation_needed, ot_mermin_response_probe, &
19 : ot_mermin_response_shadow_followup, ot_mermin_secant_curvature
20 : USE qs_scf_loop_utils, ONLY: qs_scf_kp_ref_refresh_ready,&
21 : qs_scf_kp_search_endpoint
22 :
23 : IMPLICIT NONE
24 :
25 : LOGICAL :: curvature_valid, response_good, valid_response
26 : REAL(KIND=dp) :: baseline_drop, damping_shift, infinity, &
27 : mixed_curvature, &
28 : measured_drop, not_a_number, predicted_drop, &
29 : response_advantage, response_gain, response_quality, &
30 : response_scale, response_weight, residual_ratio, &
31 : secant_curvature, sy_damped, yy_damped
32 :
33 2 : IF (.NOT. broyden_history_restart_required(.TRUE., .FALSE., .TRUE.)) THEN
34 0 : ERROR STOP "Mermin Broyden did not restart a non-descent product history"
35 : END IF
36 2 : IF (.NOT. broyden_history_restart_required(.TRUE., .TRUE., .FALSE.)) THEN
37 0 : ERROR STOP "Explicit Broyden history restart was ignored"
38 : END IF
39 2 : IF (broyden_history_restart_required(.FALSE., .TRUE., .TRUE.)) THEN
40 0 : ERROR STOP "Broyden history restarted for a descent direction"
41 : END IF
42 2 : IF (broyden_history_restart_required(.TRUE., .FALSE., .FALSE.)) THEN
43 0 : ERROR STOP "Orbital-only Broyden default behavior changed"
44 : END IF
45 :
46 2 : IF (.NOT. cg_history_restart_required(.TRUE., 0.5_dp + 32.0_dp*EPSILON(1.0_dp), 0.5_dp)) THEN
47 0 : ERROR STOP "Occupation-preconditioned CG retained unresolved free-energy history"
48 : END IF
49 2 : IF (cg_history_restart_required(.TRUE., 0.5_dp + 256.0_dp*EPSILON(1.0_dp), 0.5_dp)) THEN
50 0 : ERROR STOP "Occupation-preconditioned CG discarded resolved free-energy history"
51 : END IF
52 2 : IF (cg_history_restart_required(.FALSE., 0.5_dp, 0.5_dp)) THEN
53 0 : ERROR STOP "Conventional CG history changed at an energy plateau"
54 : END IF
55 2 : IF (.NOT. cg_history_restart_required(.TRUE., 1.0E6_dp + &
56 : 32.0_dp*EPSILON(1.0_dp)*1.0E6_dp, 1.0E6_dp)) THEN
57 0 : ERROR STOP "CG free-energy history restart is not scale aware"
58 : END IF
59 :
60 2 : damping_shift = lbfgs_curvature_damping_shift(0.6_dp, 1.0_dp, 1.0_dp, 0.5_dp)
61 2 : IF (damping_shift /= 0.0_dp) ERROR STOP "Well-conditioned L-BFGS curvature was damped"
62 :
63 2 : damping_shift = lbfgs_curvature_damping_shift(0.1_dp, 1.0_dp, 1.0_dp, 0.5_dp)
64 2 : IF (damping_shift <= 0.0_dp) ERROR STOP "Weak L-BFGS curvature was not damped"
65 2 : sy_damped = 0.1_dp + damping_shift
66 2 : yy_damped = 1.0_dp + 2.0_dp*damping_shift*0.1_dp + damping_shift**2
67 2 : IF (sy_damped <= 0.5_dp*SQRT(yy_damped)) THEN
68 0 : ERROR STOP "L-BFGS damping did not satisfy the relative-curvature bound"
69 : END IF
70 :
71 2 : damping_shift = lbfgs_curvature_damping_shift(-0.1_dp, 1.0_dp, 1.0_dp, 0.2_dp)
72 2 : IF (damping_shift <= 0.0_dp) ERROR STOP "Negative L-BFGS curvature was not regularized"
73 :
74 2 : IF (lbfgs_curvature_damping_shift(0.1_dp, 0.0_dp, 1.0_dp, 0.5_dp) /= 0.0_dp) THEN
75 0 : ERROR STOP "L-BFGS damping accepted a zero-length step"
76 : END IF
77 :
78 2 : IF (lbfgs_history_restart_required(100.0_dp, 1.0_dp)) THEN
79 0 : ERROR STOP "L-BFGS history restarted at the exact gradient-growth threshold"
80 : END IF
81 2 : IF (.NOT. lbfgs_history_restart_required(100.0001_dp, 1.0_dp)) THEN
82 0 : ERROR STOP "L-BFGS history did not restart above the gradient-growth threshold"
83 : END IF
84 2 : IF (.NOT. lbfgs_history_restart_required(400.0004_dp, 4.0_dp)) THEN
85 0 : ERROR STOP "L-BFGS gradient-growth restart is not scale invariant"
86 : END IF
87 2 : IF (lbfgs_history_restart_required(1.0_dp, 0.0_dp)) THEN
88 0 : ERROR STOP "L-BFGS history restarted without a valid preceding gradient"
89 : END IF
90 :
91 2 : IF (lbfgs_step_restart_required(1.0E-6_dp*0.1_dp, 0.1_dp)) THEN
92 0 : ERROR STOP "L-BFGS step restarted at the exact line-search collapse threshold"
93 : END IF
94 2 : IF (.NOT. lbfgs_step_restart_required(0.999E-6_dp*0.1_dp, 0.1_dp)) THEN
95 0 : ERROR STOP "L-BFGS step did not restart below the line-search collapse threshold"
96 : END IF
97 2 : IF (.NOT. lbfgs_step_restart_required(-0.999E-6_dp*2.5_dp, 2.5_dp)) THEN
98 0 : ERROR STOP "L-BFGS step restart does not use the accepted-step magnitude"
99 : END IF
100 2 : IF (lbfgs_step_restart_required(0.0_dp, 0.0_dp)) THEN
101 0 : ERROR STOP "L-BFGS step restarted without a valid reference step"
102 : END IF
103 :
104 : CALL lbfgs_response_secant_parameters( &
105 2 : 1.0_dp, 4.0_dp, 2.0_dp, 9.0_dp, response_scale, response_weight, valid_response)
106 2 : IF (.NOT. valid_response) ERROR STOP "Valid L-BFGS response secant was rejected"
107 2 : IF (ABS(response_scale - 1.5_dp) > 1.0E-14_dp) THEN
108 0 : ERROR STOP "L-BFGS response secant did not match the H0 direction norm"
109 : END IF
110 2 : IF (response_weight /= 1.0_dp) ERROR STOP "Well-aligned L-BFGS response secant was damped"
111 :
112 : CALL lbfgs_response_secant_parameters( &
113 2 : 0.01_dp, 4.0_dp, 2.0_dp, 9.0_dp, response_scale, response_weight, valid_response)
114 2 : IF (.NOT. valid_response) ERROR STOP "Dampable L-BFGS response secant was rejected"
115 : mixed_curvature = response_weight*response_scale*0.01_dp + &
116 2 : (1.0_dp - response_weight)*2.0_dp
117 2 : IF (ABS(mixed_curvature - 0.4_dp) > 1.0E-14_dp) THEN
118 0 : ERROR STOP "L-BFGS response secant does not satisfy the Powell curvature bound"
119 : END IF
120 2 : IF (response_weight <= 0.0_dp .OR. response_weight >= 1.0_dp) THEN
121 0 : ERROR STOP "Weak L-BFGS response secant was not convexly damped"
122 : END IF
123 :
124 : CALL lbfgs_response_secant_parameters( &
125 2 : -1.0_dp, 4.0_dp, 2.0_dp, 9.0_dp, response_scale, response_weight, valid_response)
126 2 : IF (valid_response) ERROR STOP "Non-descent L-BFGS response secant was accepted"
127 2 : IF (response_weight /= 0.0_dp) ERROR STOP "Rejected L-BFGS response secant changed H0"
128 :
129 : CALL ot_mermin_response_assess( &
130 : 10.0_dp, 9.6_dp, 1.0_dp, 0.8_dp, 1.0_dp, 1.0_dp, 0.5_dp, 0.1_dp, &
131 2 : predicted_drop, measured_drop, response_quality, residual_ratio, response_good)
132 : IF (.NOT. response_good .OR. ABS(predicted_drop - 0.375_dp) > 1.0E-14_dp .OR. &
133 : ABS(measured_drop - 0.4_dp) > 1.0E-14_dp .OR. &
134 2 : ABS(response_quality - 0.4_dp/0.375_dp) > 1.0E-14_dp .OR. &
135 : ABS(residual_ratio - 0.8_dp) > 1.0E-14_dp) THEN
136 0 : ERROR STOP "Useful finite Mermin response sample was not accepted"
137 : END IF
138 : CALL ot_mermin_response_assess( &
139 : 10.0_dp, 9.6_dp, 1.0_dp, 1.5_dp, 1.0_dp, 1.0_dp, 0.5_dp, 0.1_dp, &
140 2 : predicted_drop, measured_drop, response_quality, residual_ratio, response_good)
141 2 : IF (response_good) ERROR STOP "Residual-growing finite response sample was accepted"
142 : CALL ot_mermin_response_assess( &
143 : 10.0_dp, 9.6_dp, 1.0_dp, 1.05_dp, 1.0_dp, 1.0_dp, 0.5_dp, 0.1_dp, &
144 2 : predicted_drop, measured_drop, response_quality, residual_ratio, response_good)
145 2 : IF (response_good) ERROR STOP "Mildly residual-growing finite response sample was accepted"
146 : CALL ot_mermin_response_assess( &
147 : 10.0_dp, 9.6_dp, 1.0_dp, 0.8_dp, 0.1_dp, 10.0_dp, 1.0_dp, 0.1_dp, &
148 2 : predicted_drop, measured_drop, response_quality, residual_ratio, response_good)
149 2 : IF (response_good .OR. predicted_drop >= 0.0_dp) THEN
150 0 : ERROR STOP "Indefinite non-descent response prediction was accepted"
151 : END IF
152 : CALL ot_mermin_response_assess( &
153 : 10.0_dp, 9.6_dp, 1.0_dp, 0.8_dp, 1.0_dp, 1.0_dp, 1.0E-10_dp, 0.1_dp, &
154 2 : predicted_drop, measured_drop, response_quality, residual_ratio, response_good)
155 2 : IF (response_good) ERROR STOP "Collapsed response line search was accepted"
156 :
157 : CALL ot_mermin_secant_curvature(10.0_dp, 9.6_dp, 1.0_dp, 0.5_dp, &
158 2 : secant_curvature, curvature_valid)
159 2 : IF (.NOT. curvature_valid .OR. ABS(secant_curvature - 0.8_dp) > 1.0E-14_dp) THEN
160 0 : ERROR STOP "Accepted Mermin secant curvature was not recovered"
161 : END IF
162 : CALL ot_mermin_secant_curvature(10.0_dp, 9.6_dp, 1.0_dp, 0.0_dp, &
163 2 : secant_curvature, curvature_valid)
164 2 : IF (curvature_valid .OR. secant_curvature /= 0.0_dp) THEN
165 0 : ERROR STOP "Collapsed Mermin secant produced a curvature"
166 : END IF
167 :
168 : CALL ot_mermin_response_compare( &
169 : 10.0_dp, 9.6_dp, 1.0_dp, 0.8_dp, 1.0_dp, 1.0_dp, 0.5_dp, &
170 2 : response_advantage, residual_ratio, response_good)
171 2 : IF (.NOT. response_good .OR. response_advantage <= 0.0_dp .OR. &
172 : ABS(residual_ratio - 0.8_dp) > 1.0E-14_dp) THEN
173 0 : ERROR STOP "Improved passive finite response prediction was not retained"
174 : END IF
175 : CALL ot_mermin_response_compare( &
176 : 10.0_dp, 9.6_dp, 1.0_dp, 0.8_dp, 1.0_dp, -1.0_dp, 0.5_dp, &
177 2 : response_advantage, residual_ratio, response_good)
178 2 : IF (response_good .OR. response_advantage >= 0.0_dp) THEN
179 0 : ERROR STOP "Worse passive finite response prediction was retained"
180 : END IF
181 :
182 : CALL ot_mermin_response_candidate_preferred( &
183 : 1.0_dp, 1.0_dp, 1.0_dp, 1.0_dp, 2.0_dp, 0.5_dp, &
184 2 : baseline_drop, predicted_drop, response_gain, response_good)
185 : IF (.NOT. response_good .OR. ABS(baseline_drop - 0.25_dp) > 1.0E-14_dp .OR. &
186 2 : ABS(predicted_drop - 0.375_dp) > 1.0E-14_dp .OR. response_gain <= 0.0_dp) THEN
187 0 : ERROR STOP "Better coupled Mermin candidate was not preferred"
188 : END IF
189 : CALL ot_mermin_response_candidate_preferred( &
190 : 1.0_dp, 1.0_dp, 3.0_dp, 1.0_dp, 2.0_dp, 0.5_dp, &
191 2 : baseline_drop, predicted_drop, response_gain, response_good)
192 2 : IF (response_good .OR. response_gain >= 0.0_dp) THEN
193 0 : ERROR STOP "Worse coupled Mermin candidate was preferred"
194 : END IF
195 : CALL ot_mermin_response_candidate_preferred( &
196 : 4.0_dp, 4.0_dp, 4.0_dp, 4.0_dp, 8.0_dp, 0.5_dp, &
197 2 : baseline_drop, predicted_drop, response_gain, response_good)
198 2 : IF (.NOT. response_good .OR. ABS(response_gain - 1.0_dp/3.0_dp) > 1.0E-14_dp) THEN
199 0 : ERROR STOP "Coupled Mermin candidate comparison is not scale invariant"
200 : END IF
201 : CALL ot_mermin_response_candidate_preferred( &
202 : 1.0_dp, 1.0_dp, 1.0_dp, 0.0_dp, 2.0_dp, 0.5_dp, &
203 2 : baseline_drop, predicted_drop, response_gain, response_good)
204 2 : IF (response_good) ERROR STOP "Coupled Mermin candidate ignored unresolved accepted curvature"
205 :
206 2 : IF (.NOT. ot_mermin_response_probe(.TRUE., 1.0E-3_dp, 8, 3, 0, 0)) THEN
207 0 : ERROR STOP "Uncalibrated finite response probe was not scheduled"
208 : END IF
209 2 : IF (ot_mermin_response_probe(.TRUE., 1.0E-3_dp, 8, 2, 0, 0)) THEN
210 0 : ERROR STOP "Finite response probe ignored passive accepted-step calibration"
211 : END IF
212 2 : IF (ot_mermin_response_probe(.TRUE., 1.0E-3_dp, 7, 3, 0, 0)) THEN
213 0 : ERROR STOP "Uncalibrated finite response probe was scheduled too early"
214 : END IF
215 2 : IF (.NOT. ot_mermin_response_probe(.TRUE., 1.0E-3_dp, 9, 3, 0, 0)) THEN
216 0 : ERROR STOP "Calibrated finite response missed its one-step eligibility window"
217 : END IF
218 2 : IF (.NOT. ot_mermin_response_probe(.TRUE., 1.0E-3_dp, 4, 3, 3, 0)) THEN
219 0 : ERROR STOP "Calibrated finite response probe did not use the shorter interval"
220 : END IF
221 2 : IF (ot_mermin_response_probe(.TRUE., 1.0E-3_dp, 8, 3, 3, 1)) THEN
222 0 : ERROR STOP "Finite response cooldown was ignored"
223 : END IF
224 2 : IF (ot_mermin_response_probe(.TRUE., 3.0E-3_dp, 8, 3, 3, 0)) THEN
225 0 : ERROR STOP "Finite response was probed outside its local residual regime"
226 : END IF
227 :
228 2 : IF (.NOT. ot_mermin_response_preparation_needed( &
229 : 1.0_dp, 5, 3, 0, 6, .TRUE.)) THEN
230 0 : ERROR STOP "Pending finite-response shadow was not completed"
231 : END IF
232 2 : IF (ot_mermin_response_preparation_needed( &
233 : 5.0E-3_dp, 6, 2, 0, 0, .FALSE.)) THEN
234 0 : ERROR STOP "Finite response was prepared outside its calibration residual regime"
235 : END IF
236 2 : IF (.NOT. ot_mermin_response_preparation_needed( &
237 : 3.0E-3_dp, 4, 2, 0, 0, .FALSE.)) THEN
238 0 : ERROR STOP "Finite response did not prepare its initial shadow calibration"
239 : END IF
240 2 : IF (.NOT. ot_mermin_response_preparation_needed( &
241 : 1.0E-3_dp, 6, 3, 0, 0, .FALSE.)) THEN
242 0 : ERROR STOP "Finite response did not seed the next sparse probe window"
243 : END IF
244 2 : IF (ot_mermin_response_preparation_needed( &
245 : 1.0E-3_dp, 5, 3, 0, 0, .FALSE.)) THEN
246 0 : ERROR STOP "Finite response was prepared between sparse probe windows"
247 : END IF
248 2 : IF (ot_mermin_response_preparation_needed( &
249 : 1.0E-3_dp, 6, 3, 0, 2, .FALSE.)) THEN
250 0 : ERROR STOP "Finite response preparation ignored an active cooldown"
251 : END IF
252 :
253 2 : IF (.NOT. ot_mermin_response_shadow_followup(1.0E-3_dp, 7, 3, 0, 0)) THEN
254 0 : ERROR STOP "Finite response did not retain the probe-window seed shadow"
255 : END IF
256 2 : IF (.NOT. ot_mermin_response_shadow_followup(1.0E-3_dp, 8, 3, 0, 0)) THEN
257 0 : ERROR STOP "Finite response did not retain the paired probe shadow"
258 : END IF
259 2 : IF (ot_mermin_response_shadow_followup(1.0E-3_dp, 9, 3, 0, 0)) THEN
260 0 : ERROR STOP "Finite response retained a shadow beyond its probe window"
261 : END IF
262 2 : IF (.NOT. ot_mermin_response_shadow_followup(3.0E-3_dp, 2, 2, 0, 0)) THEN
263 0 : ERROR STOP "Finite response stopped before completing initial calibration"
264 : END IF
265 :
266 2 : IF (qs_scf_kp_ref_refresh_ready(11, 1.0E-4_dp, 1.0E-6_dp, .TRUE.)) THEN
267 0 : ERROR STOP "K-point REF refresh accepted insufficient history"
268 : END IF
269 2 : IF (qs_scf_kp_ref_refresh_ready(12, 1.0E-4_dp, 1.0E-6_dp, .FALSE.)) THEN
270 0 : ERROR STOP "K-point REF refresh accepted a line-search trial point"
271 : END IF
272 2 : IF (qs_scf_kp_ref_refresh_ready(12, 0.999E-5_dp, 1.0E-6_dp, .TRUE.)) THEN
273 0 : ERROR STOP "K-point REF refresh accepted a converged residual"
274 : END IF
275 2 : IF (qs_scf_kp_ref_refresh_ready(12, 2.0001E-4_dp, 1.0E-6_dp, .TRUE.)) THEN
276 0 : ERROR STOP "K-point REF refresh accepted a residual outside the asymptotic window"
277 : END IF
278 2 : IF (.NOT. qs_scf_kp_ref_refresh_ready(12, 1.0E-4_dp, 1.0E-6_dp, .TRUE.)) THEN
279 0 : ERROR STOP "K-point REF refresh rejected a mature asymptotic state"
280 : END IF
281 2 : IF (qs_scf_kp_ref_refresh_ready(12, 0.75E-6_dp, 1.0E-6_dp, .TRUE.)) THEN
282 0 : ERROR STOP "K-point REF refresh entered the exit window before a physical rebuild"
283 : END IF
284 2 : IF (.NOT. qs_scf_kp_ref_refresh_ready(12, 0.75E-6_dp, 1.0E-6_dp, .TRUE., .TRUE.)) THEN
285 0 : ERROR STOP "K-point REF refresh rejected a mature exit-only state"
286 : END IF
287 2 : IF (.NOT. qs_scf_kp_ref_refresh_ready(12, 5.0E-6_dp, 1.0E-6_dp, .TRUE., .TRUE.)) THEN
288 0 : ERROR STOP "K-point REF refresh rejected a stagnant post-rebuild tail"
289 : END IF
290 2 : IF (qs_scf_kp_ref_refresh_ready(12, 0.2E-6_dp, 1.0E-6_dp, .TRUE., .TRUE.)) THEN
291 0 : ERROR STOP "K-point REF refresh accepted a safely converged exit state"
292 : END IF
293 2 : IF (.NOT. ALL([qs_scf_kp_search_endpoint("OT CG"), &
294 : qs_scf_kp_search_endpoint("OT SD"), &
295 : qs_scf_kp_search_endpoint("OT DIIS"), &
296 : qs_scf_kp_search_endpoint("OT BROY"), &
297 : qs_scf_kp_search_endpoint("OT LBFGS")])) THEN
298 0 : ERROR STOP "K-point REF refresh rejected a supported minimizer endpoint"
299 : END IF
300 2 : IF (qs_scf_kp_search_endpoint("OT LS") .OR. qs_scf_kp_search_endpoint("OT Sync")) THEN
301 0 : ERROR STOP "K-point REF refresh counted a non-endpoint OT iteration"
302 : END IF
303 :
304 2 : infinity = ieee_value(0.0_dp, ieee_positive_inf)
305 2 : not_a_number = ieee_value(0.0_dp, ieee_quiet_nan)
306 2 : IF (lbfgs_history_restart_required(infinity, 1.0_dp)) THEN
307 0 : ERROR STOP "L-BFGS history restarted for an infinite current gradient"
308 : END IF
309 2 : IF (lbfgs_history_restart_required(1.0_dp, infinity)) THEN
310 0 : ERROR STOP "L-BFGS history restarted for an infinite preceding gradient"
311 : END IF
312 2 : IF (lbfgs_history_restart_required(not_a_number, 1.0_dp)) THEN
313 0 : ERROR STOP "L-BFGS history restarted for a NaN current gradient"
314 : END IF
315 2 : IF (lbfgs_history_restart_required(1.0_dp, not_a_number)) THEN
316 0 : ERROR STOP "L-BFGS history restarted for a NaN preceding gradient"
317 : END IF
318 2 : IF (lbfgs_step_restart_required(infinity, 1.0_dp)) THEN
319 0 : ERROR STOP "L-BFGS step restarted for an infinite accepted step"
320 : END IF
321 2 : IF (lbfgs_step_restart_required(1.0_dp, infinity)) THEN
322 0 : ERROR STOP "L-BFGS step restarted for an infinite reference step"
323 : END IF
324 2 : IF (lbfgs_step_restart_required(not_a_number, 1.0_dp)) THEN
325 0 : ERROR STOP "L-BFGS step restarted for a NaN accepted step"
326 : END IF
327 2 : IF (lbfgs_curvature_damping_shift(not_a_number, 1.0_dp, 1.0_dp, 0.5_dp) /= 0.0_dp) THEN
328 0 : ERROR STOP "L-BFGS damping accepted NaN curvature"
329 : END IF
330 : CALL lbfgs_response_secant_parameters( &
331 2 : not_a_number, 1.0_dp, 1.0_dp, 1.0_dp, response_scale, response_weight, valid_response)
332 2 : IF (valid_response) ERROR STOP "L-BFGS response secant accepted NaN curvature"
333 :
334 2 : END PROGRAM qs_ot_minimizer_unittest
|