| 1430 | }; |
| 1431 | |
| 1432 | ElectronicConvergenceState resolve_electronic_convergence( |
| 1433 | double delta_e_ha, |
| 1434 | double eval_change_abs_ha, |
| 1435 | double residual_inf, |
| 1436 | int electronic_step, |
| 1437 | bool density_update_delayed, |
| 1438 | int imix_effective, |
| 1439 | const ScfSettings& settings, |
| 1440 | bool has_nonlocal, |
| 1441 | const EnergyComponents& energy |
| 1442 | ) { |
| 1443 | ElectronicConvergenceState state; |
| 1444 | const bool use_ediff = settings.ediff_ha > 0.0 && !settings.use_rho_tol_primary; |
| 1445 | // The primary SCF stop metric is exclusive: contract-facing calls use ediff, |
| 1446 | // while explicit rho_tol_inf requests opt into the MaterialDFT density criterion. |
| 1447 | const bool use_rho_tol = settings.rho_tol_inf > 0.0 && !use_ediff; |
| 1448 | state.ediff_met = use_ediff |
| 1449 | && std::isfinite(delta_e_ha) |
| 1450 | && std::isfinite(eval_change_abs_ha) |
| 1451 | && std::max(delta_e_ha, eval_change_abs_ha) < settings.ediff_ha; |
| 1452 | state.energy_converged = state.ediff_met; |
| 1453 | state.density_converged = use_rho_tol && residual_inf < settings.rho_tol_inf; |
| 1454 | bool candidate_converged = state.energy_converged || state.density_converged; |
| 1455 | if (should_reset_convergence_for_nonlocal_mismatch( |
| 1456 | has_nonlocal, |
| 1457 | settings.smearing_sigma_ha, |
| 1458 | candidate_converged, |
| 1459 | energy.e_nl_mismatch_ha)) { |
| 1460 | state = ElectronicConvergenceState{}; |
| 1461 | candidate_converged = false; |
| 1462 | } |
| 1463 | if (should_throw_for_nonlocal_mismatch( |
| 1464 | has_nonlocal, |
| 1465 | settings.smearing_sigma_ha, |
| 1466 | settings.rho_tol_inf, |
| 1467 | candidate_converged, |
| 1468 | energy.e_nl_mismatch_ha)) { |
| 1469 | throw std::runtime_error(nonlocal_mismatch_error_message(energy)); |
| 1470 | } |
| 1471 | state.mixing_gate_open = !density_update_delayed || imix_effective == 0; |
| 1472 | const int requested_nelmdl_delay_steps = settings.nelmdl == std::numeric_limits<int>::min() |
| 1473 | ? std::numeric_limits<int>::max() |
| 1474 | : std::abs(settings.nelmdl); |
| 1475 | state.nelmdl_gate_open = electronic_step > requested_nelmdl_delay_steps; |
| 1476 | const int effective_nelmin = std::min(std::max(1, settings.nelmin), settings.max_iter); |
| 1477 | state.nelmin_gate_open = electronic_step >= effective_nelmin; |
| 1478 | state.nelm_gate_hit = electronic_step >= settings.max_iter; |
| 1479 | state.converged_now = candidate_converged |
| 1480 | && state.mixing_gate_open |
| 1481 | && state.nelmdl_gate_open |
| 1482 | && state.nelmin_gate_open; |
| 1483 | if (state.nelm_gate_hit && !state.converged_now) { |
| 1484 | state.abort_without_conv = settings.max_iter > 1; |
| 1485 | state.stop_reason = "nelm_reached_unconverged"; |
| 1486 | return state; |
| 1487 | } |
| 1488 | if (state.converged_now) { |
| 1489 | state.stop_reason = state.energy_converged |
no test coverage detected