use crate::constraints::props::{Prune, Propagate};
use crate::variables::{VarId, Val};
use crate::variables::views::{Context, View};
#[derive(Clone, Debug)]
pub struct IntLinEq {
coefficients: Vec<i32>,
variables: Vec<VarId>,
constant: i32,
}
impl IntLinEq {
pub fn new(coefficients: Vec<i32>, variables: Vec<VarId>, constant: i32) -> Self {
Self { coefficients, variables, constant }
}
}
impl Prune for IntLinEq {
fn prune(&self, ctx: &mut Context) -> Option<()> {
for i in 0..self.variables.len() {
let var_id = self.variables[i];
let coeff = self.coefficients[i];
if coeff == 0 {
continue;
}
let mut min_other = 0i32;
let mut max_other = 0i32;
for j in 0..self.variables.len() {
if i == j {
continue;
}
let other_var = self.variables[j];
let other_coeff = self.coefficients[j];
let lb = other_var.min(ctx);
let ub = other_var.max(ctx);
let (min_term, max_term) = match (lb, ub) {
(Val::ValI(l), Val::ValI(u)) => {
if other_coeff > 0 {
(other_coeff * l, other_coeff * u)
} else {
(other_coeff * u, other_coeff * l)
}
}
_ => return Some(()), };
min_other = min_other.saturating_add(min_term);
max_other = max_other.saturating_add(max_term);
}
let target_min = self.constant.saturating_sub(max_other);
let target_max = self.constant.saturating_sub(min_other);
let (new_min, new_max) = if coeff > 0 {
let min_val = target_min.div_euclid(coeff);
let max_val = target_max.div_euclid(coeff);
(min_val, max_val)
} else {
let min_val = target_max.div_euclid(coeff);
let max_val = target_min.div_euclid(coeff);
(min_val, max_val)
};
var_id.try_set_min(Val::ValI(new_min), ctx)?;
var_id.try_set_max(Val::ValI(new_max), ctx)?;
}
Some(())
}
}
impl Propagate for IntLinEq {
fn list_trigger_vars(&self) -> impl Iterator<Item = VarId> {
self.variables.iter().copied()
}
}
#[derive(Clone, Debug)]
pub struct IntLinLe {
coefficients: Vec<i32>,
variables: Vec<VarId>,
constant: i32,
}
impl IntLinLe {
pub fn new(coefficients: Vec<i32>, variables: Vec<VarId>, constant: i32) -> Self {
Self { coefficients, variables, constant }
}
}
impl Prune for IntLinLe {
fn prune(&self, ctx: &mut Context) -> Option<()> {
for i in 0..self.variables.len() {
let var_id = self.variables[i];
let coeff = self.coefficients[i];
if coeff == 0 {
continue;
}
let mut min_other = 0i32;
for j in 0..self.variables.len() {
if i == j {
continue;
}
let other_var = self.variables[j];
let other_coeff = self.coefficients[j];
let lb = other_var.min(ctx);
let ub = other_var.max(ctx);
let min_term = match (lb, ub) {
(Val::ValI(l), Val::ValI(u)) => {
if other_coeff > 0 {
other_coeff * l
} else {
other_coeff * u
}
}
_ => return Some(()),
};
min_other = min_other.saturating_add(min_term);
}
let remaining = self.constant.saturating_sub(min_other);
if coeff > 0 {
let max_val = remaining.div_euclid(coeff);
var_id.try_set_max(Val::ValI(max_val), ctx)?;
} else {
let min_val = remaining.div_euclid(coeff);
var_id.try_set_min(Val::ValI(min_val), ctx)?;
}
}
Some(())
}
}
impl Propagate for IntLinLe {
fn list_trigger_vars(&self) -> impl Iterator<Item = VarId> {
self.variables.iter().copied()
}
}
#[derive(Clone, Debug)]
pub struct IntLinNe {
coefficients: Vec<i32>,
variables: Vec<VarId>,
constant: i32,
}
impl IntLinNe {
pub fn new(coefficients: Vec<i32>, variables: Vec<VarId>, constant: i32) -> Self {
Self { coefficients, variables, constant }
}
}
impl Prune for IntLinNe {
fn prune(&self, ctx: &mut Context) -> Option<()> {
let mut unfixed_idx = None;
let mut fixed_sum = 0i32;
for i in 0..self.variables.len() {
let var_id = self.variables[i];
let coeff = self.coefficients[i];
let lb = var_id.min(ctx);
let ub = var_id.max(ctx);
match (lb, ub) {
(Val::ValI(l), Val::ValI(u)) if l == u => {
fixed_sum = fixed_sum.saturating_add(coeff * l);
}
(Val::ValI(_), Val::ValI(_)) => {
if unfixed_idx.is_some() {
return Some(());
}
unfixed_idx = Some(i);
}
_ => return Some(()),
}
}
if unfixed_idx.is_none() {
if fixed_sum == self.constant {
return None; }
return Some(()); }
let idx = unfixed_idx.unwrap();
let var_id = self.variables[idx];
let coeff = self.coefficients[idx];
if coeff == 0 {
if fixed_sum == self.constant {
return None; }
return Some(());
}
let forbidden_value_num = self.constant.saturating_sub(fixed_sum);
if forbidden_value_num % coeff == 0 {
let forbidden = forbidden_value_num / coeff;
exclude_value(var_id, Val::ValI(forbidden), ctx)?;
}
Some(())
}
}
impl Propagate for IntLinNe {
fn list_trigger_vars(&self) -> impl Iterator<Item = VarId> {
self.variables.iter().copied()
}
}
#[derive(Clone, Debug)]
pub struct IntLinEqReif {
coefficients: Vec<i32>,
variables: Vec<VarId>,
constant: i32,
reif_var: VarId,
}
impl IntLinEqReif {
pub fn new(coefficients: Vec<i32>, variables: Vec<VarId>, constant: i32, reif_var: VarId) -> Self {
Self { coefficients, variables, constant, reif_var }
}
}
impl Prune for IntLinEqReif {
fn prune(&self, ctx: &mut Context) -> Option<()> {
let reif_min = self.reif_var.min(ctx);
let reif_max = self.reif_var.max(ctx);
if reif_min == Val::ValI(1) && reif_max == Val::ValI(1) {
return prune_int_lin_eq(&self.coefficients, &self.variables, self.constant, ctx);
}
if reif_min == Val::ValI(0) && reif_max == Val::ValI(0) {
if let Some(sum) = compute_fixed_sum(&self.coefficients, &self.variables, ctx) {
if sum == self.constant {
return None; }
}
return Some(());
}
if let Some(sum) = compute_fixed_sum(&self.coefficients, &self.variables, ctx) {
if sum == self.constant {
self.reif_var.try_set_min(Val::ValI(1), ctx)?;
self.reif_var.try_set_max(Val::ValI(1), ctx)?;
} else {
self.reif_var.try_set_min(Val::ValI(0), ctx)?;
self.reif_var.try_set_max(Val::ValI(0), ctx)?;
}
}
Some(())
}
}
impl Propagate for IntLinEqReif {
fn list_trigger_vars(&self) -> impl Iterator<Item = VarId> {
self.variables.iter().copied().chain(std::iter::once(self.reif_var))
}
}
#[derive(Clone, Debug)]
pub struct IntLinLeReif {
coefficients: Vec<i32>,
variables: Vec<VarId>,
constant: i32,
reif_var: VarId,
}
impl IntLinLeReif {
pub fn new(coefficients: Vec<i32>, variables: Vec<VarId>, constant: i32, reif_var: VarId) -> Self {
Self { coefficients, variables, constant, reif_var }
}
}
impl Prune for IntLinLeReif {
fn prune(&self, ctx: &mut Context) -> Option<()> {
let reif_min = self.reif_var.min(ctx);
let reif_max = self.reif_var.max(ctx);
if reif_min == Val::ValI(1) && reif_max == Val::ValI(1) {
return prune_int_lin_le(&self.coefficients, &self.variables, self.constant, ctx);
}
if reif_min == Val::ValI(0) && reif_max == Val::ValI(0) {
if let Some(sum) = compute_fixed_sum(&self.coefficients, &self.variables, ctx) {
if sum <= self.constant {
return None; }
}
return Some(());
}
let (min_sum, max_sum) = compute_sum_bounds(&self.coefficients, &self.variables, ctx)?;
if max_sum <= self.constant {
self.reif_var.try_set_min(Val::ValI(1), ctx)?;
self.reif_var.try_set_max(Val::ValI(1), ctx)?;
} else if min_sum > self.constant {
self.reif_var.try_set_min(Val::ValI(0), ctx)?;
self.reif_var.try_set_max(Val::ValI(0), ctx)?;
}
Some(())
}
}
impl Propagate for IntLinLeReif {
fn list_trigger_vars(&self) -> impl Iterator<Item = VarId> {
self.variables.iter().copied().chain(std::iter::once(self.reif_var))
}
}
#[derive(Clone, Debug)]
pub struct IntLinNeReif {
coefficients: Vec<i32>,
variables: Vec<VarId>,
constant: i32,
reif_var: VarId,
}
impl IntLinNeReif {
pub fn new(coefficients: Vec<i32>, variables: Vec<VarId>, constant: i32, reif_var: VarId) -> Self {
Self { coefficients, variables, constant, reif_var }
}
}
impl Prune for IntLinNeReif {
fn prune(&self, ctx: &mut Context) -> Option<()> {
let reif_min = self.reif_var.min(ctx);
let reif_max = self.reif_var.max(ctx);
if reif_min == Val::ValI(1) && reif_max == Val::ValI(1) {
return prune_int_lin_ne(&self.coefficients, &self.variables, self.constant, ctx);
}
if reif_min == Val::ValI(0) && reif_max == Val::ValI(0) {
return prune_int_lin_eq(&self.coefficients, &self.variables, self.constant, ctx);
}
if let Some(sum) = compute_fixed_sum(&self.coefficients, &self.variables, ctx) {
if sum != self.constant {
self.reif_var.try_set_min(Val::ValI(1), ctx)?;
self.reif_var.try_set_max(Val::ValI(1), ctx)?;
} else {
self.reif_var.try_set_min(Val::ValI(0), ctx)?;
self.reif_var.try_set_max(Val::ValI(0), ctx)?;
}
}
Some(())
}
}
impl Propagate for IntLinNeReif {
fn list_trigger_vars(&self) -> impl Iterator<Item = VarId> {
self.variables.iter().copied().chain(std::iter::once(self.reif_var))
}
}
#[derive(Clone, Debug)]
pub struct FloatLinEq {
coefficients: Vec<f64>,
variables: Vec<VarId>,
constant: f64,
}
impl FloatLinEq {
pub fn new(coefficients: Vec<f64>, variables: Vec<VarId>, constant: f64) -> Self {
Self { coefficients, variables, constant }
}
pub(crate) fn coefficients(&self) -> &[f64] {
&self.coefficients
}
pub(crate) fn variables(&self) -> &[VarId] {
&self.variables
}
pub(crate) fn constant(&self) -> f64 {
self.constant
}
}
impl Prune for FloatLinEq {
fn prune(&self, ctx: &mut Context) -> Option<()> {
let debug = std::env::var("DEBUG_FLOAT_LIN").is_ok();
for i in 0..self.variables.len() {
let var_id = self.variables[i];
let coeff = self.coefficients[i];
if coeff.abs() < 1e-12 {
continue;
}
let mut min_other = 0.0;
let mut max_other = 0.0;
for j in 0..self.variables.len() {
if i == j {
continue;
}
let other_var = self.variables[j];
let other_coeff = self.coefficients[j];
let lb = other_var.min(ctx);
let ub = other_var.max(ctx);
let (min_term, max_term) = match (lb, ub) {
(Val::ValF(l), Val::ValF(u)) => {
if other_coeff > 0.0 {
(other_coeff * l, other_coeff * u)
} else {
(other_coeff * u, other_coeff * l)
}
}
(Val::ValI(l), Val::ValI(u)) => {
let lf = l as f64;
let uf = u as f64;
if other_coeff > 0.0 {
(other_coeff * lf, other_coeff * uf)
} else {
(other_coeff * uf, other_coeff * lf)
}
}
_ => return Some(()),
};
min_other += min_term;
max_other += max_term;
}
let target_min = self.constant - max_other;
let target_max = self.constant - min_other;
let (mut new_min, mut new_max) = if coeff > 0.0 {
(target_min / coeff, target_max / coeff)
} else {
(target_max / coeff, target_min / coeff)
};
if new_min > new_max {
if debug {
eprintln!("DEBUG: FloatLinEq swapping bounds: new_min={} > new_max={}", new_min, new_max);
}
std::mem::swap(&mut new_min, &mut new_max);
}
let current_min = match var_id.min(ctx) {
Val::ValF(f) => f,
Val::ValI(i) => i as f64,
};
let current_max = match var_id.max(ctx) {
Val::ValF(f) => f,
Val::ValI(i) => i as f64,
};
let tolerance = 1e-6;
if new_max < current_min && (current_min - new_max) < tolerance {
new_max = current_min;
}
if new_min > current_max && (new_min - current_max) < tolerance {
new_min = current_max;
}
if debug {
let cur_min = var_id.min(ctx);
let cur_max = var_id.max(ctx);
eprintln!("DEBUG: FloatLinEq var {:?}: current=[{:?}, {:?}], setting=[{}, {}]",
var_id, cur_min, cur_max, new_min, new_max);
}
let is_fixed = (current_max - current_min).abs() < 1e-9;
let min_close = (new_min - current_min).abs() < 1e-4;
if is_fixed && min_close {
if debug {
eprintln!("DEBUG: FloatLinEq skipping update for fixed var {:?} (current={}, new_min={})",
var_id, current_min, new_min);
}
continue;
}
let min_result = var_id.try_set_min(Val::ValF(new_min), ctx);
if debug && min_result.is_none() {
eprintln!("DEBUG: FloatLinEq FAILED on try_set_min({}) for var {:?}", new_min, var_id);
}
min_result?;
let max_result = var_id.try_set_max(Val::ValF(new_max), ctx);
if debug && max_result.is_none() {
eprintln!("DEBUG: FloatLinEq FAILED on try_set_max({}) for var {:?}", new_max, var_id);
}
max_result?;
}
Some(())
}
}
impl Propagate for FloatLinEq {
fn list_trigger_vars(&self) -> impl Iterator<Item = VarId> {
self.variables.iter().copied()
}
}
#[derive(Clone, Debug)]
pub struct FloatLinLe {
coefficients: Vec<f64>,
variables: Vec<VarId>,
constant: f64,
}
impl FloatLinLe {
pub fn new(coefficients: Vec<f64>, variables: Vec<VarId>, constant: f64) -> Self {
Self { coefficients, variables, constant }
}
pub(crate) fn coefficients(&self) -> &[f64] {
&self.coefficients
}
pub(crate) fn variables(&self) -> &[VarId] {
&self.variables
}
pub(crate) fn constant(&self) -> f64 {
self.constant
}
}
impl Prune for FloatLinLe {
fn prune(&self, ctx: &mut Context) -> Option<()> {
for i in 0..self.variables.len() {
let var_id = self.variables[i];
let coeff = self.coefficients[i];
if coeff.abs() < 1e-12 {
continue;
}
let mut min_other = 0.0;
for j in 0..self.variables.len() {
if i == j {
continue;
}
let other_var = self.variables[j];
let other_coeff = self.coefficients[j];
let lb = other_var.min(ctx);
let ub = other_var.max(ctx);
let min_term = match (lb, ub) {
(Val::ValF(l), Val::ValF(u)) => {
if other_coeff > 0.0 {
other_coeff * l
} else {
other_coeff * u
}
}
(Val::ValI(l), Val::ValI(u)) => {
let lf = l as f64;
let uf = u as f64;
if other_coeff > 0.0 {
other_coeff * lf
} else {
other_coeff * uf
}
}
_ => return Some(()),
};
min_other += min_term;
}
let remaining = self.constant - min_other;
if coeff > 0.0 {
let max_val = remaining / coeff;
if max_val.is_finite() {
if let Val::ValF(current_max) = var_id.max(ctx) {
if max_val < current_max {
var_id.try_set_max(Val::ValF(max_val), ctx)?;
}
}
}
} else {
let min_val = remaining / coeff;
let normalized_min = if min_val == 0.0 { 0.0 } else { min_val };
if normalized_min.is_finite() {
if let Val::ValF(current_min) = var_id.min(ctx) {
if normalized_min > current_min {
var_id.try_set_min(Val::ValF(normalized_min), ctx)?;
}
}
}
}
}
Some(())
}
}
impl Propagate for FloatLinLe {
fn list_trigger_vars(&self) -> impl Iterator<Item = VarId> {
self.variables.iter().copied()
}
}
#[derive(Clone, Debug)]
pub struct FloatLinNe {
coefficients: Vec<f64>,
variables: Vec<VarId>,
constant: f64,
}
impl FloatLinNe {
pub fn new(coefficients: Vec<f64>, variables: Vec<VarId>, constant: f64) -> Self {
Self { coefficients, variables, constant }
}
}
impl Prune for FloatLinNe {
fn prune(&self, ctx: &mut Context) -> Option<()> {
let mut unfixed_idx = None;
let mut fixed_sum = 0.0;
for i in 0..self.variables.len() {
let var_id = self.variables[i];
let coeff = self.coefficients[i];
let lb = var_id.min(ctx);
let ub = var_id.max(ctx);
match (lb, ub) {
(Val::ValF(l), Val::ValF(u)) if (l - u).abs() < 1e-12 => {
fixed_sum += coeff * l;
}
(Val::ValI(l), Val::ValI(u)) if l == u => {
fixed_sum += coeff * (l as f64);
}
_ => {
if unfixed_idx.is_some() {
return Some(());
}
unfixed_idx = Some(i);
}
}
}
if unfixed_idx.is_none() {
if (fixed_sum - self.constant).abs() < 1e-12 {
return None;
}
return Some(());
}
let idx = unfixed_idx.unwrap();
let var_id = self.variables[idx];
let coeff = self.coefficients[idx];
if coeff.abs() < 1e-12 {
if (fixed_sum - self.constant).abs() < 1e-12 {
return None;
}
return Some(());
}
let forbidden = (self.constant - fixed_sum) / coeff;
exclude_value(var_id, Val::ValF(forbidden), ctx)?;
Some(())
}
}
impl Propagate for FloatLinNe {
fn list_trigger_vars(&self) -> impl Iterator<Item = VarId> {
self.variables.iter().copied()
}
}
#[derive(Clone, Debug)]
pub struct FloatLinEqReif {
coefficients: Vec<f64>,
variables: Vec<VarId>,
constant: f64,
reif_var: VarId,
}
impl FloatLinEqReif {
pub fn new(coefficients: Vec<f64>, variables: Vec<VarId>, constant: f64, reif_var: VarId) -> Self {
Self { coefficients, variables, constant, reif_var }
}
}
impl Prune for FloatLinEqReif {
fn prune(&self, ctx: &mut Context) -> Option<()> {
let reif_min = self.reif_var.min(ctx);
let reif_max = self.reif_var.max(ctx);
if reif_min == Val::ValI(1) && reif_max == Val::ValI(1) {
return prune_float_lin_eq(&self.coefficients, &self.variables, self.constant, ctx);
}
if reif_min == Val::ValI(0) && reif_max == Val::ValI(0) {
if let Some(sum) = compute_fixed_sum_float(&self.coefficients, &self.variables, ctx) {
if (sum - self.constant).abs() < 1e-12 {
return None;
}
}
return Some(());
}
if let Some(sum) = compute_fixed_sum_float(&self.coefficients, &self.variables, ctx) {
if (sum - self.constant).abs() < 1e-12 {
self.reif_var.try_set_min(Val::ValI(1), ctx)?;
self.reif_var.try_set_max(Val::ValI(1), ctx)?;
} else {
self.reif_var.try_set_min(Val::ValI(0), ctx)?;
self.reif_var.try_set_max(Val::ValI(0), ctx)?;
}
}
Some(())
}
}
impl Propagate for FloatLinEqReif {
fn list_trigger_vars(&self) -> impl Iterator<Item = VarId> {
self.variables.iter().copied().chain(std::iter::once(self.reif_var))
}
}
#[derive(Clone, Debug)]
pub struct FloatLinLeReif {
coefficients: Vec<f64>,
variables: Vec<VarId>,
constant: f64,
reif_var: VarId,
}
impl FloatLinLeReif {
pub fn new(coefficients: Vec<f64>, variables: Vec<VarId>, constant: f64, reif_var: VarId) -> Self {
Self { coefficients, variables, constant, reif_var }
}
}
impl Prune for FloatLinLeReif {
fn prune(&self, ctx: &mut Context) -> Option<()> {
let reif_min = self.reif_var.min(ctx);
let reif_max = self.reif_var.max(ctx);
if reif_min == Val::ValI(1) && reif_max == Val::ValI(1) {
return prune_float_lin_le(&self.coefficients, &self.variables, self.constant, ctx);
}
if reif_min == Val::ValI(0) && reif_max == Val::ValI(0) {
if let Some(sum) = compute_fixed_sum_float(&self.coefficients, &self.variables, ctx) {
if sum <= self.constant {
return None;
}
}
return Some(());
}
let (min_sum, max_sum) = compute_sum_bounds_float(&self.coefficients, &self.variables, ctx)?;
if max_sum <= self.constant {
self.reif_var.try_set_min(Val::ValI(1), ctx)?;
self.reif_var.try_set_max(Val::ValI(1), ctx)?;
} else if min_sum > self.constant {
self.reif_var.try_set_min(Val::ValI(0), ctx)?;
self.reif_var.try_set_max(Val::ValI(0), ctx)?;
}
Some(())
}
}
impl Propagate for FloatLinLeReif {
fn list_trigger_vars(&self) -> impl Iterator<Item = VarId> {
self.variables.iter().copied().chain(std::iter::once(self.reif_var))
}
}
#[derive(Clone, Debug)]
pub struct FloatLinNeReif {
coefficients: Vec<f64>,
variables: Vec<VarId>,
constant: f64,
reif_var: VarId,
}
impl FloatLinNeReif {
pub fn new(coefficients: Vec<f64>, variables: Vec<VarId>, constant: f64, reif_var: VarId) -> Self {
Self { coefficients, variables, constant, reif_var }
}
}
impl Prune for FloatLinNeReif {
fn prune(&self, ctx: &mut Context) -> Option<()> {
let reif_min = self.reif_var.min(ctx);
let reif_max = self.reif_var.max(ctx);
if reif_min == Val::ValI(1) && reif_max == Val::ValI(1) {
return prune_float_lin_ne(&self.coefficients, &self.variables, self.constant, ctx);
}
if reif_min == Val::ValI(0) && reif_max == Val::ValI(0) {
return prune_float_lin_eq(&self.coefficients, &self.variables, self.constant, ctx);
}
if let Some(sum) = compute_fixed_sum_float(&self.coefficients, &self.variables, ctx) {
if (sum - self.constant).abs() >= 1e-12 {
self.reif_var.try_set_min(Val::ValI(1), ctx)?;
self.reif_var.try_set_max(Val::ValI(1), ctx)?;
} else {
self.reif_var.try_set_min(Val::ValI(0), ctx)?;
self.reif_var.try_set_max(Val::ValI(0), ctx)?;
}
}
Some(())
}
}
impl Propagate for FloatLinNeReif {
fn list_trigger_vars(&self) -> impl Iterator<Item = VarId> {
self.variables.iter().copied().chain(std::iter::once(self.reif_var))
}
}
fn compute_sum_bounds(coefficients: &[i32], variables: &[VarId], ctx: &Context) -> Option<(i32, i32)> {
let mut min_sum = 0i32;
let mut max_sum = 0i32;
for (&coeff, &var) in coefficients.iter().zip(variables.iter()) {
let lb = var.min(ctx);
let ub = var.max(ctx);
match (lb, ub) {
(Val::ValI(l), Val::ValI(u)) => {
let (min_term, max_term) = if coeff > 0 {
(coeff * l, coeff * u)
} else {
(coeff * u, coeff * l)
};
min_sum = min_sum.saturating_add(min_term);
max_sum = max_sum.saturating_add(max_term);
}
_ => return None, }
}
Some((min_sum, max_sum))
}
fn compute_fixed_sum(coefficients: &[i32], variables: &[VarId], ctx: &Context) -> Option<i32> {
let mut sum = 0i32;
for (&coeff, &var) in coefficients.iter().zip(variables.iter()) {
let lb = var.min(ctx);
let ub = var.max(ctx);
match (lb, ub) {
(Val::ValI(l), Val::ValI(u)) if l == u => {
sum = sum.saturating_add(coeff * l);
}
_ => return None, }
}
Some(sum)
}
fn prune_int_lin_eq(coefficients: &[i32], variables: &[VarId], constant: i32, ctx: &mut Context) -> Option<()> {
for i in 0..variables.len() {
let var_id = variables[i];
let coeff = coefficients[i];
if coeff == 0 {
continue;
}
let mut min_other = 0i32;
let mut max_other = 0i32;
for j in 0..variables.len() {
if i == j {
continue;
}
let other_var = variables[j];
let other_coeff = coefficients[j];
let lb = other_var.min(ctx);
let ub = other_var.max(ctx);
let (min_term, max_term) = match (lb, ub) {
(Val::ValI(l), Val::ValI(u)) => {
if other_coeff > 0 {
(other_coeff * l, other_coeff * u)
} else {
(other_coeff * u, other_coeff * l)
}
}
_ => return Some(()),
};
min_other = min_other.saturating_add(min_term);
max_other = max_other.saturating_add(max_term);
}
let target_min = constant.saturating_sub(max_other);
let target_max = constant.saturating_sub(min_other);
let (new_min, new_max) = if coeff > 0 {
let min_val = target_min.div_euclid(coeff);
let max_val = target_max.div_euclid(coeff);
(min_val, max_val)
} else {
let min_val = target_max.div_euclid(coeff);
let max_val = target_min.div_euclid(coeff);
(min_val, max_val)
};
var_id.try_set_min(Val::ValI(new_min), ctx)?;
var_id.try_set_max(Val::ValI(new_max), ctx)?;
}
Some(())
}
fn prune_int_lin_le(coefficients: &[i32], variables: &[VarId], constant: i32, ctx: &mut Context) -> Option<()> {
for i in 0..variables.len() {
let var_id = variables[i];
let coeff = coefficients[i];
if coeff == 0 {
continue;
}
let mut min_other = 0i32;
for j in 0..variables.len() {
if i == j {
continue;
}
let other_var = variables[j];
let other_coeff = coefficients[j];
let lb = other_var.min(ctx);
let ub = other_var.max(ctx);
let min_term = match (lb, ub) {
(Val::ValI(l), Val::ValI(u)) => {
if other_coeff > 0 {
other_coeff * l
} else {
other_coeff * u
}
}
_ => return Some(()),
};
min_other = min_other.saturating_add(min_term);
}
let remaining = constant.saturating_sub(min_other);
if coeff > 0 {
let max_val = remaining.div_euclid(coeff);
var_id.try_set_max(Val::ValI(max_val), ctx)?;
} else {
let min_val = remaining.div_euclid(coeff);
var_id.try_set_min(Val::ValI(min_val), ctx)?;
}
}
Some(())
}
fn prune_int_lin_ne(coefficients: &[i32], variables: &[VarId], constant: i32, ctx: &mut Context) -> Option<()> {
let mut unfixed_idx = None;
let mut fixed_sum = 0i32;
for i in 0..variables.len() {
let var_id = variables[i];
let coeff = coefficients[i];
let lb = var_id.min(ctx);
let ub = var_id.max(ctx);
match (lb, ub) {
(Val::ValI(l), Val::ValI(u)) if l == u => {
fixed_sum = fixed_sum.saturating_add(coeff * l);
}
(Val::ValI(_), Val::ValI(_)) => {
if unfixed_idx.is_some() {
return Some(());
}
unfixed_idx = Some(i);
}
_ => return Some(()),
}
}
if unfixed_idx.is_none() {
if fixed_sum == constant {
return None;
}
return Some(());
}
let idx = unfixed_idx.unwrap();
let var_id = variables[idx];
let coeff = coefficients[idx];
if coeff == 0 {
if fixed_sum == constant {
return None;
}
return Some(());
}
let forbidden_value_num = constant.saturating_sub(fixed_sum);
if forbidden_value_num % coeff == 0 {
let forbidden = forbidden_value_num / coeff;
exclude_value(var_id, Val::ValI(forbidden), ctx)?;
}
Some(())
}
fn exclude_value(var_id: VarId, forbidden_value: Val, ctx: &mut Context) -> Option<()> {
let current_min = var_id.min(ctx);
let current_max = var_id.max(ctx);
if forbidden_value < current_min || forbidden_value > current_max {
return Some(());
}
if current_min == current_max && current_min == forbidden_value {
return None; }
if current_min == forbidden_value {
let new_min = match forbidden_value {
Val::ValI(i) => Val::ValI(i + 1),
Val::ValF(f) => Val::ValF(f + 1e-4),
};
var_id.try_set_min(new_min, ctx)?;
return Some(());
}
if current_max == forbidden_value {
let new_max = match forbidden_value {
Val::ValI(i) => Val::ValI(i - 1),
Val::ValF(f) => Val::ValF(f - 1e-4),
};
var_id.try_set_max(new_max, ctx)?;
return Some(());
}
Some(())
}
fn compute_sum_bounds_float(coefficients: &[f64], variables: &[VarId], ctx: &Context) -> Option<(f64, f64)> {
let mut min_sum = 0.0;
let mut max_sum = 0.0;
for (&coeff, &var) in coefficients.iter().zip(variables.iter()) {
let lb = var.min(ctx);
let ub = var.max(ctx);
let (min_term, max_term) = match (lb, ub) {
(Val::ValF(l), Val::ValF(u)) => {
if coeff > 0.0 {
(coeff * l, coeff * u)
} else {
(coeff * u, coeff * l)
}
}
(Val::ValI(l), Val::ValI(u)) => {
let lf = l as f64;
let uf = u as f64;
if coeff > 0.0 {
(coeff * lf, coeff * uf)
} else {
(coeff * uf, coeff * lf)
}
}
_ => return None,
};
min_sum += min_term;
max_sum += max_term;
}
Some((min_sum, max_sum))
}
fn compute_fixed_sum_float(coefficients: &[f64], variables: &[VarId], ctx: &Context) -> Option<f64> {
let mut sum = 0.0;
for (&coeff, &var) in coefficients.iter().zip(variables.iter()) {
let lb = var.min(ctx);
let ub = var.max(ctx);
match (lb, ub) {
(Val::ValF(l), Val::ValF(u)) if (l - u).abs() < 1e-12 => {
sum += coeff * l;
}
(Val::ValI(l), Val::ValI(u)) if l == u => {
sum += coeff * (l as f64);
}
_ => return None,
}
}
Some(sum)
}
fn prune_float_lin_eq(coefficients: &[f64], variables: &[VarId], constant: f64, ctx: &mut Context) -> Option<()> {
for i in 0..variables.len() {
let var_id = variables[i];
let coeff = coefficients[i];
if coeff.abs() < 1e-12 {
continue;
}
let mut min_other = 0.0;
let mut max_other = 0.0;
let mut has_unbounded_other = false;
for j in 0..variables.len() {
if i == j {
continue;
}
let other_var = variables[j];
let other_coeff = coefficients[j];
let lb = other_var.min(ctx);
let ub = other_var.max(ctx);
let (min_term, max_term) = match (lb, ub) {
(Val::ValF(l), Val::ValF(u)) => {
if l.is_infinite() || u.is_infinite() {
has_unbounded_other = true;
break;
}
if other_coeff > 0.0 {
(other_coeff * l, other_coeff * u)
} else {
(other_coeff * u, other_coeff * l)
}
}
(Val::ValI(l), Val::ValI(u)) => {
let lf = l as f64;
let uf = u as f64;
if other_coeff > 0.0 {
(other_coeff * lf, other_coeff * uf)
} else {
(other_coeff * uf, other_coeff * lf)
}
}
_ => return Some(()),
};
min_other += min_term;
max_other += max_term;
}
if has_unbounded_other {
continue;
}
let target_min = constant - max_other;
let target_max = constant - min_other;
let (mut new_min, mut new_max) = if coeff > 0.0 {
(target_min / coeff, target_max / coeff)
} else {
(target_max / coeff, target_min / coeff)
};
if new_min > new_max {
std::mem::swap(&mut new_min, &mut new_max);
}
let current_min = match var_id.min(ctx) {
Val::ValF(f) => f,
Val::ValI(i) => i as f64,
};
let current_max = match var_id.max(ctx) {
Val::ValF(f) => f,
Val::ValI(i) => i as f64,
};
let tolerance = 1e-6;
if new_max < current_min && (current_min - new_max) < tolerance {
new_max = current_min;
}
if new_min > current_max && (new_min - current_max) < tolerance {
new_min = current_max;
}
let is_fixed = (current_max - current_min).abs() < 1e-9;
let min_close = (new_min - current_min).abs() < 1e-4;
if is_fixed && min_close {
continue;
}
var_id.try_set_min(Val::ValF(new_min), ctx)?;
var_id.try_set_max(Val::ValF(new_max), ctx)?;
}
Some(())
}
fn prune_float_lin_le(coefficients: &[f64], variables: &[VarId], constant: f64, ctx: &mut Context) -> Option<()> {
for i in 0..variables.len() {
let var_id = variables[i];
let coeff = coefficients[i];
if coeff.abs() < 1e-12 {
continue;
}
let mut min_other = 0.0;
let mut has_unbounded_other = false;
for j in 0..variables.len() {
if i == j {
continue;
}
let other_var = variables[j];
let other_coeff = coefficients[j];
let lb = other_var.min(ctx);
let ub = other_var.max(ctx);
let min_term = match (lb, ub) {
(Val::ValF(l), Val::ValF(u)) => {
if l.is_infinite() || u.is_infinite() {
has_unbounded_other = true;
break;
}
if other_coeff > 0.0 {
other_coeff * l
} else {
other_coeff * u
}
}
(Val::ValI(l), Val::ValI(u)) => {
let lf = l as f64;
let uf = u as f64;
if other_coeff > 0.0 {
other_coeff * lf
} else {
other_coeff * uf
}
}
_ => return Some(()),
};
min_other += min_term;
}
if has_unbounded_other {
continue;
}
let remaining = constant - min_other;
if coeff > 0.0 {
let max_val = remaining / coeff;
var_id.try_set_max(Val::ValF(max_val), ctx)?;
} else {
let min_val = remaining / coeff;
var_id.try_set_min(Val::ValF(min_val), ctx)?;
}
}
Some(())
}
fn prune_float_lin_ne(coefficients: &[f64], variables: &[VarId], constant: f64, ctx: &mut Context) -> Option<()> {
let mut unfixed_idx = None;
let mut fixed_sum = 0.0;
for i in 0..variables.len() {
let var_id = variables[i];
let coeff = coefficients[i];
let lb = var_id.min(ctx);
let ub = var_id.max(ctx);
match (lb, ub) {
(Val::ValF(l), Val::ValF(u)) if (l - u).abs() < 1e-12 => {
fixed_sum += coeff * l;
}
(Val::ValI(l), Val::ValI(u)) if l == u => {
fixed_sum += coeff * (l as f64);
}
_ => {
if unfixed_idx.is_some() {
return Some(());
}
unfixed_idx = Some(i);
}
}
}
if unfixed_idx.is_none() {
if (fixed_sum - constant).abs() < 1e-12 {
return None;
}
return Some(());
}
let idx = unfixed_idx.unwrap();
let var_id = variables[idx];
let coeff = coefficients[idx];
if coeff.abs() < 1e-12 {
if (fixed_sum - constant).abs() < 1e-12 {
return None;
}
return Some(());
}
let forbidden = (constant - fixed_sum) / coeff;
exclude_value(var_id, Val::ValF(forbidden), ctx)?;
Some(())
}