@@ -471,6 +471,9 @@ impl FloatLinEq {
471471
472472impl Prune for FloatLinEq {
473473 fn prune ( & self , ctx : & mut Context ) -> Option < ( ) > {
474+ // DEBUG: Enable for debugging
475+ let debug = std:: env:: var ( "DEBUG_FLOAT_LIN" ) . is_ok ( ) ;
476+
474477 for i in 0 ..self . variables . len ( ) {
475478 let var_id = self . variables [ i] ;
476479 let coeff = self . coefficients [ i] ;
@@ -520,14 +523,78 @@ impl Prune for FloatLinEq {
520523 let target_min = self . constant - max_other;
521524 let target_max = self . constant - min_other;
522525
523- let ( new_min, new_max) = if coeff > 0.0 {
526+ let ( mut new_min, mut new_max) = if coeff > 0.0 {
524527 ( target_min / coeff, target_max / coeff)
525528 } else {
526529 ( target_max / coeff, target_min / coeff)
527530 } ;
528531
529- var_id. try_set_min ( Val :: ValF ( new_min) , ctx) ?;
530- var_id. try_set_max ( Val :: ValF ( new_max) , ctx) ?;
532+ // Handle floating-point rounding: ensure new_min <= new_max
533+ // When constraints are tight (equality), new_min and new_max should be nearly equal
534+ // but may differ slightly due to floating-point arithmetic
535+ if new_min > new_max {
536+ if debug {
537+ eprintln ! ( "DEBUG: FloatLinEq swapping bounds: new_min={} > new_max={}" , new_min, new_max) ;
538+ }
539+ // Swap if they're reversed due to rounding errors
540+ std:: mem:: swap ( & mut new_min, & mut new_max) ;
541+ }
542+
543+ // FIX: Clamp computed bounds to current bounds to handle accumulated precision errors
544+ // When propagating tight equality constraints, the computed bounds may slightly
545+ // violate the current bounds due to cascading precision errors from previous propagations
546+ let current_min = match var_id. min ( ctx) {
547+ Val :: ValF ( f) => f,
548+ Val :: ValI ( i) => i as f64 ,
549+ _ => new_min, // Fallback to computed value if type mismatch
550+ } ;
551+ let current_max = match var_id. max ( ctx) {
552+ Val :: ValF ( f) => f,
553+ Val :: ValI ( i) => i as f64 ,
554+ _ => new_max, // Fallback to computed value if type mismatch
555+ } ;
556+
557+ // Only apply clamping if the difference is small (precision error, not real infeasibility)
558+ let tolerance = 1e-6 ;
559+ if new_max < current_min && ( current_min - new_max) < tolerance {
560+ new_max = current_min;
561+ }
562+ if new_min > current_max && ( new_min - current_max) < tolerance {
563+ new_min = current_max;
564+ }
565+
566+ if debug {
567+ let cur_min = var_id. min ( ctx) ;
568+ let cur_max = var_id. max ( ctx) ;
569+ eprintln ! ( "DEBUG: FloatLinEq var {:?}: current=[{:?}, {:?}], setting=[{}, {}]" ,
570+ var_id, cur_min, cur_max, new_min, new_max) ;
571+ }
572+
573+ // FIX: If variable is already fixed and computed min is close to current value,
574+ // skip update to avoid cascading precision errors. A fixed variable shouldn't
575+ // be perturbed by small precision errors in back-propagation.
576+ let is_fixed = ( current_max - current_min) . abs ( ) < 1e-9 ;
577+ let min_close = ( new_min - current_min) . abs ( ) < 1e-4 ;
578+ if is_fixed && min_close {
579+ // Variable already at the right value, no update needed
580+ if debug {
581+ eprintln ! ( "DEBUG: FloatLinEq skipping update for fixed var {:?} (current={}, new_min={})" ,
582+ var_id, current_min, new_min) ;
583+ }
584+ continue ;
585+ }
586+
587+ let min_result = var_id. try_set_min ( Val :: ValF ( new_min) , ctx) ;
588+ if debug && min_result. is_none ( ) {
589+ eprintln ! ( "DEBUG: FloatLinEq FAILED on try_set_min({}) for var {:?}" , new_min, var_id) ;
590+ }
591+ min_result?;
592+
593+ let max_result = var_id. try_set_max ( Val :: ValF ( new_max) , ctx) ;
594+ if debug && max_result. is_none ( ) {
595+ eprintln ! ( "DEBUG: FloatLinEq FAILED on try_set_max({}) for var {:?}" , new_max, var_id) ;
596+ }
597+ max_result?;
531598 }
532599
533600 Some ( ( ) )
@@ -604,10 +671,27 @@ impl Prune for FloatLinLe {
604671
605672 if coeff > 0.0 {
606673 let max_val = remaining / coeff;
607- var_id. try_set_max ( Val :: ValF ( max_val) , ctx) ?;
674+ // Only tighten if the new bound is finite and improves current bound
675+ if max_val. is_finite ( ) {
676+ if let Val :: ValF ( current_max) = var_id. max ( ctx) {
677+ if max_val < current_max {
678+ var_id. try_set_max ( Val :: ValF ( max_val) , ctx) ?;
679+ }
680+ }
681+ }
608682 } else {
609683 let min_val = remaining / coeff;
610- var_id. try_set_min ( Val :: ValF ( min_val) , ctx) ?;
684+ // Normalize -0.0 to 0.0 to avoid negative zero artifacts
685+ // This can occur when remaining=0.0 and coeff<0, giving 0.0/-1.0 = -0.0
686+ let normalized_min = if min_val == 0.0 { 0.0 } else { min_val } ;
687+ // Only tighten if the new bound is finite and improves current bound
688+ if normalized_min. is_finite ( ) {
689+ if let Val :: ValF ( current_min) = var_id. min ( ctx) {
690+ if normalized_min > current_min {
691+ var_id. try_set_min ( Val :: ValF ( normalized_min) , ctx) ?;
692+ }
693+ }
694+ }
611695 }
612696 }
613697
@@ -1218,12 +1302,46 @@ fn prune_float_lin_eq(coefficients: &[f64], variables: &[VarId], constant: f64,
12181302 let target_min = constant - max_other;
12191303 let target_max = constant - min_other;
12201304
1221- let ( new_min, new_max) = if coeff > 0.0 {
1305+ let ( mut new_min, mut new_max) = if coeff > 0.0 {
12221306 ( target_min / coeff, target_max / coeff)
12231307 } else {
12241308 ( target_max / coeff, target_min / coeff)
12251309 } ;
12261310
1311+ // Handle floating-point rounding: ensure new_min <= new_max
1312+ if new_min > new_max {
1313+ std:: mem:: swap ( & mut new_min, & mut new_max) ;
1314+ }
1315+
1316+ // FIX: Clamp computed bounds to current bounds to handle accumulated precision errors
1317+ let current_min = match var_id. min ( ctx) {
1318+ Val :: ValF ( f) => f,
1319+ Val :: ValI ( i) => i as f64 ,
1320+ _ => new_min,
1321+ } ;
1322+ let current_max = match var_id. max ( ctx) {
1323+ Val :: ValF ( f) => f,
1324+ Val :: ValI ( i) => i as f64 ,
1325+ _ => new_max,
1326+ } ;
1327+
1328+ let tolerance = 1e-6 ;
1329+ if new_max < current_min && ( current_min - new_max) < tolerance {
1330+ new_max = current_min;
1331+ }
1332+ if new_min > current_max && ( new_min - current_max) < tolerance {
1333+ new_min = current_max;
1334+ }
1335+
1336+ // FIX: If variable is already fixed and computed min is close to current value,
1337+ // skip update to avoid cascading precision errors in constraint chains
1338+ let is_fixed = ( current_max - current_min) . abs ( ) < 1e-9 ;
1339+ let min_close = ( new_min - current_min) . abs ( ) < 1e-4 ;
1340+ if is_fixed && min_close {
1341+ // Variable already at the right value, no update needed
1342+ continue ;
1343+ }
1344+
12271345 var_id. try_set_min ( Val :: ValF ( new_min) , ctx) ?;
12281346 var_id. try_set_max ( Val :: ValF ( new_max) , ctx) ?;
12291347 }
0 commit comments