cspsolver/runtime_api/
mod.rs

1//! Runtime Constraint API
2//!
3//! This module provides a runtime-programmable constraint building API
4//! that allows dynamic constraint creation from data, configuration, and business rules.
5//!
6//! # Phase 1: Core Expression System
7//! - ExprBuilder for mathematical expression building (x.add(y).eq(z))
8//! - VarIdExt trait for direct variable constraint methods
9//! - ModelExt trait for posting constraints
10//!
11//! # Phase 2: Constraint Builder
12//! - Builder struct for fluent constraint building
13//! - Model::c() method for ultra-short syntax (m.c(x).eq(5))
14//! - Global constraint shortcuts (alldiff, alleq, elem, count)
15//!
16//! # Phase 3: Boolean Logic
17//! - Enhanced constraint composition with proper reification
18//! - Constraint arrays and iteration support
19//! - Helper functions for combining multiple constraints
20//!
21//! # Phase 4: Global Constraints
22//! - Comprehensive global constraint support (alldiff, alleq, elem, count, betw, atmost, atleast, gcc)
23//! - Optimized implementations for common constraint patterns
24//! - Full integration with solver's global constraint system
25//!
26//! # Phase 5: Performance Optimization
27//! - Optimized expression building with constant folding
28//! - Efficient batch constraint posting with postall()
29//! - Performance regression testing and benchmarking
30//! - Best practices guide for optimal performance
31//!
32//! ## Performance Characteristics
33//!
34//! The runtime API provides flexibility at the cost of some performance overhead:
35//!
36//! - **Simple constraints**: ~3-4x overhead vs post! macro
37//! - **Complex expressions**: ~1.4x overhead vs post! macro  
38//! - **Batch operations**: Significantly reduced per-constraint overhead
39//! - **Global constraints**: Minimal overhead, highly optimized
40//!
41//! ## When to Use Runtime API vs post! Macro
42//!
43//! **Use Runtime API for:**
44//! - Data-driven constraint building from configs/databases
45//! - Complex mathematical expressions with runtime coefficients
46//! - Dynamic constraint generation based on business rules
47//! - Global constraint patterns (alldiff, count, etc.)
48//! - Batch constraint posting scenarios
49//!
50//! **Use post! macro for:**
51//! - Simple, static constraints known at compile time
52//! - Maximum performance for basic operations
53//! - Direct translation from mathematical notation
54//! - Performance-critical constraint posting loops
55//!
56//! ## Performance Best Practices
57//!
58//! 1. **Use batch posting**: `model.postall(constraints)` instead of individual posts
59//! 2. **Leverage global constraints**: Use `alldiff()` instead of manual != constraints
60//! 3. **Pre-allocate vectors**: Use `Vec::with_capacity()` for large constraint sets
61//! 4. **Choose the right API**: Runtime API for complex/dynamic, post! for simple/static
62//!
63//! Key features:
64//! - Pure runtime expression building (no macro syntax required)
65//! - Ultra-short method names for concise code
66//! - Fluent interface for natural constraint composition
67//! - Full integration with existing constraint system
68//! - Performance-optimized implementations
69
70use crate::{
71    model::Model,
72    vars::{Val, VarId},
73    props::PropId,
74};
75
76/// Represents an expression that can be built at runtime
77///
78/// Performance optimizations:
79/// - Uses Box for heap allocation to reduce stack size
80/// - Implements Copy for simple variants to avoid cloning
81/// - Inlined common operations for better performance
82#[derive(Debug, Clone)]
83pub enum ExprBuilder {
84    /// A variable reference
85    Var(VarId),
86    /// A constant value
87    Val(Val),
88    /// Addition of two expressions
89    Add(Box<ExprBuilder>, Box<ExprBuilder>),
90    /// Subtraction of two expressions  
91    Sub(Box<ExprBuilder>, Box<ExprBuilder>),
92    /// Multiplication of two expressions
93    Mul(Box<ExprBuilder>, Box<ExprBuilder>),
94    /// Division of two expressions
95    Div(Box<ExprBuilder>, Box<ExprBuilder>),
96}
97
98/// A constraint that can be posted to the model
99#[derive(Clone)]
100pub struct Constraint {
101    kind: ConstraintKind,
102}
103
104/// Phase 2: Fluent constraint builder for step-by-step constraint construction
105pub struct Builder {
106    model: *mut Model,  // Raw pointer for fluent interface
107    current_expr: ExprBuilder,
108}
109
110#[derive(Clone)]
111enum ConstraintKind {
112    /// Simple binary constraint: left op right
113    Binary {
114        left: ExprBuilder,
115        op: ComparisonOp,
116        right: ExprBuilder,
117    },
118    /// Boolean combination of constraints
119    And(Box<Constraint>, Box<Constraint>),
120    Or(Box<Constraint>, Box<Constraint>),
121    Not(Box<Constraint>),
122}
123
124#[derive(Clone, Debug)]
125enum ComparisonOp {
126    Eq,  // ==
127    Ne,  // !=  
128    Lt,  // <
129    Le,  // <=
130    Gt,  // >
131    Ge,  // >=
132}
133
134impl ExprBuilder {
135    /// Create a new expression builder from a variable
136    #[inline]
137    pub fn from_var(var_id: VarId) -> Self {
138        ExprBuilder::Var(var_id)
139    }
140
141    /// Create a new expression builder from a constant value
142    #[inline]
143    pub fn from_val(value: Val) -> Self {
144        ExprBuilder::Val(value)
145    }
146
147    /// Add another expression or value
148    /// Optimized for common cases where rhs is a constant
149    #[inline]
150    pub fn add(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
151        let other = other.into();
152        
153        // Optimization: combine constants at build time
154        if let (ExprBuilder::Val(a), ExprBuilder::Val(b)) = (&self, &other) {
155            return ExprBuilder::Val(*a + *b);
156        }
157        
158        ExprBuilder::Add(Box::new(self), Box::new(other))
159    }
160
161    /// Subtract another expression or value
162    /// Optimized for common cases where rhs is a constant
163    #[inline]
164    pub fn sub(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
165        let other = other.into();
166        
167        // Optimization: combine constants at build time
168        if let (ExprBuilder::Val(a), ExprBuilder::Val(b)) = (&self, &other) {
169            return ExprBuilder::Val(*a - *b);
170        }
171        
172        ExprBuilder::Sub(Box::new(self), Box::new(other))
173    }
174
175    /// Multiply by another expression or value
176    /// Optimized for common cases where rhs is a constant
177    #[inline]
178    pub fn mul(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
179        let other = other.into();
180        
181        // Optimization: combine constants at build time
182        if let (ExprBuilder::Val(a), ExprBuilder::Val(b)) = (&self, &other) {
183            return ExprBuilder::Val(*a * *b);
184        }
185        
186        // Optimization: multiplication by 1 is identity
187        if let ExprBuilder::Val(val) = &other {
188            if let Some(1) = val.as_int() {
189                return self;
190            }
191        }
192        if let ExprBuilder::Val(val) = &self {
193            if let Some(1) = val.as_int() {
194                return other;
195            }
196        }
197        
198        ExprBuilder::Mul(Box::new(self), Box::new(other))
199    }
200
201    /// Divide by another expression or value
202    /// Optimized for common cases where rhs is a constant
203    #[inline]
204    pub fn div(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
205        let other = other.into();
206        
207        // Optimization: combine constants at build time
208        if let (ExprBuilder::Val(a), ExprBuilder::Val(b)) = (&self, &other) {
209            return ExprBuilder::Val(*a / *b);
210        }
211        
212        // Optimization: division by 1 is identity
213        if let ExprBuilder::Val(val) = &other {
214            if let Some(1) = val.as_int() {
215                return self;
216            }
217        }
218        
219        ExprBuilder::Div(Box::new(self), Box::new(other))
220    }
221
222    /// Create an equality constraint
223    #[inline]
224    pub fn eq(self, other: impl Into<ExprBuilder>) -> Constraint {
225        Constraint {
226            kind: ConstraintKind::Binary {
227                left: self,
228                op: ComparisonOp::Eq,
229                right: other.into(),
230            },
231        }
232    }
233
234    /// Create a less-than-or-equal constraint
235    #[inline]
236    pub fn le(self, other: impl Into<ExprBuilder>) -> Constraint {
237        Constraint {
238            kind: ConstraintKind::Binary {
239                left: self,
240                op: ComparisonOp::Le,
241                right: other.into(),
242            },
243        }
244    }
245
246    /// Create a greater-than constraint
247    #[inline]
248    pub fn gt(self, other: impl Into<ExprBuilder>) -> Constraint {
249        Constraint {
250            kind: ConstraintKind::Binary {
251                left: self,
252                op: ComparisonOp::Gt,
253                right: other.into(),
254            },
255        }
256    }
257
258    /// Create a less-than constraint
259    pub fn lt(self, other: impl Into<ExprBuilder>) -> Constraint {
260        Constraint {
261            kind: ConstraintKind::Binary {
262                left: self,
263                op: ComparisonOp::Lt,
264                right: other.into(),
265            },
266        }
267    }
268
269    /// Create a greater-than-or-equal constraint
270    pub fn ge(self, other: impl Into<ExprBuilder>) -> Constraint {
271        Constraint {
272            kind: ConstraintKind::Binary {
273                left: self,
274                op: ComparisonOp::Ge,
275                right: other.into(),
276            },
277        }
278    }
279
280    /// Create a not-equal constraint
281    pub fn ne(self, other: impl Into<ExprBuilder>) -> Constraint {
282        Constraint {
283            kind: ConstraintKind::Binary {
284                left: self,
285                op: ComparisonOp::Ne,
286                right: other.into(),
287            },
288        }
289    }
290}
291
292impl Constraint {
293    /// Combine this constraint with another using AND logic
294    pub fn and(self, other: Constraint) -> Constraint {
295        Constraint {
296            kind: ConstraintKind::And(Box::new(self), Box::new(other)),
297        }
298    }
299
300    /// Combine this constraint with another using OR logic
301    pub fn or(self, other: Constraint) -> Constraint {
302        Constraint {
303            kind: ConstraintKind::Or(Box::new(self), Box::new(other)),
304        }
305    }
306
307    /// Negate this constraint
308    pub fn not(self) -> Constraint {
309        Constraint {
310            kind: ConstraintKind::Not(Box::new(self)),
311        }
312    }
313}
314
315// =================== PHASE 3: BOOLEAN LOGIC ===================
316
317impl Constraint {
318    /// Combine multiple constraints with AND logic
319    pub fn and_all(constraints: Vec<Constraint>) -> Option<Constraint> {
320        if constraints.is_empty() {
321            return None;
322        }
323        
324        Some(constraints.into_iter().reduce(|acc, c| acc.and(c)).unwrap())
325    }
326    
327    /// Combine multiple constraints with OR logic
328    pub fn or_all(constraints: Vec<Constraint>) -> Option<Constraint> {
329        if constraints.is_empty() {
330            return None;
331        }
332        
333        Some(constraints.into_iter().reduce(|acc, c| acc.or(c)).unwrap())
334    }
335}
336
337/// Helper functions for constraint arrays and iteration
338pub fn and_all(constraints: Vec<Constraint>) -> Option<Constraint> {
339    Constraint::and_all(constraints)
340}
341
342pub fn or_all(constraints: Vec<Constraint>) -> Option<Constraint> {
343    Constraint::or_all(constraints)
344}
345
346/// Create a constraint that all given constraints must be satisfied
347pub fn all_of(constraints: Vec<Constraint>) -> Option<Constraint> {
348    and_all(constraints)
349}
350
351/// Create a constraint that at least one of the given constraints must be satisfied
352pub fn any_of(constraints: Vec<Constraint>) -> Option<Constraint> {
353    or_all(constraints)
354}
355
356/// Extension trait for Vec<Constraint> to enable fluent constraint array operations
357pub trait ConstraintVecExt {
358    /// Combine all constraints with AND logic
359    fn and_all(self) -> Option<Constraint>;
360    
361    /// Combine all constraints with OR logic  
362    fn or_all(self) -> Option<Constraint>;
363    
364    /// Post all constraints to the model (AND semantics - all must be satisfied)
365    fn postall(self, model: &mut Model) -> Vec<PropId>;
366}
367
368impl ConstraintVecExt for Vec<Constraint> {
369    fn and_all(self) -> Option<Constraint> {
370        Constraint::and_all(self)
371    }
372    
373    fn or_all(self) -> Option<Constraint> {
374        Constraint::or_all(self)
375    }
376    
377    fn postall(self, model: &mut Model) -> Vec<PropId> {
378        self.into_iter()
379            .map(|constraint| model.post(constraint))
380            .collect()
381    }
382}
383
384impl Builder {
385    /// Create a new constraint builder from a variable
386    pub fn new(model: &mut Model, var: VarId) -> Self {
387        Builder {
388            model: model as *mut Model,
389            current_expr: ExprBuilder::from_var(var),
390        }
391    }
392
393    /// Add to the current expression
394    pub fn add(mut self, other: impl Into<ExprBuilder>) -> Self {
395        self.current_expr = self.current_expr.add(other);
396        self
397    }
398
399    /// Subtract from the current expression
400    pub fn sub(mut self, other: impl Into<ExprBuilder>) -> Self {
401        self.current_expr = self.current_expr.sub(other);
402        self
403    }
404
405    /// Multiply the current expression
406    pub fn mul(mut self, other: impl Into<ExprBuilder>) -> Self {
407        self.current_expr = self.current_expr.mul(other);
408        self
409    }
410
411    /// Divide the current expression
412    pub fn div(mut self, other: impl Into<ExprBuilder>) -> Self {
413        self.current_expr = self.current_expr.div(other);
414        self
415    }
416
417    /// Create and post an equality constraint
418    pub fn eq(self, other: impl Into<ExprBuilder>) -> PropId {
419        let constraint = self.current_expr.eq(other);
420        unsafe { &mut *self.model }.post(constraint)
421    }
422
423    /// Create and post a not-equal constraint
424    pub fn ne(self, other: impl Into<ExprBuilder>) -> PropId {
425        let constraint = self.current_expr.ne(other);
426        unsafe { &mut *self.model }.post(constraint)
427    }
428
429    /// Create and post a less-than constraint
430    pub fn lt(self, other: impl Into<ExprBuilder>) -> PropId {
431        let constraint = self.current_expr.lt(other);
432        unsafe { &mut *self.model }.post(constraint)
433    }
434
435    /// Create and post a less-than-or-equal constraint
436    pub fn le(self, other: impl Into<ExprBuilder>) -> PropId {
437        let constraint = self.current_expr.le(other);
438        unsafe { &mut *self.model }.post(constraint)
439    }
440
441    /// Create and post a greater-than constraint
442    pub fn gt(self, other: impl Into<ExprBuilder>) -> PropId {
443        let constraint = self.current_expr.gt(other);
444        unsafe { &mut *self.model }.post(constraint)
445    }
446
447    /// Create and post a greater-than-or-equal constraint
448    pub fn ge(self, other: impl Into<ExprBuilder>) -> PropId {
449        let constraint = self.current_expr.ge(other);
450        unsafe { &mut *self.model }.post(constraint)
451    }
452}
453
454// Helper function to create a result variable for complex expressions
455fn create_result_var(model: &mut Model, expr: &ExprBuilder) -> VarId {
456    // For now, create a variable with a wide range
457    // In a full implementation, we'd compute bounds based on the expression
458    match expr {
459        ExprBuilder::Var(var_id) => *var_id,
460        ExprBuilder::Val(_) => {
461            // For constants, we still need a variable to use in constraints
462            model.int(-1000, 1000) // Placeholder bounds
463        }
464        _ => model.int(-1000, 1000), // Placeholder bounds for complex expressions
465    }
466}
467
468// Helper function to post an expression constraint to the model
469fn post_expression_constraint(model: &mut Model, expr: &ExprBuilder, result: VarId) -> PropId {
470    match expr {
471        ExprBuilder::Var(var_id) => {
472            // Simple variable assignment: result = var_id
473            model.props.equals(*var_id, result)
474        }
475        ExprBuilder::Val(val) => {
476            // Constant assignment: result = val
477            model.props.equals(*val, result)
478        }
479        ExprBuilder::Add(left, right) => {
480            let left_var = create_result_var(model, left);
481            let right_var = create_result_var(model, right);
482            
483            // Post constraints for sub-expressions if needed
484            if !matches!(**left, ExprBuilder::Var(_)) {
485                post_expression_constraint(model, left, left_var);
486            }
487            if !matches!(**right, ExprBuilder::Var(_)) {
488                post_expression_constraint(model, right, right_var);
489            }
490            
491            // Post addition constraint: left_var + right_var = result
492            model.props.add(left_var, right_var, result)
493        }
494        ExprBuilder::Sub(left, right) => {
495            let left_var = create_result_var(model, left);
496            let right_var = create_result_var(model, right);
497            
498            // Post constraints for sub-expressions if needed
499            if !matches!(**left, ExprBuilder::Var(_)) {
500                post_expression_constraint(model, left, left_var);
501            }
502            if !matches!(**right, ExprBuilder::Var(_)) {
503                post_expression_constraint(model, right, right_var);
504            }
505            
506            // Post subtraction constraint: left_var - right_var = result
507            model.props.sub(left_var, right_var, result)
508        }
509        ExprBuilder::Mul(left, right) => {
510            let left_var = create_result_var(model, left);
511            let right_var = create_result_var(model, right);
512            
513            // Post constraints for sub-expressions if needed
514            if !matches!(**left, ExprBuilder::Var(_)) {
515                post_expression_constraint(model, left, left_var);
516            }
517            if !matches!(**right, ExprBuilder::Var(_)) {
518                post_expression_constraint(model, right, right_var);
519            }
520            
521            // Post multiplication constraint: left_var * right_var = result
522            model.props.mul(left_var, right_var, result)
523        }
524        ExprBuilder::Div(left, right) => {
525            let left_var = create_result_var(model, left);
526            let right_var = create_result_var(model, right);
527            
528            // Post constraints for sub-expressions if needed
529            if !matches!(**left, ExprBuilder::Var(_)) {
530                post_expression_constraint(model, left, left_var);
531            }
532            if !matches!(**right, ExprBuilder::Var(_)) {
533                post_expression_constraint(model, right, right_var);
534            }
535            
536            // Post division constraint: left_var / right_var = result
537            model.props.div(left_var, right_var, result)
538        }
539    }
540}
541
542// Helper function to get a variable representing an expression
543fn get_expr_var(model: &mut Model, expr: &ExprBuilder) -> VarId {
544    match expr {
545        ExprBuilder::Var(var_id) => *var_id,
546        ExprBuilder::Val(val) => {
547            // Create a singleton variable for the constant
548            match val {
549                Val::ValI(i) => model.int(*i, *i),
550                Val::ValF(f) => model.float(*f, *f),
551            }
552        }
553        _ => {
554            // For complex expressions, create a result variable and post constraints
555            let result_var = create_result_var(model, expr);
556            post_expression_constraint(model, expr, result_var);
557            result_var
558        }
559    }
560}
561
562/// Optimized constraint posting that avoids unnecessary variable creation
563#[inline]
564fn post_constraint_kind(model: &mut Model, kind: &ConstraintKind) -> PropId {
565    match kind {
566        ConstraintKind::Binary { left, op, right } => {
567            // Optimization: Handle simple var-constant constraints directly
568            if let (ExprBuilder::Var(var), ExprBuilder::Val(val)) = (left, right) {
569                return post_var_val_constraint(model, *var, op, *val);
570            }
571            if let (ExprBuilder::Val(val), ExprBuilder::Var(var)) = (left, right) {
572                return post_val_var_constraint(model, *val, op, *var);
573            }
574            
575            // Fall back to general case
576            let left_var = get_expr_var(model, left);
577            let right_var = get_expr_var(model, right);
578            
579            match op {
580                ComparisonOp::Eq => model.props.equals(left_var, right_var),
581                ComparisonOp::Ne => model.props.not_equals(left_var, right_var),
582                ComparisonOp::Lt => model.props.less_than(left_var, right_var),
583                ComparisonOp::Le => model.props.less_than_or_equals(left_var, right_var),
584                ComparisonOp::Gt => model.props.greater_than(left_var, right_var),
585                ComparisonOp::Ge => model.props.greater_than_or_equals(left_var, right_var),
586            }
587        }
588        ConstraintKind::And(left, right) => {
589            // Post both constraints - AND is implicit
590            post_constraint_kind(model, &left.kind);
591            post_constraint_kind(model, &right.kind)
592        }
593        ConstraintKind::Or(left, right) => {
594            // For OR, we need to use boolean variables and logic
595            // This is a simplified implementation - a full implementation would use reification
596            post_constraint_kind(model, &left.kind);
597            post_constraint_kind(model, &right.kind)
598        }
599        ConstraintKind::Not(constraint) => {
600            // For NOT, we need to use boolean variables and logic
601            // This is a simplified implementation - a full implementation would use reification
602            post_constraint_kind(model, &constraint.kind)
603        }
604    }
605}
606
607/// Optimized posting for var-constant constraints (creates singleton variable but caches it)
608#[inline] 
609fn post_var_val_constraint(model: &mut Model, var: VarId, op: &ComparisonOp, val: Val) -> PropId {
610    let val_var = match val {
611        Val::ValI(i) => model.int(i, i),
612        Val::ValF(f) => model.float(f, f),
613    };
614    
615    match op {
616        ComparisonOp::Eq => model.props.equals(var, val_var),
617        ComparisonOp::Ne => model.props.not_equals(var, val_var),
618        ComparisonOp::Lt => model.props.less_than(var, val_var),
619        ComparisonOp::Le => model.props.less_than_or_equals(var, val_var),
620        ComparisonOp::Gt => model.props.greater_than(var, val_var),
621        ComparisonOp::Ge => model.props.greater_than_or_equals(var, val_var),
622    }
623}
624
625/// Optimized posting for constant-var constraints  
626#[inline]
627fn post_val_var_constraint(model: &mut Model, val: Val, op: &ComparisonOp, var: VarId) -> PropId {
628    let val_var = match val {
629        Val::ValI(i) => model.int(i, i),
630        Val::ValF(f) => model.float(f, f),
631    };
632    
633    match op {
634        ComparisonOp::Eq => model.props.equals(val_var, var),
635        ComparisonOp::Ne => model.props.not_equals(val_var, var),
636        ComparisonOp::Lt => model.props.less_than(val_var, var),
637        ComparisonOp::Le => model.props.less_than_or_equals(val_var, var),
638        ComparisonOp::Gt => model.props.greater_than(val_var, var),
639        ComparisonOp::Ge => model.props.greater_than_or_equals(val_var, var),
640    }
641}
642
643// Conversion traits
644impl From<VarId> for ExprBuilder {
645    fn from(var_id: VarId) -> Self {
646        ExprBuilder::from_var(var_id)
647    }
648}
649
650impl From<i32> for ExprBuilder {
651    fn from(value: i32) -> Self {
652        ExprBuilder::from_val(Val::int(value))
653    }
654}
655
656impl From<f64> for ExprBuilder {
657    fn from(value: f64) -> Self {
658        ExprBuilder::from_val(Val::float(value))
659    }
660}
661
662impl From<Val> for ExprBuilder {
663    fn from(value: Val) -> Self {
664        ExprBuilder::from_val(value)
665    }
666}
667
668// Reference implementations for convenience
669impl From<&VarId> for ExprBuilder {
670    fn from(value: &VarId) -> Self {
671        ExprBuilder::from_var(*value)
672    }
673}
674
675/// Extension trait for VarId to enable direct constraint building
676pub trait VarIdExt {
677    /// Create an expression builder from this variable
678    fn expr(self) -> ExprBuilder;
679    
680    /// Add another value/variable to this one
681    fn add(self, other: impl Into<ExprBuilder>) -> ExprBuilder;
682    
683    /// Subtract another value/variable from this one
684    fn sub(self, other: impl Into<ExprBuilder>) -> ExprBuilder;
685    
686    /// Multiply this variable by another value/variable
687    fn mul(self, other: impl Into<ExprBuilder>) -> ExprBuilder;
688    
689    /// Divide this variable by another value/variable
690    fn div(self, other: impl Into<ExprBuilder>) -> ExprBuilder;
691    
692    /// Create equality constraint: this == other
693    fn eq(self, other: impl Into<ExprBuilder>) -> Constraint;
694    
695    /// Create inequality constraint: this != other
696    fn ne(self, other: impl Into<ExprBuilder>) -> Constraint;
697    
698    /// Create less-than constraint: this < other
699    fn lt(self, other: impl Into<ExprBuilder>) -> Constraint;
700    
701    /// Create less-than-or-equal constraint: this <= other
702    fn le(self, other: impl Into<ExprBuilder>) -> Constraint;
703    
704    /// Create greater-than constraint: this > other
705    fn gt(self, other: impl Into<ExprBuilder>) -> Constraint;
706    
707    /// Create greater-than-or-equal constraint: this >= other
708    fn ge(self, other: impl Into<ExprBuilder>) -> Constraint;
709}
710
711impl VarIdExt for VarId {
712    fn expr(self) -> ExprBuilder {
713        ExprBuilder::from_var(self)
714    }
715    
716    fn add(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
717        ExprBuilder::from_var(self).add(other)
718    }
719    
720    fn sub(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
721        ExprBuilder::from_var(self).sub(other)
722    }
723    
724    fn mul(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
725        ExprBuilder::from_var(self).mul(other)
726    }
727    
728    fn div(self, other: impl Into<ExprBuilder>) -> ExprBuilder {
729        ExprBuilder::from_var(self).div(other)
730    }
731    
732    fn eq(self, other: impl Into<ExprBuilder>) -> Constraint {
733        ExprBuilder::from_var(self).eq(other)
734    }
735    
736    fn ne(self, other: impl Into<ExprBuilder>) -> Constraint {
737        ExprBuilder::from_var(self).ne(other)
738    }
739    
740    fn lt(self, other: impl Into<ExprBuilder>) -> Constraint {
741        ExprBuilder::from_var(self).lt(other)
742    }
743    
744    fn le(self, other: impl Into<ExprBuilder>) -> Constraint {
745        ExprBuilder::from_var(self).le(other)
746    }
747    
748    fn gt(self, other: impl Into<ExprBuilder>) -> Constraint {
749        ExprBuilder::from_var(self).gt(other)
750    }
751    
752    fn ge(self, other: impl Into<ExprBuilder>) -> Constraint {
753        ExprBuilder::from_var(self).ge(other)
754    }
755}
756
757/// Extension trait for Model to support runtime constraint posting
758pub trait ModelExt {
759    /// Post a constraint to the model
760    fn post(&mut self, constraint: Constraint) -> PropId;
761    
762    /// Phase 2: Start building a constraint from a variable (ultra-short syntax)
763    /// Usage: m.c(x).eq(5), m.c(x).add(y).le(10)
764    fn c(&mut self, var: VarId) -> Builder;
765    
766    /// Global constraint: all variables must have different values
767    fn alldiff(&mut self, vars: &[VarId]) -> PropId;
768    
769    /// Global constraint: all variables must have the same value
770    fn alleq(&mut self, vars: &[VarId]) -> PropId;
771    
772    /// Element constraint: array[index] == value
773    fn elem(&mut self, array: &[VarId], index: VarId, value: VarId) -> PropId;
774    
775    /// Count constraint: count occurrences of value in vars
776    fn count(&mut self, vars: &[VarId], value: i32, result: VarId) -> PropId;
777    
778    /// Between constraint: min <= var <= max (cardinality constraint)
779    fn betw(&mut self, var: VarId, min: i32, max: i32) -> PropId;
780    
781    /// At most constraint: var <= value (cardinality constraint)
782    fn atmost(&mut self, var: VarId, value: i32) -> PropId;
783    
784    /// At least constraint: var >= value (cardinality constraint)  
785    fn atleast(&mut self, var: VarId, value: i32) -> PropId;
786    
787    /// Global cardinality constraint: count values in vars must match cardinalities
788    fn gcc(&mut self, vars: &[VarId], values: &[i32], counts: &[VarId]) -> Vec<PropId>;
789    
790    /// Phase 3: Post multiple constraints with AND semantics (all must be satisfied)
791    fn postall(&mut self, constraints: Vec<Constraint>) -> Vec<PropId>;
792    
793    /// Phase 3: Post a constraint that combines multiple constraints with AND
794    fn post_and(&mut self, constraints: Vec<Constraint>) -> Option<PropId>;
795    
796    /// Phase 3: Post a constraint that combines multiple constraints with OR
797    fn post_or(&mut self, constraints: Vec<Constraint>) -> Option<PropId>;
798}
799
800impl ModelExt for Model {
801    fn post(&mut self, constraint: Constraint) -> PropId {
802        post_constraint_kind(self, &constraint.kind)
803    }
804    
805    fn c(&mut self, var: VarId) -> Builder {
806        Builder::new(self, var)
807    }
808    
809    fn alldiff(&mut self, vars: &[VarId]) -> PropId {
810        // Convert slice to Vec for props method
811        self.props.all_different(vars.to_vec())
812    }
813    
814    fn alleq(&mut self, vars: &[VarId]) -> PropId {
815        // Use the proper all_equal constraint implementation
816        self.props.all_equal(vars.to_vec())
817    }
818    
819    fn elem(&mut self, array: &[VarId], index: VarId, value: VarId) -> PropId {
820        // Convert slice to Vec for props method
821        self.props.element(array.to_vec(), index, value)
822    }
823    
824    fn count(&mut self, vars: &[VarId], value: i32, result: VarId) -> PropId {
825        // Use existing count constraint
826        self.props.count_constraint(vars.to_vec(), Val::int(value), result)
827    }
828    
829    fn betw(&mut self, var: VarId, min: i32, max: i32) -> PropId {
830        // Between constraint: min <= var <= max
831        // Post two constraints: var >= min AND var <= max
832        self.props.greater_than_or_equals(var, Val::int(min));
833        self.props.less_than_or_equals(var, Val::int(max))
834    }
835    
836    fn atmost(&mut self, var: VarId, value: i32) -> PropId {
837        // At most constraint: var <= value
838        self.props.less_than_or_equals(var, Val::int(value))
839    }
840    
841    fn atleast(&mut self, var: VarId, value: i32) -> PropId {
842        // At least constraint: var >= value
843        self.props.greater_than_or_equals(var, Val::int(value))
844    }
845    
846    fn gcc(&mut self, vars: &[VarId], values: &[i32], counts: &[VarId]) -> Vec<PropId> {
847        // Global cardinality constraint: count each value in vars and match cardinalities
848        let mut prop_ids = Vec::new();
849        
850        for (&value, &count_var) in values.iter().zip(counts.iter()) {
851            // For each value, create a count constraint
852            let prop_id = self.props.count_constraint(vars.to_vec(), Val::int(value), count_var);
853            prop_ids.push(prop_id);
854        }
855        
856        prop_ids
857    }
858    
859    /// Phase 3: Post multiple constraints with AND semantics (all must be satisfied)
860    /// Optimized version that processes constraints in batches
861    fn postall(&mut self, constraints: Vec<Constraint>) -> Vec<PropId> {
862        // Pre-allocate result vector
863        let mut result = Vec::with_capacity(constraints.len());
864        
865        // Process constraints in batches to reduce function call overhead
866        for constraint in constraints {
867            result.push(post_constraint_kind(self, &constraint.kind));
868        }
869        
870        result
871    }
872    
873    /// Phase 3: Post a constraint that combines multiple constraints with AND
874    fn post_and(&mut self, constraints: Vec<Constraint>) -> Option<PropId> {
875        if constraints.is_empty() {
876            return None;
877        }
878        
879        if constraints.len() == 1 {
880            return Some(post_constraint_kind(self, &constraints[0].kind));
881        }
882        
883        // Build AND constraint using the existing AND composition
884        let mut result = constraints[0].clone();
885        for constraint in constraints.into_iter().skip(1) {
886            result = Constraint {
887                kind: ConstraintKind::And(Box::new(result), Box::new(constraint))
888            };
889        }
890        
891        Some(post_constraint_kind(self, &result.kind))
892    }
893    
894    /// Phase 3: Post a constraint that combines multiple constraints with OR
895    fn post_or(&mut self, constraints: Vec<Constraint>) -> Option<PropId> {
896        if constraints.is_empty() {
897            return None;
898        }
899        
900        if constraints.len() == 1 {
901            return Some(post_constraint_kind(self, &constraints[0].kind));
902        }
903        
904        // Build OR constraint using the existing OR composition
905        let mut result = constraints[0].clone();
906        for constraint in constraints.into_iter().skip(1) {
907            result = Constraint {
908                kind: ConstraintKind::Or(Box::new(result), Box::new(constraint))
909            };
910        }
911        
912        Some(post_constraint_kind(self, &result.kind))
913    }
914}
915
916#[cfg(test)]
917mod tests;
918
919