#![allow(clippy::uninlined_format_args)]
use geometric_pyo3::prelude::*;
use pyo3::prelude::*;
pub struct Model {
b: [[f64; 3]; 3],
w: [[f64; 3]; 3],
pub current_energy: Option<f64>,
pub current_coords: Option<Vec<f64>>,
}
pub struct ModelDriver<'a> {
model: &'a mut Model,
}
impl GeomDriverAPI for ModelDriver<'_> {
fn calc_new(&mut self, coords: &[f64], _dirname: &str) -> GradOutput {
self.model.calc_eng_grad(coords)
}
}
fn main_test() -> PyResult<()> {
let mut model = Model::new();
pyo3::prepare_freethreaded_python();
let elem = ["O", "H", "H"];
let xyzs = vec![vec![0.0, 0.3, 0.0, 0.9, 0.8, 0.0, -0.9, 0.5, 0.0]];
let molecule = init_pyo3_molecule(&elem, &xyzs)?;
println!("Molecule: {:?}", molecule);
let optimizer_params = r#"
transition = true # evaluate transition state instead of local minimum
convergence_energy = 1.0e-8 # Eh
convergence_grms = 1.0e-6 # Eh/Bohr
convergence_gmax = 1.0e-6 # Eh/Bohr
convergence_drms = 1.0e-4 # Angstrom
convergence_dmax = 1.0e-4 # Angstrom
"#;
let params = tomlstr2py(optimizer_params)?;
let input = None;
let pyo3_engine_cls = get_pyo3_engine_cls()?;
let driver = ModelDriver { model: &mut model };
let driver: PyGeomDriver = driver.into();
Python::with_gil(|py| -> PyResult<()> {
let custom_engine = pyo3_engine_cls.call1(py, (molecule,))?;
custom_engine.call_method1(py, "set_driver", (driver,))?;
let res = run_optimization(custom_engine, ¶ms, input)?;
let coords = res
.getattr(py, "xyzs")?
.call_method1(py, "__getitem__", (-1,))?
.call_method0(py, "flatten")?
.call_method0(py, "tolist")?
.extract::<Vec<f64>>(py)?;
println!("Optimized Coordinates (Angstrom): {:?}", coords);
let energy = res
.getattr(py, "qm_energies")?
.call_method1(py, "__getitem__", (-1,))?
.extract::<f64>(py)?;
println!("Optimized Energy (Eh): {:?}", energy);
assert!((energy - 0.32).abs() < 1.0e-8);
Ok(())
})?;
let coords = model.current_coords.as_ref().unwrap();
println!("Model Coordinates (Bohr): {:?}", coords);
let energy = model.current_energy.as_ref().unwrap();
println!("Model Energy (Eh): {:?}", energy);
Ok(())
}
#[allow(clippy::new_without_default)]
impl Model {
pub fn new() -> Self {
let b = [[0.0, 1.8, 1.8], [1.8, 0.0, 2.8], [1.8, 2.8, 0.0]];
let w = [[0.0, 1.0, 1.0], [1.0, 0.0, 0.5], [1.0, 0.5, 0.0]];
Model { b, w, current_coords: None, current_energy: None }
}
pub fn calc_eng_grad(&mut self, coords: &[f64]) -> GradOutput {
self.current_coords = Some(coords.to_vec());
let coords: Vec<&[f64]> = coords.chunks(3).collect();
let natm = coords.len();
let b = self.b;
let w = self.w;
let mut dr = vec![vec![vec![0.0; 3]; natm]; natm];
for i in 0..natm {
for j in 0..natm {
dr[i][j][0] = coords[i][0] - coords[j][0];
dr[i][j][1] = coords[i][1] - coords[j][1];
dr[i][j][2] = coords[i][2] - coords[j][2];
}
}
let mut dist = vec![vec![0.0; natm]; natm];
for i in 0..natm {
for j in 0..natm {
dist[i][j] = (dr[i][j][0] * dr[i][j][0]
+ dr[i][j][1] * dr[i][j][1]
+ dr[i][j][2] * dr[i][j][2])
.sqrt();
}
}
let mut energy = 0.0;
for i in 0..natm {
for j in 0..natm {
energy += w[i][j] * (dist[i][j] - b[i][j]).powi(2);
}
}
let mut tmp = vec![vec![0.0; natm]; natm];
for i in 0..natm {
for j in 0..natm {
tmp[i][j] = 2.0 * w[i][j] * (dist[i][j] - b[i][j]) / (dist[i][j] + 1e-60);
}
}
let mut grad = vec![vec![0.0; 3]; natm];
for i in 0..natm {
for j in 0..natm {
grad[i][0] += tmp[i][j] * dr[i][j][0];
grad[i][1] += tmp[i][j] * dr[i][j][1];
grad[i][2] += tmp[i][j] * dr[i][j][2];
grad[j][0] -= tmp[i][j] * dr[i][j][0];
grad[j][1] -= tmp[i][j] * dr[i][j][1];
grad[j][2] -= tmp[i][j] * dr[i][j][2];
}
}
let gradient = grad.iter().flat_map(|x| x.iter()).copied().collect();
self.current_energy = Some(energy);
GradOutput { energy, gradient }
}
}
#[test]
fn test() {
main_test().unwrap();
}
fn main() {
main_test().unwrap();
}