1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub enum ConstraintType {
13 Equality { target: f64 },
15 LessThanOrEqual { bound: f64 },
17 GreaterThanOrEqual { bound: f64 },
19 Range { lower: f64, upper: f64 },
21 OneHot,
23 Cardinality { k: usize },
25 IntegerEncoding { min: i32, max: i32 },
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct Constraint {
32 pub name: String,
33 pub constraint_type: ConstraintType,
34 pub expression: Expression,
35 pub variables: Vec<String>,
36 pub penalty_weight: Option<f64>,
37 pub slack_variables: Vec<String>,
38}
39
40impl Constraint {
41 pub fn violation(&self, assignment: &HashMap<String, bool>) -> f64 {
44 let value = self.expression.evaluate(assignment);
45 self.constraint_type.violation(value)
46 }
47}
48
49impl ConstraintType {
50 #[must_use]
54 pub fn violation(&self, value: f64) -> f64 {
55 match *self {
56 Self::Equality { target } => value - target,
57 Self::LessThanOrEqual { bound } => (value - bound).max(0.0),
58 Self::GreaterThanOrEqual { bound } => (bound - value).max(0.0),
59 Self::Range { lower, upper } => {
60 if value < lower {
61 lower - value
62 } else if value > upper {
63 value - upper
64 } else {
65 0.0
66 }
67 }
68 Self::OneHot | Self::Cardinality { .. } => value,
73 Self::IntegerEncoding { .. } => 0.0,
80 }
81 }
82}
83
84pub struct ConstraintHandler {
86 constraints: Vec<Constraint>,
87 slack_variable_counter: usize,
88 encoding_cache: HashMap<String, EncodingInfo>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93struct EncodingInfo {
94 pub variable_name: String,
95 pub bit_variables: Vec<String>,
96 pub min_value: i32,
97 pub max_value: i32,
98 pub encoding_type: EncodingType,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
103pub enum EncodingType {
104 Binary,
105 Unary,
106 OneHot,
107 Gray,
108}
109
110impl Default for ConstraintHandler {
111 fn default() -> Self {
112 Self::new()
113 }
114}
115
116impl ConstraintHandler {
117 pub fn new() -> Self {
119 Self {
120 constraints: Vec::new(),
121 slack_variable_counter: 0,
122 encoding_cache: HashMap::new(),
123 }
124 }
125
126 pub fn add_constraint(&mut self, constraint: Constraint) {
128 self.constraints.push(constraint);
129 }
130
131 pub fn add_equality(
133 &mut self,
134 name: String,
135 expression: Expression,
136 target: f64,
137 ) -> Result<(), Box<dyn std::error::Error>> {
138 let variables = expression.get_variables();
139
140 self.add_constraint(Constraint {
141 name,
142 constraint_type: ConstraintType::Equality { target },
143 expression,
144 variables,
145 penalty_weight: None,
146 slack_variables: Vec::new(),
147 });
148
149 Ok(())
150 }
151
152 pub fn add_inequality(
154 &mut self,
155 name: String,
156 expression: Expression,
157 bound: f64,
158 less_than: bool,
159 ) -> Result<(), Box<dyn std::error::Error>> {
160 let variables = expression.get_variables();
161 let mut constraint = Constraint {
162 name: name.clone(),
163 constraint_type: if less_than {
164 ConstraintType::LessThanOrEqual { bound }
165 } else {
166 ConstraintType::GreaterThanOrEqual { bound }
167 },
168 expression,
169 variables,
170 penalty_weight: None,
171 slack_variables: Vec::new(),
172 };
173
174 if less_than {
176 let slack_var = self.create_slack_variable(&name);
178 constraint.slack_variables.push(slack_var);
179 } else {
180 let slack_var = self.create_slack_variable(&name);
182 constraint.slack_variables.push(slack_var);
183 }
184
185 self.add_constraint(constraint);
186 Ok(())
187 }
188
189 pub fn add_one_hot(
191 &mut self,
192 name: String,
193 variables: Vec<String>,
194 ) -> Result<(), Box<dyn std::error::Error>> {
195 let mut expr = Expression::zero();
197 for var in &variables {
198 expr = expr + Variable::new(var.clone()).into();
199 }
200 expr = expr - 1.0.into();
201
202 self.add_constraint(Constraint {
203 name,
204 constraint_type: ConstraintType::OneHot,
205 expression: expr,
206 variables,
207 penalty_weight: None,
208 slack_variables: Vec::new(),
209 });
210
211 Ok(())
212 }
213
214 pub fn add_cardinality(
216 &mut self,
217 name: String,
218 variables: Vec<String>,
219 k: usize,
220 ) -> Result<(), Box<dyn std::error::Error>> {
221 let mut expr = Expression::zero();
223 for var in &variables {
224 expr = expr + Variable::new(var.clone()).into();
225 }
226 expr = expr - (k as f64).into();
227
228 self.add_constraint(Constraint {
229 name,
230 constraint_type: ConstraintType::Cardinality { k },
231 expression: expr,
232 variables,
233 penalty_weight: None,
234 slack_variables: Vec::new(),
235 });
236
237 Ok(())
238 }
239
240 pub fn add_integer_encoding(
242 &mut self,
243 name: String,
244 base_name: String,
245 min: i32,
246 max: i32,
247 encoding_type: EncodingType,
248 ) -> Result<Vec<String>, Box<dyn std::error::Error>> {
249 let num_bits = ((max - min + 1) as f64).log2().ceil() as usize;
250 let mut bit_variables = Vec::new();
251
252 for i in 0..num_bits {
254 bit_variables.push(format!("{base_name}_{i}"));
255 }
256
257 self.encoding_cache.insert(
259 base_name.clone(),
260 EncodingInfo {
261 variable_name: base_name,
262 bit_variables: bit_variables.clone(),
263 min_value: min,
264 max_value: max,
265 encoding_type,
266 },
267 );
268
269 match encoding_type {
271 EncodingType::Binary => {
272 }
274 EncodingType::Unary => {
275 for i in 1..bit_variables.len() {
277 let expr: Expression = Variable::new(bit_variables[i].clone()).into();
278 let prev_expr: Expression = Variable::new(bit_variables[i - 1].clone()).into();
279 let constraint_expr = expr - prev_expr;
280
281 self.add_inequality(format!("{name}_unary_{i}"), constraint_expr, 0.0, true)?;
282 }
283 }
284 EncodingType::OneHot => {
285 self.add_one_hot(format!("{name}_onehot"), bit_variables.clone())?;
287 }
288 EncodingType::Gray => {
289 }
291 }
292
293 self.add_constraint(Constraint {
294 name,
295 constraint_type: ConstraintType::IntegerEncoding { min, max },
296 expression: Expression::zero(), variables: bit_variables.clone(),
298 penalty_weight: None,
299 slack_variables: Vec::new(),
300 });
301
302 Ok(bit_variables)
303 }
304
305 pub fn generate_penalty_terms(
313 &mut self,
314 penalty_weights: &HashMap<String, f64>,
315 ) -> Result<Expression, Box<dyn std::error::Error>> {
316 let mut total_penalty = Expression::zero();
317
318 for idx in 0..self.constraints.len() {
319 let constraint = self.constraints[idx].clone();
320 let weight = penalty_weights
321 .get(&constraint.name)
322 .or(constraint.penalty_weight.as_ref())
323 .copied()
324 .unwrap_or(1.0);
325
326 let penalty_expr = match &constraint.constraint_type {
327 ConstraintType::Equality { target } => {
328 let diff = constraint.expression.clone() - (*target).into();
330 diff.clone() * diff
331 }
332 ConstraintType::LessThanOrEqual { bound } => {
333 if let Some(slack_var) = constraint.slack_variables.first() {
335 let expr_with_slack =
336 constraint.expression.clone() + Variable::new(slack_var.clone()).into();
337 let diff = expr_with_slack - (*bound).into();
338 diff.clone() * diff
339 } else {
340 let (penalty, slack_name) =
343 self.generate_inequality_penalty(&constraint.expression, *bound, true);
344 self.constraints[idx].slack_variables.push(slack_name);
345 penalty
346 }
347 }
348 ConstraintType::GreaterThanOrEqual { bound } => {
349 if let Some(slack_var) = constraint.slack_variables.first() {
351 let expr_with_slack =
352 constraint.expression.clone() - Variable::new(slack_var.clone()).into();
353 let diff = expr_with_slack - (*bound).into();
354 diff.clone() * diff
355 } else {
356 let (penalty, slack_name) =
359 self.generate_inequality_penalty(&constraint.expression, *bound, false);
360 self.constraints[idx].slack_variables.push(slack_name);
361 penalty
362 }
363 }
364 ConstraintType::Range { lower, upper } => {
365 let (lower_penalty, lower_slack) =
368 self.generate_inequality_penalty(&constraint.expression, *lower, false);
369 let (upper_penalty, upper_slack) =
370 self.generate_inequality_penalty(&constraint.expression, *upper, true);
371 self.constraints[idx].slack_variables.push(lower_slack);
372 self.constraints[idx].slack_variables.push(upper_slack);
373 lower_penalty + upper_penalty
374 }
375 ConstraintType::OneHot => {
376 let expr = constraint.expression.clone();
378 expr.clone() * expr
379 }
380 ConstraintType::Cardinality { k: _ } => {
381 let expr = constraint.expression.clone();
383 expr.clone() * expr
384 }
385 ConstraintType::IntegerEncoding { .. } => {
386 Expression::zero()
388 }
389 };
390
391 total_penalty = total_penalty + weight * penalty_expr;
392 }
393
394 Ok(total_penalty)
395 }
396
397 fn generate_inequality_penalty(
405 &mut self,
406 expression: &Expression,
407 bound: f64,
408 less_than: bool,
409 ) -> (Expression, String) {
410 let slack_name = self.create_slack_variable("ineq");
411 let slack_expr: Expression = Variable::new(slack_name.clone()).into();
412
413 let penalty = if less_than {
414 let diff = expression.clone() + slack_expr - bound.into();
416 diff.clone() * diff
417 } else {
418 let diff = expression.clone() - slack_expr - bound.into();
420 diff.clone() * diff
421 };
422
423 (penalty, slack_name)
424 }
425
426 fn create_slack_variable(&mut self, constraint_name: &str) -> String {
428 let var_name = format!("_slack_{}_{}", constraint_name, self.slack_variable_counter);
429 self.slack_variable_counter += 1;
430 var_name
431 }
432
433 pub fn get_all_variables(&self) -> Vec<String> {
435 let mut variables = Vec::new();
436
437 for constraint in &self.constraints {
438 variables.extend(constraint.variables.clone());
439 variables.extend(constraint.slack_variables.clone());
440 }
441
442 for encoding in self.encoding_cache.values() {
444 variables.extend(encoding.bit_variables.clone());
445 }
446
447 variables.sort();
449 variables.dedup();
450
451 variables
452 }
453
454 pub fn decode_integer(
456 &self,
457 variable_name: &str,
458 assignment: &HashMap<String, bool>,
459 ) -> Option<i32> {
460 let encoding = self.encoding_cache.get(variable_name)?;
461
462 match encoding.encoding_type {
463 EncodingType::Binary => {
464 let mut value = 0;
465 for (i, bit_var) in encoding.bit_variables.iter().enumerate() {
466 if *assignment.get(bit_var).unwrap_or(&false) {
467 value += 1 << i;
468 }
469 }
470 Some(encoding.min_value + value)
471 }
472 EncodingType::Unary => {
473 let mut count = 0;
474 for bit_var in &encoding.bit_variables {
475 if *assignment.get(bit_var).unwrap_or(&false) {
476 count += 1;
477 } else {
478 break;
479 }
480 }
481 Some(encoding.min_value + count)
482 }
483 EncodingType::OneHot => {
484 for (i, bit_var) in encoding.bit_variables.iter().enumerate() {
485 if *assignment.get(bit_var).unwrap_or(&false) {
486 return Some(encoding.min_value + i as i32);
487 }
488 }
489 None
490 }
491 EncodingType::Gray => {
492 let mut gray_value = 0;
494 for (i, bit_var) in encoding.bit_variables.iter().enumerate() {
495 if *assignment.get(bit_var).unwrap_or(&false) {
496 gray_value |= 1 << i;
497 }
498 }
499
500 let mut binary_value = gray_value;
502 binary_value ^= binary_value >> 16;
503 binary_value ^= binary_value >> 8;
504 binary_value ^= binary_value >> 4;
505 binary_value ^= binary_value >> 2;
506 binary_value ^= binary_value >> 1;
507
508 Some(encoding.min_value + binary_value)
509 }
510 }
511 }
512
513 pub fn analyze_constraints(&self) -> ConstraintAnalysis {
515 let total_constraints = self.constraints.len();
516 let total_variables = self.get_all_variables().len();
517
518 let mut type_counts = HashMap::new();
519 let mut avg_variables_per_constraint = 0.0;
520 let mut max_variables_in_constraint = 0;
521
522 for constraint in &self.constraints {
523 let type_name = match constraint.constraint_type {
524 ConstraintType::Equality { .. } => "equality",
525 ConstraintType::LessThanOrEqual { .. } => "less_than",
526 ConstraintType::GreaterThanOrEqual { .. } => "greater_than",
527 ConstraintType::Range { .. } => "range",
528 ConstraintType::OneHot => "one_hot",
529 ConstraintType::Cardinality { .. } => "cardinality",
530 ConstraintType::IntegerEncoding { .. } => "integer",
531 };
532
533 *type_counts.entry(type_name.to_string()).or_insert(0) += 1;
534
535 let var_count = constraint.variables.len();
536 avg_variables_per_constraint += var_count as f64;
537 max_variables_in_constraint = max_variables_in_constraint.max(var_count);
538 }
539
540 if total_constraints > 0 {
541 avg_variables_per_constraint /= total_constraints as f64;
542 }
543
544 ConstraintAnalysis {
545 total_constraints,
546 total_variables,
547 slack_variables: self.slack_variable_counter,
548 constraint_types: type_counts,
549 avg_variables_per_constraint,
550 max_variables_in_constraint,
551 encoding_info: self.encoding_cache.len(),
552 }
553 }
554}
555
556#[derive(Debug, Clone, Serialize, Deserialize)]
558pub struct ConstraintAnalysis {
559 pub total_constraints: usize,
560 pub total_variables: usize,
561 pub slack_variables: usize,
562 pub constraint_types: HashMap<String, usize>,
563 pub avg_variables_per_constraint: f64,
564 pub max_variables_in_constraint: usize,
565 pub encoding_info: usize,
566}
567
568trait ExpressionExt {
570 fn zero() -> Self;
571 fn get_variables(&self) -> Vec<String>;
572}
573
574impl ExpressionExt for Expression {
575 fn zero() -> Self {
576 Self::Constant(0.0)
577 }
578
579 fn get_variables(&self) -> Vec<String> {
580 fn collect(expr: &Expression, vars: &mut Vec<String>) {
581 match expr {
582 Expression::Constant(_) => {}
583 Expression::Variable(name) => {
584 if !vars.contains(name) {
585 vars.push(name.clone());
586 }
587 }
588 Expression::Add(lhs, rhs) | Expression::Multiply(lhs, rhs) => {
589 collect(lhs, vars);
590 collect(rhs, vars);
591 }
592 }
593 }
594
595 let mut vars = Vec::new();
596 collect(self, &mut vars);
597 vars
598 }
599}
600
601#[derive(Debug, Clone)]
603pub struct Variable {
604 name: String,
605}
606
607impl Variable {
608 pub const fn new(name: String) -> Self {
609 Self { name }
610 }
611}
612
613#[derive(Debug, Clone, Serialize, Deserialize)]
617pub enum Expression {
618 Constant(f64),
619 Variable(String),
620 Add(Box<Self>, Box<Self>),
621 Multiply(Box<Self>, Box<Self>),
622}
623
624impl Expression {
625 #[must_use]
629 pub fn evaluate(&self, assignment: &HashMap<String, bool>) -> f64 {
630 match self {
631 Self::Constant(c) => *c,
632 Self::Variable(name) => {
633 if assignment.get(name).copied().unwrap_or(false) {
634 1.0
635 } else {
636 0.0
637 }
638 }
639 Self::Add(lhs, rhs) => lhs.evaluate(assignment) + rhs.evaluate(assignment),
640 Self::Multiply(lhs, rhs) => lhs.evaluate(assignment) * rhs.evaluate(assignment),
641 }
642 }
643}
644
645impl From<f64> for Expression {
646 fn from(value: f64) -> Self {
647 Self::Constant(value)
648 }
649}
650
651impl From<Variable> for Expression {
652 fn from(var: Variable) -> Self {
653 Self::Variable(var.name)
654 }
655}
656
657impl std::ops::Add for Expression {
658 type Output = Self;
659
660 fn add(self, rhs: Self) -> Self::Output {
661 Self::Add(Box::new(self), Box::new(rhs))
662 }
663}
664
665impl std::ops::Sub for Expression {
666 type Output = Self;
667
668 fn sub(self, rhs: Self) -> Self::Output {
669 Self::Add(
670 Box::new(self),
671 Box::new(Self::Multiply(
672 Box::new(Self::Constant(-1.0)),
673 Box::new(rhs),
674 )),
675 )
676 }
677}
678
679impl std::ops::Mul for Expression {
680 type Output = Self;
681
682 fn mul(self, rhs: Self) -> Self::Output {
683 Self::Multiply(Box::new(self), Box::new(rhs))
684 }
685}
686
687impl std::ops::Mul<Expression> for f64 {
688 type Output = Expression;
689
690 fn mul(self, rhs: Expression) -> Self::Output {
691 Expression::Multiply(Box::new(Expression::Constant(self)), Box::new(rhs))
692 }
693}
694
695#[cfg(test)]
696mod tests {
697 use super::*;
698
699 fn assignment(pairs: &[(&str, bool)]) -> HashMap<String, bool> {
700 pairs
701 .iter()
702 .map(|(name, value)| ((*name).to_string(), *value))
703 .collect()
704 }
705
706 #[test]
707 fn test_get_variables_collects_all_leaves() {
708 let expr: Expression = (Expression::from(Variable::new("x0".to_string()))
709 + Variable::new("x1".to_string()).into())
710 * Variable::new("x0".to_string()).into();
711
712 let mut vars = expr.get_variables();
713 vars.sort();
714 assert_eq!(vars, vec!["x0".to_string(), "x1".to_string()]);
715 }
716
717 #[test]
718 fn test_expression_evaluate() {
719 let expr: Expression = (Expression::from(Variable::new("x0".to_string()))
721 + Variable::new("x1".to_string()).into())
722 * Variable::new("x0".to_string()).into();
723
724 let assign = assignment(&[("x0", true), ("x1", true)]);
725 assert_eq!(expr.evaluate(&assign), 2.0);
726
727 let assign = assignment(&[("x0", false), ("x1", true)]);
728 assert_eq!(expr.evaluate(&assign), 0.0);
729 }
730
731 #[test]
732 fn test_constraint_violation_equality() {
733 let mut handler = ConstraintHandler::new();
734 let expr: Expression = Variable::new("x0".to_string()).into();
735 handler
736 .add_equality("eq".to_string(), expr, 1.0)
737 .expect("add_equality should succeed");
738
739 let satisfied = assignment(&[("x0", true)]);
740 let violated = assignment(&[("x0", false)]);
741
742 assert_eq!(handler.constraints[0].violation(&satisfied), 0.0);
743 assert_eq!(handler.constraints[0].violation(&violated), -1.0);
744 assert_eq!(handler.constraints[0].variables, vec!["x0".to_string()]);
746 }
747
748 #[test]
749 fn test_generate_penalty_terms_inequality_is_not_zero() {
750 let mut handler = ConstraintHandler::new();
751 let expr: Expression = Variable::new("x0".to_string()).into();
754 handler.add_constraint(Constraint {
755 name: "le".to_string(),
756 constraint_type: ConstraintType::LessThanOrEqual { bound: 0.0 },
757 expression: expr,
758 variables: vec!["x0".to_string()],
759 penalty_weight: None,
760 slack_variables: Vec::new(),
761 });
762
763 let penalty = handler
764 .generate_penalty_terms(&HashMap::new())
765 .expect("penalty generation should succeed");
766
767 let vars = penalty.get_variables();
770 assert!(
771 vars.iter().any(|v| v.starts_with("_slack_")),
772 "expected a slack variable in generated penalty, got {vars:?}"
773 );
774
775 let violated = assignment(&[("x0", true)]);
780 assert!(penalty.evaluate(&violated) > 0.0);
781 }
782}