use crate::model::Model;
use crate::variables::{VarId, Val};
use crate::runtime_api::{ExprBuilder, ModelExt};
use crate::constraints::props::PropId;
pub fn add(x: impl Into<ExprBuilder>, y: impl Into<ExprBuilder>) -> ExprBuilder {
ExprBuilder::Add(Box::new(x.into()), Box::new(y.into()))
}
pub fn sub(x: impl Into<ExprBuilder>, y: impl Into<ExprBuilder>) -> ExprBuilder {
ExprBuilder::Sub(Box::new(x.into()), Box::new(y.into()))
}
pub fn mul(x: impl Into<ExprBuilder>, y: impl Into<ExprBuilder>) -> ExprBuilder {
ExprBuilder::Mul(Box::new(x.into()), Box::new(y.into()))
}
pub fn div(x: impl Into<ExprBuilder>, y: impl Into<ExprBuilder>) -> ExprBuilder {
ExprBuilder::Div(Box::new(x.into()), Box::new(y.into()))
}
pub fn modulo(model: &mut Model, x: VarId, y: VarId) -> VarId {
model.modulo(x, y)
}
pub fn eq(model: &mut Model, x: impl Into<ExprBuilder>, y: impl Into<ExprBuilder>) {
model.new(x.into().eq(y));
}
pub fn ne(model: &mut Model, x: impl Into<ExprBuilder>, y: impl Into<ExprBuilder>) {
model.new(x.into().ne(y));
}
pub fn lt(model: &mut Model, x: impl Into<ExprBuilder>, y: impl Into<ExprBuilder>) {
model.new(x.into().lt(y));
}
pub fn le(model: &mut Model, x: impl Into<ExprBuilder>, y: impl Into<ExprBuilder>) {
model.new(x.into().le(y));
}
pub fn gt(model: &mut Model, x: impl Into<ExprBuilder>, y: impl Into<ExprBuilder>) {
model.new(x.into().gt(y));
}
pub fn ge(model: &mut Model, x: impl Into<ExprBuilder>, y: impl Into<ExprBuilder>) {
model.new(x.into().ge(y));
}
pub fn lin_eq<T: LinearCoeff>(model: &mut Model, coeffs: &[T], vars: &[VarId], constant: T) {
T::post_lin_eq(model, coeffs, vars, constant);
}
pub fn lin_le<T: LinearCoeff>(model: &mut Model, coeffs: &[T], vars: &[VarId], constant: T) {
T::post_lin_le(model, coeffs, vars, constant);
}
pub fn lin_ne<T: LinearCoeff>(model: &mut Model, coeffs: &[T], vars: &[VarId], constant: T) {
T::post_lin_ne(model, coeffs, vars, constant);
}
pub fn lin_eq_reif<T: LinearCoeff>(model: &mut Model, coeffs: &[T], vars: &[VarId], constant: T, b: VarId) {
T::post_lin_eq_reif(model, coeffs, vars, constant, b);
}
pub fn lin_le_reif<T: LinearCoeff>(model: &mut Model, coeffs: &[T], vars: &[VarId], constant: T, b: VarId) {
T::post_lin_le_reif(model, coeffs, vars, constant, b);
}
pub fn lin_ne_reif<T: LinearCoeff>(model: &mut Model, coeffs: &[T], vars: &[VarId], constant: T, b: VarId) {
T::post_lin_ne_reif(model, coeffs, vars, constant, b);
}
pub trait LinearCoeff: Copy {
fn post_lin_eq(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self);
fn post_lin_le(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self);
fn post_lin_ne(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self);
fn post_lin_eq_reif(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self, b: VarId);
fn post_lin_le_reif(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self, b: VarId);
fn post_lin_ne_reif(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self, b: VarId);
}
impl LinearCoeff for i32 {
fn post_lin_eq(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self) {
if coeffs.len() != vars.len() {
model.constraint_validation_errors.push(crate::core::SolverError::InvalidConstraint {
message: format!(
"Linear constraint validation error: coefficients and variables must have same length (got {} coefficients but {} variables)",
coeffs.len(),
vars.len()
),
constraint_name: Some("lin_eq".to_string()),
variables: None,
});
return;
}
use crate::runtime_api::{ConstraintKind, ComparisonOp};
let ast = ConstraintKind::LinearInt {
coeffs: coeffs.to_vec(),
vars: vars.to_vec(),
op: ComparisonOp::Eq,
constant,
};
model.pending_constraint_asts.push(ast);
}
fn post_lin_le(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self) {
if coeffs.len() != vars.len() {
model.constraint_validation_errors.push(crate::core::SolverError::InvalidConstraint {
message: format!(
"Linear constraint validation error: coefficients and variables must have same length (got {} coefficients but {} variables)",
coeffs.len(),
vars.len()
),
constraint_name: Some("lin_le".to_string()),
variables: None,
});
return;
}
use crate::runtime_api::{ConstraintKind, ComparisonOp};
let ast = ConstraintKind::LinearInt {
coeffs: coeffs.to_vec(),
vars: vars.to_vec(),
op: ComparisonOp::Le,
constant,
};
model.pending_constraint_asts.push(ast);
}
fn post_lin_ne(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self) {
if coeffs.len() != vars.len() {
model.constraint_validation_errors.push(crate::core::SolverError::InvalidConstraint {
message: format!(
"Linear constraint validation error: coefficients and variables must have same length (got {} coefficients but {} variables)",
coeffs.len(),
vars.len()
),
constraint_name: Some("lin_ne".to_string()),
variables: None,
});
return;
}
use crate::runtime_api::{ConstraintKind, ComparisonOp};
let ast = ConstraintKind::LinearInt {
coeffs: coeffs.to_vec(),
vars: vars.to_vec(),
op: ComparisonOp::Ne,
constant,
};
model.pending_constraint_asts.push(ast);
}
fn post_lin_eq_reif(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self, b: VarId) {
use crate::runtime_api::{ConstraintKind, ComparisonOp};
let ast = ConstraintKind::ReifiedLinearInt {
coeffs: coeffs.to_vec(),
vars: vars.to_vec(),
op: ComparisonOp::Eq,
constant,
reif_var: b,
};
model.pending_constraint_asts.push(ast);
}
fn post_lin_le_reif(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self, b: VarId) {
use crate::runtime_api::{ConstraintKind, ComparisonOp};
let ast = ConstraintKind::ReifiedLinearInt {
coeffs: coeffs.to_vec(),
vars: vars.to_vec(),
op: ComparisonOp::Le,
constant,
reif_var: b,
};
model.pending_constraint_asts.push(ast);
}
fn post_lin_ne_reif(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self, b: VarId) {
use crate::runtime_api::{ConstraintKind, ComparisonOp};
let ast = ConstraintKind::ReifiedLinearInt {
coeffs: coeffs.to_vec(),
vars: vars.to_vec(),
op: ComparisonOp::Ne,
constant,
reif_var: b,
};
model.pending_constraint_asts.push(ast);
}
}
impl LinearCoeff for f64 {
fn post_lin_eq(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self) {
if coeffs.len() != vars.len() {
model.constraint_validation_errors.push(crate::core::SolverError::InvalidConstraint {
message: format!(
"Linear constraint validation error: coefficients and variables must have same length (got {} coefficients but {} variables)",
coeffs.len(),
vars.len()
),
constraint_name: Some("lin_eq".to_string()),
variables: None,
});
return;
}
use crate::runtime_api::{ConstraintKind, ComparisonOp};
let ast = ConstraintKind::LinearFloat {
coeffs: coeffs.to_vec(),
vars: vars.to_vec(),
op: ComparisonOp::Eq,
constant,
};
model.pending_constraint_asts.push(ast);
}
fn post_lin_le(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self) {
if coeffs.len() != vars.len() {
model.constraint_validation_errors.push(crate::core::SolverError::InvalidConstraint {
message: format!(
"Linear constraint validation error: coefficients and variables must have same length (got {} coefficients but {} variables)",
coeffs.len(),
vars.len()
),
constraint_name: Some("lin_le".to_string()),
variables: None,
});
return;
}
use crate::runtime_api::{ConstraintKind, ComparisonOp};
let ast = ConstraintKind::LinearFloat {
coeffs: coeffs.to_vec(),
vars: vars.to_vec(),
op: ComparisonOp::Le,
constant,
};
model.pending_constraint_asts.push(ast);
}
fn post_lin_ne(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self) {
if coeffs.len() != vars.len() {
model.constraint_validation_errors.push(crate::core::SolverError::InvalidConstraint {
message: format!(
"Linear constraint validation error: coefficients and variables must have same length (got {} coefficients but {} variables)",
coeffs.len(),
vars.len()
),
constraint_name: Some("lin_ne".to_string()),
variables: None,
});
return;
}
use crate::runtime_api::{ConstraintKind, ComparisonOp};
let ast = ConstraintKind::LinearFloat {
coeffs: coeffs.to_vec(),
vars: vars.to_vec(),
op: ComparisonOp::Ne,
constant,
};
model.pending_constraint_asts.push(ast);
}
fn post_lin_eq_reif(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self, b: VarId) {
use crate::runtime_api::{ConstraintKind, ComparisonOp};
let ast = ConstraintKind::ReifiedLinearFloat {
coeffs: coeffs.to_vec(),
vars: vars.to_vec(),
op: ComparisonOp::Eq,
constant,
reif_var: b,
};
model.pending_constraint_asts.push(ast);
}
fn post_lin_le_reif(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self, b: VarId) {
use crate::runtime_api::{ConstraintKind, ComparisonOp};
let ast = ConstraintKind::ReifiedLinearFloat {
coeffs: coeffs.to_vec(),
vars: vars.to_vec(),
op: ComparisonOp::Le,
constant,
reif_var: b,
};
model.pending_constraint_asts.push(ast);
}
fn post_lin_ne_reif(model: &mut Model, coeffs: &[Self], vars: &[VarId], constant: Self, b: VarId) {
use crate::runtime_api::{ConstraintKind, ComparisonOp};
let ast = ConstraintKind::ReifiedLinearFloat {
coeffs: coeffs.to_vec(),
vars: vars.to_vec(),
op: ComparisonOp::Ne,
constant,
reif_var: b,
};
model.pending_constraint_asts.push(ast);
}
}
pub fn alldiff(model: &mut Model, vars: &[VarId]) {
model.props.all_different(vars.to_vec());
}
pub fn alleq(model: &mut Model, vars: &[VarId]) {
model.props.all_equal(vars.to_vec());
}
pub fn min(model: &mut Model, vars: &[VarId]) -> crate::core::SolverResult<VarId> {
model.min(vars)
}
pub fn max(model: &mut Model, vars: &[VarId]) -> crate::core::SolverResult<VarId> {
model.max(vars)
}
pub fn sum(model: &mut Model, vars: &[VarId]) -> VarId {
model.sum(vars)
}
pub fn abs(model: &mut Model, var: VarId) -> VarId {
model.abs(var)
}
pub fn eq_reif(model: &mut Model, x: VarId, y: VarId, b: VarId) {
model.props.int_eq_reif(x, y, b);
}
pub fn ne_reif(model: &mut Model, x: VarId, y: VarId, b: VarId) {
model.props.int_ne_reif(x, y, b);
}
pub fn lt_reif(model: &mut Model, x: VarId, y: VarId, b: VarId) {
model.props.int_lt_reif(x, y, b);
}
pub fn le_reif(model: &mut Model, x: VarId, y: VarId, b: VarId) {
model.props.int_le_reif(x, y, b);
}
pub fn gt_reif(model: &mut Model, x: VarId, y: VarId, b: VarId) {
model.props.int_lt_reif(y, x, b);
}
pub fn ge_reif(model: &mut Model, x: VarId, y: VarId, b: VarId) {
model.props.int_le_reif(y, x, b);
}
pub fn and(model: &mut Model, b1: VarId, b2: VarId) -> VarId {
model.bool_and(&[b1, b2])
}
pub fn or(model: &mut Model, b1: VarId, b2: VarId) -> VarId {
model.bool_or(&[b1, b2])
}
pub fn not(model: &mut Model, b: VarId) -> VarId {
model.bool_not(b)
}
pub fn xor(model: &mut Model, b1: VarId, b2: VarId) -> VarId {
let b1_or_b2 = model.bool_or(&[b1, b2]);
let b1_and_b2 = model.bool_and(&[b1, b2]);
let not_both = model.bool_not(b1_and_b2);
model.bool_and(&[b1_or_b2, not_both])
}
pub fn implies(model: &mut Model, b1: VarId, b2: VarId) {
let not_b1 = model.bool_not(b1);
let _ = model.bool_or(&[not_b1, b2]);
}
pub fn element(model: &mut Model, array: &[VarId], index: VarId) -> VarId {
let result = model.int(-1000, 1000); model.element(array, index, result);
result
}
pub fn table(model: &mut Model, vars: &[VarId], tuples: &[Vec<Val>]) -> PropId {
model.table(vars, tuples.to_vec())
}
pub fn gcc(model: &mut Model, vars: &[VarId], values: &[i32], counts: &[VarId]) -> Vec<PropId> {
model.gcc(vars, values, counts)
}
pub fn cumulative(
model: &mut Model,
starts: &[VarId],
durations: &[i32],
demands: &[i32],
capacity: i32,
) {
if !(starts.len() == durations.len() && starts.len() == demands.len()) {
return; }
let n = starts.len();
for i in 0..n {
for j in i+1..n {
let demand_sum = demands[i] + demands[j];
if demand_sum > capacity {
let start_i = starts[i];
let start_j = starts[j];
let dur_i = durations[i];
let dur_j = durations[j];
let end_i = model.add(start_i, Val::int(dur_i));
let end_j = model.add(start_j, Val::int(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_result = model.bool();
model.bool_or(&[b1, b2, b_result]);
model.props.equals(b_result, Val::int(1));
}
}
}
}
pub fn int2float(model: &mut Model, int_var: VarId) -> VarId {
use crate::variables::views::ViewRaw;
let int_min = int_var.min_raw(&model.vars);
let int_max = int_var.max_raw(&model.vars);
let float_min = match int_min {
Val::ValI(i) => i as f64,
Val::ValF(f) => f.floor(),
};
let float_max = match int_max {
Val::ValI(i) => i as f64,
Val::ValF(f) => f.ceil(),
};
let float_var = model.float(float_min, float_max);
let int_as_float = model.mul(int_var, Val::ValF(1.0));
model.props.equals(float_var, int_as_float);
float_var
}
pub fn bool2int(model: &mut Model, bool_var: VarId) -> VarId {
let int_var = model.int(0, 1);
model.props.equals(int_var, bool_var);
int_var
}
pub fn floor(model: &mut Model, float_var: VarId) -> VarId {
use crate::variables::views::ViewRaw;
let float_min = float_var.min_raw(&model.vars);
let float_max = float_var.max_raw(&model.vars);
let (floor_min, floor_max) = match (float_min, float_max) {
(Val::ValF(f_min), Val::ValF(f_max)) => (f_min.floor() as i32, f_max.floor() as i32),
(Val::ValI(i_min), Val::ValI(i_max)) => (i_min, i_max),
(Val::ValF(f), Val::ValI(i)) => (f.floor() as i32, i),
(Val::ValI(i), Val::ValF(f)) => (i, f.floor() as i32),
};
let int_var = model.int(floor_min, floor_max);
model.props.less_than_or_equals(int_var, float_var);
let int_as_float = model.add(int_var, Val::ValF(0.0));
let int_plus_one_float = model.add(int_as_float, Val::ValF(1.0));
model.props.less_than(float_var, int_plus_one_float);
int_var
}
pub fn ceil(model: &mut Model, float_var: VarId) -> VarId {
use crate::variables::views::ViewRaw;
let float_min = float_var.min_raw(&model.vars);
let float_max = float_var.max_raw(&model.vars);
let (ceil_min, ceil_max) = match (float_min, float_max) {
(Val::ValF(f_min), Val::ValF(f_max)) => (f_min.ceil() as i32, f_max.ceil() as i32),
(Val::ValI(i_min), Val::ValI(i_max)) => (i_min, i_max),
(Val::ValF(f), Val::ValI(i)) => (f.ceil() as i32, i),
(Val::ValI(i), Val::ValF(f)) => (i, f.ceil() as i32),
};
let int_var = model.int(ceil_min, ceil_max);
model.props.less_than_or_equals(float_var, int_var);
let int_as_float = model.add(int_var, Val::ValF(0.0));
let int_minus_one_float = model.sub(int_as_float, Val::ValF(1.0));
model.props.less_than(int_minus_one_float, float_var);
int_var
}
pub fn round(model: &mut Model, float_var: VarId) -> VarId {
use crate::variables::views::ViewRaw;
let float_min = float_var.min_raw(&model.vars);
let float_max = float_var.max_raw(&model.vars);
let (round_min, round_max) = match (float_min, float_max) {
(Val::ValF(f_min), Val::ValF(f_max)) => (f_min.round() as i32, f_max.round() as i32),
(Val::ValI(i_min), Val::ValI(i_max)) => (i_min, i_max),
(Val::ValF(f), Val::ValI(i)) => (f.round() as i32, i),
(Val::ValI(i), Val::ValF(f)) => (i, f.round() as i32),
};
let int_var = model.int(round_min, round_max);
let int_minus_half = model.add(int_var, Val::ValF(-0.5));
let int_plus_half = model.add(int_var, Val::ValF(0.5));
model.props.less_than_or_equals(int_minus_half, float_var);
model.props.less_than(float_var, int_plus_half);
int_var
}