use clarabel::{
algebra::*,
solver::*,
};
use crate::{
Sample,
common::utils,
};
use crate::hypothesis::Classifier;
pub(super) struct LPModel {
pub(self) lin_obj: Vec<f64>, pub(self) nonzero: Vec<f64>, pub(self) col_ptr: Vec<usize>, pub(self) row_val: Vec<usize>, pub(self) n_examples: usize, pub(self) n_hypotheses: usize, pub(self) weights: Vec<f64>, pub(self) dist: Vec<f64>, }
impl LPModel {
pub(super) fn init(size: usize, upper_bound: f64) -> Self {
let n_examples = size;
let mut lin_obj = vec![1f64/upper_bound; n_examples+1];
lin_obj[0] = -1f64;
let mut col_ptr = vec![0usize];
let mut row_val = (0usize..n_examples).collect::<Vec<usize>>();
let mut nonzero = vec![1f64; n_examples];
for r in 0..n_examples {
col_ptr.push(row_val.len());
row_val.push(r);
nonzero.push(-1f64);
row_val.push(n_examples + 1 + r);
nonzero.push(-1f64);
}
Self {
lin_obj,
nonzero,
col_ptr,
row_val,
n_examples,
n_hypotheses: 0usize,
weights: Vec::with_capacity(0usize),
dist: Vec::with_capacity(0usize),
}
}
pub(super) fn update<F>(
&mut self,
sample: &Sample,
clf: &F
) -> f64
where F: Classifier
{
self.n_hypotheses += 1;
let margins = utils::margins_of_hypothesis(sample, clf);
self.col_ptr.push(self.row_val.len());
for (i, yh) in margins.into_iter().enumerate() {
self.row_val.push(i);
self.nonzero.push(-yh);
}
self.row_val.push(self.n_examples);
self.nonzero.push(1f64);
self.row_val.push(2*self.n_examples + self.n_hypotheses);
self.nonzero.push(-1f64);
let n_rows = 2 * self.n_examples + self.n_hypotheses + 1;
let n_cols = self.n_examples + self.n_hypotheses + 1;
let mut col_ptr = self.col_ptr.clone();
col_ptr.push(self.row_val.len());
let row_val = self.row_val.clone();
let nonzero = self.nonzero.clone();
let constraint_matrix = CscMatrix::new(
n_rows, n_cols, col_ptr, row_val, nonzero, );
let mut rhs = vec![0f64; 2*self.n_examples + self.n_hypotheses + 1];
rhs[self.n_examples] = 1f64;
let cones = [
NonnegativeConeT(self.n_examples),
ZeroConeT(1),
NonnegativeConeT(self.n_examples),
NonnegativeConeT(self.n_hypotheses),
];
let settings = DefaultSettingsBuilder::default()
.equilibrate_enable(true)
.verbose(false)
.build()
.unwrap();
let n_variables = 1 + self.n_examples + self.n_hypotheses;
let zero_mat = CscMatrix::<f64>::zeros((n_variables, n_variables));
self.lin_obj.push(0f64);
let mut solver = DefaultSolver::new(
&zero_mat,
&self.lin_obj,
&constraint_matrix,
&rhs[..],
&cones,
settings
);
solver.solve();
let size = 1 + self.n_examples;
self.weights = solver.solution.x[size..].to_vec();
self.dist = solver.solution.z[..self.n_examples].to_vec();
let wsum = self.weights.iter().sum::<f64>();
if (wsum - 1f64).abs() > 1e-6 {
eprintln!(
"[WRN] weight sum on hypotheses far from 1. sum is: {wsum}"
);
}
let dsum = self.dist.iter().sum::<f64>();
if (dsum - 1f64).abs() > 1e-6 {
eprintln!(
"[WRN] dist sum on examples far from 1. sum is: {dsum}"
);
}
- solver.solution.obj_val
}
pub(super) fn distribution(&self)
-> Vec<f64>
{
self.dist.clone()
}
pub(super) fn weight(&self) -> impl Iterator<Item=f64> + '_
{
self.weights.iter().copied()
}
}