use self::internal::*;
use super::callbacks::{Callback, CallbackFcnFFI, ClarabelCallbackFn};
use super::cones::Cone;
use super::{traits::*, SettingsError};
use crate::algebra::*;
use crate::solver::core::callbacks::SolverCallbacks;
use crate::solver::core::ffi::*;
use crate::solver::SolverError;
use crate::timers::*;
use std::io::Write;
#[repr(C)]
#[derive(PartialEq, Eq, Clone, Debug, Copy, Default)]
pub enum SolverStatus {
#[default]
Unsolved = 0,
Solved,
PrimalInfeasible,
DualInfeasible,
AlmostSolved,
AlmostPrimalInfeasible,
AlmostDualInfeasible,
MaxIterations,
MaxTime,
NumericalError,
InsufficientProgress,
CallbackTerminated,
}
impl SolverStatus {
pub(crate) fn is_infeasible(&self) -> bool {
matches!(
*self,
|SolverStatus::PrimalInfeasible| SolverStatus::DualInfeasible
| SolverStatus::AlmostPrimalInfeasible
| SolverStatus::AlmostDualInfeasible
)
}
pub(crate) fn is_errored(&self) -> bool {
matches!(
*self,
SolverStatus::NumericalError | SolverStatus::InsufficientProgress
)
}
}
#[repr(u32)]
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
pub enum StepDirection {
Affine,
Combined,
}
#[repr(u32)]
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
pub enum ScalingStrategy {
PrimalDual,
Dual,
}
#[repr(u32)]
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
enum StrategyCheckpoint {
Update(ScalingStrategy), NoUpdate, Fail, }
impl std::fmt::Display for SolverStatus {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
#[cfg(feature = "serde")]
pub trait SolverJSONReadWrite<T>: Sized
where
T: FloatT,
{
fn save_to_file(&self, file: &mut std::fs::File) -> Result<(), std::io::Error>;
fn load_from_file(
file: &mut std::fs::File,
settings: Option<crate::solver::DefaultSettings<T>>,
) -> Result<Self, SolverError>;
}
pub struct Solver<T, D, V, R, K, C, I, SO, SE>
where
I: ClarabelFFI<I>,
SE: Settings<T>,
T: FloatT,
{
pub data: D,
pub variables: V,
pub residuals: R,
pub kktsystem: K,
pub cones: C,
pub step_lhs: V,
pub step_rhs: V,
pub prev_vars: V,
pub info: I,
pub solution: SO,
pub(crate) settings: SE, pub timers: Option<Timers>,
pub(crate) callbacks: SolverCallbacks<I, I::FFI>,
pub(crate) phantom: std::marker::PhantomData<T>,
}
fn _print_banner(out: &mut dyn Write, is_verbose: bool) -> std::io::Result<()> {
if !is_verbose {
return std::io::Result::Ok(());
}
writeln!(
out,
"-------------------------------------------------------------"
)?;
writeln!(
out,
" Clarabel.rs v{} - Clever Acronym ",
crate::VERSION
)?;
#[cfg(debug_assertions)]
writeln!(
out,
" *** debug build *** ",
)?;
#[cfg(not(debug_assertions))]
writeln!(out)?;
writeln!(
out,
" (c) Paul Goulart "
)?;
writeln!(
out,
" University of Oxford, 2022 "
)?;
writeln!(
out,
"-------------------------------------------------------------"
)?;
std::io::Result::Ok(())
}
impl<T, D, V, R, K, C, I, SO, SE> Solver<T, D, V, R, K, C, I, SO, SE>
where
I: Info<T, D = D, V = V, R = R, C = C, SE = SE>,
SE: Settings<T>,
T: FloatT,
{
pub fn set_termination_callback(&mut self, callback: impl ClarabelCallbackFn<I> + 'static) {
self.callbacks.termination_callback = Callback::Rust(Box::new(callback));
}
pub fn set_termination_callback_c(
&mut self,
callback: CallbackFcnFFI<I::FFI>,
data_ptr: *mut std::ffi::c_void,
) {
self.callbacks.termination_callback = Callback::new_c(callback, data_ptr);
}
pub fn unset_termination_callback(&mut self) {
self.callbacks.termination_callback = Callback::None;
}
pub fn settings(&self) -> &SE {
&self.settings
}
pub fn update_settings(&mut self, settings: SE) -> Result<(), SettingsError> {
settings.validate_as_update(&self.settings)?;
self.settings = settings;
Ok(())
}
}
pub trait IPSolver<T, D, V, R, K, C, I, SO, SE> {
fn solve(&mut self);
}
impl<T, D, V, R, K, C, I, SO, SE> IPSolver<T, D, V, R, K, C, I, SO, SE>
for Solver<T, D, V, R, K, C, I, SO, SE>
where
T: FloatT,
D: ProblemData<T, V = V>,
V: Variables<T, D = D, R = R, C = C, SE = SE>,
R: Residuals<T, D = D, V = V>,
K: KKTSystem<T, D = D, V = V, C = C, SE = SE>,
C: Cone<T>,
I: Info<T, D = D, V = V, R = R, C = C, SE = SE>,
SO: Solution<T, D = D, V = V, I = I, SE = SE>,
SE: Settings<T>,
{
fn solve(&mut self) {
let mut iter: u32 = 0;
let mut σ = T::one();
let mut α = T::zero();
let mut μ;
let mut timers = self.timers.take().unwrap();
notimeit! {timers; {
_print_banner(self.info.print_target(), self.settings.core().verbose).unwrap();
self.info.print_configuration(&self.settings, &self.data, &self.cones).unwrap();
self.info.print_status_header(&self.settings).unwrap();
}}
self.info.reset(&mut timers);
timeit! {timers => "solve"; {
timeit!{timers => "default start"; {
self.default_start();
}}
timeit!{timers => "IP iteration"; {
let mut scaling = {
if self.cones.allows_primal_dual_scaling() {ScalingStrategy::PrimalDual}
else {ScalingStrategy::Dual}
};
loop {
self.residuals.update(&self.variables, &self.data);
μ = self.variables.calc_mu(&self.residuals, &self.cones);
self.info.save_scalars(μ, α, σ, iter);
self.info.update(
&mut self.data,
&self.variables,
&self.residuals,&timers);
notimeit!{timers; {
self.info.print_status(&self.settings).unwrap();
}}
if self.callbacks.check_termination(&self.info) {
self.info.set_status(SolverStatus::CallbackTerminated);
break;
}
let is_done = self.info.check_termination(&self.residuals, &self.settings, iter);
if is_done{
match self.strategy_checkpoint_insufficient_progress(scaling){
StrategyCheckpoint::NoUpdate | StrategyCheckpoint::Fail => {break}
StrategyCheckpoint::Update(s) => {scaling = s; continue}
}
}
let is_scaling_success;
timeit!{timers => "scale cones"; {
is_scaling_success = self.variables.scale_cones(&mut self.cones,μ,scaling);
}}
match self.strategy_checkpoint_is_scaling_success(is_scaling_success,scaling){
StrategyCheckpoint::Fail => {break}
StrategyCheckpoint::NoUpdate => {} StrategyCheckpoint::Update(_) => {unreachable!()}
}
iter += 1;
let mut is_kkt_solve_success : bool;
timeit!{timers => "kkt update"; {
is_kkt_solve_success = self.kktsystem.update(&self.data, &self.cones, &self.settings);
}}
self.step_rhs
.affine_step_rhs(&self.residuals, &self.variables, &self.cones);
timeit!{timers => "kkt solve"; {
is_kkt_solve_success = is_kkt_solve_success &&
self.kktsystem.solve(
&mut self.step_lhs,
&self.step_rhs,
&self.data,
&self.variables,
&mut self.cones,
StepDirection::Affine,
&self.settings,
);
}}
if is_kkt_solve_success {
α = self.get_step_length(StepDirection::Affine, scaling);
σ = self.centering_parameter(α);
let m = if iter > 1 {T::one()} else {α};
self.step_rhs.combined_step_rhs(
&self.residuals,
&self.variables,
&mut self.cones,
&mut self.step_lhs,
σ,
μ,
m
);
timeit!{timers => "kkt solve" ; {
is_kkt_solve_success =
self.kktsystem.solve(
&mut self.step_lhs,
&self.step_rhs,
&self.data,
&self.variables,
&mut self.cones,
StepDirection::Combined,
&self.settings,
);
}} }
match self.strategy_checkpoint_numerical_error(is_kkt_solve_success,scaling) {
StrategyCheckpoint::NoUpdate => {}
StrategyCheckpoint::Update(s) => {α = T::zero(); scaling = s; continue}
StrategyCheckpoint::Fail => {α = T::zero(); break}
}
α = self.get_step_length(StepDirection::Combined,scaling);
match self.strategy_checkpoint_small_step(α, scaling) {
StrategyCheckpoint::NoUpdate => {}
StrategyCheckpoint::Update(s) => {α = T::zero(); scaling = s; continue}
StrategyCheckpoint::Fail => {α = T::zero(); break}
}
self.info.save_prev_iterate(&self.variables,&mut self.prev_vars);
self.variables.add_step(&self.step_lhs, α);
}
}}
}}
if α.is_zero() {
self.info.save_scalars(μ, α, σ, iter);
notimeit! {timers; {self.info.print_status(&self.settings).unwrap();}}
}
timeit! {timers => "post-process"; {
self.info.post_process(&self.residuals, &self.settings);
self.solution
.post_process(&self.data, &mut self.variables, &self.info, &self.settings);
}}
self.info.finalize(&mut timers);
self.solution.finalize(&self.info);
self.info.print_footer(&self.settings).unwrap();
self.timers.replace(timers);
}
}
mod internal {
use super::super::traits::*;
use super::*;
pub(super) trait IPSolverInternals<T, D, V, R, K, C, I, SO, SE> {
fn default_start(&mut self);
fn centering_parameter(&self, α: T) -> T;
fn get_step_length(&mut self, step_direction: StepDirection, scaling: ScalingStrategy)
-> T;
fn backtrack_step_to_barrier(&mut self, αinit: T) -> T;
fn strategy_checkpoint_insufficient_progress(
&mut self,
scaling: ScalingStrategy,
) -> StrategyCheckpoint;
fn strategy_checkpoint_numerical_error(
&mut self,
is_kkt_solve_success: bool,
scaling: ScalingStrategy,
) -> StrategyCheckpoint;
fn strategy_checkpoint_small_step(
&mut self,
α: T,
scaling: ScalingStrategy,
) -> StrategyCheckpoint;
fn strategy_checkpoint_is_scaling_success(
&mut self,
is_scaling_success: bool,
scaling: ScalingStrategy,
) -> StrategyCheckpoint;
}
impl<T, D, V, R, K, C, I, SO, SE> IPSolverInternals<T, D, V, R, K, C, I, SO, SE>
for Solver<T, D, V, R, K, C, I, SO, SE>
where
T: FloatT,
D: ProblemData<T, V = V>,
V: Variables<T, D = D, R = R, C = C, SE = SE>,
R: Residuals<T, D = D, V = V>,
K: KKTSystem<T, D = D, V = V, C = C, SE = SE>,
C: Cone<T>,
I: Info<T, D = D, V = V, R = R, C = C, SE = SE>,
SO: Solution<T, D = D, V = V, I = I>,
SE: Settings<T>,
{
fn default_start(&mut self) {
if self.cones.is_symmetric() {
self.cones.set_identity_scaling();
self.kktsystem
.update(&self.data, &self.cones, &self.settings);
self.kktsystem
.solve_initial_point(&mut self.variables, &self.data, &self.settings);
self.variables.symmetric_initialization(&mut self.cones);
} else {
self.variables.unit_initialization(&self.cones);
}
}
fn centering_parameter(&self, α: T) -> T {
T::powi(T::one() - α, 3)
}
fn get_step_length(
&mut self,
step_direction: StepDirection,
scaling: ScalingStrategy,
) -> T {
let mut α = self.variables.calc_step_length(
&self.step_lhs,
&mut self.cones,
&self.settings,
step_direction,
);
if !self.cones.is_symmetric()
&& step_direction == StepDirection::Combined
&& scaling == ScalingStrategy::Dual
{
let αinit = α;
α = self.backtrack_step_to_barrier(αinit);
}
α
}
fn backtrack_step_to_barrier(&mut self, αinit: T) -> T {
let step = self.settings.core().linesearch_backtrack_step;
let mut α = αinit;
for _ in 0..50 {
let barrier = self.variables.barrier(&self.step_lhs, α, &mut self.cones);
if barrier < T::one() {
return α;
} else {
α = step * α;
}
}
α
}
fn strategy_checkpoint_insufficient_progress(
&mut self,
scaling: ScalingStrategy,
) -> StrategyCheckpoint {
let output;
if self.info.get_status() != SolverStatus::InsufficientProgress {
output = StrategyCheckpoint::NoUpdate;
} else {
self.info
.reset_to_prev_iterate(&mut self.variables, &self.prev_vars);
if !self.cones.is_symmetric() && (scaling == ScalingStrategy::PrimalDual) {
self.info.set_status(SolverStatus::Unsolved);
output = StrategyCheckpoint::Update(ScalingStrategy::Dual);
} else {
output = StrategyCheckpoint::Fail;
}
}
output
}
fn strategy_checkpoint_numerical_error(
&mut self,
is_kkt_solve_success: bool,
scaling: ScalingStrategy,
) -> StrategyCheckpoint {
let output;
if is_kkt_solve_success {
output = StrategyCheckpoint::NoUpdate;
}
else if !self.cones.is_symmetric() && (scaling == ScalingStrategy::PrimalDual) {
output = StrategyCheckpoint::Update(ScalingStrategy::Dual);
} else {
self.info.set_status(SolverStatus::NumericalError);
output = StrategyCheckpoint::Fail;
}
output
}
fn strategy_checkpoint_small_step(
&mut self,
α: T,
scaling: ScalingStrategy,
) -> StrategyCheckpoint {
let output;
if !self.cones.is_symmetric()
&& scaling == ScalingStrategy::PrimalDual
&& α < self.settings.core().min_switch_step_length
{
output = StrategyCheckpoint::Update(ScalingStrategy::Dual);
} else if α <= T::max(T::zero(), self.settings.core().min_terminate_step_length) {
self.info.set_status(SolverStatus::InsufficientProgress);
output = StrategyCheckpoint::Fail;
} else {
output = StrategyCheckpoint::NoUpdate;
}
output
}
fn strategy_checkpoint_is_scaling_success(
&mut self,
is_scaling_success: bool,
_scaling: ScalingStrategy,
) -> StrategyCheckpoint {
if is_scaling_success {
StrategyCheckpoint::NoUpdate
} else {
self.info.set_status(SolverStatus::NumericalError);
StrategyCheckpoint::Fail
}
}
} }