1use std::{fmt::Write as _, time::Duration};
4
5use crate::{QpProblem, Solution, SolveStatus, Solver, WarmStart};
6
7pub trait ComparisonSolver {
12 fn name(&self) -> &'static str;
14
15 fn solve(
21 &mut self,
22 problem: &QpProblem,
23 warm_start: Option<&WarmStart>,
24 ) -> Result<Solution, String>;
25}
26
27impl ComparisonSolver for Solver {
28 fn name(&self) -> &'static str {
29 "ledge"
30 }
31
32 fn solve(
33 &mut self,
34 problem: &QpProblem,
35 warm_start: Option<&WarmStart>,
36 ) -> Result<Solution, String> {
37 Solver::solve(self, problem, warm_start).map_err(|error| error.to_string())
38 }
39}
40
41#[derive(Clone, Debug, PartialEq)]
43pub struct BenchmarkRecord {
44 pub instance: String,
46 pub solver: String,
48 pub status: SolveStatus,
50 pub objective: f64,
52 pub primal_residual: f64,
54 pub dual_residual: f64,
56 pub iterations: usize,
58 pub duration: Duration,
60}
61
62impl BenchmarkRecord {
63 #[must_use]
65 pub fn markdown_row(&self) -> String {
66 format!(
67 "| {} | {} | {:?} | {:.8e} | {:.3e} | {:.3e} | {} | {:.3} |",
68 self.instance,
69 self.solver,
70 self.status,
71 self.objective,
72 self.primal_residual,
73 self.dual_residual,
74 self.iterations,
75 self.duration.as_secs_f64() * 1_000.0
76 )
77 }
78}
79
80#[derive(Default)]
82pub struct BenchmarkRunner {
83 solvers: Vec<Box<dyn ComparisonSolver>>,
84}
85
86impl BenchmarkRunner {
87 #[must_use]
89 pub const fn new() -> Self {
90 Self {
91 solvers: Vec::new(),
92 }
93 }
94
95 pub fn add_solver(&mut self, solver: Box<dyn ComparisonSolver>) {
97 self.solvers.push(solver);
98 }
99
100 pub fn run(
102 &mut self,
103 instance_name: &str,
104 problem: &QpProblem,
105 warm_start: Option<&WarmStart>,
106 ) -> Vec<Result<BenchmarkRecord, String>> {
107 self.solvers
108 .iter_mut()
109 .map(|solver| {
110 let solver_name = solver.name().to_owned();
111 solver
112 .solve(problem, warm_start)
113 .map(|solution| BenchmarkRecord {
114 instance: instance_name.to_owned(),
115 solver: solver_name.clone(),
116 status: solution.status,
117 objective: solution.objective,
118 primal_residual: solution.residuals.primal,
119 dual_residual: solution.residuals.dual,
120 iterations: solution.iterations,
121 duration: solution.solve_time,
122 })
123 .map_err(|error| {
124 let mut message = String::new();
125 let _ = write!(message, "{solver_name}: {error}");
126 message
127 })
128 })
129 .collect()
130 }
131
132 #[must_use]
134 pub const fn markdown_header() -> &'static str {
135 "| instance | solver | status | objective | primal residual | dual residual | iterations | time (ms) |\n\
136 |---|---|---:|---:|---:|---:|---:|---:|"
137 }
138}