use crate::process::ProcessModel;
use crate::*;
use knitro_rs::*;
use std::array;
struct ProcessCallback<'a, R, P, const N: usize> {
parameters: Option<&'a [f64]>,
property_model: &'a R,
process: &'a P,
}
impl<'a, R, P, const N: usize> ProcessCallback<'a, R, P, N> {
fn new<M>(problem: &'a OptimizationProblem<M, R, P, N>, parameters: Option<&'a [f64]>) -> Self {
Self {
parameters,
property_model: &problem.property_model,
process: &problem.process,
}
}
}
impl<'a, R: PropertyModel<N>, P: ProcessModel<R::EquationOfState>, const N: usize> EvalCallback
for ProcessCallback<'a, R, P, N>
{
fn callback(&self, x: &[f64], obj: &mut f64, c: &mut [f64]) -> i32 {
let (x, p) = x.split_at(self.process.variables().len());
let p = self.parameters.unwrap_or(p);
let eos = self.property_model.build_eos(p);
match self.process.solve(&eos, x) {
Ok((target, eq_constraints, ineq_constraints)) => {
let constraints = [eq_constraints, ineq_constraints].concat();
if constraints.len() != c.len() {
println!("Wrong number of constraints returned from process model!");
return KN_RC_EVAL_ERR;
}
c.copy_from_slice(&constraints);
*obj = target;
0
}
Err(_) => {
KN_RC_EVAL_ERR
}
}
}
}
impl<
M: MolecularRepresentation,
R: PropertyModel<N>,
P: ProcessModel<R::EquationOfState>,
const N: usize,
> OptimizationProblem<M, R, P, N>
{
pub fn solve_fixed(
&mut self,
y: &[Vec<f64>; N],
options: Option<&str>,
) -> Result<OptimizationResult, KnitroError> {
let (y_usize, smiles) = self.smiles(y);
if let Some(res) = self.solutions.iter().find(|r| r.y == y_usize) {
Ok(res.clone())
} else {
let f = array::from_fn(|i| self.molecules[i].evaluate_feature_variables(&y[i]));
let p = self.property_model.evaluate_parameter_variables(&f);
let kc = Knitro::new()?;
self.process.variables().setup_knitro(&kc, None)?;
let index_eq_cons = kc.add_cons(self.process.equality_constraints())?;
for &i in &index_eq_cons {
kc.set_con_eqbnd(i, 0.0)?;
}
let index_ineq_cons = kc.add_cons(self.process.inequality_constraints())?;
for &i in &index_ineq_cons {
kc.set_con_lobnd(i, 0.0)?;
}
let index_cons = [index_eq_cons.clone(), index_ineq_cons.clone()].concat();
let mut callback = ProcessCallback::new(self, Some(&p));
kc.add_eval_callback(true, &index_cons, &mut callback)?;
if let Some(options) = options {
kc.load_param_file(options)?;
}
kc.solve()?;
let target = kc.get_obj_value()?;
let x = kc.get_var_primal_values_all()?;
let res = OptimizationResult::new(target, smiles, x, y_usize);
self.solutions.insert(res.clone());
Ok(res)
}
}
pub(super) fn solve_gradients(
&mut self,
y: &[Vec<f64>; N],
x: &[f64],
options: Option<&str>,
) -> Result<(Vec<f64>, Gradients, Vec<f64>), KnitroError> {
let f = array::from_fn(|i| self.molecules[i].evaluate_feature_variables(&y[i]));
let p = self.property_model.evaluate_parameter_variables(&f);
let kc = Knitro::new()?;
for &x in x {
Variable::fixed(x).setup_knitro(&kc, "fixed_process_variable")?;
}
let index_eq_cons = kc.add_cons(self.process.equality_constraints())?;
for &i in &index_eq_cons {
kc.set_con_eqbnd(i, 0.0)?;
}
let index_ineq_cons = kc.add_cons(self.process.inequality_constraints())?;
for &i in &index_ineq_cons {
kc.set_con_lobnd(i, 0.0)?;
}
let index_cons = [index_eq_cons.clone(), index_ineq_cons.clone()].concat();
for p in p {
Variable::fixed(p).setup_knitro(&kc, "fixed_param")?;
}
let mut callback = ProcessCallback::new(self, None);
kc.add_eval_callback(true, &index_cons, &mut callback)?;
if let Some(options) = options {
kc.load_param_file(options)?;
}
kc.solve()?;
let x = kc.get_var_primal_values_all()?;
let n_vars = x.len();
let f = kc.get_obj_value()?;
let h_vec = kc.get_con_values(&index_eq_cons)?;
let g_vec = kc.get_con_values(&index_ineq_cons)?;
let (_, nabla_f) = kc.get_objgrad_values()?;
let f = Gradient::new(f, nabla_f);
let (_, _, jac) = kc.get_jacobian_values()?;
let lambda = kc.get_con_dual_values(&index_eq_cons)?;
let mut h = Vec::with_capacity(index_eq_cons.len());
let mut g = Vec::with_capacity(index_ineq_cons.len());
let mut k = 0;
for val in h_vec {
let mut nabla = Vec::with_capacity(n_vars);
for _ in 0..n_vars {
nabla.push(jac[k]);
k += 1;
}
h.push(Gradient::new(val, nabla));
}
for val in g_vec {
let mut nabla = Vec::with_capacity(n_vars);
for _ in 0..n_vars {
nabla.push(jac[k]);
k += 1;
}
g.push(Gradient::new(val, nabla));
}
Ok((x, (f, g, h), lambda))
}
}
#[derive(Clone, Default)]
pub(super) struct Gradient {
val: f64,
nabla: Vec<f64>,
}
impl Gradient {
fn new(val: f64, nabla: Vec<f64>) -> Self {
Self { val, nabla }
}
pub(super) fn linearize(self, x: &[f64]) -> (Vec<f64>, f64) {
let rhs = self
.nabla
.iter()
.zip(x.iter())
.map(|(&n, &x)| n * x)
.sum::<f64>();
(self.nabla, rhs - self.val)
}
}
pub(super) type Gradients = (Gradient, Vec<Gradient>, Vec<Gradient>);