pub fn bfgs_step(
x0: &DVector<f64>,
g0: &DVector<f64>,
inv_hessian: &DMatrix<f64>,
config: &Config,
_adaptive_scale: f64,
) -> DVector<f64>Expand description
Performs a BFGS optimization step.
BFGS (Broyden-Fletcher-Goldfarb-Shanno) is a quasi-Newton optimization method that approximates the inverse Hessian using gradient information. It provides good convergence for the first few iterations while building curvature information.
The BFGS step direction is computed by solving:
d = -H^(-1) * gwhere H is the Hessian approximation and g is the gradient. The step size is
automatically limited by config.max_step_size to prevent overshooting.
§Arguments
x0- Current geometry coordinatesg0- Current MECP gradienthessian- Current Hessian approximation matrixconfig- Configuration with step size limits and other parameters
§Returns
Returns the new geometry coordinates after the BFGS step as a DVector<f64>.
§Examples
use omecp::optimizer::bfgs_step;
use nalgebra::DVector;
let x0 = DVector::from_vec(vec![0.0, 0.0, 0.0]);
let g0 = DVector::from_vec(vec![0.1, 0.2, 0.3]);
let hessian = DMatrix::identity(3, 3);
// let x_new = bfgs_step(&x0, &g0, &hessian, &config, 1.0);pub fn bfgs_step(
x0: &DVector
// Step 1: Compute Newton direction dk = -H^-1 * g
let neg_g = -g0;
let mut dk = hessian.clone().lu().solve(&neg_g).unwrap_or_else(|| {
// Fallback to steepest descent when Hessian is singular
println!("BFGS Step: Hessian is singular, falling back to steepest descent");
-g0 / (g0.norm() + 1e-14)
});
// Step 2: Cap dk to 0.1 Angstrom
// Convert to Bohr since internal coordinates are in Bohr
let dk_cap = 0.1 * ANGSTROM_TO_BOHR; // 0.1 Angstrom in Bohr
let dk_norm = dk.norm();
if dk_norm > dk_cap {
println!(
"BFGS: dk norm {:.6} > {:.6}, capping direction",
dk_norm, dk_cap
);
dk *= dk_cap / dk_norm;
}
// Step 3: Apply rho multiplier (rho=15 for MECP optimization)
// This aggressive multiplier helps escape shallow regions quickly
// Note: dk is in Bohr (same as coordinates), so no unit conversion needed
let rho = config.bfgs_rho;
let x_new = x0 + &dk * rho;
// Step 4: MaxStep - limit total step to max_step_size
let step = &x_new - x0;
let step_norm = step.norm();
// Debug: print step details
let step_angstrom = step_norm * crate::config::BOHR_TO_ANGSTROM;
println!(
"BFGS: dk_norm={:.6}, dk_capped={:.6}, rho={}, raw_step={:.6} bohr ({:.6} Ang)",
dk_norm, dk.norm(), rho, step_norm, step_angstrom
);
if step_norm > config.max_step_size {
let scale = config.max_step_size / step_norm;
let final_step_angstrom = config.max_step_size * crate::config::BOHR_TO_ANGSTROM;
println!(
"BFGS step: {:.6} -> {:.6} bohr ({:.6} Ang) (MaxStep applied)",
step_norm, config.max_step_size, final_step_angstrom
);
x0 + &step * scale
} else {
println!("BFGS step: {:.6} bohr ({:.6} Ang) (within max_step_size)", step_norm, step_angstrom);
x_new
}} Performs a BFGS optimization step following the Fortran MECP implementation.
This implementation is adapted from the original Harvey Fortran code (eas) but operates in Angstrom-based units:
- Uses inverse Hessian (Ų/Ha) for Newton step
- Works in Angstrom for the Newton step computation
- Two-stage step limiting: total norm, then max component (in A)
§Algorithm (from Fortran UpdateX subroutine, adapted for Angstrom)
- First step:
ChgeX = -0.7 * G(steepest descent with H_inv diagonal = 0.7) - Later steps:
ChgeX = -H_inv * G(Newton step with BFGS-updated inverse Hessian) - Limit step: if
||ChgeX|| > 0.1*NA, scale down - Limit components: if
max(|ChgeX_i|) > 0.1A, scale down - Add Angstrom step to Angstrom coordinates
§Units
- Input coordinates (
x0): Angstrom - Input gradient (
g0): Ha/A (converted from native QM output) - Inverse Hessian: Ų/Ha (initialized to 0.7 on diagonal)
- Newton step: A (H⁻¹ × g = Ų/Ha × Ha/A = A)
- Output coordinates: Angstrom
§Requirements
Validates: Requirements 4.1, 4.2, 4.3, 4.4, 4.5