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 add_variable(&mut self, var: Variable) -> Result<VariableId> {
155 let name = CString::new(var.name().as_bytes())
156 .map_err(|e| errors::Input::from_message(e.to_string()))?;
157
158 macros::cpx_lp_result!(unsafe {
159 CPXnewcols(
160 self.env.inner,
161 self.inner,
162 1,
163 &var.weight(),
164 &var.lower_bound(),
165 &var.upper_bound(),
166 &var.type_().into_raw() as *const u8 as *const i8,
167 &mut (name.as_ptr() as *mut _),
168 )
169 })?;
170
171 let index = self.variables.len();
172 self.variables.push(var);
173 Ok(VariableId(index))
174 }
175
176 pub fn add_variables(&mut self, vars: Vec<Variable>) -> Result<Vec<VariableId>> {
180 let names = vars
181 .iter()
182 .map(|v| {
183 CString::new(v.name().as_bytes())
184 .map_err(|e| errors::Input::from_message(e.to_string()).into())
185 })
186 .collect::<Result<Vec<_>>>()?;
187
188 let mut name_ptrs = names
189 .iter()
190 .map(|n| n.as_ptr() as *mut _)
191 .collect::<Vec<_>>();
192
193 let objs = vars.iter().map(|v| v.weight()).collect::<Vec<_>>();
194 let lbs = vars.iter().map(|v| v.lower_bound()).collect::<Vec<_>>();
195 let ubs = vars.iter().map(|v| v.upper_bound()).collect::<Vec<_>>();
196 let types = vars
197 .iter()
198 .map(|v| v.type_().into_raw() as i8)
199 .collect::<Vec<_>>();
200
201 macros::cpx_lp_result!(unsafe {
202 CPXnewcols(
203 self.env.inner,
204 self.inner,
205 vars.len() as i32,
206 objs.as_ptr(),
207 lbs.as_ptr(),
208 ubs.as_ptr(),
209 types.as_ptr(),
210 name_ptrs.as_mut_ptr(),
211 )
212 })?;
213
214 let indices: Vec<VariableId> = vars
215 .iter()
216 .enumerate()
217 .map(|(idx, _)| VariableId(idx + self.variables.len()))
218 .collect();
219 self.variables.extend(vars);
220 Ok(indices)
221 }
222
223 pub fn add_constraint(&mut self, constraint: Constraint) -> Result<ConstraintId> {
227 let (ind, val): (Vec<c_int>, Vec<f64>) = constraint
228 .weights()
229 .iter()
230 .filter(|(_, weight)| *weight != 0.0)
231 .map(|(var_id, weight)| (var_id.0 as c_int, weight))
232 .unzip();
233 let nz = val.len() as c_int;
234 let name = constraint
235 .name()
236 .map(|n| {
237 CString::new(n.as_bytes()).map_err(|e| errors::Input::from_message(e.to_string()))
238 })
239 .transpose()?;
240 macros::cpx_lp_result!(unsafe {
241 CPXaddrows(
242 self.env.inner,
243 self.inner,
244 0,
245 1,
246 nz,
247 &constraint.rhs(),
248 &constraint.type_().into_raw(),
249 &0,
250 ind.as_ptr(),
251 val.as_ptr(),
252 std::ptr::null_mut(),
253 &mut (name
254 .as_ref()
255 .map(|n| n.as_ptr())
256 .unwrap_or(std::ptr::null()) as *mut _),
257 )
258 })?;
259
260 let index = self.constraints.len();
261 self.constraints.push(constraint);
262 Ok(ConstraintId(index))
263 }
264
265 pub fn add_constraints(&mut self, con: Vec<Constraint>) -> Result<Vec<ConstraintId>> {
269 if con.is_empty() {
270 return Err(errors::Input::from_message(
271 "Called add_constraints with 0 constaints".to_owned(),
272 )
273 .into());
274 }
275 let beg = std::iter::once(0)
276 .chain(con[..con.len() - 1].iter().map(|c| c.weights().len()))
277 .scan(0, |state, x| {
278 *state += x;
279 Some(*state as i32)
280 })
281 .collect::<Vec<_>>();
282
283 let (ind, val): (Vec<c_int>, Vec<f64>) = con
284 .iter()
285 .flat_map(|c| c.weights().iter())
286 .filter(|(_, weight)| *weight != 0.0)
287 .map(|(var_id, weight)| (var_id.0 as c_int, weight))
288 .unzip();
289
290 let nz = val.len() as c_int;
291 let names = con
292 .iter()
293 .map(|c| {
294 c.name()
295 .map(|n| {
296 CString::new(n.as_bytes())
297 .map_err(|e| errors::Input::from_message(e.to_string()).into())
298 })
299 .transpose()
300 })
301 .collect::<Result<Vec<_>>>()?;
302
303 let mut name_ptrs = names
304 .iter()
305 .map(|n| {
306 n.as_ref()
307 .map(|n| n.as_ptr())
308 .unwrap_or(std::ptr::null_mut()) as *mut _
309 })
310 .collect::<Vec<_>>();
311
312 let rhss = con.iter().map(|c| c.rhs()).collect::<Vec<_>>();
313 let senses = con.iter().map(|c| c.type_().into_raw()).collect::<Vec<_>>();
314
315 macros::cpx_lp_result!(unsafe {
316 CPXaddrows(
317 self.env.inner,
318 self.inner,
319 0,
320 con.len() as i32,
321 nz,
322 rhss.as_ptr(),
323 senses.as_ptr(),
324 beg.as_ptr(),
325 ind.as_ptr(),
326 val.as_ptr(),
327 std::ptr::null_mut(),
328 name_ptrs.as_mut_ptr(),
329 )
330 })?;
331
332 let indices = con
333 .iter()
334 .enumerate()
335 .map(|(idx, _)| ConstraintId(idx + self.constraints.len()))
336 .collect();
337 self.constraints.extend(con);
338 Ok(indices)
339 }
340
341 pub fn set_objective(self, ty: ObjectiveType, obj: Vec<(VariableId, f64)>) -> Result<Self> {
343 let (ind, val): (Vec<c_int>, Vec<f64>) = obj
344 .into_iter()
345 .map(|(var_id, weight)| (var_id.0 as c_int, weight))
346 .unzip();
347
348 macros::cpx_lp_result!(unsafe {
349 CPXchgobj(
350 self.env.inner,
351 self.inner,
352 ind.len() as c_int,
353 ind.as_ptr(),
354 val.as_ptr(),
355 )
356 })?;
357
358 self.set_objective_type(ty)
359 }
360
361 pub fn set_objective_type(self, ty: ObjectiveType) -> Result<Self> {
363 macros::cpx_lp_result!(unsafe { CPXchgobjsen(self.env.inner, self.inner, ty.into_raw()) })?;
364 Ok(self)
365 }
366
367 pub fn write<S>(&self, name: S) -> Result<()>
369 where
370 S: AsRef<str>,
371 {
372 let name =
373 CString::new(name.as_ref()).map_err(|e| errors::Input::from_message(e.to_string()))?;
374
375 macros::cpx_lp_result!(unsafe {
376 CPXwriteprob(self.env.inner, self.inner, name.as_ptr(), std::ptr::null())
377 })
378 }
379
380 pub fn add_initial_soln(&mut self, vars: &[VariableId], values: &[f64]) -> Result<()> {
385 if values.len() != vars.len() {
386 return Err(errors::Input::from_message(
387 "number of solution variables and values does not match".to_string(),
388 )
389 .into());
390 }
391 let vars = vars.iter().map(|&u| u.0 as c_int).collect::<Vec<_>>();
392
393 macros::cpx_lp_result!(unsafe {
394 CPXaddmipstarts(
395 self.env.inner,
396 self.inner,
397 1,
398 vars.len() as c_int,
399 &0,
400 vars.as_ptr(),
401 values.as_ptr(),
402 &0,
403 &mut std::ptr::null_mut(),
404 )
405 })
406 }
407
408 pub fn solve_as(self, pt: ProblemType) -> Result<Solution> {
411 macros::cpx_lp_result!(unsafe {
412 CPXchgprobtype(self.env.inner, self.inner, pt.into_raw())
413 })?;
414
415 let start_optim = Instant::now();
416 match pt {
417 ProblemType::MixedInteger => {
418 macros::cpx_lp_result!(unsafe { CPXmipopt(self.env.inner, self.inner) })?
419 }
420 ProblemType::Linear => {
421 macros::cpx_lp_result!(unsafe { CPXlpopt(self.env.inner, self.inner) })?
422 }
423 };
424 let elapsed = start_optim.elapsed();
425 debug!("CPLEX model solution took: {:?}", elapsed);
426
427 let code = unsafe { CPXgetstat(self.env.inner, self.inner) };
428 if code as u32 == CPX_STAT_INFEASIBLE || code as u32 == CPX_STAT_INForUNBD {
429 return Err(crate::errors::Cplex::Unfeasible {
430 code,
431 message: "Unfeasible problem".to_string(),
432 }
433 .into());
434 }
435
436 if code as u32 == CPX_STAT_UNBOUNDED || code as u32 == CPXMIP_UNBOUNDED {
437 return Err(crate::errors::Cplex::Unbounded {
438 code,
439 message: "Unbounded problem".to_string(),
440 }
441 .into());
442 }
443
444 let mut objective_value: f64 = 0.0;
445 macros::cpx_lp_result!(unsafe {
446 CPXgetobjval(self.env.inner, self.inner, &mut objective_value)
447 })?;
448
449 let mut variable_values = vec![0f64; self.variables.len()];
450 macros::cpx_lp_result!(unsafe {
451 CPXgetx(
452 self.env.inner,
453 self.inner,
454 variable_values.as_mut_ptr(),
455 0,
456 self.variables.len() as c_int - 1,
457 )
458 })?;
459
460 Ok(Solution::new(variable_values, objective_value))
461 }
462}
463
464impl Drop for Problem {
465 fn drop(&mut self) {
466 unsafe {
467 assert_eq!(CPXfreeprob(self.env.inner, &mut self.inner), 0);
468 }
469 }
470}
471
472#[cfg(test)]
473mod test {
474 use constants::INFINITY;
475 use constraints::ConstraintType;
476
477 use super::*;
478 use variables::{Variable, VariableType};
479
480 #[test]
481 fn mipex1() {
482 let env = Environment::new().unwrap();
483 let mut problem = Problem::new(env, "mipex1").unwrap();
484
485 let x0 = problem
486 .add_variable(Variable::new(
487 VariableType::Continuous,
488 1.0,
489 0.0,
490 40.0,
491 "x0",
492 ))
493 .unwrap();
494
495 let x1 = problem
496 .add_variable(Variable::new(
497 VariableType::Continuous,
498 2.0,
499 0.0,
500 INFINITY,
501 "x1",
502 ))
503 .unwrap();
504
505 let x2 = problem
506 .add_variable(Variable::new(
507 VariableType::Continuous,
508 3.0,
509 0.0,
510 INFINITY,
511 "x2",
512 ))
513 .unwrap();
514
515 let x3 = problem
516 .add_variable(Variable::new(VariableType::Integer, 1.0, 2.0, 3.0, "x3"))
517 .unwrap();
518
519 assert_eq!(x0, VariableId(0));
520 assert_eq!(x1, VariableId(1));
521 assert_eq!(x2, VariableId(2));
522 assert_eq!(x3, VariableId(3));
523
524 let c0 = problem
525 .add_constraint(Constraint::new(
526 ConstraintType::LessThanEq,
527 20.0,
528 None,
529 vec![(x0, -1.0), (x1, 1.0), (x2, 1.0), (x3, 10.0)],
530 ))
531 .unwrap();
532
533 let c1 = problem
534 .add_constraint(Constraint::new(
535 ConstraintType::LessThanEq,
536 30.0,
537 None,
538 vec![(x0, 1.0), (x1, -3.0), (x2, 1.0)],
539 ))
540 .unwrap();
541
542 let c2 = problem
543 .add_constraint(Constraint::new(
544 ConstraintType::Eq,
545 0.0,
546 None,
547 vec![(x1, 1.0), (x3, -3.5)],
548 ))
549 .unwrap();
550
551 assert_eq!(c0, ConstraintId(0));
552 assert_eq!(c1, ConstraintId(1));
553 assert_eq!(c2, ConstraintId(2));
554
555 let problem = problem.set_objective_type(ObjectiveType::Maximize).unwrap();
556
557 let solution = problem.solve_as(ProblemType::MixedInteger).unwrap();
558
559 assert_eq!(solution.objective_value(), 122.5);
560 }
561
562 #[test]
563 fn mipex1_batch() {
564 let env = Environment::new().unwrap();
565 let mut problem = Problem::new(env, "mipex1").unwrap();
566
567 let vars = problem
568 .add_variables(vec![
569 Variable::new(VariableType::Continuous, 1.0, 0.0, 40.0, "x0"),
570 Variable::new(VariableType::Continuous, 2.0, 0.0, INFINITY, "x1"),
571 Variable::new(VariableType::Continuous, 3.0, 0.0, INFINITY, "x2"),
572 Variable::new(VariableType::Integer, 1.0, 2.0, 3.0, "x3"),
573 ])
574 .unwrap();
575
576 assert_eq!(
577 vars,
578 vec![VariableId(0), VariableId(1), VariableId(2), VariableId(3)]
579 );
580
581 let cons = problem
582 .add_constraints(vec![
583 Constraint::new(
584 ConstraintType::LessThanEq,
585 20.0,
586 None,
587 vec![
588 (vars[0], -1.0),
589 (vars[1], 1.0),
590 (vars[2], 1.0),
591 (vars[3], 10.0),
592 ],
593 ),
594 Constraint::new(
595 ConstraintType::LessThanEq,
596 30.0,
597 None,
598 vec![(vars[0], 1.0), (vars[1], -3.0), (vars[2], 1.0)],
599 ),
600 Constraint::new(
601 ConstraintType::Eq,
602 0.0,
603 None,
604 vec![(vars[1], 1.0), (vars[3], -3.5)],
605 ),
606 ])
607 .unwrap();
608
609 assert_eq!(
610 cons,
611 vec![ConstraintId(0), ConstraintId(1), ConstraintId(2)]
612 );
613
614 let problem = problem.set_objective_type(ObjectiveType::Maximize).unwrap();
615
616 let solution = problem.solve_as(ProblemType::MixedInteger).unwrap();
617
618 assert_eq!(solution.objective_value(), 122.5);
619 }
620
621 #[test]
622 fn unfeasible() {
623 let env = Environment::new().unwrap();
624 let mut problem = Problem::new(env, "unfeasible").unwrap();
625
626 let vars = problem
627 .add_variables(vec![
628 Variable::new(VariableType::Continuous, 1.0, 0.0, 1.0, "x0"),
629 Variable::new(VariableType::Continuous, 1.0, 0.0, 1.0, "x1"),
630 ])
631 .unwrap();
632
633 assert_eq!(vars, vec![VariableId(0), VariableId(1)]);
634
635 let cons = problem
636 .add_constraints(vec![
637 Constraint::new(
638 ConstraintType::Eq,
639 0.0,
640 None,
641 vec![(vars[0], 1.0), (vars[1], 1.0)],
642 ),
643 Constraint::new(
644 ConstraintType::Eq,
645 1.0,
646 None,
647 vec![(vars[0], 1.0), (vars[1], 1.0)],
648 ),
649 ])
650 .unwrap();
651
652 assert_eq!(cons, vec![ConstraintId(0), ConstraintId(1)]);
653
654 let problem = problem.set_objective_type(ObjectiveType::Maximize).unwrap();
655 assert!(matches!(
656 problem.solve_as(ProblemType::Linear),
657 Err(errors::Error::Cplex(errors::Cplex::Unfeasible { .. }))
658 ));
659 }
660
661 #[test]
662 fn unbounded() {
663 let env = Environment::new().unwrap();
664 let mut problem = Problem::new(env, "unbounded").unwrap();
665
666 problem
667 .add_variable(Variable::new(
668 VariableType::Integer,
669 1.0,
670 0.0,
671 INFINITY,
672 "x0",
673 ))
674 .unwrap();
675
676 let problem = problem.set_objective_type(ObjectiveType::Maximize).unwrap();
677
678 assert!(matches!(
679 problem.solve_as(ProblemType::MixedInteger),
680 Err(errors::Error::Cplex(errors::Cplex::Unbounded { .. }))
681 ));
682 }
683}