use std::error::Error;
use std::fmt::Write as _;
use std::path::Path;
use crate::model::{Constraint, VariableType};
use crate::problem::LpProblem;
pub trait LpCsvWriter {
fn write_constraints(&self, base_path: &Path) -> Result<(), Box<dyn Error>>;
fn write_objectives(&self, base_path: &Path) -> Result<(), Box<dyn Error>>;
fn write_variables(&self, base_path: &Path) -> Result<(), Box<dyn Error>>;
fn to_csv(&self, base_path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
self.write_objectives(base_path)?;
self.write_constraints(base_path)?;
self.write_variables(base_path)?;
Ok(())
}
}
#[inline]
fn write_f64_to_buf(buf: &mut String, value: f64) -> &[u8] {
debug_assert!(value.is_finite(), "write_f64_to_buf called with non-finite value: {value}");
buf.clear();
write!(buf, "{value}").expect("writing f64 to String cannot fail");
buf.as_bytes()
}
impl LpCsvWriter for LpProblem {
fn write_constraints(&self, base_path: &Path) -> Result<(), Box<dyn Error>> {
let headers = ["constraint_name", "constraint_type", "variable_name", "coefficient", "operator", "rhs", "sos_type"];
let mut const_writer = csv::Writer::from_path(base_path.join("constraints.csv"))?;
const_writer.write_record(headers)?;
let mut buf_a = String::with_capacity(24);
let mut buf_b = String::with_capacity(24);
for (name_id, constraint) in &self.constraints {
let name = self.interner.resolve(*name_id);
let name_bytes = name.as_bytes();
match constraint {
Constraint::Standard { coefficients, operator: op, rhs, .. } => {
for c in coefficients {
let var_name = self.interner.resolve(c.name);
let coeff_bytes = write_f64_to_buf(&mut buf_a, c.value).to_owned();
let rhs_bytes = write_f64_to_buf(&mut buf_b, *rhs);
let vals: [&[u8]; 7] = [name_bytes, b"Standard", var_name.as_bytes(), &coeff_bytes, op.as_ref(), rhs_bytes, b""];
const_writer.write_record(vals)?;
}
}
Constraint::SOS { sos_type, weights, .. } => {
for c in weights {
let var_name = self.interner.resolve(c.name);
let vals =
[name_bytes, b"SOS", var_name.as_bytes(), write_f64_to_buf(&mut buf_a, c.value), b"", b"", sos_type.as_ref()];
const_writer.write_record(vals)?;
}
}
}
}
const_writer.flush()?;
Ok(())
}
fn write_objectives(&self, base_path: &Path) -> Result<(), Box<dyn Error>> {
let mut obj_writer = csv::Writer::from_path(base_path.join("objectives.csv"))?;
obj_writer.write_record(["objective_name", "variable_name", "coefficient"])?;
let mut buf = String::with_capacity(24);
for (name_id, objective) in &self.objectives {
let name = self.interner.resolve(*name_id);
for coef in &objective.coefficients {
let var_name = self.interner.resolve(coef.name);
let vals = [name.as_bytes(), var_name.as_bytes(), write_f64_to_buf(&mut buf, coef.value)];
obj_writer.write_record(vals)?;
}
}
obj_writer.flush()?;
Ok(())
}
fn write_variables(&self, base_path: &Path) -> Result<(), Box<dyn Error>> {
let mut var_writer = csv::Writer::from_path(base_path.join("variables.csv"))?;
var_writer.write_record(["variable_name", "type", "lower_bound", "upper_bound"])?;
let mut buf_a = String::with_capacity(24);
let mut buf_b = String::with_capacity(24);
for (name_id, var) in &self.variables {
let name = self.interner.resolve(*name_id);
let name_bytes = name.as_bytes();
match var.var_type {
VariableType::LowerBound(lb) => {
let vals = [name_bytes, var.var_type.as_ref(), write_f64_to_buf(&mut buf_a, lb), b""];
var_writer.write_record(vals)?;
}
VariableType::UpperBound(ub) => {
let vals = [name_bytes, var.var_type.as_ref(), b"" as &[u8], write_f64_to_buf(&mut buf_a, ub)];
var_writer.write_record(vals)?;
}
VariableType::DoubleBound(lb, ub) => {
let lb_bytes = write_f64_to_buf(&mut buf_a, lb).to_owned();
let ub_bytes = write_f64_to_buf(&mut buf_b, ub);
let vals: [&[u8]; 4] = [name_bytes, var.var_type.as_ref(), &lb_bytes, ub_bytes];
var_writer.write_record(vals)?;
}
_ => {
var_writer.write_record([name_bytes, var.var_type.as_ref(), b"", b""])?;
}
}
}
var_writer.flush()?;
Ok(())
}
}