1#![allow(non_upper_case_globals, non_camel_case_types, non_snake_case)]
2
3#[cfg(feature = "link")]
4use std::os::raw::{c_char, c_double, c_int};
5
6#[repr(i32)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum OrtoolsStatus {
11 Unknown = 0,
12 Optimal = 1,
13 Feasible = 2,
14 Infeasible = 3,
15 Unbounded = 4,
16 ModelInvalid = 5,
17 Error = 6,
18}
19
20impl OrtoolsStatus {
21 pub fn is_success(self) -> bool {
22 matches!(self, Self::Optimal | Self::Feasible)
23 }
24}
25
26#[repr(C)]
29pub struct CpModelBuilder {
30 _p: [u8; 0],
31}
32#[repr(C)]
33pub struct CpSolverResponse {
34 _p: [u8; 0],
35}
36
37#[repr(i32)]
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum LpSolverType {
40 Glop = 0,
41}
42
43#[repr(C)]
44pub struct MpSolver {
45 _p: [u8; 0],
46}
47
48#[cfg(feature = "link")]
51unsafe extern "C" {
52 pub fn cpmodel_new() -> *mut CpModelBuilder;
53 pub fn cpmodel_free(m: *mut CpModelBuilder);
54 pub fn cpmodel_new_int_var(
55 m: *mut CpModelBuilder,
56 lb: i64,
57 ub: i64,
58 name: *const c_char,
59 ) -> i32;
60 pub fn cpmodel_new_bool_var(m: *mut CpModelBuilder, name: *const c_char) -> i32;
61 pub fn cpmodel_add_linear_le(
62 m: *mut CpModelBuilder,
63 idx: *const i32,
64 c: *const i64,
65 n: usize,
66 rhs: i64,
67 );
68 pub fn cpmodel_add_linear_ge(
69 m: *mut CpModelBuilder,
70 idx: *const i32,
71 c: *const i64,
72 n: usize,
73 rhs: i64,
74 );
75 pub fn cpmodel_add_linear_eq(
76 m: *mut CpModelBuilder,
77 idx: *const i32,
78 c: *const i64,
79 n: usize,
80 rhs: i64,
81 );
82 pub fn cpmodel_add_all_different(m: *mut CpModelBuilder, idx: *const i32, n: usize);
83 pub fn cpmodel_minimize(m: *mut CpModelBuilder, idx: *const i32, c: *const i64, n: usize);
84 pub fn cpmodel_maximize(m: *mut CpModelBuilder, idx: *const i32, c: *const i64, n: usize);
85 pub fn cpmodel_solve(m: *mut CpModelBuilder, time_limit: c_double) -> *mut CpSolverResponse;
86 pub fn cpresponse_status(r: *const CpSolverResponse) -> OrtoolsStatus;
87 pub fn cpresponse_objective_value(r: *const CpSolverResponse) -> i64;
88 pub fn cpresponse_value(r: *const CpSolverResponse, var_index: i32) -> i64;
89 pub fn cpresponse_wall_time(r: *const CpSolverResponse) -> c_double;
90 pub fn cpresponse_free(r: *mut CpSolverResponse);
91
92 pub fn cpmodel_new_interval_var(
93 m: *mut CpModelBuilder,
94 start: c_int,
95 size: i64,
96 end: c_int,
97 name: *const c_char,
98 ) -> c_int;
99 pub fn cpmodel_new_optional_interval_var(
100 m: *mut CpModelBuilder,
101 start: c_int,
102 size: i64,
103 end: c_int,
104 lit: c_int,
105 name: *const c_char,
106 ) -> c_int;
107 pub fn cpmodel_add_circuit(
108 m: *mut CpModelBuilder,
109 tails: *const c_int,
110 heads: *const c_int,
111 lits: *const c_int,
112 n: usize,
113 );
114 pub fn cpmodel_add_no_overlap(m: *mut CpModelBuilder, idx: *const c_int, n: usize);
115
116 pub fn mpsolver_new(name: *const c_char, t: LpSolverType) -> *mut MpSolver;
117 pub fn mpsolver_free(s: *mut MpSolver);
118 pub fn mpsolver_num_var(
119 s: *mut MpSolver,
120 lb: c_double,
121 ub: c_double,
122 name: *const c_char,
123 ) -> i32;
124 pub fn mpsolver_int_var(
125 s: *mut MpSolver,
126 lb: c_double,
127 ub: c_double,
128 name: *const c_char,
129 ) -> i32;
130 pub fn mpsolver_bool_var(s: *mut MpSolver, name: *const c_char) -> i32;
131 pub fn mpsolver_add_constraint(
132 s: *mut MpSolver,
133 lb: c_double,
134 ub: c_double,
135 name: *const c_char,
136 ) -> i32;
137 pub fn mpsolver_set_constraint_coeff(s: *mut MpSolver, ci: c_int, vi: c_int, coeff: c_double);
138 pub fn mpsolver_set_objective_coeff(s: *mut MpSolver, vi: c_int, coeff: c_double);
139 pub fn mpsolver_minimize(s: *mut MpSolver);
140 pub fn mpsolver_maximize(s: *mut MpSolver);
141 pub fn mpsolver_solve(s: *mut MpSolver) -> OrtoolsStatus;
142 pub fn mpsolver_objective_value(s: *const MpSolver) -> c_double;
143 pub fn mpsolver_var_value(s: *const MpSolver, vi: c_int) -> c_double;
144}
145
146#[cfg(feature = "link")]
149pub mod safe {
150 #[allow(clippy::wildcard_imports)]
151 use super::*;
152 use std::{ffi::CString, ptr::NonNull};
153
154 pub struct CpModel {
155 ptr: NonNull<CpModelBuilder>,
156 }
157
158 impl CpModel {
159 pub fn new() -> Self {
160 unsafe {
161 Self {
162 ptr: NonNull::new(cpmodel_new()).expect("cpmodel_new returned null"),
163 }
164 }
165 }
166
167 pub fn new_int_var(&mut self, lb: i64, ub: i64, name: &str) -> i32 {
168 let c = CString::new(name).unwrap();
169 unsafe { cpmodel_new_int_var(self.ptr.as_ptr(), lb, ub, c.as_ptr()) }
170 }
171
172 pub fn new_bool_var(&mut self, name: &str) -> i32 {
173 let c = CString::new(name).unwrap();
174 unsafe { cpmodel_new_bool_var(self.ptr.as_ptr(), c.as_ptr()) }
175 }
176
177 pub fn add_linear_le(&mut self, vars: &[i32], coeffs: &[i64], rhs: i64) {
178 assert_eq!(vars.len(), coeffs.len());
179 unsafe {
180 cpmodel_add_linear_le(
181 self.ptr.as_ptr(),
182 vars.as_ptr(),
183 coeffs.as_ptr(),
184 vars.len(),
185 rhs,
186 );
187 }
188 }
189
190 pub fn add_linear_ge(&mut self, vars: &[i32], coeffs: &[i64], rhs: i64) {
191 assert_eq!(vars.len(), coeffs.len());
192 unsafe {
193 cpmodel_add_linear_ge(
194 self.ptr.as_ptr(),
195 vars.as_ptr(),
196 coeffs.as_ptr(),
197 vars.len(),
198 rhs,
199 );
200 }
201 }
202
203 pub fn add_linear_eq(&mut self, vars: &[i32], coeffs: &[i64], rhs: i64) {
204 assert_eq!(vars.len(), coeffs.len());
205 unsafe {
206 cpmodel_add_linear_eq(
207 self.ptr.as_ptr(),
208 vars.as_ptr(),
209 coeffs.as_ptr(),
210 vars.len(),
211 rhs,
212 );
213 }
214 }
215
216 pub fn add_all_different(&mut self, vars: &[i32]) {
217 unsafe {
218 cpmodel_add_all_different(self.ptr.as_ptr(), vars.as_ptr(), vars.len());
219 }
220 }
221
222 pub fn minimize(&mut self, vars: &[i32], coeffs: &[i64]) {
223 assert_eq!(vars.len(), coeffs.len());
224 unsafe {
225 cpmodel_minimize(
226 self.ptr.as_ptr(),
227 vars.as_ptr(),
228 coeffs.as_ptr(),
229 vars.len(),
230 );
231 }
232 }
233
234 pub fn maximize(&mut self, vars: &[i32], coeffs: &[i64]) {
235 assert_eq!(vars.len(), coeffs.len());
236 unsafe {
237 cpmodel_maximize(
238 self.ptr.as_ptr(),
239 vars.as_ptr(),
240 coeffs.as_ptr(),
241 vars.len(),
242 );
243 }
244 }
245
246 pub fn new_fixed_interval_var(
247 &mut self,
248 start: i32,
249 size: i64,
250 end: i32,
251 name: &str,
252 ) -> i32 {
253 let c = CString::new(name).unwrap();
254 unsafe { cpmodel_new_interval_var(self.ptr.as_ptr(), start, size, end, c.as_ptr()) }
255 }
256
257 pub fn new_optional_interval_var(
258 &mut self,
259 start: i32,
260 size: i64,
261 end: i32,
262 lit: i32,
263 name: &str,
264 ) -> i32 {
265 let c = CString::new(name).unwrap();
266 unsafe {
267 cpmodel_new_optional_interval_var(
268 self.ptr.as_ptr(),
269 start,
270 size,
271 end,
272 lit,
273 c.as_ptr(),
274 )
275 }
276 }
277
278 pub fn add_circuit(&mut self, tails: &[i32], heads: &[i32], lits: &[i32]) {
285 assert_eq!(tails.len(), heads.len());
286 assert_eq!(tails.len(), lits.len());
287 unsafe {
288 cpmodel_add_circuit(
289 self.ptr.as_ptr(),
290 tails.as_ptr(),
291 heads.as_ptr(),
292 lits.as_ptr(),
293 tails.len(),
294 );
295 }
296 }
297
298 pub fn add_no_overlap(&mut self, intervals: &[i32]) {
299 unsafe {
300 cpmodel_add_no_overlap(self.ptr.as_ptr(), intervals.as_ptr(), intervals.len());
301 }
302 }
303
304 pub fn solve(&self, time_limit_seconds: f64) -> CpSolution {
305 unsafe {
306 CpSolution {
307 ptr: NonNull::new(cpmodel_solve(self.ptr.as_ptr(), time_limit_seconds))
308 .expect("cpmodel_solve returned null"),
309 }
310 }
311 }
312 }
313
314 impl Default for CpModel {
315 fn default() -> Self {
316 Self::new()
317 }
318 }
319
320 impl Drop for CpModel {
321 fn drop(&mut self) {
322 unsafe { cpmodel_free(self.ptr.as_ptr()) }
323 }
324 }
325
326 pub struct CpSolution {
327 ptr: NonNull<CpSolverResponse>,
328 }
329
330 impl CpSolution {
331 pub fn status(&self) -> OrtoolsStatus {
332 unsafe { cpresponse_status(self.ptr.as_ptr()) }
333 }
334 pub fn objective_value(&self) -> i64 {
335 unsafe { cpresponse_objective_value(self.ptr.as_ptr()) }
336 }
337 pub fn value(&self, var_index: i32) -> i64 {
338 unsafe { cpresponse_value(self.ptr.as_ptr(), var_index) }
339 }
340 pub fn wall_time(&self) -> f64 {
341 unsafe { cpresponse_wall_time(self.ptr.as_ptr()) }
342 }
343 }
344
345 impl Drop for CpSolution {
346 fn drop(&mut self) {
347 unsafe { cpresponse_free(self.ptr.as_ptr()) }
348 }
349 }
350
351 pub struct LinearSolver {
352 ptr: NonNull<MpSolver>,
353 }
354
355 impl LinearSolver {
356 pub fn new_glop(name: &str) -> Self {
357 let c = CString::new(name).unwrap();
358 unsafe {
359 Self {
360 ptr: NonNull::new(mpsolver_new(c.as_ptr(), LpSolverType::Glop))
361 .expect("mpsolver_new returned null"),
362 }
363 }
364 }
365
366 pub fn num_var(&mut self, lb: f64, ub: f64, name: &str) -> i32 {
367 let c = CString::new(name).unwrap();
368 unsafe { mpsolver_num_var(self.ptr.as_ptr(), lb, ub, c.as_ptr()) }
369 }
370
371 pub fn add_constraint(&mut self, lb: f64, ub: f64, name: &str) -> i32 {
372 let c = CString::new(name).unwrap();
373 unsafe { mpsolver_add_constraint(self.ptr.as_ptr(), lb, ub, c.as_ptr()) }
374 }
375
376 pub fn set_constraint_coeff(&mut self, ci: i32, vi: i32, coeff: f64) {
377 unsafe { mpsolver_set_constraint_coeff(self.ptr.as_ptr(), ci, vi, coeff) }
378 }
379
380 pub fn set_objective_coeff(&mut self, vi: i32, coeff: f64) {
381 unsafe { mpsolver_set_objective_coeff(self.ptr.as_ptr(), vi, coeff) }
382 }
383
384 pub fn maximize(&mut self) {
385 unsafe { mpsolver_maximize(self.ptr.as_ptr()) }
386 }
387 pub fn minimize(&mut self) {
388 unsafe { mpsolver_minimize(self.ptr.as_ptr()) }
389 }
390
391 pub fn solve(&mut self) -> OrtoolsStatus {
392 unsafe { mpsolver_solve(self.ptr.as_ptr()) }
393 }
394
395 pub fn objective_value(&self) -> f64 {
396 unsafe { mpsolver_objective_value(self.ptr.as_ptr()) }
397 }
398
399 pub fn var_value(&self, vi: i32) -> f64 {
400 unsafe { mpsolver_var_value(self.ptr.as_ptr(), vi) }
401 }
402 }
403
404 impl Drop for LinearSolver {
405 fn drop(&mut self) {
406 unsafe { mpsolver_free(self.ptr.as_ptr()) }
407 }
408 }
409}
410
411#[cfg(test)]
414mod tests {
415 use super::*;
416
417 #[test]
418 fn status_values() {
419 assert_eq!(OrtoolsStatus::Optimal as i32, 1);
420 assert!(OrtoolsStatus::Optimal.is_success());
421 assert!(!OrtoolsStatus::Infeasible.is_success());
422 }
423
424 #[cfg(feature = "link")]
425 mod integration {
426 use super::super::safe::*;
427
428 #[test]
429 fn cpsat_minimize() {
430 let mut m = CpModel::new();
431 let x = m.new_int_var(0, 10, "x");
432 let y = m.new_int_var(0, 10, "y");
433 m.add_linear_eq(&[x, y], &[1, 1], 10);
434 m.minimize(&[x], &[1]);
435 let s = m.solve(60.0);
436 assert!(s.status().is_success());
437 assert_eq!(s.value(x), 0);
438 assert_eq!(s.value(y), 10);
439 }
440
441 #[test]
442 fn cpsat_all_different() {
443 let mut model = CpModel::new();
444 let va = model.new_int_var(1, 3, "a");
445 let vb = model.new_int_var(1, 3, "b");
446 let vc = model.new_int_var(1, 3, "c");
447 model.add_all_different(&[va, vb, vc]);
448 let sol = model.solve(60.0);
449 assert!(sol.status().is_success());
450 let vals = [sol.value(va), sol.value(vb), sol.value(vc)];
451 assert!(vals.contains(&1) && vals.contains(&2) && vals.contains(&3));
452 }
453
454 #[test]
455 fn glop_maximize() {
456 let mut s = LinearSolver::new_glop("test");
457 let x = s.num_var(0.0, f64::INFINITY, "x");
458 let y = s.num_var(0.0, f64::INFINITY, "y");
459 let c = s.add_constraint(f64::NEG_INFINITY, 10.0, "c1");
460 s.set_constraint_coeff(c, x, 1.0);
461 s.set_constraint_coeff(c, y, 1.0);
462 s.set_objective_coeff(x, 1.0);
463 s.set_objective_coeff(y, 1.0);
464 s.maximize();
465 assert!(s.solve().is_success());
466 assert!((s.objective_value() - 10.0).abs() < 1e-6);
467 }
468 }
469}