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(&mut self, start: i32, size: i64, end: i32, name: &str) -> i32 {
247 let c = CString::new(name).unwrap();
248 unsafe { cpmodel_new_interval_var(self.ptr.as_ptr(), start, size, end, c.as_ptr()) }
249 }
250
251 pub fn new_optional_interval_var(
252 &mut self,
253 start: i32,
254 size: i64,
255 end: i32,
256 lit: i32,
257 name: &str,
258 ) -> i32 {
259 let c = CString::new(name).unwrap();
260 unsafe {
261 cpmodel_new_optional_interval_var(
262 self.ptr.as_ptr(),
263 start,
264 size,
265 end,
266 lit,
267 c.as_ptr(),
268 )
269 }
270 }
271
272 pub fn add_circuit(&mut self, tails: &[i32], heads: &[i32], lits: &[i32]) {
279 assert_eq!(tails.len(), heads.len());
280 assert_eq!(tails.len(), lits.len());
281 unsafe {
282 cpmodel_add_circuit(
283 self.ptr.as_ptr(),
284 tails.as_ptr(),
285 heads.as_ptr(),
286 lits.as_ptr(),
287 tails.len(),
288 );
289 }
290 }
291
292 pub fn add_no_overlap(&mut self, intervals: &[i32]) {
293 unsafe {
294 cpmodel_add_no_overlap(self.ptr.as_ptr(), intervals.as_ptr(), intervals.len());
295 }
296 }
297
298 pub fn solve(&self, time_limit_seconds: f64) -> CpSolution {
299 unsafe {
300 CpSolution {
301 ptr: NonNull::new(cpmodel_solve(self.ptr.as_ptr(), time_limit_seconds))
302 .expect("cpmodel_solve returned null"),
303 }
304 }
305 }
306 }
307
308 impl Default for CpModel {
309 fn default() -> Self {
310 Self::new()
311 }
312 }
313
314 impl Drop for CpModel {
315 fn drop(&mut self) {
316 unsafe { cpmodel_free(self.ptr.as_ptr()) }
317 }
318 }
319
320 pub struct CpSolution {
321 ptr: NonNull<CpSolverResponse>,
322 }
323
324 impl CpSolution {
325 pub fn status(&self) -> OrtoolsStatus {
326 unsafe { cpresponse_status(self.ptr.as_ptr()) }
327 }
328 pub fn objective_value(&self) -> i64 {
329 unsafe { cpresponse_objective_value(self.ptr.as_ptr()) }
330 }
331 pub fn value(&self, var_index: i32) -> i64 {
332 unsafe { cpresponse_value(self.ptr.as_ptr(), var_index) }
333 }
334 pub fn wall_time(&self) -> f64 {
335 unsafe { cpresponse_wall_time(self.ptr.as_ptr()) }
336 }
337 }
338
339 impl Drop for CpSolution {
340 fn drop(&mut self) {
341 unsafe { cpresponse_free(self.ptr.as_ptr()) }
342 }
343 }
344
345 pub struct LinearSolver {
346 ptr: NonNull<MpSolver>,
347 }
348
349 impl LinearSolver {
350 pub fn new_glop(name: &str) -> Self {
351 let c = CString::new(name).unwrap();
352 unsafe {
353 Self {
354 ptr: NonNull::new(mpsolver_new(c.as_ptr(), LpSolverType::Glop))
355 .expect("mpsolver_new returned null"),
356 }
357 }
358 }
359
360 pub fn num_var(&mut self, lb: f64, ub: f64, name: &str) -> i32 {
361 let c = CString::new(name).unwrap();
362 unsafe { mpsolver_num_var(self.ptr.as_ptr(), lb, ub, c.as_ptr()) }
363 }
364
365 pub fn add_constraint(&mut self, lb: f64, ub: f64, name: &str) -> i32 {
366 let c = CString::new(name).unwrap();
367 unsafe { mpsolver_add_constraint(self.ptr.as_ptr(), lb, ub, c.as_ptr()) }
368 }
369
370 pub fn set_constraint_coeff(&mut self, ci: i32, vi: i32, coeff: f64) {
371 unsafe { mpsolver_set_constraint_coeff(self.ptr.as_ptr(), ci, vi, coeff) }
372 }
373
374 pub fn set_objective_coeff(&mut self, vi: i32, coeff: f64) {
375 unsafe { mpsolver_set_objective_coeff(self.ptr.as_ptr(), vi, coeff) }
376 }
377
378 pub fn maximize(&mut self) {
379 unsafe { mpsolver_maximize(self.ptr.as_ptr()) }
380 }
381 pub fn minimize(&mut self) {
382 unsafe { mpsolver_minimize(self.ptr.as_ptr()) }
383 }
384
385 pub fn solve(&mut self) -> OrtoolsStatus {
386 unsafe { mpsolver_solve(self.ptr.as_ptr()) }
387 }
388
389 pub fn objective_value(&self) -> f64 {
390 unsafe { mpsolver_objective_value(self.ptr.as_ptr()) }
391 }
392
393 pub fn var_value(&self, vi: i32) -> f64 {
394 unsafe { mpsolver_var_value(self.ptr.as_ptr(), vi) }
395 }
396 }
397
398 impl Drop for LinearSolver {
399 fn drop(&mut self) {
400 unsafe { mpsolver_free(self.ptr.as_ptr()) }
401 }
402 }
403}
404
405#[cfg(test)]
408mod tests {
409 use super::*;
410
411 #[test]
412 fn status_values() {
413 assert_eq!(OrtoolsStatus::Optimal as i32, 1);
414 assert!(OrtoolsStatus::Optimal.is_success());
415 assert!(!OrtoolsStatus::Infeasible.is_success());
416 }
417
418 #[cfg(feature = "link")]
419 mod integration {
420 use super::super::safe::*;
421
422 #[test]
423 fn cpsat_minimize() {
424 let mut m = CpModel::new();
425 let x = m.new_int_var(0, 10, "x");
426 let y = m.new_int_var(0, 10, "y");
427 m.add_linear_eq(&[x, y], &[1, 1], 10);
428 m.minimize(&[x], &[1]);
429 let s = m.solve(60.0);
430 assert!(s.status().is_success());
431 assert_eq!(s.value(x), 0);
432 assert_eq!(s.value(y), 10);
433 }
434
435 #[test]
436 fn cpsat_all_different() {
437 let mut model = CpModel::new();
438 let va = model.new_int_var(1, 3, "a");
439 let vb = model.new_int_var(1, 3, "b");
440 let vc = model.new_int_var(1, 3, "c");
441 model.add_all_different(&[va, vb, vc]);
442 let sol = model.solve(60.0);
443 assert!(sol.status().is_success());
444 let vals = [sol.value(va), sol.value(vb), sol.value(vc)];
445 assert!(vals.contains(&1) && vals.contains(&2) && vals.contains(&3));
446 }
447
448 #[test]
449 fn glop_maximize() {
450 let mut s = LinearSolver::new_glop("test");
451 let x = s.num_var(0.0, f64::INFINITY, "x");
452 let y = s.num_var(0.0, f64::INFINITY, "y");
453 let c = s.add_constraint(f64::NEG_INFINITY, 10.0, "c1");
454 s.set_constraint_coeff(c, x, 1.0);
455 s.set_constraint_coeff(c, y, 1.0);
456 s.set_objective_coeff(x, 1.0);
457 s.set_objective_coeff(y, 1.0);
458 s.maximize();
459 assert!(s.solve().is_success());
460 assert!((s.objective_value() - 10.0).abs() < 1e-6);
461 }
462 }
463}