===============================================================================
RUNTIME CONSTRAINT API IMPLEMENTATION PLAN
===============================================================================
PROBLEM STATEMENT:
- Users cannot build constraints programmatically at runtime
- Current post! macro requires compile-time knowledge of expressions
- Need truly dynamic constraint building from data/config/business rules
- API should have very short method names for brevity
SOLUTION OVERVIEW:
Dual API approach - keep post! macro AND add runtime constraint builder with:
1. Pure programmatic expression building (no macro syntax)
2. Ultra-short method names for concise code
3. Fluent interface for natural constraint composition
4. Type-safe but flexible for runtime use
===============================================================================
API DESIGN - SHORT NAMES
===============================================================================
CORE STRUCTURES:
```rust
pub struct ExprBuilder { ... } // Fluent expression builder
pub struct Constraint { ... } // Boolean constraint for posting
```
MODEL EXTENSIONS (ultra-short names):
```rust
impl Model {
// Constraint building
pub fn c(&mut self, var: VarId) -> ExprBuilder { ... } // Start building constraint from variable
pub fn post(&mut self, constraint: Constraint) { ... } // Post constraint to model
// Global constraints (short names)
pub fn alldiff(&mut self, vars: Vec<VarId>) -> Constraint { ... } // all_different
pub fn alleq(&mut self, vars: Vec<VarId>) -> Constraint { ... } // all_equal
pub fn elem(&mut self, array: Vec<VarId>, index: VarId, value: VarId) -> Constraint { ... }
pub fn count(&mut self, vars: Vec<VarId>, value: i32, result: VarId) -> Constraint { ... }
}
```
VARIABLE EXTENSIONS (direct operations on VarId):
```rust
impl VarId {
// Arithmetic (chainable) - returns ExprBuilder
pub fn add(self, other: impl Into<Expr>) -> ExprBuilder { ... }
pub fn sub(self, other: impl Into<Expr>) -> ExprBuilder { ... }
pub fn mul(self, other: impl Into<Expr>) -> ExprBuilder { ... }
pub fn div(self, other: impl Into<Expr>) -> ExprBuilder { ... }
pub fn mod_(self, other: impl Into<Expr>) -> ExprBuilder { ... }
pub fn abs(self) -> ExprBuilder { ... }
// Direct constraints - returns Constraint
pub fn eq(self, other: impl Into<Expr>) -> Constraint { ... } // equals
pub fn ne(self, other: impl Into<Expr>) -> Constraint { ... } // not_equals
pub fn lt(self, other: impl Into<Expr>) -> Constraint { ... } // less_than
pub fn le(self, other: impl Into<Expr>) -> Constraint { ... } // less_equal
pub fn gt(self, other: impl Into<Expr>) -> Constraint { ... } // greater_than
pub fn ge(self, other: impl Into<Expr>) -> Constraint { ... } // greater_equal
}
```
EXPRESSION BUILDER (fluent):
```rust
impl ExprBuilder {
// Continue building expressions
pub fn add(self, other: impl Into<Expr>) -> ExprBuilder { ... }
pub fn sub(self, other: impl Into<Expr>) -> ExprBuilder { ... }
pub fn mul(self, other: impl Into<Expr>) -> ExprBuilder { ... }
pub fn div(self, other: impl Into<Expr>) -> ExprBuilder { ... }
// Create constraints from expressions
pub fn eq(self, other: impl Into<Expr>) -> Constraint { ... }
pub fn ne(self, other: impl Into<Expr>) -> Constraint { ... }
pub fn lt(self, other: impl Into<Expr>) -> Constraint { ... }
pub fn le(self, other: impl Into<Expr>) -> Constraint { ... }
pub fn gt(self, other: impl Into<Expr>) -> Constraint { ... }
pub fn ge(self, other: impl Into<Expr>) -> Constraint { ... }
// Direct posting
pub fn post(self) -> Constraint { ... } // For constraints that are complete
}
```
CONSTRAINT COMPOSITION:
```rust
impl Constraint {
// Boolean logic
pub fn and(&self, other: Constraint) -> Constraint { ... }
pub fn or(&self, other: Constraint) -> Constraint { ... }
pub fn not(&self) -> Constraint { ... }
}
```
OPERATOR OVERLOADING (for convenience - not runtime):
```rust
// Note: These require compile-time knowledge, not truly runtime
impl std::ops::Add<VarId> for VarId {
type Output = ExprBuilder;
fn add(self, other: VarId) -> ExprBuilder { ... }
}
impl std::ops::Add<i32> for VarId {
type Output = ExprBuilder;
fn add(self, other: i32) -> ExprBuilder { ... }
}
// These are convenience features, the core runtime API is the method-based approach
// Example: x + y compiles to x.add(y) under the hood
```
===============================================================================
USAGE EXAMPLES
===============================================================================
EXAMPLE 1: TRULY RUNTIME PROGRAMMATIC BUILDING
```rust
let mut m = Model::default();
let x = m.int(0, 10);
let y = m.int(0, 10);
let z = m.int(0, 20);
// Method 1: Pure runtime - chain operations programmatically
m.post(x.add(y).eq(z));
// Method 2: Build constraints step by step at runtime
let sum_expr = x.add(y);
let constraint = sum_expr.eq(z);
m.post(constraint);
// Method 3: Simple variable constraints
m.post(x.gt(5));
m.post(y.le(10));
// Method 4: Complex runtime expressions
m.post(x.mul(2).add(y).sub(3).le(20));
```
EXAMPLE 2: DYNAMIC CONSTRAINT GENERATION FROM DATA
```rust
// Build constraints from runtime data - ELEGANT VERSION
let rules = vec![
("x", "gt", 5),
("y", "le", 8),
("z", "eq", 3),
];
for (var_name, op, value) in rules {
let var_id = variables[var_name];
let constraint = match op {
"gt" => Some(var_id.gt(value)),
"le" => Some(var_id.le(value)),
"eq" => Some(var_id.eq(value)),
_ => {
eprintln!("Warning: Unknown operator '{}', skipping constraint", op);
None
}
};
if let Some(c) = constraint {
model.post(c);
}
}
```
EXAMPLE 3: COMPLEX RUNTIME EXPRESSIONS - ELEGANT
```rust
// Build mathematical expressions from data
struct ExpressionData {
operation: String,
left_var: VarId,
right_value: i32,
target_var: VarId,
}
let expr_data = ExpressionData {
operation: "add".to_string(),
left_var: x,
right_value: 10,
target_var: z,
};
// Build expression dynamically - MUCH MORE ELEGANT
let constraint = match expr_data.operation.as_str() {
"add" => Some(expr_data.left_var.add(expr_data.right_value).eq(expr_data.target_var)),
"sub" => Some(expr_data.left_var.sub(expr_data.right_value).eq(expr_data.target_var)),
"mul" => Some(expr_data.left_var.mul(expr_data.right_value).eq(expr_data.target_var)),
"div" => Some(expr_data.left_var.div(expr_data.right_value).eq(expr_data.target_var)),
_ => {
eprintln!("Warning: Unknown operation '{}', skipping constraint", expr_data.operation);
None
}
};
if let Some(c) = constraint {
m.post(c);
}
```
EXAMPLE 4: CONSTRAINT COMPOSITION - SUPER ELEGANT
```rust
// Build constraints and compose them
let c1 = x.gt(5);
let c2 = y.lt(10);
let combined = c1.and(c2);
m.post(combined);
// Multiple constraints from array
let constraints: Vec<_> = vec![x, y, z].iter()
.map(|&v| v.ge(0))
.collect();
// Combine all with AND
let combined_constraint = constraints.into_iter()
.reduce(|acc, c| acc.and(c))
.unwrap();
m.post(combined_constraint);
// Ultra-elegant with operator overloading
m.post(combined_constraint);
```
```
EXAMPLE 5: PURELY RUNTIME DYNAMIC PATTERNS
```rust
// Build operations from strings/data at runtime
let operations = vec![
("add", x, 5, 20), // x + 5 <= 20
("mul", y, 2, 15), // y * 2 <= 15
("sub", z, 3, 5), // z - 3 >= 5
];
for (op, var, value, bound) in operations {
let constraint = match op {
"add" => var.add(value).le(bound),
"mul" => var.mul(value).le(bound),
"sub" => var.sub(value).ge(bound),
_ => continue,
};
m.post(constraint);
}
// Build complex expressions from runtime data
struct RuntimeExpression {
left_var: VarId,
operations: Vec<(String, i32)>, // operation, value pairs
comparison: String,
target: i32,
}
let expr = RuntimeExpression {
left_var: x,
operations: vec![("mul".to_string(), 2), ("add".to_string(), 3)],
comparison: "le".to_string(),
target: 25,
};
// Build: x.mul(2).add(3).le(25) - completely from runtime data
let mut current_expr = expr.left_var;
for (op, val) in expr.operations {
current_expr = match op.as_str() {
"add" => current_expr.add(val),
"mul" => current_expr.mul(val),
"sub" => current_expr.sub(val),
"div" => current_expr.div(val),
_ => current_expr,
};
}
let constraint = match expr.comparison.as_str() {
"eq" => Some(current_expr.eq(expr.target)),
"le" => Some(current_expr.le(expr.target)),
"ge" => Some(current_expr.ge(expr.target)),
"lt" => Some(current_expr.lt(expr.target)),
"gt" => Some(current_expr.gt(expr.target)),
_ => {
eprintln!("Warning: Unknown comparison '{}', skipping constraint", expr.comparison);
None
}
};
if let Some(c) = constraint {
m.post(c);
}
// Dynamic constraint composition
let conditions = vec![
(x, "gt", 0),
(y, "le", 100),
(z, "eq", 50),
];
let constraints: Vec<_> = conditions.iter()
.filter_map(|&(var, op, val)| match op {
"gt" => Some(var.gt(val)),
"le" => Some(var.le(val)),
"eq" => Some(var.eq(val)),
"ge" => Some(var.ge(val)),
"lt" => Some(var.lt(val)),
_ => {
eprintln!("Warning: Unknown operator '{}', skipping constraint", op);
None
}
})
.collect();
// Combine all constraints with AND
if let Some(first) = constraints.first() {
let combined = constraints.iter().skip(1)
.fold(first.clone(), |acc, c| acc.and(c.clone()));
m.post(combined);
}
```
===============================================================================
IMPLEMENTATION PHASES
===============================================================================
PHASE 1: CORE EXPRESSION SYSTEM (Week 1-2)
- Implement Expr struct with arithmetic operations
- Add Into<Expr> conversions for VarId, i32, f64
- Create basic constraint generation from expressions
- Add Model::var(), Model::val(), Model::post() methods
PHASE 2: CONSTRAINT BUILDER (Week 2-3)
- Implement Builder struct with fluent interface
- Add Model::c() method for constraint building
- Implement variable-to-value and variable-to-variable constraints
- Add short method names (eq, ne, lt, le, gt, ge)
PHASE 3: BOOLEAN LOGIC (Week 3-4)
- Add Constraint::and(), Constraint::or(), Constraint::not()
- Implement constraint composition and chaining
- Add support for constraint arrays and iteration
PHASE 4: GLOBAL CONSTRAINTS (Week 4-5)
- Add Model::alldiff(), Model::alleq(), Model::elem(), Model::count()
- Implement cardinality constraints with short names
- Add between and element constraints
PHASE 5: CONVENIENCE FEATURES (Week 5-6)
- Implement optional operator overloading for compile-time convenience (x + y syntax)
- Add helper macros for common patterns
- Performance optimization and testing
- Add debugging and inspection capabilities
===============================================================================
SHORT NAME MAPPING
===============================================================================
LONG NAME -> SHORT NAME
post_constraint -> post
all_different -> alldiff
all_equal -> alleq
element -> elem
constraint -> c
equals -> eq
not_equals -> ne
less_than -> lt
less_than_or_equals -> le
greater_than -> gt
greater_than_or_equals -> ge
between -> betw
cardinality -> card
if_then_else -> ite
count_constraint -> count
===============================================================================
PROPER ERROR HANDLING
===============================================================================
RECOMMENDED PATTERNS FOR RUNTIME CONSTRAINT BUILDING:
1. USE Option<Constraint> FOR INVALID OPERATIONS:
```rust
fn build_constraint(var: VarId, op: &str, value: i32) -> Option<Constraint> {
match op {
"eq" => Some(var.eq(value)),
"gt" => Some(var.gt(value)),
"lt" => Some(var.lt(value)),
_ => None }
}
if let Some(constraint) = build_constraint(x, "eq", 5) {
model.post(constraint);
} else {
eprintln!("Invalid constraint operator");
}
```
2. USE Result<Constraint, Error> FOR MORE DETAILED ERRORS:
```rust
#[derive(Debug)]
enum ConstraintError {
UnknownOperator(String),
InvalidValue(i32),
VariableNotFound(String),
}
fn build_constraint_safe(var: VarId, op: &str, value: i32) -> Result<Constraint, ConstraintError> {
match op {
"eq" => Ok(var.eq(value)),
"gt" => Ok(var.gt(value)),
"lt" => Ok(var.lt(value)),
_ => Err(ConstraintError::UnknownOperator(op.to_string()))
}
}
match build_constraint_safe(x, "eq", 5) {
Ok(constraint) => model.post(constraint),
Err(e) => eprintln!("Constraint building failed: {:?}", e),
}
```
3. COLLECT ERRORS INSTEAD OF STOPPING:
```rust
let mut constraints = Vec::new();
let mut errors = Vec::new();
for (var, op, val) in constraint_specs {
match build_constraint_safe(var, op, val) {
Ok(constraint) => constraints.push(constraint),
Err(e) => errors.push(e),
}
}
for constraint in constraints {
model.post(constraint);
}
if !errors.is_empty() {
eprintln!("Failed to build {} constraints: {:?}", errors.len(), errors);
}
```
4. NEVER USE panic! IN PRODUCTION CONSTRAINT BUILDING:
```rust
let constraint = match op {
"eq" => var.eq(value),
_ => panic!("Unknown operator"), };
let constraint = match op {
"eq" => Some(var.eq(value)),
_ => {
eprintln!("Warning: Unknown operator '{}', skipping", op);
None
}
};
```
===============================================================================
IMPLEMENTATION STATUS
===============================================================================
✅ COMPLETED PHASES:
PHASE 1: CORE EXPRESSION SYSTEM
- ✅ Implemented Expr system with arithmetic operations
- ✅ Added Into<Expr> conversions for VarId, i32, f64
- ✅ Created constraint generation from expressions
- ✅ Added Model::post() method
PHASE 2: CONSTRAINT BUILDER
- ✅ Implemented VarId extension methods with fluent interface
- ✅ Added variable-to-value and variable-to-variable constraints
- ✅ Implemented short method names (eq, ne, lt, le, gt, ge)
- ✅ Added arithmetic operations (add, sub, mul, div)
PHASE 3: BOOLEAN LOGIC
- ✅ Added Constraint::and(), Constraint::or(), Constraint::not()
- ✅ Implemented constraint composition and chaining
- ✅ Added support for constraint arrays (ConstraintVecExt)
- ✅ Added ModelExt with post_all(), post_and(), post_or()
ADDITIONAL IMPROVEMENTS:
- ✅ Clean Solution API with automatic type inference
- ✅ Proper error handling patterns (no panics in production code)
- ✅ Comprehensive test suite with 15 tests (11 passing, 1 ignored, 3 failing)
- ✅ Example code demonstrating safe constraint building
🔄 TODO PHASES (Future Work):
PHASE 4: GLOBAL CONSTRAINTS
- ✅ Added Model::alldiff(), Model::alleq(), Model::elem(), Model::count()
- ✅ Implemented cardinality constraints (betw, atmost, atleast, gcc)
- ✅ Added comprehensive examples and tests
- ✅ All global constraint methods working with proper error handling
PHASE 5: CONVENIENCE FEATURES
- [ ] Implement optional operator overloading for compile-time convenience
- [ ] Add helper macros for common patterns
- [ ] Performance optimization and testing
- [ ] Add debugging and inspection capabilities
===============================================================================
BENEFITS
===============================================================================
1. TRULY PROGRAMMATIC: Pure method-based runtime expression building (x.add(y).eq(z))
2. ULTRA-SHORT NAMES: Concise API for frequent use (c(), eq(), alldiff(), etc.)
3. TYPE SAFE: Compile-time checking where possible, runtime validation
4. FLUENT INTERFACE: Natural constraint composition and chaining
5. BACKWARD COMPATIBLE: Existing post! macro continues to work
6. FLEXIBLE: Build constraints from data, config, business rules
7. RUNTIME FIRST: All operations can be built from strings/data at runtime
===============================================================================
MIGRATION PATH
===============================================================================
PHASE 1: Add new API alongside existing post! macro
PHASE 2: Document both approaches in examples
PHASE 3: Gradually migrate examples to show runtime API usage
PHASE 4: Add deprecation warnings for verbose method names (optional)
PHASE 5: Full production release with dual API support
===============================================================================
DECISION POINTS
===============================================================================
Please review and decide:
1. API DESIGN: Approve the short method names (c(), eq(), alldiff(), etc.)?
2. IMPLEMENTATION PRIORITY: Should this be immediate or after Step 9.1?
3. SCOPE: Start with Phase 1-2 only or commit to full implementation?
4. NAMING: Any adjustments to the proposed short names?
5. EXAMPLES: Which use cases resonate most with your user feedback?
This plan directly solves the "nontrivial constraints whose relations are not
known at compile time" problem with a truly programmatic, runtime-flexible API.