use std::borrow::Borrow;
use std::ops::Index;
use std::time::Duration;
use crate::variables::{Val, VarId, VarIdBin};
#[derive(Debug, Clone, PartialEq)]
pub enum ValueAccessError {
ExpectedInteger { variable: VarId, actual_value: Val },
ExpectedFloat { variable: VarId, actual_value: Val },
ExpectedBoolean { variable: VarId, actual_value: Val },
}
impl std::fmt::Display for ValueAccessError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValueAccessError::ExpectedInteger { variable, actual_value } => {
write!(f, "Expected integer value for variable {:?}, but found {:?}", variable, actual_value)
}
ValueAccessError::ExpectedFloat { variable, actual_value } => {
write!(f, "Expected float value for variable {:?}, but found {:?}", variable, actual_value)
}
ValueAccessError::ExpectedBoolean { variable, actual_value } => {
write!(f, "Expected boolean value (0 or 1) for variable {:?}, but found {:?}", variable, actual_value)
}
}
}
}
impl std::error::Error for ValueAccessError {}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct SolveStats {
pub propagation_count: usize,
pub node_count: usize,
pub solve_time: Duration,
pub init_time: Duration,
pub peak_memory_mb: usize,
pub variables: usize,
pub int_variables: usize,
pub bool_variables: usize,
pub float_variables: usize,
pub set_variables: usize,
pub constraint_count: usize,
pub propagators: usize,
pub objective: f64,
pub objective_bound: f64,
pub lp_solver_used: bool,
pub lp_constraint_count: usize,
pub lp_variable_count: usize,
pub lp_stats: Option<crate::lpsolver::types::LpStats>,
}
impl SolveStats {
pub fn new(
propagation_count: usize,
node_count: usize,
solve_time: Duration,
variable_count: usize,
constraint_count: usize,
peak_memory_mb: usize,
) -> Self {
Self {
propagation_count,
node_count,
solve_time,
variables: variable_count,
constraint_count,
peak_memory_mb,
init_time: Duration::ZERO,
int_variables: 0,
bool_variables: 0,
float_variables: 0,
set_variables: 0,
propagators: 0,
objective: 0.0,
objective_bound: 0.0,
lp_solver_used: false,
lp_constraint_count: 0,
lp_variable_count: 0,
lp_stats: None,
}
}
pub fn with_all_fields(
propagation_count: usize,
node_count: usize,
solve_time: Duration,
init_time: Duration,
variables: usize,
int_variables: usize,
bool_variables: usize,
float_variables: usize,
set_variables: usize,
constraint_count: usize,
propagators: usize,
peak_memory_mb: usize,
objective: f64,
objective_bound: f64,
) -> Self {
Self {
propagation_count,
node_count,
solve_time,
init_time,
peak_memory_mb,
variables,
int_variables,
bool_variables,
float_variables,
set_variables,
constraint_count,
propagators,
objective,
objective_bound,
lp_solver_used: false,
lp_constraint_count: 0,
lp_variable_count: 0,
lp_stats: None,
}
}
pub fn efficiency(&self) -> f64 {
if self.node_count > 0 {
self.propagation_count as f64 / self.node_count as f64
} else {
0.0
}
}
pub fn time_per_propagation(&self) -> Duration {
if self.propagation_count > 0 {
self.solve_time / self.propagation_count as u32
} else {
Duration::ZERO
}
}
pub fn time_per_node(&self) -> Duration {
if self.node_count > 0 {
self.solve_time / self.node_count as u32
} else {
Duration::ZERO
}
}
pub fn display_summary(&self) {
println!("=== Solving Statistics ===");
println!("Timing:");
println!(" Solve time: {:.3}ms", self.solve_time.as_secs_f64() * 1000.0);
println!(" Init time: {:.3}ms", self.init_time.as_secs_f64() * 1000.0);
println!("Memory:");
println!(" Total peak usage: {}MB", self.peak_memory_mb);
if self.lp_solver_used {
if let Some(ref lp_stats) = self.lp_stats {
println!(" (includes LP solver: {:.2}MB)", lp_stats.peak_memory_mb);
}
}
println!("Problem:");
println!(" Total variables: {}", self.variables);
if self.int_variables > 0 {
println!(" - Integer: {}", self.int_variables);
}
if self.bool_variables > 0 {
println!(" - Bool: {}", self.bool_variables);
}
if self.float_variables > 0 {
println!(" - Float: {}", self.float_variables);
}
if self.set_variables > 0 {
println!(" - Set: {}", self.set_variables);
}
println!(" Constraints: {}", self.constraint_count);
println!(" Propagators: {}", self.propagators);
if self.lp_solver_used {
println!("LP Solver:");
println!(" Linear constraints: {}", self.lp_constraint_count);
println!(" Linear variables: {}", self.lp_variable_count);
}
println!("Search:");
println!(" Nodes: {}", self.node_count);
println!(" Propagations: {}", self.propagation_count);
if self.objective != 0.0 || self.objective_bound != 0.0 {
println!("Objective:");
println!(" Current value: {}", self.objective);
println!(" Bound: {}", self.objective_bound);
}
if self.node_count > 0 {
println!("Efficiency:");
println!(" {:.1} propagations/node", self.efficiency());
println!(" {:.2}μs/propagation",
self.time_per_propagation().as_nanos() as f64 / 1000.0);
println!(" {:.2}μs/node",
self.time_per_node().as_nanos() as f64 / 1000.0);
} else if self.propagation_count > 0 {
println!("Efficiency: Pure propagation (no search required)");
println!(" {:.2}μs/propagation",
self.time_per_propagation().as_nanos() as f64 / 1000.0);
}
println!("==========================");
}
}
#[derive(Debug, PartialEq)]
pub struct Solution {
values: Vec<Val>,
pub stats: SolveStats,
}
impl Index<VarId> for Solution {
type Output = Val;
fn index(&self, index: VarId) -> &Self::Output {
&self.values[index]
}
}
impl Solution {
pub fn new(values: Vec<Val>, stats: SolveStats) -> Self {
Self { values, stats }
}
pub fn from_values(values: Vec<Val>) -> Self {
Self {
values,
stats: SolveStats::default(),
}
}
pub fn stats(&self) -> &SolveStats {
&self.stats
}
#[must_use]
pub fn get_values(&self, vs: &[VarId]) -> Vec<Val> {
self.get_values_iter(vs.iter().copied()).collect()
}
#[must_use]
pub fn get_values_array<const N: usize>(&self, vs: &[VarId; N]) -> [Val; N] {
vs.map(|v| self[v])
}
pub fn get_values_iter<'a, I>(&'a self, vs: I) -> impl Iterator<Item = Val> + 'a
where
I: IntoIterator + 'a,
I::Item: Borrow<VarId>,
{
vs.into_iter().map(|v| self[*v.borrow()])
}
#[must_use]
pub fn get_value_binary(&self, v: impl Borrow<VarIdBin>) -> bool {
self.values[v.borrow().0] == Val::ValI(1)
}
#[must_use]
pub fn get_values_binary(&self, vs: &[VarIdBin]) -> Vec<bool> {
self.get_values_binary_iter(vs.iter().copied()).collect()
}
#[must_use]
pub fn get_values_binary_array<const N: usize>(&self, vs: &[VarIdBin; N]) -> [bool; N] {
vs.map(|v| self.get_value_binary(v))
}
pub fn get_values_binary_iter<'a, I>(&'a self, vs: I) -> impl Iterator<Item = bool> + 'a
where
I: IntoIterator + 'a,
I::Item: Borrow<VarIdBin>,
{
vs.into_iter().map(|v| self.get_value_binary(v))
}
#[must_use]
pub fn get_int(&self, var: VarId) -> i32 {
self.try_get_int(var).unwrap()
}
#[must_use]
pub fn get_float(&self, var: VarId) -> f64 {
self.try_get_float(var).unwrap()
}
#[must_use]
pub fn get_bool(&self, var: VarId) -> Result<bool, ValueAccessError> {
match self[var] {
Val::ValI(0) => Ok(false),
Val::ValI(1) => Ok(true),
actual_value => Err(ValueAccessError::ExpectedBoolean {
variable: var,
actual_value,
}),
}
}
#[must_use]
pub fn try_get_int(&self, var: VarId) -> Result<i32, ValueAccessError> {
match self[var] {
Val::ValI(i) => Ok(i),
actual_value => Err(ValueAccessError::ExpectedInteger {
variable: var,
actual_value
}),
}
}
#[must_use]
pub fn try_get_float(&self, var: VarId) -> Result<f64, ValueAccessError> {
match self[var] {
Val::ValF(f) => Ok(f),
actual_value => Err(ValueAccessError::ExpectedFloat {
variable: var,
actual_value
}),
}
}
#[must_use]
pub fn try_get_bool(&self, var: VarId) -> Result<bool, ValueAccessError> {
match self[var] {
Val::ValI(0) => Ok(false),
Val::ValI(1) => Ok(true),
actual_value => Err(ValueAccessError::ExpectedBoolean {
variable: var,
actual_value
}),
}
}
#[must_use]
pub fn get_int_unchecked(&self, var: VarId) -> i32 {
self.get_int(var)
}
#[must_use]
pub fn get_float_unchecked(&self, var: VarId) -> f64 {
self.get_float(var)
}
#[must_use]
pub fn as_int(&self, var: VarId) -> Option<i32> {
match self[var] {
Val::ValI(i) => Some(i),
Val::ValF(_) => None,
}
}
#[must_use]
pub fn as_float(&self, var: VarId) -> Option<f64> {
match self[var] {
Val::ValF(f) => Some(f),
Val::ValI(_) => None,
}
}
#[must_use]
pub fn as_bool(&self, var: VarId) -> Option<bool> {
match self[var] {
Val::ValI(0) => Some(false),
Val::ValI(1) => Some(true),
_ => None,
}
}
pub fn get<T>(&self, var: VarId) -> T
where
Self: GetValue<T>
{
self.get_value(var)
}
pub fn try_get<T>(&self, var: VarId) -> Result<T, ValueAccessError>
where
Self: TryGetValue<T>
{
self.try_get_value(var)
}
}
pub trait GetValue<T> {
fn get_value(&self, var: VarId) -> T;
}
pub trait TryGetValue<T> {
fn try_get_value(&self, var: VarId) -> Result<T, ValueAccessError>;
}
impl GetValue<i32> for Solution {
fn get_value(&self, var: VarId) -> i32 {
self.get_int_unchecked(var)
}
}
impl GetValue<f64> for Solution {
fn get_value(&self, var: VarId) -> f64 {
self.get_float_unchecked(var)
}
}
impl GetValue<Option<i32>> for Solution {
fn get_value(&self, var: VarId) -> Option<i32> {
self.as_int(var)
}
}
impl GetValue<Option<f64>> for Solution {
fn get_value(&self, var: VarId) -> Option<f64> {
self.as_float(var)
}
}
impl TryGetValue<i32> for Solution {
fn try_get_value(&self, var: VarId) -> Result<i32, ValueAccessError> {
self.try_get_int(var)
}
}
impl TryGetValue<f64> for Solution {
fn try_get_value(&self, var: VarId) -> Result<f64, ValueAccessError> {
self.try_get_float(var)
}
}
impl From<Vec<Val>> for Solution {
fn from(values: Vec<Val>) -> Self {
Self::from_values(values)
}
}