Skip to main content

numopt/model/
model_std.rs

1//! Structures and traits for transforming optimization models to standard form.
2
3use std::collections::{HashSet, HashMap};
4
5use crate::matrix::coo::CooMat;
6use crate::problem::base::Problem;
7use crate::problem::minlp::ProblemMinlp;
8use crate::problem::milp::ProblemMilp;
9use crate::problem::nlp::ProblemNlp;
10use crate::problem::lp::ProblemLp;
11
12use crate::model::node::Node;
13use crate::model::node_base::NodeBase;
14use crate::model::node_std::{NodeStd, NodeStdComp};
15use crate::model::constant::ConstantScalar;
16use crate::model::constraint::Constraint;
17use crate::model::constraint_std::{ConstraintStd, ConstraintStdComp};
18use crate::model::model::{Model, Objective};
19
20const INF: f64 = 1e8;
21
22/// Optimization model standard components.
23pub struct ModelStdComp {
24
25    /// Standard components of objective expression.
26    pub obj: NodeStdComp,
27
28    /// Standard components of constraints.
29    pub constr: ConstraintStdComp,
30}
31
32/// Optimization model standard problem.
33pub struct ModelStdProb {
34
35    /// Problem in standard form.
36    pub prob: Problem,
37
38    /// Map between optimization model variables and their index
39    /// in the vector of variables for the problem in standard form.
40    pub var2index: HashMap<Node, usize>,
41
42    /// Map between linear equality constraint row and model constraint.
43    pub aindex2constr: HashMap<usize, Constraint>,
44    
45    /// Map between nonlinear equality constraint row and model constraint.
46    pub jindex2constr: HashMap<usize, Constraint>,
47
48    /// Map between variable upper limit and model constraint.
49    pub uindex2constr: HashMap<usize, Constraint>,
50
51    /// Map between variable lower limit and model constraint.
52    pub lindex2constr: HashMap<usize, Constraint>,
53}
54
55/// A trait for transforming optimization models to problems in standard form.
56pub trait ModelStd {
57
58    /// Obtains standard components of model.
59    fn std_components(&self) -> ModelStdComp;
60
61    /// Obtains standard problem of model.
62    fn std_problem(&self) -> ModelStdProb;
63}
64
65impl ModelStd for Model {
66
67    fn std_components(&self) -> ModelStdComp {
68
69        // Objective std comp
70        let obj = match self.objective() {
71            Objective::Maximize(f) => (-f).std_components(),
72            Objective::Minimize(f) => f.std_components(),
73            Objective::Empty => ConstantScalar::new(0.).std_components(),
74        };
75
76        // Constraint std comp
77        let mut arow: usize = 0;
78        let mut jrow: usize = 0;
79        let mut constr = ConstraintStdComp::new();
80        for c in self.constraints().iter() {
81            constr += c.std_components(&mut arow, &mut jrow);
82        }
83
84        // Return
85        ModelStdComp {
86            obj: obj,
87            constr: constr,
88        }
89    }
90
91    fn std_problem(&self) -> ModelStdProb {
92
93        // Components
94        let comp = self.std_components();
95
96        // Variables
97        let mut varset: HashSet<Node> = comp.obj.prop.a.keys()
98                                                       .map(|x| x.clone())
99                                                       .collect();
100        for p in comp.constr.prop.iter() {
101            varset.extend(p.a.keys().map(|x| x.clone()));
102        }
103        let num_vars: usize = varset.len();
104        let mut vars: Vec<Node> = varset.into_iter().collect();
105        vars.sort_by(|x,y| x.name().partial_cmp(y.name()).unwrap());
106        let var2index: HashMap<Node, usize> = vars.into_iter()
107                                                  .enumerate()
108                                                  .map(|(i,v)| (v,i))
109                                                  .collect();
110        let var2index_eval: HashMap<Node, usize> = var2index.iter()
111                                                            .map(|(v,i)| (v.clone(), *i))
112                                                            .collect();
113    
114        // Objective (phi)
115        let phi_data = comp.obj.phi;
116        let mut gphi_indices: Vec<usize> = Vec::with_capacity(comp.obj.gphi.len());
117        let mut gphi_data: Vec<Node> = Vec::with_capacity(comp.obj.gphi.len());
118        for (v, e) in comp.obj.gphi.into_iter() {
119            gphi_indices.push(*var2index.get(&v).unwrap());
120            gphi_data.push(e);
121        }
122        let mut hphi_row: Vec<usize> = Vec::with_capacity(comp.obj.hphi.len());
123        let mut hphi_col: Vec<usize> = Vec::with_capacity(comp.obj.hphi.len());
124        let mut hphi_data: Vec<Node> = Vec::with_capacity(comp.obj.hphi.len());
125        for (v1, v2, e) in comp.obj.hphi.into_iter() {
126            let i = *var2index.get(&v1).unwrap();
127            let j = *var2index.get(&v2).unwrap();
128            if i >= j {
129                hphi_row.push(i);
130                hphi_col.push(j);
131            }
132            else {
133                hphi_row.push(j);
134                hphi_col.push(i);
135            }
136            hphi_data.push(e);
137        }
138        let hphi_mat = CooMat::new(
139            (num_vars, num_vars),
140            hphi_row,
141            hphi_col,
142            vec![0.; hphi_data.len()]
143        );
144
145        // Objective grad (c)
146        let mut c_data: Vec<f64> = vec![0.; num_vars];
147        for (var, val) in comp.obj.prop.a.iter() {
148            c_data[*var2index.get(var).unwrap()] = *val;
149        }
150
151        // Linear equality constraints (Ax = b)
152        let aindex2constr: HashMap<usize, Constraint> = comp.constr.ca.into_iter()
153                                                                      .enumerate()
154                                                                      .collect();
155        let num_a: usize = comp.constr.b.len();
156        let mut a_row: Vec<usize> = Vec::with_capacity(comp.constr.a.len());
157        let mut a_col: Vec<usize> = Vec::with_capacity(comp.constr.a.len());
158        let mut a_data: Vec<f64> = Vec::with_capacity(comp.constr.a.len());
159        for (row, var, val) in comp.constr.a.into_iter() {
160            a_row.push(row);
161            a_col.push(*var2index.get(&var).unwrap());
162            a_data.push(val);
163        }
164        let a_mat = CooMat::new(
165            (num_a, num_vars),
166            a_row,
167            a_col,
168            a_data
169        );
170
171        let b_data = comp.constr.b;
172
173        // Nonlinear equality constraints (f(x) = 0)
174        let jindex2constr: HashMap<usize, Constraint> = comp.constr.cj.into_iter()
175                                                                      .enumerate()
176                                                                      .collect();
177        let num_j: usize = comp.constr.f.len();
178        let mut j_row: Vec<usize> = Vec::with_capacity(comp.constr.j.len());
179        let mut j_col: Vec<usize> = Vec::with_capacity(comp.constr.j.len());
180        let mut j_data: Vec<Node> = Vec::with_capacity(comp.constr.j.len());
181        for (row, var, exp) in comp.constr.j.into_iter() {
182            j_row.push(row);
183            j_col.push(*var2index.get(&var).unwrap());
184            j_data.push(exp);
185        }
186        let j_mat = CooMat::new(
187            (num_j, num_vars),
188            j_row,
189            j_col,
190            vec![0.; j_data.len()]
191        );
192        let f_data = comp.constr.f;
193        let mut h_data: Vec<Vec<Node>> = Vec::with_capacity(num_j);
194        let mut h_vec: Vec<CooMat<f64>> = Vec::with_capacity(num_j);
195        for hh in comp.constr.h.into_iter() {
196            let mut hh_row: Vec<usize> = Vec::with_capacity(hh.len());
197            let mut hh_col: Vec<usize> = Vec::with_capacity(hh.len());
198            let mut hh_data: Vec<Node> = Vec::with_capacity(hh.len());
199            for (v1, v2, exp) in hh.into_iter() {
200                let i = *var2index.get(&v1).unwrap();
201                let j = *var2index.get(&v2).unwrap();
202                if i >= j {
203                    hh_row.push(i);
204                    hh_col.push(j);
205                }
206                else {
207                    hh_row.push(j);
208                    hh_col.push(i);
209                }
210                hh_data.push(exp);
211            }
212            h_vec.push(CooMat::new(
213                (num_vars, num_vars),
214                hh_row,
215                hh_col,
216                vec![0.; hh_data.len()]
217            ));
218            h_data.push(hh_data);
219        }
220
221        // Bounds (l <= x <= u)
222        let mut uindex2constr: HashMap<usize, Constraint> = HashMap::new();
223        let mut lindex2constr: HashMap<usize, Constraint> = HashMap::new();
224        let mut u_data = vec![INF; num_vars];
225        let mut l_data = vec![-INF; num_vars];
226        for (var, val, constr) in comp.constr.u.into_iter() {
227            let index = *var2index.get(&var).unwrap();
228            if val <= u_data[index] {
229                u_data[index] = val;
230                uindex2constr.insert(index, constr);
231            }
232        }
233        for (var, val, constr) in comp.constr.l.into_iter() {
234            let index = *var2index.get(&var).unwrap();
235            if val >= l_data[index] {
236                l_data[index] = val;
237                lindex2constr.insert(index, constr);
238            }
239        }
240
241        // Integer restrictions
242        let mut num_int: usize = 0;
243        let mut p_data = vec![false; num_vars];
244        for (var, index) in var2index.iter() {
245            match var {
246                Node::VariableScalar(x) => {
247                    if x.is_integer() {
248                        p_data[*index] = true;
249                        num_int += 1;
250                    }
251                }
252                _ => (),
253            }
254        }
255
256        // Initial values
257        let mut x0_data: Vec<f64> = vec![0.; num_vars];
258        for (var, val) in self.init_primals().iter() {
259            match var2index.get(var) {
260                Some(index) => x0_data[*index] = *val,
261                None => (), 
262            }
263        }
264       
265        // Eval
266        let eval_fn = Box::new(move | phi: &mut f64, 
267                                      gphi: &mut Vec<f64>, 
268                                      hphi: &mut CooMat<f64>,
269                                      f: &mut Vec<f64>,
270                                      j: &mut CooMat<f64>,
271                                      h: &mut Vec<CooMat<f64>>,
272                                      x: &[f64] | {
273
274            // Var values
275            let mut var_values: HashMap<&Node, f64> = HashMap::with_capacity(x.len());
276            for (var, index) in var2index_eval.iter() {
277                var_values.insert(var, x[*index]);
278            }
279
280            // phi
281            *phi = phi_data.evaluate(&var_values);
282
283            // gphi
284            for (index, exp) in gphi_indices.iter().zip(gphi_data.iter()) {
285                (*gphi)[*index] = exp.evaluate(&var_values);
286            }
287
288            // hphi
289            let hphi_dest = hphi.data_mut();
290            for (val, exp) in hphi_dest.iter_mut().zip(hphi_data.iter()) {
291                *val = exp.evaluate(&var_values);           
292            }
293
294            // f
295            for (val, exp) in f.iter_mut().zip(f_data.iter()) {
296                *val = exp.evaluate(&var_values);
297            }
298
299            // j
300            let j_dest = j.data_mut();
301            for (val, exp) in j_dest.iter_mut().zip(j_data.iter()) {
302                *val = exp.evaluate(&var_values);
303            }
304            
305            // h
306            for (hh, hh_data) in h.iter_mut().zip(h_data.iter()) {
307                let hh_dest = hh.data_mut();
308                for (val, exp) in hh_dest.iter_mut().zip(hh_data.iter()) {
309                    *val = exp.evaluate(&var_values);
310                }
311            }
312        });
313
314        // Problem
315        let problem: Problem;
316
317        // Lp
318        if comp.obj.prop.affine && num_j == 0 && num_int == 0 {
319            problem = Problem::Lp(
320                ProblemLp::new(
321                    c_data,
322                    a_mat,
323                    b_data,
324                    l_data,
325                    u_data,
326                    Some(x0_data)
327                )
328            );
329        }
330
331        // Milp
332        else if comp.obj.prop.affine && num_j == 0 && num_int > 0 {
333            problem = Problem::Milp(
334                ProblemMilp::new(
335                    c_data,
336                    a_mat,
337                    b_data,
338                    l_data,
339                    u_data,
340                    p_data,
341                    Some(x0_data)
342                )
343            );
344        }
345
346        // Nlp
347        else if num_int == 0 {
348            problem = Problem::Nlp(
349                ProblemNlp::new(
350                    hphi_mat,
351                    a_mat,
352                    b_data,
353                    j_mat,
354                    h_vec,
355                    l_data,
356                    u_data,
357                    Some(x0_data),
358                    eval_fn,
359                )
360            );
361        }
362
363        // Minlp
364        else {
365            problem = Problem::Minlp(
366                ProblemMinlp::new(
367                    hphi_mat,
368                    a_mat,
369                    b_data,
370                    j_mat,
371                    h_vec,
372                    l_data,
373                    u_data,
374                    p_data,
375                    Some(x0_data),
376                    eval_fn,
377                )
378            );
379        }
380
381        // Return
382        ModelStdProb {
383            prob: problem,
384            var2index: var2index,
385            aindex2constr: aindex2constr,
386            jindex2constr: jindex2constr,
387            uindex2constr: uindex2constr,
388            lindex2constr: lindex2constr,
389        }
390    }
391}
392
393#[cfg(test)]
394mod tests {
395
396    use maplit::hashmap;
397
398    use super::*;
399    use crate::problem::base::Problem;
400    use crate::model::node_func::NodeFunc;
401    use crate::model::node_cmp::NodeCmp;
402    use crate::model::variable::VariableScalar;
403    use crate::assert_vec_approx_eq;
404
405    #[test]
406    fn model_std_problem_lp() {
407
408        let x = VariableScalar::new_continuous("x");
409        let y = VariableScalar::new_continuous("y");
410
411        let c1 = (2.*&x + &y).equal(2.);
412        let c2 = x.leq(5.);
413        let c3 = x.geq(0.);
414        let c4 = y.leq(5.);
415        let c5 = y.geq(0.);
416        let c6 = (7.*&x + 5.*&y - 10.).geq(&x + &y - 3.);
417
418        let mut m = Model::new();
419        m.set_objective(Objective::maximize(&(3.*&x + 4.*&y + 1.)));
420        m.add_constraint(&c1);
421        m.add_constraint(&c2);
422        m.add_constraint(&c3);
423        m.add_constraint(&c4);
424        m.add_constraint(&c5);
425        m.add_constraint(&c6);
426        m.set_init_primals(&hashmap!{ &x => 2., &y => 3. });
427
428        let std_p = m.std_problem();
429        let lp = match std_p.prob {
430            Problem::Lp(x) => x,
431            _ => panic!("invalid std problem")
432        };
433
434        assert_vec_approx_eq!(lp.x0().unwrap(), vec![0., 2., 3.], epsilon=0.);
435        assert_vec_approx_eq!(lp.c(), vec![0., -3., -4.,], epsilon=0.);
436        assert_eq!(lp.na(), 2);
437        assert_eq!(lp.nx(), 3);
438        assert_eq!(lp.a().nnz(), 5);
439        for (row, col, val) in lp.a().iter() {
440            if *row == 0 && *col == 1 {
441                assert_eq!(*val, 2.);
442            }
443            else if *row == 0 && *col == 2 {
444                assert_eq!(*val, 1.);
445            }
446            else if *row == 1 && *col == 0 {
447                assert_eq!(*val, -1.);
448            }
449            else if *row == 1 && *col == 1 {
450                assert_eq!(*val, 6.);
451            }
452            else if *row == 1 && *col == 2 {
453                assert_eq!(*val, 4.);
454            }
455            else {
456                panic!("invalid a matrix")
457            }
458        }
459        assert_vec_approx_eq!(lp.b(), vec![2., 7.], epsilon=0.);
460        assert_vec_approx_eq!(lp.l(), vec![0., 0., 0.], epsilon=0.);
461        assert_vec_approx_eq!(lp.u(), vec![1e8, 5., 5.], epsilon=0.);
462
463        assert_eq!(std_p.var2index.len(), 3);
464        for (var, index) in std_p.var2index.iter() {
465            if *var == x {
466                assert_eq!(*index, 1);
467            }
468            else if *var == y {
469                assert_eq!(*index, 2);
470            }
471            else if (*var).name() == "_s_a1_" {
472                assert_eq!(*index, 0);
473            }
474            else {
475                panic!("invalid var2index data");
476            }
477        }
478        assert_eq!(std_p.aindex2constr.len(), 2);
479        assert_eq!(*std_p.aindex2constr.get(&0).unwrap(), c1);
480        assert_eq!(*std_p.aindex2constr.get(&1).unwrap(), c6);
481        assert_eq!(std_p.jindex2constr.len(), 0);
482        assert_eq!(std_p.uindex2constr.len(), 2);
483        assert_eq!(*std_p.uindex2constr.get(&1).unwrap(), c2);
484        assert_eq!(*std_p.uindex2constr.get(&2).unwrap(), c4);
485        assert_eq!(std_p.lindex2constr.len(), 3);
486        assert_eq!(*std_p.lindex2constr.get(&0).unwrap(), c6);
487        assert_eq!(*std_p.lindex2constr.get(&1).unwrap(), c3);
488        assert_eq!(*std_p.lindex2constr.get(&2).unwrap(), c5);
489    }
490
491    #[test]
492    fn model_std_problem_milp() {
493
494        let x = VariableScalar::new_integer("x");
495        let y = VariableScalar::new_continuous("y");
496
497        let c1 = (2.*&x + &y).equal(2.);
498        let c2 = x.leq(5.);
499        let c3 = x.geq(0.);
500        let c4 = y.leq(5.);
501        let c5 = y.geq(0.);
502
503        let mut m = Model::new();
504        m.set_objective(Objective::minimize(&(3.*&x + 4.*&y + 1.)));
505        m.add_constraint(&c1);
506        m.add_constraint(&c2);
507        m.add_constraint(&c3);
508        m.add_constraint(&c4);
509        m.add_constraint(&c5);
510        m.set_init_primals(&hashmap!{ &x => 2., &y => 3. });
511
512        let std_p = m.std_problem();
513        let milp = match std_p.prob {
514            Problem::Milp(x) => x,
515            _ => panic!("invalid std problem")
516        };
517
518        assert_vec_approx_eq!(milp.x0().unwrap(), vec![2., 3.], epsilon=0.);
519        assert_vec_approx_eq!(milp.c(), vec![3., 4.,], epsilon=0.);
520        assert_eq!(milp.na(), 1);
521        assert_eq!(milp.nx(), 2);
522        assert_eq!(milp.a().nnz(), 2);
523        for (row, col, val) in milp.a().iter() {
524            if *row == 0 && *col == 0 {
525                assert_eq!(*val, 2.);
526            }
527            else if *row == 0 && *col == 1 {
528                assert_eq!(*val, 1.);
529            }
530            else {
531                panic!("invalid a matrix")
532            }
533        }
534        assert_vec_approx_eq!(milp.b(), vec![2.], epsilon=0.);
535        assert_vec_approx_eq!(milp.l(), vec![0., 0.], epsilon=0.);
536        assert_vec_approx_eq!(milp.u(), vec![5., 5.], epsilon=0.);
537        assert_eq!(milp.p().len(), 2);
538        assert!(milp.p()[0]);
539        assert!(!milp.p()[1]);
540
541        assert_eq!(std_p.var2index.len(), 2);
542        assert_eq!(*std_p.var2index.get(&x).unwrap(), 0);
543        assert_eq!(*std_p.var2index.get(&y).unwrap(), 1);
544        assert_eq!(std_p.aindex2constr.len(), 1);
545        assert_eq!(*std_p.aindex2constr.get(&0).unwrap(), c1);
546        assert_eq!(std_p.jindex2constr.len(), 0);
547        assert_eq!(std_p.uindex2constr.len(), 2);
548        assert_eq!(*std_p.uindex2constr.get(&0).unwrap(), c2);
549        assert_eq!(*std_p.uindex2constr.get(&1).unwrap(), c4);
550        assert_eq!(std_p.lindex2constr.len(), 2);
551        assert_eq!(*std_p.lindex2constr.get(&0).unwrap(), c3);
552        assert_eq!(*std_p.lindex2constr.get(&1).unwrap(), c5);
553    }
554
555    #[test]
556    fn model_std_problem_nlp() {
557
558        let x = VariableScalar::new_continuous("x");
559        let y = VariableScalar::new_continuous("y");
560
561        let f = 3.*x.cos() + 4.*&y*&y - 10.;
562        let c1 = (2.*&x + 3.*&y).equal(7.);
563        let c2 = (x.sin() + &x*&y + 4.).leq(&x + 10.);
564        let c3 = 5_f64.geq(&x*&x);
565
566        let mut m = Model::new();
567        m.set_objective(Objective::maximize(&f));
568        m.add_constraint(&c1);
569        m.add_constraint(&c2);
570        m.add_constraint(&c3);
571        m.set_init_primals(&hashmap!{ &x => 2., &y => 3. });
572
573        let std_p = m.std_problem();
574        let mut nlp = match std_p.prob {
575            Problem::Nlp(x) => x,
576            _ => panic!("invalid std problem")
577        };
578
579        nlp.evaluate(&vec![2., 3., 4., 5.]);
580        nlp.combine_h(&vec![1.5, 2.3]);
581
582        assert_vec_approx_eq!(nlp.x0().unwrap(), vec![0., 0., 2., 3.], epsilon=0.);
583        assert_eq!(nlp.phi(), -(3.*4_f64.cos() + 4.*5.*5. - 10.));
584        assert_vec_approx_eq!(nlp.gphi(), 
585                              vec![0., 0., 3.*4_f64.sin(), -8.*5.],
586                              epsilon=1e-8);
587        assert_eq!(nlp.hphi().nnz(), 2);
588        for (v1, v2, val) in nlp.hphi().iter() {
589            if *v1 == 2 && *v2 == 2 {
590                assert_eq!(*val, 3.*4_f64.cos());
591            }
592            else if *v1 == 3 && *v2 == 3 {
593                assert_eq!(*val, -8.);
594            }
595            else {
596                panic!("invalid variable pair");
597            }
598        }
599
600        assert_eq!(nlp.a().nnz(), 2);
601        for (row, col, val) in nlp.a().iter() {
602            if *row == 0 && *col == 2 {
603                assert_eq!(*val, 2.);
604            }
605            else if *row == 0 && *col == 3 {
606                assert_eq!(*val, 3.);
607            }
608            else {
609                panic!("invalid a matrix entry");
610            }
611        }
612        assert_vec_approx_eq!(nlp.b(), vec![7.], epsilon=0.);
613
614        assert_eq!(nlp.f().len(), 2);
615        assert_eq!(nlp.f()[0], 4_f64.sin() + 4.*5. + 4. - 4. - 10. - 2.);
616        assert_eq!(nlp.f()[1], 5. - 4.*4. - 3.);
617        assert_eq!(nlp.j().nnz(), 5);
618        for (row, col, val) in nlp.j().iter() {
619            if *row == 0 && *col == 2 {
620                assert_eq!(*val, 4_f64.cos() + 5. - 1.);
621            }
622            else if *row == 0 && *col == 3 {
623                assert_eq!(*val, 4.);
624            }
625            else if *row == 0 && *col == 0 {
626                assert_eq!(*val, -1.);
627            }
628            else if *row == 1 && *col == 2 {
629                assert_eq!(*val, -2.*4.);
630            }
631            else if *row == 1 && *col == 1 {
632                assert_eq!(*val, -1.);
633            }
634            else {
635                panic!("invalid j matrix entry");
636            }
637        }
638        assert_eq!(nlp.h().len(), 2);
639        assert_eq!(nlp.h()[0].nnz(), 2);
640        assert_eq!(nlp.h()[1].nnz(), 1);
641        for (var1, var2, val) in nlp.h()[0].iter() {
642            if *var1 == 2 && *var2 == 2 {
643                assert_eq!(*val, -4_f64.sin());
644            }
645            else if *var1 == 3 && *var2 == 2 {
646                assert_eq!(*val, 1.);
647            }
648            else {
649                panic!("invalid h[0] entry")
650            }
651        }
652        for (var1, var2, val) in nlp.h()[1].iter() {
653            if *var1 == 2 && *var2 == 2 {
654                assert_eq!(*val, -2.);
655            }
656            else {
657                panic!("invalid h[1] entry")
658            }
659        }
660        assert_eq!(nlp.hcomb().nnz(), 3);
661        let mut first = true;
662        for (var1, var2, val) in nlp.hcomb().iter() {
663            if *var1 == 2 && *var2 == 2 && first {
664                assert_eq!(*val, 1.5*(-4_f64.sin()));
665                first = false;
666            }
667            else if *var1 == 2 && *var2 == 2 && !first {
668                assert_eq!(*val, 2.3*-2.);
669            }
670            else if *var1 == 3 && *var2 == 2 {
671                assert_eq!(*val, 1.*1.5);
672            }
673            else {
674                panic!("invalid hcomb entry")
675            }
676        }
677        assert_vec_approx_eq!(nlp.l(), vec![-1e8, 0., -1e8, -1e8], epsilon=0.);
678        assert_vec_approx_eq!(nlp.u(), vec![0., 1e8, 1e8, 1e8], epsilon=0.);
679
680        assert_eq!(std_p.var2index.len(), 4);
681        for (var, index) in std_p.var2index.iter() {
682            if *var == x{
683                assert_eq!(*index, 2);
684            }
685            else if *var == y {
686                assert_eq!(*index, 3);
687            }
688            else if (*var).name() == "_s_j0_" {
689                assert_eq!(*index, 0);
690            }
691            else if (*var).name() == "_s_j1_" {
692                assert_eq!(*index, 1);
693            }
694            else {
695                panic!("invalid var2index entry");
696            }
697        }
698        assert_eq!(std_p.aindex2constr.len(), 1);
699        assert_eq!(*std_p.aindex2constr.get(&0).unwrap(), c1);
700        assert_eq!(std_p.jindex2constr.len(), 2);
701        assert_eq!(*std_p.jindex2constr.get(&0).unwrap(), c2);
702        assert_eq!(*std_p.jindex2constr.get(&1).unwrap(), c3);
703        assert_eq!(std_p.uindex2constr.len(), 1);
704        assert_eq!(*std_p.uindex2constr.get(&0).unwrap(), c2);
705        assert_eq!(std_p.lindex2constr.len(), 1);
706        assert_eq!(*std_p.lindex2constr.get(&1).unwrap(), c3);
707    }
708}