Description:
This task represents the final core implementation phase before exposing the public API. It focuses on creating the QEqSolver engine, which orchestrates the entire charge equilibration process. This solver will integrate the atomic parameters from the params module and the shielded Coulomb potential from the math module to iteratively solve for equilibrium charges.
The implementation will reside in a new top-level solver module and will use the faer crate for high-performance, numerically stable linear algebra. The core logic will be a Self-Consistent Field (SCF) loop to handle the charge-dependent hardness of hydrogen, making the solver applicable to all elements described in the Rappe & Goddard paper.
Tasks:
Description:
This task represents the final core implementation phase before exposing the public API. It focuses on creating the
QEqSolverengine, which orchestrates the entire charge equilibration process. This solver will integrate the atomic parameters from theparamsmodule and the shielded Coulomb potential from themathmodule to iteratively solve for equilibrium charges.The implementation will reside in a new top-level
solvermodule and will use thefaercrate for high-performance, numerically stable linear algebra. The core logic will be a Self-Consistent Field (SCF) loop to handle the charge-dependent hardness of hydrogen, making the solver applicable to all elements described in the Rappe & Goddard paper.Tasks:
Phase 1: Module Scaffolding and Dependencies
faercrate (version = "0.17") toCargo.toml.src/solver/withmod.rs.src/solver/options.rsand define theSolverOptionsstruct withtolerance,max_iterations, andlambda_scale, including aDefaultimplementation.src/solver/solver.rsand define theQEqSolverstruct. It should hold a reference toParametersand an instance ofSolverOptions.QEqSolver::new()and a builder-stylewith_options()method.Phase 2: Implement the System Builder (
build_system)solver.rs, create the private helper functionbuild_system.fn build_system<A: AtomView>(&self, atoms: &[A], element_data: &[&'p ElementData], current_charges: ColRef<f64>) -> Result<(Mat<f64>, Col<f64>), CheqError>.(N+1) x (N+1)matrixAand an(N+1)vectorbusingfaer.ifrom0toN-1:A[i, i](HardnessJ_ii):n=1oratomic_number=1).J_ii = J_ii⁰ * (1 + Q_i * H_CHARGE_FACTOR).J_ii⁰directly fromelement_data.A[i, j](CoulombJ_ij):jfromi+1toN-1.R_ijin Angstroms.math::shielding::gaussian_coulomb_integral.A[i, j]andA[j, i].b[i](Electronegativityχ_i⁰):b[i] = -element_data[i].electronegativity.A(rows0..N) with-1.0.A(cols0..N) with1.0.A[N, N]remains0.0.b[N] = total_charge.Ok((A, b)).Phase 3: Implement the SCF Solver Logic (
solve)pub fn solve<A: AtomView>(&self, atoms: &[A], total_charge: f64) -> Result<CalculationResult, CheqError>.atomsslice, returningCheqError::NoAtoms.ElementDatafor the atoms, handlingCheqError::ParameterNotFound.chargesvector to all zeros.self.options.max_iterations.build_systemto get the current matrixAand vectorb.Ax = businga.lu().solve(&b). Handle potentialNoneresult by returningCheqError::LinalgError.Q_newfrom the firstNelements of the solution vector.max_charge_delta = max(|Q_new - Q_old|).charges = new_charges.!has_hydrogen(exit after one iteration) ormax_charge_delta < self.options.tolerance, break the loop and proceed to success.χ̄ = -solution[N], createCalculationResult, and returnOk(...).Err(CheqError::NotConverged).Phase 4: Integration Testing
testssubmodule withinsrc/solver/solver.rs.COorN₂. Assert that charges are computed in 1 iteration and have the correct signs and rough magnitudes.solvewith an element not in the default parameters (e.g., Uranium, if not present) and assert it returnsCheqError::ParameterNotFound.CheqError::NoAtoms.