@@ -22,20 +22,59 @@ impl<U: View, V: View> Prune for Modulo<U, V> {
2222 let x_max = self . x . max ( ctx) ;
2323 let y_min = self . y . min ( ctx) ;
2424 let y_max = self . y . max ( ctx) ;
25+ let s_min = self . s . min ( ctx) ;
26+ let s_max = self . s . max ( ctx) ;
2527
2628 // If y contains zero or values too close to zero, we can't safely compute modulo
2729 if Val :: range_contains_unsafe_divisor ( y_min, y_max) {
2830 // We can still try to propagate some constraints if parts of the domain are safe
2931 return Some ( ( ) ) ;
3032 }
3133
32- // Calculate possible modulo results
33- let mut s_candidates = Vec :: new ( ) ;
34-
35- // For modulo, the result is always in range [0, |y|-1] for positive y
36- // and [-|y|+1, 0] for negative y, but we need to be more careful with mixed signs
34+ // CASE 1: Both x and y are fixed → exact computation
35+ if x_min == x_max && y_min == y_max {
36+ if let Some ( exact_result) = x_min. safe_mod ( y_min) {
37+ // Set s to this exact value
38+ self . s . try_set_min ( exact_result, ctx) ?;
39+ self . s . try_set_max ( exact_result, ctx) ?;
40+ return Some ( ( ) ) ;
41+ }
42+ }
43+
44+ // CASE 2: y is fixed (and non-zero) → compute s bounds based on x range
45+ if y_min == y_max {
46+ if let Val :: ValI ( y_val) = y_min {
47+ if y_val != 0 {
48+ // For modulo: s is in range [0, |y|-1] when y > 0
49+ // or [-(|y|-1), 0] when y < 0
50+ if y_val > 0 {
51+ let s_theoretical_min = Val :: ValI ( 0 ) ;
52+ let s_theoretical_max = Val :: ValI ( y_val - 1 ) ;
53+
54+ let new_s_min = if s_theoretical_min > s_min { s_theoretical_min } else { s_min } ;
55+ let new_s_max = if s_theoretical_max < s_max { s_theoretical_max } else { s_max } ;
56+
57+ self . s . try_set_min ( new_s_min, ctx) ?;
58+ self . s . try_set_max ( new_s_max, ctx) ?;
59+ } else {
60+ // y_val < 0
61+ let s_theoretical_min = Val :: ValI ( y_val + 1 ) ;
62+ let s_theoretical_max = Val :: ValI ( 0 ) ;
63+
64+ let new_s_min = if s_theoretical_min > s_min { s_theoretical_min } else { s_min } ;
65+ let new_s_max = if s_theoretical_max < s_max { s_theoretical_max } else { s_max } ;
66+
67+ self . s . try_set_min ( new_s_min, ctx) ?;
68+ self . s . try_set_max ( new_s_max, ctx) ?;
69+ }
70+ }
71+ }
72+ }
73+
74+ // CASE 3: Both x and y are in bounded ranges → compute s bounds
75+ let mut s_candidates = Vec :: with_capacity ( 4 ) ;
3776
38- // Sample points at domain boundaries and some intermediate values
77+ // Sample points at domain boundaries
3978 let x_samples = if x_min == x_max {
4079 vec ! [ x_min]
4180 } else {
@@ -52,7 +91,7 @@ impl<U: View, V: View> Prune for Modulo<U, V> {
5291 for & x_val in & x_samples {
5392 for & y_val in & y_samples {
5493 if let Some ( mod_result) = x_val. safe_mod ( y_val) {
55- // Check if the result is not NaN or infinite
94+ // Check if the result is valid
5695 match mod_result {
5796 Val :: ValF ( f) if f. is_finite ( ) => s_candidates. push ( mod_result) ,
5897 Val :: ValI ( _) => s_candidates. push ( mod_result) ,
@@ -64,70 +103,52 @@ impl<U: View, V: View> Prune for Modulo<U, V> {
64103
65104 if !s_candidates. is_empty ( ) {
66105 // Find bounds for s based on modulo properties
67- let s_min = s_candidates. iter ( ) . fold ( s_candidates[ 0 ] , |acc, & x| if x < acc { x } else { acc } ) ;
68- let s_max = s_candidates. iter ( ) . fold ( s_candidates[ 0 ] , |acc, & x| if x > acc { x } else { acc } ) ;
69-
70- // For modulo, we know more about the bounds:
71- // If y > 0: 0 <= s < y
72- // If y < 0: y < s <= 0
73- // We can use this to tighten bounds further
74- let y_abs_min = match ( y_min, y_max) {
75- ( Val :: ValI ( min_i) , Val :: ValI ( max_i) ) => {
76- if min_i > 0 { Some ( Val :: ValI ( 0 ) ) }
77- else if max_i < 0 { Some ( Val :: ValI ( max_i + 1 ) ) }
78- else { None }
79- } ,
80- ( Val :: ValF ( min_f) , Val :: ValF ( max_f) ) => {
81- if min_f > 0.0 { Some ( Val :: ValF ( 0.0 ) ) }
82- else if max_f < 0.0 { Some ( Val :: ValF ( max_f + 1.0 ) ) }
83- else { None }
84- } ,
85- _ => None ,
86- } ;
106+ let s_computed_min = s_candidates. iter ( ) . fold ( s_candidates[ 0 ] , |acc, & x| if x < acc { x } else { acc } ) ;
107+ let s_computed_max = s_candidates. iter ( ) . fold ( s_candidates[ 0 ] , |acc, & x| if x > acc { x } else { acc } ) ;
87108
88- let y_abs_max = match ( y_min, y_max) {
89- ( Val :: ValI ( min_i) , Val :: ValI ( max_i) ) => {
90- if min_i > 0 { Some ( Val :: ValI ( max_i - 1 ) ) }
91- else if max_i < 0 { Some ( Val :: ValI ( 0 ) ) }
92- else { None }
93- } ,
94- ( Val :: ValF ( min_f) , Val :: ValF ( max_f) ) => {
95- if min_f > 0.0 { Some ( Val :: ValF ( max_f - f64:: EPSILON ) ) }
96- else if max_f < 0.0 { Some ( Val :: ValF ( 0.0 ) ) }
97- else { None }
98- } ,
99- _ => None ,
100- } ;
101-
102- // Use the tighter bounds if available
103- let final_s_min = if let Some ( theoretical_min) = y_abs_min {
104- if theoretical_min > s_min { theoretical_min } else { s_min }
105- } else { s_min } ;
106-
107- let final_s_max = if let Some ( theoretical_max) = y_abs_max {
108- if theoretical_max < s_max { theoretical_max } else { s_max }
109- } else { s_max } ;
110-
111- // Propagate bounds to s
112- let _min = self . s . try_set_min ( final_s_min, ctx) ?;
113- let _max = self . s . try_set_max ( final_s_max, ctx) ?;
109+ // CRITICAL FIX: Allow expansion if current domain is too narrow
110+ // This can happen when result variable was created before deferred constraints applied
111+ // and those deferred constraints now require larger modulo values.
112+ // We must try to set the bounds, and if it fails, return None (fail the space)
113+ self . s . try_set_min ( s_computed_min, ctx) ?;
114+ self . s . try_set_max ( s_computed_max, ctx) ?;
114115 }
115-
116- // Back-propagation is complex for modulo, so we do limited propagation
117- // We can at least ensure that if s is known and y is known, we can constrain x
118- let s_min = self . s . min ( ctx) ;
119- let s_max = self . s . max ( ctx) ;
120-
121- // If y and s are both fixed, we can derive some constraints on x
116+
117+ // CASE 4: Back-propagation from s to x (when y and s are fixed)
122118 if y_min == y_max && s_min == s_max {
123- // x = k * y + s for some integer k
124- // We need to find valid values of k such that x is in its domain
125- let y_val = y_min;
126- let s_val = s_min;
127-
128- if let ( Some ( _) , Some ( _) ) = ( y_val. safe_div ( Val :: ValI ( 1 ) ) , s_val. safe_div ( Val :: ValI ( 1 ) ) ) {
129- // For now, we don't do complex back-propagation for modulo
130- // This would require more sophisticated interval arithmetic
119+ if let ( Val :: ValI ( y_val) , Val :: ValI ( s_val) ) = ( y_min, s_min) {
120+ if y_val != 0 && s_val >= 0 && s_val < y_val. abs ( ) {
121+ // x = k * y + s for some integer k
122+ // We need to find the range of k such that x remains in bounds
123+ let x_current_min = x_min;
124+ let x_current_max = x_max;
125+
126+ // Find the minimum and maximum k
127+ let mut valid_x_values = Vec :: with_capacity ( 8 ) ;
128+
129+ if let ( Val :: ValI ( x_curr_min) , Val :: ValI ( x_curr_max) ) = ( x_current_min, x_current_max) {
130+ // Try k values that produce x in the valid range
131+ let k_min_theoretical = ( x_curr_min - s_val) / y_val;
132+ let k_max_theoretical = ( x_curr_max - s_val) / y_val;
133+
134+ // Try a range around these theoretical k values
135+ for k in ( k_min_theoretical - 1 ) ..=( k_max_theoretical + 1 ) {
136+ let candidate_x = k * y_val + s_val;
137+ if candidate_x >= x_curr_min && candidate_x <= x_curr_max {
138+ valid_x_values. push ( Val :: ValI ( candidate_x) ) ;
139+ }
140+ }
141+
142+ if !valid_x_values. is_empty ( ) {
143+ let new_x_min = valid_x_values. iter ( ) . fold ( valid_x_values[ 0 ] , |acc, & x| if x < acc { x } else { acc } ) ;
144+ let new_x_max = valid_x_values. iter ( ) . fold ( valid_x_values[ 0 ] , |acc, & x| if x > acc { x } else { acc } ) ;
145+
146+ // Try to tighten x bounds
147+ self . x . try_set_min ( new_x_min, ctx) ?;
148+ self . x . try_set_max ( new_x_max, ctx) ?;
149+ }
150+ }
151+ }
131152 }
132153 }
133154
0 commit comments