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
//! Precision-Aware Constraint Boundary Optimization
//!
//! This module addresses the precision gap in constrained optimization by using
//! constraint metadata to implement ULP-aware boundary handling for floating-point
//! constraints. It ensures that constraints like x < 5.5 properly respect
//! floating-point precision and allow values as close as possible to the boundary.
use crate::variables::{VarId, Vars};
use crate::optimization::constraint_metadata::{
ConstraintRegistry, VariableConstraintAnalysis
};
use crate::optimization::ulp_utils::UlpUtils;
use std::collections::HashMap;
/// Precision-aware constraint optimizer
pub struct PrecisionOptimizer {
/// Step size for floating-point domain discretization
step_size: f64,
/// Cache for computed precision-aware bounds
bound_cache: HashMap<VarId, PrecisionBounds>,
}
/// Precision-aware bounds for a variable
#[derive(Debug, Clone)]
pub struct PrecisionBounds {
/// Effective upper bound considering all constraints and precision
pub upper_bound: Option<f64>,
/// Effective lower bound considering all constraints and precision
pub lower_bound: Option<f64>,
/// Whether bounds have been adjusted for precision
pub precision_adjusted: bool,
/// Original constraint bounds before precision adjustment
pub original_upper: Option<f64>,
pub original_lower: Option<f64>,
}
impl PrecisionOptimizer {
/// Create a new precision optimizer with the given step size
pub fn new(step_size: f64) -> Self {
Self {
step_size,
bound_cache: HashMap::new(),
}
}
/// Optimize variable bounds using constraint metadata for precision-aware handling
pub fn optimize_bounds(
&mut self,
var_id: VarId,
registry: &ConstraintRegistry,
vars: &Vars,
) -> Result<PrecisionBounds, String> {
// Check cache first
if let Some(cached_bounds) = self.bound_cache.get(&var_id) {
return Ok(cached_bounds.clone());
}
// Analyze constraints for this variable
let analysis = registry.analyze_variable_constraints(var_id);
// Compute precision-aware bounds
let bounds = self.compute_precision_bounds(var_id, &analysis, vars)?;
// Cache the result
self.bound_cache.insert(var_id, bounds.clone());
Ok(bounds)
}
/// Compute precision-aware bounds from constraint analysis
fn compute_precision_bounds(
&self,
var_id: VarId,
analysis: &VariableConstraintAnalysis,
vars: &Vars,
) -> Result<PrecisionBounds, String> {
// Get current variable domain
let var_domain = &vars[var_id];
let (current_min, current_max) = match var_domain {
crate::variables::Var::VarF(interval) => (interval.min, interval.max),
crate::variables::Var::VarI(sparse_set) => {
let min_val = sparse_set.min();
let max_val = sparse_set.max();
(min_val as f64, max_val as f64)
}
};
// Start with current bounds
let mut lower_bound = Some(current_min);
let mut upper_bound = Some(current_max);
let mut precision_adjusted = false;
// Process constraint-derived bounds
let constraint_upper = analysis.get_effective_upper_bound(self.step_size);
let constraint_lower = analysis.get_effective_lower_bound(self.step_size);
// Apply constraint bounds while considering precision
if let Some(constraint_max) = constraint_upper {
let precision_max = if analysis.strict_upper_bounds.iter().any(|&b| (b - constraint_max).abs() < f64::EPSILON) {
// This is a strict upper bound (x < constraint_max)
UlpUtils::strict_upper_bound(constraint_max)
} else {
// This is a non-strict upper bound (x <= constraint_max)
constraint_max
};
upper_bound = Some(upper_bound.map_or(precision_max, |current| current.min(precision_max)));
if (precision_max - constraint_max).abs() > f64::EPSILON {
precision_adjusted = true;
}
}
if let Some(constraint_min) = constraint_lower {
let precision_min = if analysis.strict_lower_bounds.iter().any(|&b| (b - constraint_min).abs() < f64::EPSILON) {
// This is a strict lower bound (x > constraint_min)
UlpUtils::strict_lower_bound(constraint_min)
} else {
// This is a non-strict lower bound (x >= constraint_min)
constraint_min
};
lower_bound = Some(lower_bound.map_or(precision_min, |current| current.max(precision_min)));
if (precision_min - constraint_min).abs() > f64::EPSILON {
precision_adjusted = true;
}
}
// Handle equality constraints with precision
if !analysis.equality_values.is_empty() {
// For equality constraints, use the constraint value exactly
let eq_value = analysis.equality_values[0]; // Take first equality value
lower_bound = Some(eq_value);
upper_bound = Some(eq_value);
precision_adjusted = false; // Equality constraints are exact
}
Ok(PrecisionBounds {
upper_bound,
lower_bound,
precision_adjusted,
original_upper: constraint_upper,
original_lower: constraint_lower,
})
}
/// Apply precision-aware bounds to a variable's domain
/// Note: This is a conceptual method - actual domain modification requires
/// access to mutable variable structures which aren't available here
pub fn get_optimized_bounds(
&self,
_var_id: VarId,
bounds: &PrecisionBounds,
) -> Result<(Option<f64>, Option<f64>), String> {
if bounds.lower_bound.is_none() && bounds.upper_bound.is_none() {
return Ok((None, None)); // No changes needed
}
Ok((bounds.lower_bound, bounds.upper_bound))
}
/// Get step size used by this optimizer
pub fn get_step_size(&self) -> f64 {
self.step_size
}
/// Clear the bound cache (useful when constraints change)
pub fn clear_cache(&mut self) {
self.bound_cache.clear();
}
/// Get optimization statistics
pub fn get_stats(&self) -> OptimizationStats {
let precision_adjusted_count = self.bound_cache.values()
.filter(|bounds| bounds.precision_adjusted)
.count();
OptimizationStats {
total_variables_optimized: self.bound_cache.len(),
precision_adjusted_variables: precision_adjusted_count,
cache_size: self.bound_cache.len(),
}
}
}
/// Statistics about precision optimization
#[derive(Debug, Clone)]
pub struct OptimizationStats {
/// Total number of variables that have been optimized
pub total_variables_optimized: usize,
/// Number of variables where precision adjustments were made
pub precision_adjusted_variables: usize,
/// Current size of the bound cache
pub cache_size: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ulp_utils() {
// Test ULP calculation
let value = 5.5;
let ulp = UlpUtils::ulp(value);
assert!(ulp > 0.0);
assert!(ulp < 1e-10); // Should be very small for this value range
// Test strict bounds
let strict_upper = UlpUtils::strict_upper_bound(5.5);
assert!(strict_upper < 5.5);
assert!(strict_upper > 5.4); // Should be very close to 5.5
let strict_lower = UlpUtils::strict_lower_bound(5.5);
assert!(strict_lower > 5.5);
assert!(strict_lower < 5.6); // Should be very close to 5.5
}
#[test]
fn test_precision_bounds() {
let mut optimizer = PrecisionOptimizer::new(1e-10);
// Test step size
assert_eq!(optimizer.get_step_size(), 1e-10);
// Test cache operations
optimizer.clear_cache();
let stats = optimizer.get_stats();
assert_eq!(stats.cache_size, 0);
assert_eq!(stats.total_variables_optimized, 0);
}
}