#[path = "../benchmarks/electrolyte/problems.rs"]
mod problems;
use problems::*;
use ripopt::{NlpProblem, SolveStatus, SolverOptions};
use std::time::Instant;
fn default_options() -> SolverOptions {
SolverOptions {
tol: 1e-6,
max_iter: 3000,
print_level: 0,
mu_init: 1e-3,
..SolverOptions::default()
}
}
fn max_cv(problem: &dyn NlpProblem, g: &[f64]) -> f64 {
let m = problem.num_constraints();
if m == 0 {
return 0.0;
}
let mut g_l = vec![0.0; m];
let mut g_u = vec![0.0; m];
problem.constraint_bounds(&mut g_l, &mut g_u);
let mut cv = 0.0_f64;
for i in 0..m {
cv = cv.max((g_l[i] - g[i]).max(0.0)).max((g[i] - g_u[i]).max(0.0));
}
cv
}
macro_rules! electrolyte_test {
($name:ident, $problem:expr, $check:expr) => {
#[test]
fn $name() {
let problem = $problem;
let options = default_options();
let start = Instant::now();
let result = ripopt::solve(&problem, &options);
let elapsed = start.elapsed();
let cv = max_cv(&problem, &result.constraint_values);
eprintln!(
"{}: status={:?}, obj={:.6e}, cv={:.2e}, iters={}, time={:.3}s",
stringify!($name), result.status, result.objective, cv,
result.iterations, elapsed.as_secs_f64()
);
assert!(
result.status == SolveStatus::Optimal,
"Expected Optimal/Acceptable, got {:?}", result.status
);
if problem.num_constraints() > 0 {
assert!(cv < 1e-4, "Constraint violation {:.2e} too large", cv);
}
#[allow(clippy::redundant_closure_call)]
($check)(&result);
}
};
}
electrolyte_test!(electrolyte_01_water_autoionization, WaterAutoionization, |result: &ripopt::SolveResult| {
let m = result.x[0].exp();
assert!(m > 5e-8 && m < 5e-7, "m_H={:.3e} out of range", m);
});
electrolyte_test!(electrolyte_02_co2_water, Co2WaterSpeciation, |result: &ripopt::SolveResult| {
let m_h = result.x[3].exp();
let ph = -(m_h.log10());
assert!(ph > 4.0 && ph < 7.0, "pH={:.2} out of range", ph);
let m_h2co3 = result.x[0].exp();
assert!(m_h2co3 > 1e-4 && m_h2co3 < 2e-3, "m_H2CO3={:.3e} unexpected", m_h2co3);
});
#[test]
fn electrolyte_03_nacl_speciation() {
let problem = NaClSpeciation;
let options = default_options();
let start = Instant::now();
let result = ripopt::solve(&problem, &options);
let elapsed = start.elapsed();
let cv = max_cv(&problem, &result.constraint_values);
eprintln!(
"electrolyte_03_nacl_speciation: status={:?}, obj={:.6e}, cv={:.2e}, iters={}, time={:.3}s",
result.status, result.objective, cv, result.iterations, elapsed.as_secs_f64()
);
assert!(
result.status == SolveStatus::Optimal,
"Expected Optimal, got {:?}", result.status
);
if problem.num_constraints() > 0 {
assert!(cv < 1e-4, "Constraint violation {:.2e} too large", cv);
}
let m_na = result.x[0].exp();
let m_cl = result.x[1].exp();
assert!((m_na - 0.1).abs() < 1e-3, "m_Na={:.4e}", m_na);
assert!((m_cl - 0.1).abs() < 1e-3, "m_Cl={:.4e}", m_cl);
let m_h = result.x[2].exp();
let ph = -(m_h.log10());
assert!(ph > 5.0 && ph < 9.0, "pH={:.2}", ph);
}
electrolyte_test!(electrolyte_04_cacl2_nacl_mixed, CaCl2NaClMixed, |result: &ripopt::SolveResult| {
let m_na = result.x[1].exp();
assert!((m_na - 0.1).abs() < 1e-3, "m_Na={:.4e}", m_na);
let m_cl = result.x[2].exp();
assert!((m_cl - 0.2).abs() < 1e-3, "m_Cl={:.4e}", m_cl);
});
#[test]
fn electrolyte_05_phosphoric_acid() {
let problem = PhosphoricAcid;
let options = SolverOptions {
tol: 1e-6,
max_iter: 3000,
print_level: 0,
mu_oracle_quality_function: false,
..SolverOptions::default()
};
let start = Instant::now();
let result = ripopt::solve(&problem, &options);
let elapsed = start.elapsed();
let cv = max_cv(&problem, &result.constraint_values);
eprintln!(
"electrolyte_05_phosphoric_acid: status={:?}, obj={:.6e}, cv={:.2e}, iters={}, time={:.3}s",
result.status, result.objective, cv, result.iterations, elapsed.as_secs_f64()
);
assert!(
result.status == SolveStatus::Optimal,
"Expected Optimal, got {:?}",
result.status
);
assert!(cv < 1e-4, "Constraint violation {:.2e} too large", cv);
let m_h = result.x[4].exp();
let ph = -(m_h.log10());
assert!(ph > 1.0 && ph < 4.0, "pH={:.2}", ph);
let m_po4 = result.x[3].exp();
assert!(m_po4 < 1e-6, "m_PO4={:.3e} too large", m_po4);
}
electrolyte_test!(electrolyte_06_hcl_activity, HclMeanActivity, |result: &ripopt::SolveResult| {
assert!((result.x[0] - 1.0).abs() < 0.05, "m={:.4}", result.x[0]);
assert!(result.objective < 1e-6, "f={:.3e}", result.objective);
});
electrolyte_test!(electrolyte_07_nacl_solubility, NaClSolubility, |result: &ripopt::SolveResult| {
assert!(result.x[0] > 5.0 && result.x[0] < 8.0,
"m_sat={:.3}", result.x[0]);
assert!(result.objective < 1e-4, "f={:.3e}", result.objective);
});
electrolyte_test!(electrolyte_08_butanol_lle, ButanolWaterLle, |result: &ripopt::SolveResult| {
assert!(result.x[0] > 1e-4 && result.x[0] < 0.02,
"x_BuOH_aq={:.4}", result.x[0]);
assert!(result.x[1] > 0.3 && result.x[1] < 0.95,
"x_BuOH_org={:.4}", result.x[1]);
});
electrolyte_test!(electrolyte_09_saturated_brine, SaturatedBrine, |result: &ripopt::SolveResult| {
assert!(result.x[0] > 4.0 && result.x[0] < 9.0,
"m_NaCl={:.3}", result.x[0]);
assert!(result.x[1] > 0.6 && result.x[1] < 0.9,
"a_w={:.3}", result.x[1]);
});
electrolyte_test!(electrolyte_10_pitzer_fit, PitzerNaClFit, |result: &ripopt::SolveResult| {
assert!(result.objective < 1e-6, "f={:.3e}", result.objective);
assert!((result.x[0] - 0.0765).abs() < 0.02, "beta0={:.4}", result.x[0]);
assert!((result.x[1] - 0.2664).abs() < 0.05, "beta1={:.4}", result.x[1]);
});
electrolyte_test!(electrolyte_11_multi_salt_dh_fit, MultiSaltDhFit, |result: &ripopt::SolveResult| {
assert!(result.objective < 1e-4, "f={:.3e}", result.objective);
});
electrolyte_test!(electrolyte_12_enrtl_fit, EnrtlTempFit, |result: &ripopt::SolveResult| {
assert!(result.objective < 1e-2, "f={:.3e}", result.objective);
});
#[test]
#[ignore] fn electrolyte_13_seawater() {
let problem = SeawaterSpeciation;
let options = SolverOptions {
tol: 1e-5,
max_iter: 5000,
print_level: 0,
..SolverOptions::default()
};
let start = Instant::now();
let result = ripopt::solve(&problem, &options);
let elapsed = start.elapsed();
let cv = max_cv(&problem, &result.constraint_values);
eprintln!(
"seawater: status={:?}, obj={:.6e}, cv={:.2e}, iters={}, time={:.3}s",
result.status, result.objective, cv, result.iterations, elapsed.as_secs_f64()
);
assert!(
result.status == SolveStatus::Optimal,
"got {:?}", result.status
);
assert!(cv < 1e-3, "cv={:.2e}", cv);
let m_h = result.x[8].exp();
let ph = -(m_h.log10());
eprintln!(" pH={:.2}, [Na+]={:.4e}, [Cl-]={:.4e}, [MgSO4]={:.4e}",
ph, result.x[0].exp(), result.x[4].exp(), result.x[10].exp());
assert!(ph > 6.0 && ph < 10.0, "pH={:.2} out of range", ph);
}