use crate::{
model::Model,
variables::{Val, VarId},
constraints::props::PropId,
lpsolver::csp_integration::{LinearConstraint, ConstraintRelation},
};
const LP_DEBUG: bool = false;
#[derive(Debug, Clone)]
#[doc(hidden)]
pub enum ExprBuilder {
Var(VarId),
Val(Val),
Add(Box<ExprBuilder>, Box<ExprBuilder>),
Sub(Box<ExprBuilder>, Box<ExprBuilder>),
Mul(Box<ExprBuilder>, Box<ExprBuilder>),
Div(Box<ExprBuilder>, Box<ExprBuilder>),
Modulo(Box<ExprBuilder>, Box<ExprBuilder>),
}
#[derive(Clone, Debug)]
#[doc(hidden)]
pub struct Constraint {
kind: ConstraintKind,
}
#[doc(hidden)]
pub struct Builder<'a> {
model: &'a mut Model, current_expr: ExprBuilder,
}
#[derive(Clone)]
#[doc(hidden)]
#[derive(Debug)]
pub enum ConstraintKind {
Binary {
left: ExprBuilder,
op: ComparisonOp,
right: ExprBuilder,
},
And(Box<Constraint>, Box<Constraint>),
Or(Box<Constraint>, Box<Constraint>),
Not(Box<Constraint>),
LinearInt {
coeffs: Vec<i32>,
vars: Vec<VarId>,
op: ComparisonOp,
constant: i32,
},
LinearFloat {
coeffs: Vec<f64>,
vars: Vec<VarId>,
op: ComparisonOp,
constant: f64,
},
ReifiedBinary {
left: ExprBuilder,
op: ComparisonOp,
right: ExprBuilder,
reif_var: VarId, },
ReifiedLinearInt {
coeffs: Vec<i32>,
vars: Vec<VarId>,
op: ComparisonOp,
constant: i32,
reif_var: VarId,
},
ReifiedLinearFloat {
coeffs: Vec<f64>,
vars: Vec<VarId>,
op: ComparisonOp,
constant: f64,
reif_var: VarId,
},
BoolAnd {
x: VarId,
y: VarId,
z: VarId, },
BoolOr {
x: VarId,
y: VarId,
z: VarId, },
BoolNot {
x: VarId,
y: VarId, },
BoolXor {
x: VarId,
y: VarId,
z: VarId, },
BoolImplies {
x: VarId,
y: VarId, },
AllDifferent {
vars: Vec<VarId>,
},
AllEqual {
vars: Vec<VarId>,
},
Minimum {
vars: Vec<VarId>,
result: VarId,
},
Maximum {
vars: Vec<VarId>,
result: VarId,
},
Sum {
vars: Vec<VarId>,
result: VarId,
},
Element {
index: VarId,
array: Vec<VarId>,
value: VarId,
},
Table {
vars: Vec<VarId>,
tuples: Vec<Vec<i32>>,
},
GlobalCardinality {
vars: Vec<VarId>,
card_vars: Vec<VarId>,
covers: Vec<i32>,
},
Cumulative {
starts: Vec<VarId>,
durations: Vec<VarId>,
demands: Vec<VarId>,
capacity: VarId,
},
}
#[derive(Clone, Debug)]
#[doc(hidden)]
pub enum ComparisonOp {
Eq, Ne, Lt, Le, Gt, Ge, }
fn extract_lp_constraint(kind: &ConstraintKind) -> Option<LinearConstraint> {
match kind {
ConstraintKind::Binary { left, op, right } => {
let left_linear = extract_linear_expr(left)?;
let right_linear = extract_linear_expr(right)?;
let mut coeffs = Vec::new();
let mut vars = Vec::new();
for (var, coeff) in left_linear.terms {
vars.push(var);
coeffs.push(coeff);
}
for (var, coeff) in right_linear.terms {
if let Some(idx) = vars.iter().position(|&v| v == var) {
coeffs[idx] -= coeff;
} else {
vars.push(var);
coeffs.push(-coeff);
}
}
let rhs = right_linear.constant - left_linear.constant;
let relation = match op {
ComparisonOp::Eq => ConstraintRelation::Equality,
ComparisonOp::Le => ConstraintRelation::LessOrEqual,
ComparisonOp::Ge => ConstraintRelation::GreaterOrEqual,
ComparisonOp::Lt | ComparisonOp::Gt | ComparisonOp::Ne => {
return None;
}
};
Some(LinearConstraint::new(coeffs, vars, relation, rhs))
}
ConstraintKind::LinearInt { coeffs, vars, op, constant } => {
let relation = match op {
ComparisonOp::Eq => ConstraintRelation::Equality,
ComparisonOp::Le => ConstraintRelation::LessOrEqual,
ComparisonOp::Ge => ConstraintRelation::GreaterOrEqual,
ComparisonOp::Lt | ComparisonOp::Gt | ComparisonOp::Ne => {
return None; }
};
let f_coeffs: Vec<f64> = coeffs.iter().map(|&c| c as f64).collect();
Some(LinearConstraint::new(f_coeffs, vars.clone(), relation, *constant as f64))
}
ConstraintKind::LinearFloat { coeffs, vars, op, constant } => {
let relation = match op {
ComparisonOp::Eq => ConstraintRelation::Equality,
ComparisonOp::Le => ConstraintRelation::LessOrEqual,
ComparisonOp::Ge => ConstraintRelation::GreaterOrEqual,
ComparisonOp::Lt | ComparisonOp::Gt | ComparisonOp::Ne => {
return None; }
};
Some(LinearConstraint::new(coeffs.clone(), vars.clone(), relation, *constant))
}
ConstraintKind::Sum { vars, result } => {
let mut coeffs = vec![1.0; vars.len()];
let mut all_vars = vars.clone();
all_vars.push(*result);
coeffs.push(-1.0);
Some(LinearConstraint::new(
coeffs,
all_vars,
ConstraintRelation::Equality,
0.0
))
}
ConstraintKind::Minimum { .. } => None,
ConstraintKind::Maximum { .. } => None,
ConstraintKind::ReifiedBinary { .. } => None,
ConstraintKind::ReifiedLinearInt { .. } => None,
ConstraintKind::ReifiedLinearFloat { .. } => None,
ConstraintKind::BoolAnd { .. } => None,
ConstraintKind::BoolOr { .. } => None,
ConstraintKind::BoolNot { .. } => None,
ConstraintKind::BoolXor { .. } => None,
ConstraintKind::BoolImplies { .. } => None,
ConstraintKind::AllDifferent { .. } => None,
ConstraintKind::AllEqual { .. } => None,
ConstraintKind::Element { .. } => None,
ConstraintKind::Table { .. } => None,
ConstraintKind::GlobalCardinality { .. } => None,
ConstraintKind::Cumulative { .. } => None,
ConstraintKind::And(..) => None,
ConstraintKind::Or(..) => None,
ConstraintKind::Not(..) => None,
}
}
struct LinearExpr {
terms: Vec<(VarId, f64)>, constant: f64,
}
fn extract_linear_expr(expr: &ExprBuilder) -> Option<LinearExpr> {
match expr {
ExprBuilder::Var(var) => {
Some(LinearExpr {
terms: vec![(*var, 1.0)],
constant: 0.0,
})
}
ExprBuilder::Val(val) => {
let constant = match val {
Val::ValI(i) => *i as f64,
Val::ValF(f) => *f,
};
Some(LinearExpr {
terms: vec![],
constant,
})
}
ExprBuilder::Add(left, right) => {
let left_linear = extract_linear_expr(left)?;
let right_linear = extract_linear_expr(right)?;
let mut terms = left_linear.terms;
for (var, coeff) in right_linear.terms {
if let Some(idx) = terms.iter().position(|(v, _)| *v == var) {
terms[idx].1 += coeff;
} else {
terms.push((var, coeff));
}
}
Some(LinearExpr {
terms,
constant: left_linear.constant + right_linear.constant,
})
}
ExprBuilder::Sub(left, right) => {
let left_linear = extract_linear_expr(left)?;
let right_linear = extract_linear_expr(right)?;
let mut terms = left_linear.terms;
for (var, coeff) in right_linear.terms {
if let Some(idx) = terms.iter().position(|(v, _)| *v == var) {
terms[idx].1 -= coeff;
} else {
terms.push((var, -coeff));
}
}
Some(LinearExpr {
terms,
constant: left_linear.constant - right_linear.constant,
})
}
ExprBuilder::Mul(left, right) => {
let left_linear = extract_linear_expr(left)?;
let right_linear = extract_linear_expr(right)?;
if left_linear.terms.is_empty() {
let scalar = left_linear.constant;
let terms = right_linear.terms
.into_iter()
.map(|(var, coeff)| (var, coeff * scalar))
.collect();
return Some(LinearExpr {
terms,
constant: right_linear.constant * scalar,
});
}
if right_linear.terms.is_empty() {
let scalar = right_linear.constant;
let terms = left_linear.terms
.into_iter()
.map(|(var, coeff)| (var, coeff * scalar))
.collect();
return Some(LinearExpr {
terms,
constant: left_linear.constant * scalar,
});
}
None
}
ExprBuilder::Div(left, right) => {
let left_linear = extract_linear_expr(left)?;
let right_linear = extract_linear_expr(right)?;
if !right_linear.terms.is_empty() {
return None; }
let divisor = right_linear.constant;
if divisor.abs() < 1e-10 {
return None; }
let terms = left_linear.terms
.into_iter()
.map(|(var, coeff)| (var, coeff / divisor))
.collect();
Some(LinearExpr {
terms,
constant: left_linear.constant / divisor,
})
}
ExprBuilder::Modulo(_, _) => {
None
}
}
}
#[doc(hidden)]
impl ExprBuilder {
#[inline]
pub fn from_var(var_id: VarId) -> Self {
ExprBuilder::Var(var_id)
}
#[inline]
pub fn from_val(value: Val) -> Self {
ExprBuilder::Val(value)
}
#[inline]
pub fn add(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
let other = other.into();
if let (ExprBuilder::Val(a), ExprBuilder::Val(b)) = (&self, &other) {
return ExprBuilder::Val(*a + *b);
}
ExprBuilder::Add(Box::new(self), Box::new(other))
}
#[inline]
pub fn sub(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
let other = other.into();
if let (ExprBuilder::Val(a), ExprBuilder::Val(b)) = (&self, &other) {
return ExprBuilder::Val(*a - *b);
}
ExprBuilder::Sub(Box::new(self), Box::new(other))
}
#[inline]
pub fn mul(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
let other = other.into();
if let (ExprBuilder::Val(a), ExprBuilder::Val(b)) = (&self, &other) {
return ExprBuilder::Val(*a * *b);
}
if let ExprBuilder::Val(val) = &other {
if let Some(1) = val.as_int() {
return self;
}
}
if let ExprBuilder::Val(val) = &self {
if let Some(1) = val.as_int() {
return other;
}
}
ExprBuilder::Mul(Box::new(self), Box::new(other))
}
#[inline]
pub fn div(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
let other = other.into();
if let (ExprBuilder::Val(a), ExprBuilder::Val(b)) = (&self, &other) {
return ExprBuilder::Val(*a / *b);
}
if let ExprBuilder::Val(val) = &other {
if let Some(1) = val.as_int() {
return self;
}
}
ExprBuilder::Div(Box::new(self), Box::new(other))
}
#[inline]
pub fn modulo(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
ExprBuilder::Modulo(Box::new(self), Box::new(other.into()))
}
#[inline]
pub fn eq(self, other: impl Into<ExprBuilder>) -> Constraint {
Constraint {
kind: ConstraintKind::Binary {
left: self,
op: ComparisonOp::Eq,
right: other.into(),
},
}
}
#[inline]
pub fn le(self, other: impl Into<ExprBuilder>) -> Constraint {
Constraint {
kind: ConstraintKind::Binary {
left: self,
op: ComparisonOp::Le,
right: other.into(),
},
}
}
#[inline]
pub fn gt(self, other: impl Into<ExprBuilder>) -> Constraint {
Constraint {
kind: ConstraintKind::Binary {
left: self,
op: ComparisonOp::Gt,
right: other.into(),
},
}
}
pub fn lt(self, other: impl Into<ExprBuilder>) -> Constraint {
Constraint {
kind: ConstraintKind::Binary {
left: self,
op: ComparisonOp::Lt,
right: other.into(),
},
}
}
pub fn ge(self, other: impl Into<ExprBuilder>) -> Constraint {
Constraint {
kind: ConstraintKind::Binary {
left: self,
op: ComparisonOp::Ge,
right: other.into(),
},
}
}
pub fn ne(self, other: impl Into<ExprBuilder>) -> Constraint {
Constraint {
kind: ConstraintKind::Binary {
left: self,
op: ComparisonOp::Ne,
right: other.into(),
},
}
}
}
#[doc(hidden)]
impl Constraint {
pub fn and(self, other: Constraint) -> Constraint {
Constraint {
kind: ConstraintKind::And(Box::new(self), Box::new(other)),
}
}
pub fn or(self, other: Constraint) -> Constraint {
Constraint {
kind: ConstraintKind::Or(Box::new(self), Box::new(other)),
}
}
pub fn not(self) -> Constraint {
Constraint {
kind: ConstraintKind::Not(Box::new(self)),
}
}
}
#[doc(hidden)]
impl Constraint {
pub fn and_all(constraints: Vec<Constraint>) -> Option<Constraint> {
if constraints.is_empty() {
return None;
}
constraints.into_iter().reduce(|acc, c| acc.and(c))
}
pub fn or_all(constraints: Vec<Constraint>) -> Option<Constraint> {
if constraints.is_empty() {
return None;
}
constraints.into_iter().reduce(|acc, c| acc.or(c))
}
}
pub fn and_all(constraints: Vec<Constraint>) -> Option<Constraint> {
Constraint::and_all(constraints)
}
pub fn or_all(constraints: Vec<Constraint>) -> Option<Constraint> {
Constraint::or_all(constraints)
}
pub fn all_of(constraints: Vec<Constraint>) -> Option<Constraint> {
and_all(constraints)
}
pub fn any_of(constraints: Vec<Constraint>) -> Option<Constraint> {
or_all(constraints)
}
#[doc(hidden)]
pub trait ConstraintVecExt {
fn and_all(self) -> Option<Constraint>;
fn or_all(self) -> Option<Constraint>;
fn postall(self, model: &mut Model) -> Vec<PropId>;
}
impl ConstraintVecExt for Vec<Constraint> {
fn and_all(self) -> Option<Constraint> {
Constraint::and_all(self)
}
fn or_all(self) -> Option<Constraint> {
Constraint::or_all(self)
}
fn postall(self, m: &mut Model) -> Vec<PropId> {
self.into_iter()
.map(|constraint| m.new(constraint))
.collect()
}
}
#[doc(hidden)]
impl<'a> Builder<'a> {
pub fn new(model: &'a mut Model, var: VarId) -> Self {
Builder {
model,
current_expr: ExprBuilder::from_var(var),
}
}
pub fn add(mut self, other: impl Into<ExprBuilder>) -> Self {
self.current_expr = self.current_expr.add(other);
self
}
pub fn sub(mut self, other: impl Into<ExprBuilder>) -> Self {
self.current_expr = self.current_expr.sub(other);
self
}
pub fn mul(mut self, other: impl Into<ExprBuilder>) -> Self {
self.current_expr = self.current_expr.mul(other);
self
}
pub fn div(mut self, other: impl Into<ExprBuilder>) -> Self {
self.current_expr = self.current_expr.div(other);
self
}
pub fn eq(self, other: impl Into<ExprBuilder>) -> PropId {
let constraint = self.current_expr.eq(other);
self.model.new(constraint)
}
pub fn ne(self, other: impl Into<ExprBuilder>) -> PropId {
let constraint = self.current_expr.ne(other);
self.model.new(constraint)
}
pub fn lt(self, other: impl Into<ExprBuilder>) -> PropId {
let constraint = self.current_expr.lt(other);
self.model.new(constraint)
}
pub fn le(self, other: impl Into<ExprBuilder>) -> PropId {
let constraint = self.current_expr.le(other);
self.model.new(constraint)
}
pub fn gt(self, other: impl Into<ExprBuilder>) -> PropId {
let constraint = self.current_expr.gt(other);
self.model.new(constraint)
}
pub fn ge(self, other: impl Into<ExprBuilder>) -> PropId {
let constraint = self.current_expr.ge(other);
self.model.new(constraint)
}
}
fn create_result_var(model: &mut Model, expr: &ExprBuilder) -> VarId {
match expr {
ExprBuilder::Var(var_id) => *var_id,
ExprBuilder::Val(_) => {
model.int(-1000, 1000) }
_ => model.int(-1000, 1000), }
}
fn post_expression_constraint(model: &mut Model, expr: &ExprBuilder, result: VarId) -> PropId {
match expr {
ExprBuilder::Var(var_id) => {
model.props.equals(*var_id, result)
}
ExprBuilder::Val(val) => {
model.props.equals(*val, result)
}
ExprBuilder::Add(left, right) => {
let left_var = create_result_var(model, left);
let right_var = create_result_var(model, right);
if !matches!(**left, ExprBuilder::Var(_)) {
post_expression_constraint(model, left, left_var);
}
if !matches!(**right, ExprBuilder::Var(_)) {
post_expression_constraint(model, right, right_var);
}
model.props.add(left_var, right_var, result)
}
ExprBuilder::Sub(left, right) => {
let left_var = create_result_var(model, left);
let right_var = create_result_var(model, right);
if !matches!(**left, ExprBuilder::Var(_)) {
post_expression_constraint(model, left, left_var);
}
if !matches!(**right, ExprBuilder::Var(_)) {
post_expression_constraint(model, right, right_var);
}
model.props.sub(left_var, right_var, result)
}
ExprBuilder::Mul(left, right) => {
let left_var = create_result_var(model, left);
let right_var = create_result_var(model, right);
if !matches!(**left, ExprBuilder::Var(_)) {
post_expression_constraint(model, left, left_var);
}
if !matches!(**right, ExprBuilder::Var(_)) {
post_expression_constraint(model, right, right_var);
}
model.props.mul(left_var, right_var, result)
}
ExprBuilder::Div(left, right) => {
let left_var = create_result_var(model, left);
let right_var = create_result_var(model, right);
if !matches!(**left, ExprBuilder::Var(_)) {
post_expression_constraint(model, left, left_var);
}
if !matches!(**right, ExprBuilder::Var(_)) {
post_expression_constraint(model, right, right_var);
}
model.props.div(left_var, right_var, result)
}
ExprBuilder::Modulo(left, right) => {
let left_var = create_result_var(model, left);
let right_var = create_result_var(model, right);
if !matches!(**left, ExprBuilder::Var(_)) {
post_expression_constraint(model, left, left_var);
}
if !matches!(**right, ExprBuilder::Var(_)) {
post_expression_constraint(model, right, right_var);
}
model.props.modulo(left_var, right_var, result)
}
}
}
fn get_expr_var(model: &mut Model, expr: &ExprBuilder) -> VarId {
match expr {
ExprBuilder::Var(var_id) => *var_id,
ExprBuilder::Val(val) => {
match val {
Val::ValI(i) => model.int(*i, *i),
Val::ValF(f) => model.float(*f, *f),
}
}
_ => {
let result_var = create_result_var(model, expr);
post_expression_constraint(model, expr, result_var);
result_var
}
}
}
fn try_convert_to_linear_ast(kind: &ConstraintKind) -> ConstraintKind {
match kind {
ConstraintKind::Binary { left, op, right } => {
if let (Some((left_coeffs, left_vars, left_const)), Some((right_coeffs, right_vars, right_const))) =
(try_extract_linear_form(left), try_extract_linear_form(right)) {
let mut coeffs = Vec::new();
let mut vars = Vec::new();
let mut all_ints = true;
for (var, coeff) in left_vars.iter().zip(left_coeffs.iter()) {
vars.push(*var);
coeffs.push(*coeff);
if !matches!(coeff, LinearCoefficient::Int(_)) {
all_ints = false;
}
}
for (var, coeff) in right_vars.iter().zip(right_coeffs.iter()) {
if let Some(idx) = vars.iter().position(|v| v == var) {
coeffs[idx] = subtract_coefficients(&coeffs[idx], coeff);
} else {
vars.push(*var);
coeffs.push(negate_coefficient(coeff));
}
if !matches!(coeff, LinearCoefficient::Int(_)) {
all_ints = false;
}
}
let constant = subtract_coefficients(&left_const, &right_const);
let constant = negate_coefficient(&constant);
if !matches!(constant, LinearCoefficient::Int(_)) {
all_ints = false;
}
if all_ints {
let int_coeffs: Vec<i32> = coeffs.iter().map(|c| match c {
LinearCoefficient::Int(i) => *i,
_ => 0,
}).collect();
let int_const = match constant {
LinearCoefficient::Int(i) => i,
_ => 0,
};
return ConstraintKind::LinearInt {
coeffs: int_coeffs,
vars,
op: op.clone(),
constant: int_const,
};
} else {
let float_coeffs: Vec<f64> = coeffs.iter().map(|c| match c {
LinearCoefficient::Int(i) => *i as f64,
LinearCoefficient::Float(f) => *f,
}).collect();
let float_const = match constant {
LinearCoefficient::Int(i) => i as f64,
LinearCoefficient::Float(f) => f,
};
return ConstraintKind::LinearFloat {
coeffs: float_coeffs,
vars,
op: op.clone(),
constant: float_const,
};
}
}
kind.clone()
}
_ => kind.clone(),
}
}
#[derive(Clone, Copy, Debug)]
enum LinearCoefficient {
Int(i32),
Float(f64),
}
fn negate_coefficient(coeff: &LinearCoefficient) -> LinearCoefficient {
match coeff {
LinearCoefficient::Int(i) => LinearCoefficient::Int(-i),
LinearCoefficient::Float(f) => LinearCoefficient::Float(-f),
}
}
fn subtract_coefficients(a: &LinearCoefficient, b: &LinearCoefficient) -> LinearCoefficient {
match (a, b) {
(LinearCoefficient::Int(ia), LinearCoefficient::Int(ib)) => LinearCoefficient::Int(ia - ib),
(LinearCoefficient::Float(fa), LinearCoefficient::Float(fb)) => LinearCoefficient::Float(fa - fb),
(LinearCoefficient::Int(ia), LinearCoefficient::Float(fb)) => LinearCoefficient::Float(*ia as f64 - fb),
(LinearCoefficient::Float(fa), LinearCoefficient::Int(ib)) => LinearCoefficient::Float(fa - *ib as f64),
}
}
fn try_extract_linear_form(expr: &ExprBuilder) -> Option<(Vec<LinearCoefficient>, Vec<VarId>, LinearCoefficient)> {
match expr {
ExprBuilder::Var(var) => {
Some((vec![LinearCoefficient::Int(1)], vec![*var], LinearCoefficient::Int(0)))
}
ExprBuilder::Val(val) => {
match val {
Val::ValI(i) => Some((vec![], vec![], LinearCoefficient::Int(*i))),
Val::ValF(f) => Some((vec![], vec![], LinearCoefficient::Float(*f))),
}
}
ExprBuilder::Mul(left, right) => {
match (left.as_ref(), right.as_ref()) {
(ExprBuilder::Var(var), ExprBuilder::Val(val)) => {
let coeff = match val {
Val::ValI(i) => LinearCoefficient::Int(*i),
Val::ValF(f) => LinearCoefficient::Float(*f),
};
Some((vec![coeff], vec![*var], LinearCoefficient::Int(0)))
}
(ExprBuilder::Val(val), ExprBuilder::Var(var)) => {
let coeff = match val {
Val::ValI(i) => LinearCoefficient::Int(*i),
Val::ValF(f) => LinearCoefficient::Float(*f),
};
Some((vec![coeff], vec![*var], LinearCoefficient::Int(0)))
}
_ => None, }
}
ExprBuilder::Add(left, right) => {
let (mut left_coeffs, mut left_vars, left_const) = try_extract_linear_form(left)?;
let (right_coeffs, right_vars, right_const) = try_extract_linear_form(right)?;
for (var, coeff) in right_vars.iter().zip(right_coeffs.iter()) {
if let Some(idx) = left_vars.iter().position(|v| v == var) {
left_coeffs[idx] = add_coefficients(&left_coeffs[idx], coeff);
} else {
left_vars.push(*var);
left_coeffs.push(coeff.clone());
}
}
let combined_const = add_coefficients(&left_const, &right_const);
Some((left_coeffs, left_vars, combined_const))
}
ExprBuilder::Sub(left, right) => {
let (mut left_coeffs, mut left_vars, left_const) = try_extract_linear_form(left)?;
let (right_coeffs, right_vars, right_const) = try_extract_linear_form(right)?;
for (var, coeff) in right_vars.iter().zip(right_coeffs.iter()) {
if let Some(idx) = left_vars.iter().position(|v| v == var) {
left_coeffs[idx] = subtract_coefficients(&left_coeffs[idx], coeff);
} else {
left_vars.push(*var);
left_coeffs.push(negate_coefficient(coeff));
}
}
let combined_const = subtract_coefficients(&left_const, &right_const);
Some((left_coeffs, left_vars, combined_const))
}
ExprBuilder::Div(_,_) => None, ExprBuilder::Modulo(_,_) => None, }
}
fn add_coefficients(a: &LinearCoefficient, b: &LinearCoefficient) -> LinearCoefficient {
match (a, b) {
(LinearCoefficient::Int(ia), LinearCoefficient::Int(ib)) => LinearCoefficient::Int(ia + ib),
(LinearCoefficient::Float(fa), LinearCoefficient::Float(fb)) => LinearCoefficient::Float(fa + fb),
(LinearCoefficient::Int(ia), LinearCoefficient::Float(fb)) => LinearCoefficient::Float(*ia as f64 + fb),
(LinearCoefficient::Float(fa), LinearCoefficient::Int(ib)) => LinearCoefficient::Float(fa + *ib as f64),
}
}
fn apply_var_eq_bounds(model: &mut Model, var1: VarId, var2: VarId) {
if var1.to_index() >= model.vars.count() || var2.to_index() >= model.vars.count() {
return;
}
let (var1_min, var1_max, is_var1_int) = match &model.vars[var1] {
crate::variables::Var::VarI(ss) => (ss.min(), ss.max(), true),
crate::variables::Var::VarF(_) => (0, 0, false),
};
let (var2_min, var2_max, is_var2_int) = match &model.vars[var2] {
crate::variables::Var::VarI(ss) => (ss.min(), ss.max(), true),
crate::variables::Var::VarF(_) => (0, 0, false),
};
if is_var1_int && is_var2_int {
let intersection_min = if var1_min > var2_min { var1_min } else { var2_min };
let intersection_max = if var1_max < var2_max { var1_max } else { var2_max };
if intersection_min <= intersection_max {
let var1_to_remove: Vec<i32> = if let crate::variables::Var::VarI(ss) = &model.vars[var1] {
ss.iter()
.filter(|val| *val < intersection_min || *val > intersection_max)
.collect()
} else {
Vec::new()
};
let var2_to_remove: Vec<i32> = if let crate::variables::Var::VarI(ss) = &model.vars[var2] {
ss.iter()
.filter(|val| *val < intersection_min || *val > intersection_max)
.collect()
} else {
Vec::new()
};
if let crate::variables::Var::VarI(sparse_set) = &mut model.vars[var1] {
for val in var1_to_remove {
sparse_set.remove(val);
}
}
if let crate::variables::Var::VarI(sparse_set) = &mut model.vars[var2] {
for val in var2_to_remove {
sparse_set.remove(val);
}
}
}
}
}
#[inline]
#[doc(hidden)]
pub fn post_constraint_kind(model: &mut Model, kind: &ConstraintKind) -> PropId {
if let ConstraintKind::Binary { left, op, right } = kind {
if matches!(op, ComparisonOp::Eq) {
if let (ExprBuilder::Var(var1), ExprBuilder::Var(var2)) = (left, right) {
apply_var_eq_bounds(model, *var1, *var2);
}
}
}
if let ConstraintKind::Binary { left, op, right } = kind {
if matches!(op, ComparisonOp::Eq) {
let is_var_eq_val = matches!(left, ExprBuilder::Var(_)) && matches!(right, ExprBuilder::Val(_));
let is_val_eq_var = matches!(left, ExprBuilder::Val(_)) && matches!(right, ExprBuilder::Var(_));
if is_var_eq_val || is_val_eq_var {
let prop_id = materialize_constraint_kind(model, kind);
return prop_id;
}
}
}
let kind = try_convert_to_linear_ast(kind);
if let Some(lp_constraint) = extract_lp_constraint(&kind) {
if LP_DEBUG {
eprintln!("LP EXTRACTION: Extracted linear constraint from AST: {:?}", lp_constraint);
}
model.pending_lp_constraints.push(lp_constraint);
}
model.pending_constraint_asts.push(kind.clone());
PropId(model.pending_constraint_asts.len() - 1)
}
#[inline]
pub(crate) fn materialize_constraint_kind(model: &mut Model, kind: &ConstraintKind) -> PropId {
match kind {
ConstraintKind::Binary { left, op, right } => {
if matches!(op, ComparisonOp::Eq) {
if let (ExprBuilder::Var(var), ExprBuilder::Val(val)) = (left, right) {
if var.to_index() < model.vars.count() {
match &mut model.vars[*var] {
crate::variables::Var::VarI(sparse_set) => {
if let Val::ValI(i) = val {
sparse_set.remove_all_but(*i);
}
}
crate::variables::Var::VarF(interval) => {
if let Val::ValF(f) = val {
interval.min = *f;
interval.max = *f;
}
}
}
}
} else if let (ExprBuilder::Val(val), ExprBuilder::Var(var)) = (left, right) {
if var.to_index() < model.vars.count() {
match &mut model.vars[*var] {
crate::variables::Var::VarI(sparse_set) => {
if let Val::ValI(i) = val {
sparse_set.remove_all_but(*i);
}
}
crate::variables::Var::VarF(interval) => {
if let Val::ValF(f) = val {
interval.min = *f;
interval.max = *f;
}
}
}
}
}
}
if let (ExprBuilder::Var(var), ExprBuilder::Val(val)) = (left, right) {
return post_var_val_constraint(model, *var, op, *val);
}
if let (ExprBuilder::Val(val), ExprBuilder::Var(var)) = (left, right) {
return post_val_var_constraint(model, *val, op, *var);
}
let left_var = get_expr_var(model, left);
let right_var = get_expr_var(model, right);
match op {
ComparisonOp::Eq => model.props.equals(left_var, right_var),
ComparisonOp::Ne => model.props.not_equals(left_var, right_var),
ComparisonOp::Lt => model.props.less_than(left_var, right_var),
ComparisonOp::Le => model.props.less_than_or_equals(left_var, right_var),
ComparisonOp::Gt => model.props.greater_than(left_var, right_var),
ComparisonOp::Ge => model.props.greater_than_or_equals(left_var, right_var),
}
}
ConstraintKind::And(left, right) => {
materialize_constraint_kind(model, &left.kind);
materialize_constraint_kind(model, &right.kind)
}
ConstraintKind::Or(left, right) => {
if let (ConstraintKind::Binary { left: left_var, op: ComparisonOp::Eq, right: left_val },
ConstraintKind::Binary { left: right_var, op: ComparisonOp::Eq, right: right_val }) =
(&left.kind, &right.kind) {
if matches!((left_var, right_var), (ExprBuilder::Var(a), ExprBuilder::Var(b)) if a == b) {
if let (ExprBuilder::Var(var_id), ExprBuilder::Val(left_const), ExprBuilder::Val(right_const)) =
(left_var, left_val, right_val) {
if let (Val::ValI(left_int), Val::ValI(right_int)) = (left_const, right_const) {
let domain_vals = vec![*left_int, *right_int];
let domain_var = model.intset(domain_vals);
return model.props.equals(*var_id, domain_var);
}
}
}
}
materialize_constraint_kind(model, &left.kind);
materialize_constraint_kind(model, &right.kind)
}
ConstraintKind::Not(constraint) => {
materialize_constraint_kind(model, &constraint.kind)
}
ConstraintKind::LinearInt { coeffs, vars, op, constant } => {
match op {
ComparisonOp::Eq => model.props.int_lin_eq(coeffs.clone(), vars.clone(), *constant),
ComparisonOp::Le => model.props.int_lin_le(coeffs.clone(), vars.clone(), *constant),
ComparisonOp::Ne => model.props.int_lin_ne(coeffs.clone(), vars.clone(), *constant),
ComparisonOp::Ge => {
let neg_coeffs: Vec<i32> = coeffs.iter().map(|c| -c).collect();
model.props.int_lin_le(neg_coeffs, vars.clone(), -constant)
}
ComparisonOp::Gt => {
let neg_coeffs: Vec<i32> = coeffs.iter().map(|c| -c).collect();
model.props.int_lin_le(neg_coeffs, vars.clone(), -constant - 1)
}
ComparisonOp::Lt => {
model.props.int_lin_le(coeffs.clone(), vars.clone(), constant - 1)
}
}
}
ConstraintKind::LinearFloat { coeffs, vars, op, constant } => {
match op {
ComparisonOp::Eq => model.props.float_lin_eq(coeffs.clone(), vars.clone(), *constant),
ComparisonOp::Le => model.props.float_lin_le(coeffs.clone(), vars.clone(), *constant),
ComparisonOp::Ne => model.props.float_lin_ne(coeffs.clone(), vars.clone(), *constant),
ComparisonOp::Ge => {
let neg_coeffs: Vec<f64> = coeffs.iter().map(|c| -c).collect();
model.props.float_lin_le(neg_coeffs, vars.clone(), -constant)
}
ComparisonOp::Lt => {
let epsilon = crate::variables::domain::float_interval::precision_to_step_size(model.float_precision_digits);
model.props.float_lin_le(coeffs.clone(), vars.clone(), constant - epsilon)
}
ComparisonOp::Gt => {
let epsilon = crate::variables::domain::float_interval::precision_to_step_size(model.float_precision_digits);
let neg_coeffs: Vec<f64> = coeffs.iter().map(|c| -c).collect();
model.props.float_lin_le(neg_coeffs, vars.clone(), -constant - epsilon)
}
}
}
ConstraintKind::ReifiedBinary { left, op, right, reif_var } => {
let left_var = get_expr_var(model, left);
let right_var = get_expr_var(model, right);
match op {
ComparisonOp::Eq => model.props.int_eq_reif(left_var, right_var, *reif_var),
ComparisonOp::Ne => model.props.int_ne_reif(left_var, right_var, *reif_var),
ComparisonOp::Lt => model.props.int_lt_reif(left_var, right_var, *reif_var),
ComparisonOp::Le => model.props.int_le_reif(left_var, right_var, *reif_var),
ComparisonOp::Gt => model.props.int_gt_reif(left_var, right_var, *reif_var),
ComparisonOp::Ge => model.props.int_ge_reif(left_var, right_var, *reif_var),
}
}
ConstraintKind::ReifiedLinearInt { coeffs, vars, op, constant, reif_var } => {
match op {
ComparisonOp::Eq => model.props.int_lin_eq_reif(coeffs.clone(), vars.clone(), *constant, *reif_var),
ComparisonOp::Le => model.props.int_lin_le_reif(coeffs.clone(), vars.clone(), *constant, *reif_var),
ComparisonOp::Ne => model.props.int_lin_ne_reif(coeffs.clone(), vars.clone(), *constant, *reif_var),
_ => {
let zero_coeffs: Vec<i32> = vec![0; vars.len()];
model.props.int_lin_le_reif(zero_coeffs, vars.clone(), 1, *reif_var)
}
}
}
ConstraintKind::ReifiedLinearFloat { coeffs, vars, op, constant, reif_var } => {
match op {
ComparisonOp::Eq => model.props.float_lin_eq_reif(coeffs.clone(), vars.clone(), *constant, *reif_var),
ComparisonOp::Le => model.props.float_lin_le_reif(coeffs.clone(), vars.clone(), *constant, *reif_var),
ComparisonOp::Ne => model.props.float_lin_ne_reif(coeffs.clone(), vars.clone(), *constant, *reif_var),
_ => {
let zero_coeffs: Vec<f64> = vec![0.0; vars.len()];
model.props.float_lin_le_reif(zero_coeffs, vars.clone(), 1.0, *reif_var)
}
}
}
ConstraintKind::BoolAnd { x, y, z } => {
model.bool_and(&[*x, *y, *z]);
PropId(0) }
ConstraintKind::BoolOr { x, y, z } => {
model.bool_or(&[*x, *y, *z]);
PropId(0)
}
ConstraintKind::BoolNot { x, y } => {
model.props.bool_not(*x, *y)
}
ConstraintKind::BoolXor { x, y, z } => {
let not_x = model.bool();
let not_y = model.bool();
model.props.bool_not(*x, not_x);
model.props.bool_not(*y, not_y);
let and1 = model.bool(); let and2 = model.bool();
model.bool_and(&[*x, not_y, and1]);
model.bool_and(&[not_x, *y, and2]);
model.bool_or(&[and1, and2, *z]);
PropId(0)
}
ConstraintKind::BoolImplies { x, y } => {
let not_x = model.bool();
model.props.bool_not(*x, not_x);
let result = model.bool();
model.bool_or(&[not_x, *y, result]);
model.props.equals(result, Val::int(1));
PropId(0)
}
ConstraintKind::AllDifferent { vars } => {
model.alldiff(vars);
PropId(0)
}
ConstraintKind::AllEqual { vars } => {
model.alleq(vars);
PropId(0)
}
ConstraintKind::Minimum { vars, result } => {
let min_var = model.min(vars).unwrap();
model.props.equals(min_var, *result)
}
ConstraintKind::Maximum { vars, result } => {
let max_var = model.max(vars).unwrap();
model.props.equals(max_var, *result)
}
ConstraintKind::Sum { vars, result } => {
model.props.sum(vars.clone(), *result)
}
ConstraintKind::Element { index, array, value } => {
model.element(array, *index, *value)
}
ConstraintKind::Table { vars, tuples } => {
let val_tuples: Vec<Vec<Val>> = tuples.iter()
.map(|tuple| tuple.iter().map(|&v| Val::ValI(v)).collect())
.collect();
model.table(vars, val_tuples);
PropId(0)
}
ConstraintKind::GlobalCardinality { vars, card_vars, covers } => {
let prop_ids = model.gcc(vars, covers, card_vars);
prop_ids.first().copied().unwrap_or(PropId(0))
}
ConstraintKind::Cumulative { starts, durations, demands, capacity: _ } => {
if !(starts.len() == durations.len() && starts.len() == demands.len()) {
return PropId(0); }
let n = starts.len();
for i in 0..n {
for j in i+1..n {
let start_i = starts[i];
let start_j = starts[j];
let dur_i = durations[i];
let dur_j = durations[j];
let _demand_i = demands[i];
let _demand_j = demands[j];
let end_i = model.add(start_i, dur_i);
let end_j = model.add(start_j, dur_j);
let b1 = model.bool();
model.props.int_le_reif(end_i, start_j, b1);
let b2 = model.bool();
model.props.int_le_reif(end_j, start_i, b2);
let b_or = model.bool();
model.bool_or(&[b1, b2, b_or]);
model.props.equals(b_or, Val::int(1));
}
}
PropId(0)
}
}
}
#[inline]
fn post_var_val_constraint(model: &mut Model, var: VarId, op: &ComparisonOp, val: Val) -> PropId {
let val_var = match val {
Val::ValI(i) => model.int(i, i),
Val::ValF(f) => model.float(f, f),
};
match op {
ComparisonOp::Eq => model.props.equals(var, val_var),
ComparisonOp::Ne => model.props.not_equals(var, val_var),
ComparisonOp::Lt => model.props.less_than(var, val_var),
ComparisonOp::Le => model.props.less_than_or_equals(var, val_var),
ComparisonOp::Gt => model.props.greater_than(var, val_var),
ComparisonOp::Ge => model.props.greater_than_or_equals(var, val_var),
}
}
#[inline]
fn post_val_var_constraint(model: &mut Model, val: Val, op: &ComparisonOp, var: VarId) -> PropId {
let val_var = match val {
Val::ValI(i) => model.int(i, i),
Val::ValF(f) => model.float(f, f),
};
match op {
ComparisonOp::Eq => model.props.equals(val_var, var),
ComparisonOp::Ne => model.props.not_equals(val_var, var),
ComparisonOp::Lt => model.props.less_than(val_var, var),
ComparisonOp::Le => model.props.less_than_or_equals(val_var, var),
ComparisonOp::Gt => model.props.greater_than(val_var, var),
ComparisonOp::Ge => model.props.greater_than_or_equals(val_var, var),
}
}
impl From<VarId> for ExprBuilder {
fn from(var_id: VarId) -> Self {
ExprBuilder::from_var(var_id)
}
}
impl From<i32> for ExprBuilder {
fn from(value: i32) -> Self {
ExprBuilder::from_val(Val::int(value))
}
}
impl From<f64> for ExprBuilder {
fn from(value: f64) -> Self {
ExprBuilder::from_val(Val::float(value))
}
}
impl From<Val> for ExprBuilder {
fn from(value: Val) -> Self {
ExprBuilder::from_val(value)
}
}
impl From<&VarId> for ExprBuilder {
fn from(value: &VarId) -> Self {
ExprBuilder::from_var(*value)
}
}
#[doc(hidden)]
pub trait VarIdExt {
fn expr(self) -> ExprBuilder;
fn add(self, other: impl Into<ExprBuilder>) -> ExprBuilder;
fn sub(self, other: impl Into<ExprBuilder>) -> ExprBuilder;
fn mul(self, other: impl Into<ExprBuilder>) -> ExprBuilder;
fn div(self, other: impl Into<ExprBuilder>) -> ExprBuilder;
fn modulo(self, other: impl Into<ExprBuilder>) -> ExprBuilder;
fn eq(self, other: impl Into<ExprBuilder>) -> Constraint;
fn ne(self, other: impl Into<ExprBuilder>) -> Constraint;
fn lt(self, other: impl Into<ExprBuilder>) -> Constraint;
fn le(self, other: impl Into<ExprBuilder>) -> Constraint;
fn gt(self, other: impl Into<ExprBuilder>) -> Constraint;
fn ge(self, other: impl Into<ExprBuilder>) -> Constraint;
}
impl VarIdExt for VarId {
fn expr(self) -> ExprBuilder {
ExprBuilder::from_var(self)
}
fn add(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
ExprBuilder::from_var(self).add(other)
}
fn sub(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
ExprBuilder::from_var(self).sub(other)
}
fn mul(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
ExprBuilder::from_var(self).mul(other)
}
fn div(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
ExprBuilder::from_var(self).div(other)
}
fn modulo(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
ExprBuilder::from_var(self).modulo(other)
}
fn eq(self, other: impl Into<ExprBuilder>) -> Constraint {
ExprBuilder::from_var(self).eq(other)
}
fn ne(self, other: impl Into<ExprBuilder>) -> Constraint {
ExprBuilder::from_var(self).ne(other)
}
fn lt(self, other: impl Into<ExprBuilder>) -> Constraint {
ExprBuilder::from_var(self).lt(other)
}
fn le(self, other: impl Into<ExprBuilder>) -> Constraint {
ExprBuilder::from_var(self).le(other)
}
fn gt(self, other: impl Into<ExprBuilder>) -> Constraint {
ExprBuilder::from_var(self).gt(other)
}
fn ge(self, other: impl Into<ExprBuilder>) -> Constraint {
ExprBuilder::from_var(self).ge(other)
}
}
#[doc(hidden)]
pub trait ModelExt {
fn new(&mut self, constraint: Constraint) -> PropId;
fn c(&mut self, var: VarId) -> Builder<'_>;
fn alldiff(&mut self, vars: &[VarId]) -> PropId;
fn alleq(&mut self, vars: &[VarId]) -> PropId;
fn elem(&mut self, array: &[VarId], index: VarId, value: VarId) -> PropId;
fn count(&mut self, vars: &[VarId], value: i32, result: VarId) -> PropId;
fn betw(&mut self, var: VarId, min: i32, max: i32) -> PropId;
fn atmost(&mut self, var: VarId, value: i32) -> PropId;
fn atleast(&mut self, var: VarId, value: i32) -> PropId;
fn gcc(&mut self, vars: &[VarId], values: &[i32], counts: &[VarId]) -> Vec<PropId>;
fn postall(&mut self, constraints: Vec<Constraint>) -> Vec<PropId>;
fn post_and(&mut self, constraints: Vec<Constraint>) -> Option<PropId>;
fn post_or(&mut self, constraints: Vec<Constraint>) -> Option<PropId>;
}
impl ModelExt for Model {
fn new(&mut self, constraint: Constraint) -> PropId {
post_constraint_kind(self, &constraint.kind)
}
fn c(&mut self, var: VarId) -> Builder<'_> {
Builder::new(self, var)
}
fn alldiff(&mut self, vars: &[VarId]) -> PropId {
self.props.all_different(vars.to_vec())
}
fn alleq(&mut self, vars: &[VarId]) -> PropId {
self.props.all_equal(vars.to_vec())
}
fn elem(&mut self, array: &[VarId], index: VarId, value: VarId) -> PropId {
self.props.element(array.to_vec(), index, value)
}
fn count(&mut self, vars: &[VarId], value: i32, result: VarId) -> PropId {
let target_var = self.int(value, value);
self.props.count_constraint(vars.to_vec(), target_var, result)
}
fn betw(&mut self, var: VarId, min: i32, max: i32) -> PropId {
self.props.greater_than_or_equals(var, Val::int(min));
self.props.less_than_or_equals(var, Val::int(max))
}
fn atmost(&mut self, var: VarId, value: i32) -> PropId {
self.props.less_than_or_equals(var, Val::int(value))
}
fn atleast(&mut self, var: VarId, value: i32) -> PropId {
self.props.greater_than_or_equals(var, Val::int(value))
}
fn gcc(&mut self, vars: &[VarId], values: &[i32], counts: &[VarId]) -> Vec<PropId> {
let mut prop_ids = Vec::with_capacity(values.len());
for (&value, &count_var) in values.iter().zip(counts.iter()) {
let target_var = self.int(value, value);
let prop_id = self.props.count_constraint(vars.to_vec(), target_var, count_var);
prop_ids.push(prop_id);
}
prop_ids
}
fn postall(&mut self, constraints: Vec<Constraint>) -> Vec<PropId> {
let mut result = Vec::with_capacity(constraints.len());
for constraint in constraints {
result.push(post_constraint_kind(self, &constraint.kind));
}
result
}
fn post_and(&mut self, constraints: Vec<Constraint>) -> Option<PropId> {
if constraints.is_empty() {
return None;
}
if constraints.len() == 1 {
return Some(post_constraint_kind(self, &constraints[0].kind));
}
let mut result = constraints[0].clone();
for constraint in constraints.into_iter().skip(1) {
result = Constraint {
kind: ConstraintKind::And(Box::new(result), Box::new(constraint))
};
}
Some(post_constraint_kind(self, &result.kind))
}
fn post_or(&mut self, constraints: Vec<Constraint>) -> Option<PropId> {
if constraints.is_empty() {
return None;
}
if constraints.len() == 1 {
return Some(post_constraint_kind(self, &constraints[0].kind));
}
let mut result = constraints[0].clone();
for constraint in constraints.into_iter().skip(1) {
result = Constraint {
kind: ConstraintKind::Or(Box::new(result), Box::new(constraint))
};
}
Some(post_constraint_kind(self, &result.kind))
}
}
#[cfg(test)]
mod tests;