1use 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
22pub struct ModelStdComp {
24
25 pub obj: NodeStdComp,
27
28 pub constr: ConstraintStdComp,
30}
31
32pub struct ModelStdProb {
34
35 pub prob: Problem,
37
38 pub var2index: HashMap<Node, usize>,
41
42 pub aindex2constr: HashMap<usize, Constraint>,
44
45 pub jindex2constr: HashMap<usize, Constraint>,
47
48 pub uindex2constr: HashMap<usize, Constraint>,
50
51 pub lindex2constr: HashMap<usize, Constraint>,
53}
54
55pub trait ModelStd {
57
58 fn std_components(&self) -> ModelStdComp;
60
61 fn std_problem(&self) -> ModelStdProb;
63}
64
65impl ModelStd for Model {
66
67 fn std_components(&self) -> ModelStdComp {
68
69 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 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 ModelStdComp {
86 obj: obj,
87 constr: constr,
88 }
89 }
90
91 fn std_problem(&self) -> ModelStdProb {
92
93 let comp = self.std_components();
95
96 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 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 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 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 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 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 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 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 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 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 = phi_data.evaluate(&var_values);
282
283 for (index, exp) in gphi_indices.iter().zip(gphi_data.iter()) {
285 (*gphi)[*index] = exp.evaluate(&var_values);
286 }
287
288 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 for (val, exp) in f.iter_mut().zip(f_data.iter()) {
296 *val = exp.evaluate(&var_values);
297 }
298
299 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 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 let problem: Problem;
316
317 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 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 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 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 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}