use crate::process::ProcessModel;
use crate::*;
use feos::core::{EosResult, IdealGas, Residual};
use knitro_rs::*;
use std::array;
mod outer_approximation;
mod process_optimization;
pub use outer_approximation::OuterApproximationAlgorithm;
struct OptimizationProblemCallback<'a, R, P, const N: usize> {
property_model: &'a R,
process: &'a P,
process_vars: usize,
parameter_vars: usize,
}
impl<'a, R, P, const N: usize> OptimizationProblemCallback<'a, R, P, N> {
fn new<M>(
problem: &'a OptimizationProblem<M, R, P, N>,
process_vars: usize,
parameter_vars: usize,
) -> Self {
Self {
property_model: &problem.property_model,
process: &problem.process,
process_vars,
parameter_vars,
}
}
}
impl<
'a,
E: Residual + IdealGas,
R: PropertyModel<N, EquationOfState = E>,
P: ProcessModel<E>,
const N: usize,
> EvalCallback for OptimizationProblemCallback<'a, R, P, N>
{
fn callback(&self, x: &[f64], obj: &mut f64, c: &mut [f64]) -> i32 {
match self.evaluate(x) {
Ok((target, constraints)) => {
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<
'a,
E: Residual + IdealGas,
R: PropertyModel<N, EquationOfState = E>,
P: ProcessModel<E>,
const N: usize,
> OptimizationProblemCallback<'a, R, P, N>
{
fn evaluate(&self, vars: &[f64]) -> EosResult<(f64, Vec<f64>)> {
let x = &vars[..self.process_vars];
let p = &vars[vars.len() - self.parameter_vars..];
let eos = self.property_model.build_eos(p);
let (target, eq_constraints, ineq_constraints) = self.process.solve(&eos, x)?;
Ok((target, [eq_constraints, ineq_constraints].concat()))
}
}
#[allow(clippy::type_complexity)]
impl<
M: MolecularRepresentation,
R: PropertyModel<N>,
P: ProcessModel<R::EquationOfState>,
const N: usize,
> OptimizationProblem<M, R, P, N>
{
fn solve(
&mut self,
x0: Option<&[f64]>,
y0: Option<&[Vec<f64>; N]>,
options: Option<&str>,
target: bool,
) -> Result<(f64, Vec<f64>, [Vec<f64>; N]), KnitroError> {
let kc = Knitro::new()?;
let index_process_vars = self.process.variables().setup_knitro(&kc, x0)?;
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 index_structure_vars = array::from_fn(|_| Vec::new());
let index_feature_vars = array::from_fn(|i| {
let (y, f) = self.molecules[i]
.setup_knitro(&kc, y0.map(|y0| &y0[i][..]), target)
.unwrap();
index_structure_vars[i].extend(y);
f
});
let all_structure_vars = index_structure_vars.concat();
for solution in &self.solutions {
let y0 = &solution.y;
let coefs: Vec<_> = y0.iter().map(|&y0| 1.0 - 2.0 * y0 as f64).collect();
let lobnd = 1.0 - y0.iter().map(|&y0| y0 as f64).sum::<f64>();
Constraint::new()
.linear_struct(all_structure_vars.clone(), coefs)
.lobnd(lobnd)
.setup_knitro(&kc)?;
}
let index_parameter_vars = self.property_model.setup_knitro(&kc, &index_feature_vars)?;
let mut callback = OptimizationProblemCallback::new(
self,
index_process_vars.len(),
index_parameter_vars.len(),
);
let mut cb = kc.add_eval_callback(true, &index_cons, &mut callback)?;
if let Some(options) = options {
kc.load_param_file(options)?;
}
let vars = [index_process_vars.clone(), index_parameter_vars].concat();
let jac_vars = vec![&vars as &[i32]; index_cons.len()].concat();
let jac_cons: Vec<_> = index_cons
.iter()
.flat_map(|&i| vec![i; vars.len()])
.collect();
kc.set_cb_grad::<OptimizationProblemCallback<R, P, N>>(
&mut cb, &vars, &jac_cons, &jac_vars, None,
)?;
kc.solve()?;
let t = kc.get_obj_value()?;
let y = index_structure_vars.map(|y| kc.get_var_primal_values(&y).unwrap());
let x = kc.get_var_primal_values(&index_process_vars)?;
Ok((t, x, y))
}
pub fn solve_target(
&mut self,
x0: &[f64],
options: Option<&str>,
) -> Result<(f64, Vec<f64>, [Vec<f64>; N]), KnitroError> {
self.solve(Some(x0), None, options, true)
}
pub fn solve_knitro_once(
&mut self,
x0: &[f64],
y0: Option<&[Vec<f64>; N]>,
options: Option<&str>,
) -> Result<OptimizationResult, KnitroError> {
let (t, x, y) = self.solve(Some(x0), y0, options, false)?;
let (y, smiles) = self.smiles(&y);
Ok(OptimizationResult::new(t, smiles, x, y))
}
pub fn solve_knitro(
&mut self,
x0: &[f64],
y0: Option<&[Vec<f64>; N]>,
n_solutions: usize,
options: Option<&str>,
) {
for k in 0..n_solutions {
match self.solve_knitro_once(x0, y0, options) {
Ok(result) => {
self.solutions.insert(result.clone());
let mut solutions: Vec<_> = self.solutions.iter().collect();
solutions.sort_by(|s1, s2| s1.target.total_cmp(&s2.target));
println!("\nRun {}", k + 1);
for (k, solution) in solutions.into_iter().enumerate() {
let k = if solution == &result || solution.target < result.target {
format!("{:3}", k + 1)
} else {
" ".into()
};
println!(
"{k} {:.7} {:?} {:?} {:?}",
solution.target, solution.y, solution.smiles, solution.x
);
}
}
Err(e) => println!("\nRun {}\n{e}", k + 1),
}
}
}
}