1use pounce_common::types::{NLP_LOWER_BOUND_INF, NLP_UPPER_BOUND_INF};
14use pounce_common::Number;
15use pounce_qp::{BoundStatus, ConsStatus, WorkingSet};
16
17#[allow(clippy::too_many_arguments)]
48pub fn classify_working_set(
49 lambda_x: &[Number],
50 lambda_g: &[Number],
51 m_eq: usize,
52 x: &[Number],
53 x_l: &[Number],
54 x_u: &[Number],
55 g: &[Number],
56 g_l: &[Number],
57 g_u: &[Number],
58 mult_tol: Number,
59 primal_tol: Number,
60) -> WorkingSet {
61 let n = lambda_x.len();
62 let m = lambda_g.len();
63 debug_assert_eq!(x.len(), n);
64 debug_assert_eq!(x_l.len(), n);
65 debug_assert_eq!(x_u.len(), n);
66 debug_assert_eq!(g.len(), m);
67 debug_assert_eq!(g_l.len(), m);
68 debug_assert_eq!(g_u.len(), m);
69 debug_assert!(m_eq <= m);
70
71 let mut bounds = Vec::with_capacity(n);
77 for i in 0..n {
78 let lo_fin = x_l[i] > NLP_LOWER_BOUND_INF;
79 let up_fin = x_u[i] < NLP_UPPER_BOUND_INF;
80 if lo_fin && up_fin && (x_u[i] - x_l[i]).abs() < primal_tol {
81 bounds.push(BoundStatus::Fixed);
82 continue;
83 }
84 let mu = lambda_x[i];
85 let at_lo = lo_fin && (x[i] - x_l[i]).abs() < primal_tol;
86 let at_up = up_fin && (x_u[i] - x[i]).abs() < primal_tol;
87 let status = if mu > mult_tol && at_lo {
88 BoundStatus::AtLower
89 } else if mu < -mult_tol && at_up {
90 BoundStatus::AtUpper
91 } else if at_lo && mu >= 0.0 {
92 BoundStatus::AtLower
93 } else if at_up && mu <= 0.0 {
94 BoundStatus::AtUpper
95 } else {
96 BoundStatus::Inactive
97 };
98 bounds.push(status);
99 }
100
101 let mut constraints = Vec::with_capacity(m);
102 for i in 0..m {
103 if i < m_eq {
104 constraints.push(ConsStatus::Equality);
105 continue;
106 }
107 let lo_fin = g_l[i] > NLP_LOWER_BOUND_INF;
108 let up_fin = g_u[i] < NLP_UPPER_BOUND_INF;
109 if lo_fin && up_fin && (g_u[i] - g_l[i]).abs() < primal_tol {
110 constraints.push(ConsStatus::Equality);
111 continue;
112 }
113 let mu = lambda_g[i];
114 let at_lo = lo_fin && (g[i] - g_l[i]).abs() < primal_tol;
115 let at_up = up_fin && (g_u[i] - g[i]).abs() < primal_tol;
116 let status = if mu > mult_tol && at_lo {
117 ConsStatus::AtLower
118 } else if mu < -mult_tol && at_up {
119 ConsStatus::AtUpper
120 } else if at_lo && mu >= 0.0 {
121 ConsStatus::AtLower
122 } else if at_up && mu <= 0.0 {
123 ConsStatus::AtUpper
124 } else {
125 ConsStatus::Inactive
126 };
127 constraints.push(status);
128 }
129
130 WorkingSet {
131 bounds,
132 constraints,
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn classify_treats_nlp_bound_inf_sentinel_as_unbounded() {
142 let ws = classify_working_set(
148 &[0.0],
149 &[],
150 0,
151 &[-1.0e19],
152 &[NLP_LOWER_BOUND_INF],
153 &[NLP_UPPER_BOUND_INF],
154 &[],
155 &[],
156 &[],
157 1e-8,
158 1e-6,
159 );
160 assert_eq!(ws.bounds[0], BoundStatus::Inactive);
161 }
162
163 #[test]
164 fn classify_all_inactive_when_strictly_interior() {
165 let ws = classify_working_set(
167 &[0.0],
168 &[],
169 0,
170 &[0.5],
171 &[-1.0],
172 &[1.0],
173 &[],
174 &[],
175 &[],
176 1e-8,
177 1e-8,
178 );
179 assert_eq!(ws.bounds[0], BoundStatus::Inactive);
180 assert!(ws.constraints.is_empty());
181 }
182
183 #[test]
184 fn classify_lower_bound_active_when_primal_at_bound_and_mult_positive() {
185 let ws = classify_working_set(
186 &[2.0],
187 &[],
188 0,
189 &[0.0],
190 &[0.0],
191 &[1.0],
192 &[],
193 &[],
194 &[],
195 1e-8,
196 1e-8,
197 );
198 assert_eq!(ws.bounds[0], BoundStatus::AtLower);
199 }
200
201 #[test]
202 fn classify_upper_bound_active_when_primal_at_bound_and_mult_negative() {
203 let ws = classify_working_set(
204 &[-2.0],
205 &[],
206 0,
207 &[1.0],
208 &[0.0],
209 &[1.0],
210 &[],
211 &[],
212 &[],
213 1e-8,
214 1e-8,
215 );
216 assert_eq!(ws.bounds[0], BoundStatus::AtUpper);
217 }
218
219 #[test]
220 fn classify_fixed_when_bounds_equal() {
221 let ws = classify_working_set(
222 &[0.0],
223 &[],
224 0,
225 &[2.0],
226 &[2.0],
227 &[2.0],
228 &[],
229 &[],
230 &[],
231 1e-8,
232 1e-8,
233 );
234 assert_eq!(ws.bounds[0], BoundStatus::Fixed);
235 }
236
237 #[test]
238 fn classify_equality_constraint_always_active() {
239 let ws = classify_working_set(
241 &[],
242 &[1.0],
243 1,
244 &[],
245 &[],
246 &[],
247 &[5.0],
248 &[5.0],
249 &[5.0],
250 1e-8,
251 1e-8,
252 );
253 assert_eq!(ws.constraints[0], ConsStatus::Equality);
254 }
255
256 #[test]
257 fn classify_inequality_at_lower_bound() {
258 let ws = classify_working_set(
259 &[],
260 &[3.0],
261 0,
262 &[],
263 &[],
264 &[],
265 &[1.0],
266 &[1.0],
267 &[10.0],
268 1e-8,
269 1e-8,
270 );
271 assert_eq!(ws.constraints[0], ConsStatus::AtLower);
272 }
273
274 #[test]
275 fn classify_inequality_at_upper_bound() {
276 let ws = classify_working_set(
277 &[],
278 &[-3.0],
279 0,
280 &[],
281 &[],
282 &[],
283 &[10.0],
284 &[0.0],
285 &[10.0],
286 1e-8,
287 1e-8,
288 );
289 assert_eq!(ws.constraints[0], ConsStatus::AtUpper);
290 }
291
292 #[test]
293 fn classify_inactive_when_primal_off_bound_despite_large_multiplier() {
294 let ws = classify_working_set(
299 &[2.0],
300 &[],
301 0,
302 &[0.5],
303 &[0.0],
304 &[1.0],
305 &[],
306 &[],
307 &[],
308 1e-8,
309 1e-8,
310 );
311 assert_eq!(ws.bounds[0], BoundStatus::Inactive);
312 }
313}