1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
//! Problem classification for selecting optimal solving algorithms
//!
//! This module analyzes the structure of constraint satisfaction problems to determine
//! the most efficient solving approach. The classification is based on:
//!
//! 1. **Variable types**: Pure integer, pure float, or mixed
//! 2. **Constraint patterns**: Separable vs coupled constraints across types
//! 3. **Problem complexity**: Linear bounds vs nonlinear relationships
//!
//! The classifier enables automatic algorithm selection without user intervention.
use crate::variables::{Vars, Var, VarId};
use crate::constraints::props::Propagators;
/// Classification of constraint satisfaction problems for algorithm selection
#[derive(Debug, Clone, PartialEq)]
pub enum ProblemType {
/// All variables are floating-point - can use direct bounds optimization
/// This enables O(1) analytical solutions for simple optimization problems
PureFloat {
float_var_count: usize,
has_linear_bounds_only: bool,
},
/// All variables are integer - existing binary search works well
/// No optimization needed, current solver is appropriate for discrete domains
PureInteger {
integer_var_count: usize,
},
/// Mixed integer and float variables with separable constraints
/// Can solve float and integer parts independently, then combine
MixedSeparable {
integer_var_count: usize,
float_var_count: usize,
},
/// Mixed variables with coupled constraints requiring MINLP techniques
/// Need branch-and-bound on integers with float subproblem optimization
MixedCoupled {
integer_var_count: usize,
float_var_count: usize,
coupling_strength: CouplingStrength,
},
}
/// Degree of coupling between integer and float variables
#[derive(Debug, Clone, PartialEq)]
pub enum CouplingStrength {
/// Linear coupling (e.g., 2*x_int + 3*y_float <= 10)
/// Can be handled efficiently with interval arithmetic
Linear,
/// Nonlinear coupling (e.g., x_int * y_float <= 10)
/// Requires more sophisticated MINLP algorithms
Nonlinear,
/// Complex coupling with multiple interaction patterns
/// May need advanced branch-and-bound techniques
Complex,
}
/// Problem classifier that analyzes Model structure
/// Core classifier for analyzing CSP problem characteristics
#[derive(Debug)]
pub struct ProblemClassifier;
impl ProblemClassifier {
/// Classify a problem based on its variables and constraints
///
/// This is the main entry point for problem classification. It analyzes
/// the variable types and constraint patterns to determine the optimal
/// solving strategy.
///
/// # Arguments
/// * `vars` - The variables in the problem
/// * `props` - The constraints/propagators in the problem
///
/// # Returns
/// A `ProblemType` indicating the most efficient solving approach
pub fn classify(vars: &Vars, props: &Propagators) -> ProblemType {
let var_analysis = Self::analyze_variables(vars);
let constraint_analysis = Self::analyze_constraints(props, &var_analysis);
Self::determine_problem_type(var_analysis, constraint_analysis)
}
/// Analyze variable types in the problem
fn analyze_variables(vars: &Vars) -> VariableAnalysis {
let mut integer_count = 0;
let mut float_count = 0;
// Count variables by type
for var in vars.iter() {
match var {
Var::VarI(_) => integer_count += 1,
Var::VarF(_) => float_count += 1,
}
}
VariableAnalysis {
integer_count,
float_count,
}
}
/// Analyze constraint patterns to detect coupling
fn analyze_constraints(props: &Propagators, var_analysis: &VariableAnalysis) -> ConstraintAnalysis {
if var_analysis.integer_count == 0 || var_analysis.float_count == 0 {
// Pure problems can't have cross-type coupling
return ConstraintAnalysis {
appears_linear: true,
has_coupling: false,
coupling_strength: CouplingStrength::Linear,
};
}
// Mixed problem - analyze constraint registry for cross-type dependencies
let constraint_registry = props.get_constraint_registry();
let coupling_analysis = Self::analyze_variable_coupling(constraint_registry, var_analysis);
ConstraintAnalysis {
appears_linear: coupling_analysis.appears_linear,
has_coupling: coupling_analysis.has_coupling,
coupling_strength: coupling_analysis.coupling_strength,
}
}
/// Analyze variable coupling in mixed problems by examining constraint patterns
fn analyze_variable_coupling(
constraint_registry: &crate::optimization::constraint_metadata::ConstraintRegistry,
var_analysis: &VariableAnalysis,
) -> CouplingAnalysisResult {
// If no constraints or not a mixed problem, there can't be coupling
let has_constraints = constraint_registry.constraint_count() > 0;
if !has_constraints || var_analysis.integer_count == 0 || var_analysis.float_count == 0 {
return CouplingAnalysisResult {
has_coupling: false,
coupling_strength: CouplingStrength::Linear,
appears_linear: true,
};
}
// Check all constraints to see if any involve both integer and float variables
let has_coupling = Self::detect_cross_type_coupling(constraint_registry);
// Conservative assumption about coupling strength
let coupling_strength = CouplingStrength::Linear; // Start with linear assumption
CouplingAnalysisResult {
has_coupling,
coupling_strength,
appears_linear: true, // Conservative assumption
}
}
/// Detect if any constraints involve both integer and float variables
fn detect_cross_type_coupling(
constraint_registry: &crate::optimization::constraint_metadata::ConstraintRegistry,
) -> bool {
// Iterate through all constraints and check if any involve mixed variable types
for constraint_type in [
crate::optimization::constraint_metadata::ConstraintType::Equals,
crate::optimization::constraint_metadata::ConstraintType::LessThanOrEquals,
crate::optimization::constraint_metadata::ConstraintType::NotEquals,
crate::optimization::constraint_metadata::ConstraintType::BooleanAnd,
crate::optimization::constraint_metadata::ConstraintType::BooleanOr,
crate::optimization::constraint_metadata::ConstraintType::BooleanNot,
] {
let constraint_ids = constraint_registry.get_constraints_by_type(&constraint_type);
for constraint_id in constraint_ids {
if let Some(metadata) = constraint_registry.get_constraint(constraint_id) {
// Check if this constraint involves both integer and float variables
if Self::constraint_has_mixed_types(&metadata.variables) {
return true;
}
}
}
}
false
}
/// Check if a list of variables contains both integer and float types
fn constraint_has_mixed_types(variables: &[VarId]) -> bool {
if variables.len() < 2 {
return false;
}
// For now, we can't easily determine variable types from VarId alone
// without access to the Vars collection. This is a limitation of the current design.
// We'll return false for now and enhance this later when we have access to the m.
//
// Variable type detection from VarId not implemented:
// 1. Passing the Vars collection to this function would require refactoring
// 2. Encoding type information in VarId itself would change core architecture
// 3. Using a different approach for type detection would require design changes
false
}
/// Determine the final problem type based on analysis
fn determine_problem_type(
var_analysis: VariableAnalysis,
constraint_analysis: ConstraintAnalysis,
) -> ProblemType {
match (var_analysis.integer_count, var_analysis.float_count) {
(0, 0) => {
// No variables - this shouldn't happen in practice
ProblemType::PureInteger { integer_var_count: 0 }
},
(0, float_count) => {
// Pure float problem
ProblemType::PureFloat {
float_var_count: float_count,
has_linear_bounds_only: constraint_analysis.appears_linear,
}
},
(integer_count, 0) => {
// Pure integer problem
ProblemType::PureInteger { integer_var_count: integer_count }
},
(integer_count, float_count) => {
// Mixed problem - check for coupling
if constraint_analysis.has_coupling {
ProblemType::MixedCoupled {
integer_var_count: integer_count,
float_var_count: float_count,
coupling_strength: constraint_analysis.coupling_strength,
}
} else {
ProblemType::MixedSeparable {
integer_var_count: integer_count,
float_var_count: float_count,
}
}
},
}
}
}
/// Analysis results for variables in the problem
#[derive(Debug, Clone)]
struct VariableAnalysis {
integer_count: usize,
float_count: usize,
}
/// Analysis results for constraints in the problem
#[derive(Debug, Clone)]
struct ConstraintAnalysis {
appears_linear: bool,
has_coupling: bool,
coupling_strength: CouplingStrength,
}
/// Result of coupling analysis for mixed problems
#[derive(Debug, Clone)]
struct CouplingAnalysisResult {
has_coupling: bool,
coupling_strength: CouplingStrength,
appears_linear: bool,
}
impl ProblemType {
/// Check if this problem type can benefit from efficient float optimization
pub fn can_use_efficient_float_optimization(&self) -> bool {
match self {
ProblemType::PureFloat { has_linear_bounds_only: true, .. } => true,
ProblemType::MixedSeparable { .. } => true, // Float part can be optimized
_ => false,
}
}
/// Check if this problem requires the existing integer search
pub fn requires_integer_search(&self) -> bool {
match self {
ProblemType::PureInteger { .. } => true,
ProblemType::MixedCoupled { .. } => true, // Branch on integers
_ => false,
}
}
/// Get a human-readable description of the solving strategy
pub fn strategy_description(&self) -> &'static str {
match self {
ProblemType::PureFloat { has_linear_bounds_only: true, .. } => {
"Direct bounds optimization (O(1) analytical solution)"
},
ProblemType::PureFloat { has_linear_bounds_only: false, .. } => {
"Interval arithmetic with bounds consistency"
},
ProblemType::PureInteger { .. } => {
"Binary search with constraint propagation (current solver)"
},
ProblemType::MixedSeparable { .. } => {
"Independent optimization of float and integer parts"
},
ProblemType::MixedCoupled { coupling_strength: CouplingStrength::Linear, .. } => {
"MINLP with linear coupling (branch-and-bound + interval arithmetic)"
},
ProblemType::MixedCoupled { coupling_strength: CouplingStrength::Nonlinear, .. } => {
"MINLP with nonlinear coupling (advanced branch-and-bound)"
},
ProblemType::MixedCoupled { coupling_strength: CouplingStrength::Complex, .. } => {
"Complex MINLP (sophisticated branch-and-bound techniques)"
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::Model;
fn create_test_vars() -> Vars {
Vars::new()
}
#[test]
fn test_pure_float_classification() {
// Create a model to properly add variables
let mut m = Model::default();
let _var_id = m.float(1.0, 10.0);
let problem_type = ProblemClassifier::classify(m.get_vars(), m.get_props());
match problem_type {
ProblemType::PureFloat { float_var_count: 1, has_linear_bounds_only: true } => {
// Correct classification
},
_ => panic!("Expected PureFloat classification, got {:?}", problem_type),
}
assert!(problem_type.can_use_efficient_float_optimization());
assert!(!problem_type.requires_integer_search());
}
#[test]
fn test_pure_integer_classification() {
let mut m = Model::default();
let _var_id = m.int(1, 10);
let problem_type = ProblemClassifier::classify(m.get_vars(), m.get_props());
match problem_type {
ProblemType::PureInteger { integer_var_count: 1 } => {
// Correct classification
},
_ => panic!("Expected PureInteger classification, got {:?}", problem_type),
}
assert!(!problem_type.can_use_efficient_float_optimization());
assert!(problem_type.requires_integer_search());
}
#[test]
fn test_mixed_separable_classification() {
let mut m = Model::default();
let _int_var = m.int(1, 5);
let _float_var = m.float(1.0, 10.0);
let problem_type = ProblemClassifier::classify(m.get_vars(), m.get_props());
// With no constraints, should be classified as separable
match problem_type {
ProblemType::MixedSeparable { integer_var_count: 1, float_var_count: 1 } => {
// Correct classification
},
_ => panic!("Expected MixedSeparable classification, got {:?}", problem_type),
}
assert!(problem_type.can_use_efficient_float_optimization());
assert!(!problem_type.requires_integer_search());
}
#[test]
fn test_strategy_descriptions() {
let pure_float = ProblemType::PureFloat {
float_var_count: 1,
has_linear_bounds_only: true,
};
let description = pure_float.strategy_description();
assert!(description.contains("O(1)"));
assert!(description.contains("analytical"));
let pure_integer = ProblemType::PureInteger { integer_var_count: 1 };
let description = pure_integer.strategy_description();
assert!(description.contains("Binary search"));
assert!(description.contains("current solver"));
}
#[test]
fn test_variable_analysis() {
let vars = create_test_vars();
// Test empty variables
let analysis = ProblemClassifier::analyze_variables(&vars);
assert_eq!(analysis.integer_count, 0);
assert_eq!(analysis.float_count, 0);
// Test with actual variables from a model
let mut m = Model::default();
let _int_var1 = m.int(1, 5);
let _int_var2 = m.int(1, 3);
let _float_var = m.float(1.0, 10.0);
let analysis = ProblemClassifier::analyze_variables(m.get_vars());
assert_eq!(analysis.integer_count, 2);
assert_eq!(analysis.float_count, 1);
}
}