1pub mod constants;
25mod constraints;
26mod environment;
27pub mod errors;
28pub mod logging;
29pub mod parameters;
30mod solution;
31mod variables;
32
33pub use constraints::*;
34pub use environment::*;
35pub use errors::{Error, Result};
36pub use ffi;
37use ffi::{
38 cpxlp, CPX_STAT_INForUNBD, CPXaddmipstarts, CPXaddrows, CPXchgobj, CPXchgobjsen,
39 CPXchgprobtype, CPXcreateprob, CPXfreeprob, CPXgetobjval, CPXgetstat, CPXgetx, CPXlpopt,
40 CPXmipopt, CPXnewcols, CPXwriteprob, CPXMIP_UNBOUNDED, CPXPROB_LP, CPXPROB_MILP, CPX_MAX,
41 CPX_MIN, CPX_STAT_INFEASIBLE, CPX_STAT_UNBOUNDED,
42};
43use log::debug;
44pub use solution::*;
45pub use variables::*;
46
47use std::{
48 ffi::{c_int, CString},
49 time::Instant,
50};
51
52mod macros {
53 macro_rules! cpx_lp_result {
54 ( unsafe { $func:ident ( $env:expr, $lp:expr $(, $b:expr)* $(,)? ) } ) => {
55 {
56 let status = unsafe { $func($env, $lp $(,$b)* ) };
57 if status != 0 {
58 Err(errors::Error::from(errors::Cplex::from_code($env, $lp, status)))
59 } else {
60 Ok(())
61 }
62 }
63 };
64}
65
66 pub(super) use cpx_lp_result;
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub struct VariableId(usize);
72
73impl VariableId {
74 pub fn into_inner(self) -> usize {
75 self.0
76 }
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub struct ConstraintId(usize);
82
83impl ConstraintId {
84 pub fn into_inner(self) -> usize {
85 self.0
86 }
87}
88
89pub struct Problem {
91 inner: *mut cpxlp,
92 env: Environment,
93 variables: Vec<Variable>,
94 constraints: Vec<Constraint>,
95}
96
97unsafe impl Send for Problem {}
98
99#[derive(Copy, Clone, Debug)]
100pub enum ObjectiveType {
101 Maximize,
102 Minimize,
103}
104
105impl ObjectiveType {
106 fn into_raw(self) -> c_int {
107 match self {
108 ObjectiveType::Minimize => CPX_MIN as c_int,
109 ObjectiveType::Maximize => CPX_MAX as c_int,
110 }
111 }
112}
113
114#[derive(Copy, Clone, Debug, PartialEq, Eq)]
115pub enum ProblemType {
116 Linear,
117 MixedInteger,
118}
119
120impl ProblemType {
121 fn into_raw(self) -> c_int {
122 match self {
123 ProblemType::Linear => CPXPROB_LP as c_int,
124 ProblemType::MixedInteger => CPXPROB_MILP as c_int,
125 }
126 }
127}
128
129impl Problem {
130 pub fn new<S>(env: Environment, name: S) -> Result<Self>
132 where
133 S: AsRef<str>,
134 {
135 let mut status = 0;
136 let name =
137 CString::new(name.as_ref()).map_err(|e| errors::Input::from_message(e.to_string()))?;
138 let inner = unsafe { CPXcreateprob(env.inner, &mut status, name.as_ptr()) };
139 if inner.is_null() {
140 Err(errors::Cplex::from_code(env.inner, std::ptr::null(), status).into())
141 } else {
142 Ok(Problem {
143 inner,
144 env,
145 variables: vec![],
146 constraints: vec![],
147 })
148 }
149 }
150
151 pub fn env_mut(&mut self) -> &mut Environment {
153 &mut self.env
154 }
155
156 pub fn env(&self) -> &Environment {
158 &self.env
159 }
160
161 pub fn add_variable(&mut self, var: Variable) -> Result<VariableId> {
165 let name = CString::new(var.name().as_bytes())
166 .map_err(|e| errors::Input::from_message(e.to_string()))?;
167
168 macros::cpx_lp_result!(unsafe {
169 CPXnewcols(
170 self.env.inner,
171 self.inner,
172 1,
173 &var.weight(),
174 &var.lower_bound(),
175 &var.upper_bound(),
176 &var.type_().into_raw() as *const u8 as *const i8,
177 &mut (name.as_ptr() as *mut _),
178 )
179 })?;
180
181 let index = self.variables.len();
182 self.variables.push(var);
183 Ok(VariableId(index))
184 }
185
186 pub fn add_variables(&mut self, vars: Vec<Variable>) -> Result<Vec<VariableId>> {
190 let names = vars
191 .iter()
192 .map(|v| {
193 CString::new(v.name().as_bytes())
194 .map_err(|e| errors::Input::from_message(e.to_string()).into())
195 })
196 .collect::<Result<Vec<_>>>()?;
197
198 let mut name_ptrs = names
199 .iter()
200 .map(|n| n.as_ptr() as *mut _)
201 .collect::<Vec<_>>();
202
203 let objs = vars.iter().map(|v| v.weight()).collect::<Vec<_>>();
204 let lbs = vars.iter().map(|v| v.lower_bound()).collect::<Vec<_>>();
205 let ubs = vars.iter().map(|v| v.upper_bound()).collect::<Vec<_>>();
206 let types = vars
207 .iter()
208 .map(|v| v.type_().into_raw() as i8)
209 .collect::<Vec<_>>();
210
211 macros::cpx_lp_result!(unsafe {
212 CPXnewcols(
213 self.env.inner,
214 self.inner,
215 vars.len() as i32,
216 objs.as_ptr(),
217 lbs.as_ptr(),
218 ubs.as_ptr(),
219 types.as_ptr(),
220 name_ptrs.as_mut_ptr(),
221 )
222 })?;
223
224 let indices: Vec<VariableId> = vars
225 .iter()
226 .enumerate()
227 .map(|(idx, _)| VariableId(idx + self.variables.len()))
228 .collect();
229 self.variables.extend(vars);
230 Ok(indices)
231 }
232
233 pub fn add_constraint(&mut self, constraint: Constraint) -> Result<ConstraintId> {
237 let (ind, val): (Vec<c_int>, Vec<f64>) = constraint
238 .weights()
239 .iter()
240 .filter(|(_, weight)| *weight != 0.0)
241 .map(|(var_id, weight)| (var_id.0 as c_int, weight))
242 .unzip();
243 let nz = val.len() as c_int;
244 let name = constraint
245 .name()
246 .map(|n| {
247 CString::new(n.as_bytes()).map_err(|e| errors::Input::from_message(e.to_string()))
248 })
249 .transpose()?;
250 macros::cpx_lp_result!(unsafe {
251 CPXaddrows(
252 self.env.inner,
253 self.inner,
254 0,
255 1,
256 nz,
257 &constraint.rhs(),
258 &constraint.type_().into_raw(),
259 &0,
260 ind.as_ptr(),
261 val.as_ptr(),
262 std::ptr::null_mut(),
263 &mut (name
264 .as_ref()
265 .map(|n| n.as_ptr())
266 .unwrap_or(std::ptr::null()) as *mut _),
267 )
268 })?;
269
270 let index = self.constraints.len();
271 self.constraints.push(constraint);
272 Ok(ConstraintId(index))
273 }
274
275 pub fn add_constraints(&mut self, con: Vec<Constraint>) -> Result<Vec<ConstraintId>> {
279 if con.is_empty() {
280 return Err(errors::Input::from_message(
281 "Called add_constraints with 0 constaints".to_owned(),
282 )
283 .into());
284 }
285 let beg = std::iter::once(0)
286 .chain(con[..con.len() - 1].iter().map(|c| c.weights().len()))
287 .scan(0, |state, x| {
288 *state += x;
289 Some(*state as i32)
290 })
291 .collect::<Vec<_>>();
292
293 let (ind, val): (Vec<c_int>, Vec<f64>) = con
294 .iter()
295 .flat_map(|c| c.weights().iter())
296 .filter(|(_, weight)| *weight != 0.0)
297 .map(|(var_id, weight)| (var_id.0 as c_int, weight))
298 .unzip();
299
300 let nz = val.len() as c_int;
301 let names = con
302 .iter()
303 .map(|c| {
304 c.name()
305 .map(|n| {
306 CString::new(n.as_bytes())
307 .map_err(|e| errors::Input::from_message(e.to_string()).into())
308 })
309 .transpose()
310 })
311 .collect::<Result<Vec<_>>>()?;
312
313 let mut name_ptrs = names
314 .iter()
315 .map(|n| {
316 n.as_ref()
317 .map(|n| n.as_ptr())
318 .unwrap_or(std::ptr::null_mut()) as *mut _
319 })
320 .collect::<Vec<_>>();
321
322 let rhss = con.iter().map(|c| c.rhs()).collect::<Vec<_>>();
323 let senses = con.iter().map(|c| c.type_().into_raw()).collect::<Vec<_>>();
324
325 macros::cpx_lp_result!(unsafe {
326 CPXaddrows(
327 self.env.inner,
328 self.inner,
329 0,
330 con.len() as i32,
331 nz,
332 rhss.as_ptr(),
333 senses.as_ptr(),
334 beg.as_ptr(),
335 ind.as_ptr(),
336 val.as_ptr(),
337 std::ptr::null_mut(),
338 name_ptrs.as_mut_ptr(),
339 )
340 })?;
341
342 let indices = con
343 .iter()
344 .enumerate()
345 .map(|(idx, _)| ConstraintId(idx + self.constraints.len()))
346 .collect();
347 self.constraints.extend(con);
348 Ok(indices)
349 }
350
351 pub fn set_objective(self, ty: ObjectiveType, obj: Vec<(VariableId, f64)>) -> Result<Self> {
353 let (ind, val): (Vec<c_int>, Vec<f64>) = obj
354 .into_iter()
355 .map(|(var_id, weight)| (var_id.0 as c_int, weight))
356 .unzip();
357
358 macros::cpx_lp_result!(unsafe {
359 CPXchgobj(
360 self.env.inner,
361 self.inner,
362 ind.len() as c_int,
363 ind.as_ptr(),
364 val.as_ptr(),
365 )
366 })?;
367
368 self.set_objective_type(ty)
369 }
370
371 pub fn set_objective_type(self, ty: ObjectiveType) -> Result<Self> {
373 macros::cpx_lp_result!(unsafe { CPXchgobjsen(self.env.inner, self.inner, ty.into_raw()) })?;
374 Ok(self)
375 }
376
377 pub fn write<S>(&self, name: S) -> Result<()>
379 where
380 S: AsRef<str>,
381 {
382 let name =
383 CString::new(name.as_ref()).map_err(|e| errors::Input::from_message(e.to_string()))?;
384
385 macros::cpx_lp_result!(unsafe {
386 CPXwriteprob(self.env.inner, self.inner, name.as_ptr(), std::ptr::null())
387 })
388 }
389
390 pub fn add_initial_soln(&mut self, vars: &[VariableId], values: &[f64]) -> Result<()> {
395 if values.len() != vars.len() {
396 return Err(errors::Input::from_message(
397 "number of solution variables and values does not match".to_string(),
398 )
399 .into());
400 }
401 let vars = vars.iter().map(|&u| u.0 as c_int).collect::<Vec<_>>();
402
403 macros::cpx_lp_result!(unsafe {
404 CPXaddmipstarts(
405 self.env.inner,
406 self.inner,
407 1,
408 vars.len() as c_int,
409 &0,
410 vars.as_ptr(),
411 values.as_ptr(),
412 &0,
413 &mut std::ptr::null_mut(),
414 )
415 })
416 }
417
418 pub fn solve_as(self, pt: ProblemType) -> Result<Solution> {
421 macros::cpx_lp_result!(unsafe {
422 CPXchgprobtype(self.env.inner, self.inner, pt.into_raw())
423 })?;
424
425 let start_optim = Instant::now();
426 match pt {
427 ProblemType::MixedInteger => {
428 macros::cpx_lp_result!(unsafe { CPXmipopt(self.env.inner, self.inner) })?
429 }
430 ProblemType::Linear => {
431 macros::cpx_lp_result!(unsafe { CPXlpopt(self.env.inner, self.inner) })?
432 }
433 };
434 let elapsed = start_optim.elapsed();
435 debug!("CPLEX model solution took: {:?}", elapsed);
436
437 let code = unsafe { CPXgetstat(self.env.inner, self.inner) };
438 if code as u32 == CPX_STAT_INFEASIBLE || code as u32 == CPX_STAT_INForUNBD {
439 return Err(crate::errors::Cplex::Unfeasible {
440 code,
441 message: "Unfeasible problem".to_string(),
442 }
443 .into());
444 }
445
446 if code as u32 == CPX_STAT_UNBOUNDED || code as u32 == CPXMIP_UNBOUNDED {
447 return Err(crate::errors::Cplex::Unbounded {
448 code,
449 message: "Unbounded problem".to_string(),
450 }
451 .into());
452 }
453
454 let mut objective_value: f64 = 0.0;
455 macros::cpx_lp_result!(unsafe {
456 CPXgetobjval(self.env.inner, self.inner, &mut objective_value)
457 })?;
458
459 let mut variable_values = vec![0f64; self.variables.len()];
460 macros::cpx_lp_result!(unsafe {
461 CPXgetx(
462 self.env.inner,
463 self.inner,
464 variable_values.as_mut_ptr(),
465 0,
466 self.variables.len() as c_int - 1,
467 )
468 })?;
469
470 Ok(Solution::new(variable_values, objective_value))
471 }
472}
473
474impl Drop for Problem {
475 fn drop(&mut self) {
476 unsafe {
477 assert_eq!(CPXfreeprob(self.env.inner, &mut self.inner), 0);
478 }
479 }
480}
481
482#[cfg(test)]
483mod test {
484 use constants::INFINITY;
485 use constraints::ConstraintType;
486
487 use super::*;
488 use variables::{Variable, VariableType};
489
490 #[test]
491 fn mipex1() {
492 let env = Environment::new().unwrap();
493 let mut problem = Problem::new(env, "mipex1").unwrap();
494
495 let x0 = problem
496 .add_variable(Variable::new(
497 VariableType::Continuous,
498 1.0,
499 0.0,
500 40.0,
501 "x0",
502 ))
503 .unwrap();
504
505 let x1 = problem
506 .add_variable(Variable::new(
507 VariableType::Continuous,
508 2.0,
509 0.0,
510 INFINITY,
511 "x1",
512 ))
513 .unwrap();
514
515 let x2 = problem
516 .add_variable(Variable::new(
517 VariableType::Continuous,
518 3.0,
519 0.0,
520 INFINITY,
521 "x2",
522 ))
523 .unwrap();
524
525 let x3 = problem
526 .add_variable(Variable::new(VariableType::Integer, 1.0, 2.0, 3.0, "x3"))
527 .unwrap();
528
529 assert_eq!(x0, VariableId(0));
530 assert_eq!(x1, VariableId(1));
531 assert_eq!(x2, VariableId(2));
532 assert_eq!(x3, VariableId(3));
533
534 let c0 = problem
535 .add_constraint(Constraint::new(
536 ConstraintType::LessThanEq,
537 20.0,
538 None,
539 vec![(x0, -1.0), (x1, 1.0), (x2, 1.0), (x3, 10.0)],
540 ))
541 .unwrap();
542
543 let c1 = problem
544 .add_constraint(Constraint::new(
545 ConstraintType::LessThanEq,
546 30.0,
547 None,
548 vec![(x0, 1.0), (x1, -3.0), (x2, 1.0)],
549 ))
550 .unwrap();
551
552 let c2 = problem
553 .add_constraint(Constraint::new(
554 ConstraintType::Eq,
555 0.0,
556 None,
557 vec![(x1, 1.0), (x3, -3.5)],
558 ))
559 .unwrap();
560
561 assert_eq!(c0, ConstraintId(0));
562 assert_eq!(c1, ConstraintId(1));
563 assert_eq!(c2, ConstraintId(2));
564
565 let problem = problem.set_objective_type(ObjectiveType::Maximize).unwrap();
566
567 let solution = problem.solve_as(ProblemType::MixedInteger).unwrap();
568
569 assert_eq!(solution.objective_value(), 122.5);
570 }
571
572 #[test]
573 fn mipex1_batch() {
574 let env = Environment::new().unwrap();
575 let mut problem = Problem::new(env, "mipex1").unwrap();
576
577 let vars = problem
578 .add_variables(vec![
579 Variable::new(VariableType::Continuous, 1.0, 0.0, 40.0, "x0"),
580 Variable::new(VariableType::Continuous, 2.0, 0.0, INFINITY, "x1"),
581 Variable::new(VariableType::Continuous, 3.0, 0.0, INFINITY, "x2"),
582 Variable::new(VariableType::Integer, 1.0, 2.0, 3.0, "x3"),
583 ])
584 .unwrap();
585
586 assert_eq!(
587 vars,
588 vec![VariableId(0), VariableId(1), VariableId(2), VariableId(3)]
589 );
590
591 let cons = problem
592 .add_constraints(vec![
593 Constraint::new(
594 ConstraintType::LessThanEq,
595 20.0,
596 None,
597 vec![
598 (vars[0], -1.0),
599 (vars[1], 1.0),
600 (vars[2], 1.0),
601 (vars[3], 10.0),
602 ],
603 ),
604 Constraint::new(
605 ConstraintType::LessThanEq,
606 30.0,
607 None,
608 vec![(vars[0], 1.0), (vars[1], -3.0), (vars[2], 1.0)],
609 ),
610 Constraint::new(
611 ConstraintType::Eq,
612 0.0,
613 None,
614 vec![(vars[1], 1.0), (vars[3], -3.5)],
615 ),
616 ])
617 .unwrap();
618
619 assert_eq!(
620 cons,
621 vec![ConstraintId(0), ConstraintId(1), ConstraintId(2)]
622 );
623
624 let problem = problem.set_objective_type(ObjectiveType::Maximize).unwrap();
625
626 let solution = problem.solve_as(ProblemType::MixedInteger).unwrap();
627
628 assert_eq!(solution.objective_value(), 122.5);
629 }
630
631 #[test]
632 fn unfeasible() {
633 let env = Environment::new().unwrap();
634 let mut problem = Problem::new(env, "unfeasible").unwrap();
635
636 let vars = problem
637 .add_variables(vec![
638 Variable::new(VariableType::Continuous, 1.0, 0.0, 1.0, "x0"),
639 Variable::new(VariableType::Continuous, 1.0, 0.0, 1.0, "x1"),
640 ])
641 .unwrap();
642
643 assert_eq!(vars, vec![VariableId(0), VariableId(1)]);
644
645 let cons = problem
646 .add_constraints(vec![
647 Constraint::new(
648 ConstraintType::Eq,
649 0.0,
650 None,
651 vec![(vars[0], 1.0), (vars[1], 1.0)],
652 ),
653 Constraint::new(
654 ConstraintType::Eq,
655 1.0,
656 None,
657 vec![(vars[0], 1.0), (vars[1], 1.0)],
658 ),
659 ])
660 .unwrap();
661
662 assert_eq!(cons, vec![ConstraintId(0), ConstraintId(1)]);
663
664 let problem = problem.set_objective_type(ObjectiveType::Maximize).unwrap();
665 assert!(matches!(
666 problem.solve_as(ProblemType::Linear),
667 Err(errors::Error::Cplex(errors::Cplex::Unfeasible { .. }))
668 ));
669 }
670
671 #[test]
672 fn unbounded() {
673 let env = Environment::new().unwrap();
674 let mut problem = Problem::new(env, "unbounded").unwrap();
675
676 problem
677 .add_variable(Variable::new(
678 VariableType::Integer,
679 1.0,
680 0.0,
681 INFINITY,
682 "x0",
683 ))
684 .unwrap();
685
686 let problem = problem.set_objective_type(ObjectiveType::Maximize).unwrap();
687
688 assert!(matches!(
689 problem.solve_as(ProblemType::MixedInteger),
690 Err(errors::Error::Cplex(errors::Cplex::Unbounded { .. }))
691 ));
692 }
693}