1use std::cell::{Cell, Ref, RefCell};
2use std::marker::PhantomData;
3
4use oximo_expr::{Expr, ExprArena, ExprClass, ParamId, VarId, classify};
5use rustc_hash::FxHashMap;
6use smol_str::SmolStr;
7
8use crate::constraint::{Constraint, ConstraintExpr, ConstraintId};
9use crate::domain::Domain;
10use crate::error::{Error, Result};
11use crate::indexed::{IndexedVar, Storage, grid_offset};
12use crate::objective::{Objective, ObjectiveSense};
13use crate::param::Parameter;
14use crate::set::{Axis, FromIndexKey, IndexKey, Set};
15use crate::var::{VarBuilder, Variable};
16
17#[derive(Copy, Clone, Debug, PartialEq, Eq)]
22pub enum ModelKind {
23 LP,
24 MILP,
25 QP,
26 MIQP,
27 NLP,
28 MINLP,
29}
30
31pub struct Model {
40 pub name: String,
41 pub(crate) arena: RefCell<ExprArena>,
42 pub(crate) variables: RefCell<Vec<Variable>>,
43 pub(crate) var_names: RefCell<FxHashMap<SmolStr, VarId>>,
44 pub(crate) parameters: RefCell<Vec<Parameter>>,
45 pub(crate) param_names: RefCell<FxHashMap<SmolStr, ParamId>>,
46 pub(crate) constraints: RefCell<Vec<Constraint>>,
47 pub(crate) constraint_names: RefCell<FxHashMap<SmolStr, ConstraintId>>,
48 pub(crate) objective: RefCell<Option<Objective>>,
49 cached_kind: RefCell<Option<ModelKind>>,
50 auto_seq: Cell<u32>,
53}
54
55impl std::fmt::Debug for Model {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.debug_struct("Model")
58 .field("name", &self.name)
59 .field("vars", &self.variables.borrow().len())
60 .field("params", &self.parameters.borrow().len())
61 .field("constraints", &self.constraints.borrow().len())
62 .field("has_objective", &self.objective.borrow().is_some())
63 .finish()
64 }
65}
66
67impl Model {
68 pub fn new(name: impl Into<String>) -> Self {
69 Self {
70 name: name.into(),
71 arena: RefCell::new(ExprArena::new()),
72 variables: RefCell::new(Vec::new()),
73 var_names: RefCell::new(FxHashMap::default()),
74 parameters: RefCell::new(Vec::new()),
75 param_names: RefCell::new(FxHashMap::default()),
76 constraints: RefCell::new(Vec::new()),
77 constraint_names: RefCell::new(FxHashMap::default()),
78 objective: RefCell::new(None),
79 cached_kind: RefCell::new(None),
80 auto_seq: Cell::new(0),
81 }
82 }
83
84 #[deprecated(
87 since = "0.3.0",
88 note = "use the `variable!` macro, the builder API is scheduled for removal in 0.4.0"
89 )]
90 pub fn var(&self, name: impl Into<SmolStr>) -> VarBuilder<'_> {
91 self.__var(name)
92 }
93
94 #[doc(hidden)]
97 pub fn __var(&self, name: impl Into<SmolStr>) -> VarBuilder<'_> {
98 VarBuilder {
99 model: self,
100 name: name.into(),
101 lb: f64::NEG_INFINITY,
102 ub: f64::INFINITY,
103 domain: Domain::Real,
104 initial: None,
105 }
106 }
107
108 pub(crate) fn register_var<'a>(&'a self, b: VarBuilder<'a>) -> Expr<'a> {
111 let mut names = self.var_names.borrow_mut();
112 assert!(!names.contains_key(&b.name), "variable name {:?} already registered", b.name);
113 let mut vars = self.variables.borrow_mut();
114 let id = VarId(u32::try_from(vars.len()).expect("variable count overflow"));
115 vars.push(Variable {
116 id,
117 name: b.name.clone(),
118 domain: b.domain,
119 lb: b.lb,
120 ub: b.ub,
121 initial: b.initial,
122 });
123 names.insert(b.name, id);
124 drop(vars);
125 drop(names);
126 *self.cached_kind.borrow_mut() = None;
127 Expr::from_var(&self.arena, id)
128 }
129
130 #[deprecated(
131 since = "0.3.0",
132 note = "use the `variable!` macro, the builder API is scheduled for removal in 0.4.0"
133 )]
134 pub fn indexed_var<'a, K>(
135 &'a self,
136 name: impl Into<String>,
137 set: &Set<K>,
138 ) -> IndexedVarBuilder<'a, K> {
139 self.__indexed_var(name, set)
140 }
141
142 #[doc(hidden)]
145 pub fn __indexed_var<'a, K>(
146 &'a self,
147 name: impl Into<String>,
148 set: &Set<K>,
149 ) -> IndexedVarBuilder<'a, K> {
150 IndexedVarBuilder {
151 model: self,
152 base_name: name.into(),
153 keys: set.iter().collect(),
154 axes: set.axes().map(Box::from),
155 lb: f64::NEG_INFINITY,
156 ub: f64::INFINITY,
157 lb_by: None,
158 ub_by: None,
159 domain: Domain::Real,
160 _k: PhantomData,
161 }
162 }
163
164 pub fn variable_id(&self, name: &str) -> Option<VarId> {
165 self.var_names.borrow().get(name).copied()
166 }
167
168 pub fn variables(&self) -> Ref<'_, Vec<Variable>> {
169 self.variables.borrow()
170 }
171
172 pub fn arena(&self) -> Ref<'_, ExprArena> {
173 self.arena.borrow()
174 }
175
176 pub fn num_variables(&self) -> usize {
177 self.variables.borrow().len()
178 }
179
180 pub fn fix(&self, e: Expr<'_>, value: f64) {
188 let id = e.var_id().expect("Model::fix expects a single-variable expression");
189 self.fix_var(id, value);
190 }
191
192 pub fn fix_var(&self, id: VarId, value: f64) {
194 let mut vars = self.variables.borrow_mut();
195 let v = &mut vars[id.index()];
196 v.lb = value;
197 v.ub = value;
198 }
199
200 pub fn set_initial(&self, e: Expr<'_>, value: f64) {
208 let id = e.var_id().expect("Model::set_initial expects a single-variable expression");
209 self.variables.borrow_mut()[id.index()].initial = Some(value);
210 }
211
212 pub fn unfix_var(&self, id: VarId, lb: f64, ub: f64) {
215 let mut vars = self.variables.borrow_mut();
216 let v = &mut vars[id.index()];
217 v.lb = lb;
218 v.ub = ub;
219 }
220
221 #[deprecated(
235 since = "0.3.0",
236 note = "use the `param!` macro, the builder API is scheduled for removal in 0.4.0"
237 )]
238 pub fn param<'a>(&'a self, name: impl Into<SmolStr>, value: f64) -> Expr<'a> {
239 self.__param(name, value)
240 }
241
242 #[doc(hidden)]
245 pub fn __param<'a>(&'a self, name: impl Into<SmolStr>, value: f64) -> Expr<'a> {
246 let name = name.into();
247 assert!(
248 !self.param_names.borrow().contains_key(&name),
249 "parameter name {name:?} already registered"
250 );
251 let (id, node) = {
252 let mut a = self.arena.borrow_mut();
253 let id = a.new_param(value);
254 (id, a.param(id))
255 };
256 self.parameters.borrow_mut().push(Parameter { id, name: name.clone() });
257 self.param_names.borrow_mut().insert(name, id);
258 *self.cached_kind.borrow_mut() = None;
259 Expr::new(node, &self.arena)
260 }
261
262 pub fn set_param(&self, p: Expr<'_>, value: f64) {
268 let id = p.param_id().expect("Model::set_param expects a single-parameter expression");
269 self.set_param_id(id, value);
270 }
271
272 pub fn set_param_id(&self, id: ParamId, value: f64) {
277 self.arena.borrow_mut().set_param_value(id, value);
278 *self.cached_kind.borrow_mut() = None;
279 }
280
281 pub fn param_value(&self, id: ParamId) -> f64 {
287 self.arena.borrow().param_value(id)
288 }
289
290 pub fn param_value_of(&self, p: Expr<'_>) -> Option<f64> {
293 p.param_id().map(|id| self.param_value(id))
294 }
295
296 pub fn parameter_id(&self, name: &str) -> Option<ParamId> {
297 self.param_names.borrow().get(name).copied()
298 }
299
300 pub fn parameters(&self) -> Ref<'_, Vec<Parameter>> {
301 self.parameters.borrow()
302 }
303
304 pub fn num_parameters(&self) -> usize {
305 self.parameters.borrow().len()
306 }
307
308 #[deprecated(
317 since = "0.3.0",
318 note = "use the `constraint!` macro, the builder API is scheduled for removal in 0.4.0"
319 )]
320 pub fn constraint(&self, name: impl Into<SmolStr>, c: ConstraintExpr<'_>) -> ConstraintId {
321 self.__add_constraint(name, c)
322 }
323
324 #[doc(hidden)]
327 pub fn __add_constraint(
328 &self,
329 name: impl Into<SmolStr>,
330 c: ConstraintExpr<'_>,
331 ) -> ConstraintId {
332 let name = name.into();
333 let mut by_name = self.constraint_names.borrow_mut();
334 assert!(!by_name.contains_key(&name), "constraint name {name:?} already registered");
335 let mut all = self.constraints.borrow_mut();
336 let id = ConstraintId(u32::try_from(all.len()).expect("constraint count overflow"));
337 all.push(Constraint {
338 name: name.clone(),
339 lhs: c.lhs.id,
340 sense: c.sense,
341 rhs: c.rhs,
342 active: true,
343 });
344 by_name.insert(name, id);
345 *self.cached_kind.borrow_mut() = None;
346 id
347 }
348
349 #[doc(hidden)]
352 pub fn __add_constraint_auto(&self, c: ConstraintExpr<'_>) -> ConstraintId {
353 let name = loop {
355 let n = self.auto_seq.get();
356 self.auto_seq.set(n + 1);
357 let candidate: SmolStr = format!("_c{n}").into();
358 if !self.constraint_names.borrow().contains_key(&candidate) {
359 break candidate;
360 }
361 };
362 self.__add_constraint(name, c)
363 }
364
365 pub fn add_constraints<'a, I>(&'a self, items: I)
368 where
369 I: IntoIterator<Item = (SmolStr, ConstraintExpr<'a>)>,
370 {
371 for (name, c) in items {
372 self.__add_constraint(name, c);
373 }
374 }
375
376 #[deprecated(
394 since = "0.3.0",
395 note = "use the indexed-family form of the `constraint!` macro, the builder API is scheduled for removal in 0.4.0"
396 )]
397 pub fn add_constraints_over<'a, K, F>(&'a self, name_prefix: &str, set: &Set<K>, rule: F)
398 where
399 K: FromIndexKey,
400 F: FnMut(K) -> ConstraintExpr<'a>,
401 {
402 self.__add_constraints_over(name_prefix, set, rule);
403 }
404
405 #[doc(hidden)]
409 pub fn __add_constraints_over<'a, K, F>(&'a self, name_prefix: &str, set: &Set<K>, mut rule: F)
410 where
411 K: FromIndexKey,
412 F: FnMut(K) -> ConstraintExpr<'a>,
413 {
414 for key in set {
415 let typed = K::from_index_key(&key);
416 let c = rule(typed);
417 let name: SmolStr = format_index_name(name_prefix, &key).into();
418 self.__add_constraint(name, c);
419 }
420 }
421
422 #[doc(hidden)]
424 pub fn __add_range_constraints_over<'a, K, F>(&'a self, name: &str, set: &Set<K>, mut rule: F)
425 where
426 K: FromIndexKey,
427 F: FnMut(K) -> (ConstraintExpr<'a>, ConstraintExpr<'a>),
428 {
429 let lo_prefix = format!("{name}_lo");
430 let hi_prefix = format!("{name}_hi");
431 for key in set {
432 let (lo, hi) = rule(K::from_index_key(&key));
433 self.__add_constraint(format_index_name(&lo_prefix, &key), lo);
434 self.__add_constraint(format_index_name(&hi_prefix, &key), hi);
435 }
436 }
437
438 pub fn constraints(&self) -> Ref<'_, Vec<Constraint>> {
439 self.constraints.borrow()
440 }
441
442 pub fn num_constraints(&self) -> usize {
443 self.constraints.borrow().len()
444 }
445
446 pub fn constraint_id(&self, name: &str) -> Option<ConstraintId> {
447 self.constraint_names.borrow().get(name).copied()
448 }
449
450 #[deprecated(
453 since = "0.3.0",
454 note = "use `objective!(m, Min, ..)`, the builder API is scheduled for removal in 0.4.0"
455 )]
456 pub fn minimize(&self, expr: Expr<'_>) {
457 self.__minimize(expr);
458 }
459
460 #[deprecated(
461 since = "0.3.0",
462 note = "use `objective!(m, Max, ..)`, the builder API is scheduled for removal in 0.4.0"
463 )]
464 pub fn maximize(&self, expr: Expr<'_>) {
465 self.__maximize(expr);
466 }
467
468 #[doc(hidden)]
470 pub fn __minimize(&self, expr: Expr<'_>) {
471 self.set_objective(expr, ObjectiveSense::Minimize);
472 }
473
474 #[doc(hidden)]
476 pub fn __maximize(&self, expr: Expr<'_>) {
477 self.set_objective(expr, ObjectiveSense::Maximize);
478 }
479
480 fn set_objective(&self, expr: Expr<'_>, sense: ObjectiveSense) {
481 *self.objective.borrow_mut() = Some(Objective { expr: expr.id, sense });
482 *self.cached_kind.borrow_mut() = None;
483 }
484
485 pub fn objective(&self) -> Ref<'_, Option<Objective>> {
486 self.objective.borrow()
487 }
488
489 pub fn try_objective(&self) -> Result<Objective> {
495 self.objective.borrow().clone().ok_or(Error::NoObjective)
496 }
497
498 pub fn kind(&self) -> ModelKind {
504 if let Some(k) = *self.cached_kind.borrow() {
505 return k;
506 }
507 let arena = self.arena.borrow();
508 let has_int = self.variables.borrow().iter().any(|v| v.domain.is_integer());
509
510 let mut class = ExprClass::Linear;
513 if let Some(o) = self.objective.borrow().as_ref() {
514 class = class.max(classify(&arena, o.expr));
515 }
516 for c in self.constraints.borrow().iter() {
517 if class == ExprClass::Nonlinear {
518 break;
519 }
520 class = class.max(classify(&arena, c.lhs));
521 }
522
523 let k = match (has_int, class) {
524 (false, ExprClass::Linear) => ModelKind::LP,
525 (true, ExprClass::Linear) => ModelKind::MILP,
526 (false, ExprClass::Quadratic) => ModelKind::QP,
527 (true, ExprClass::Quadratic) => ModelKind::MIQP,
528 (false, ExprClass::Nonlinear) => ModelKind::NLP,
529 (true, ExprClass::Nonlinear) => ModelKind::MINLP,
530 };
531 *self.cached_kind.borrow_mut() = Some(k);
532 k
533 }
534}
535
536type BoundFn<'a> = Box<dyn Fn(&IndexKey) -> f64 + 'a>;
545
546#[must_use = "IndexedVarBuilder does nothing until you call .build()"]
547pub struct IndexedVarBuilder<'a, K = IndexKey> {
548 model: &'a Model,
549 base_name: String,
550 keys: Vec<IndexKey>,
551 axes: Option<Box<[Axis]>>,
552 lb: f64,
553 ub: f64,
554 lb_by: Option<BoundFn<'a>>,
555 ub_by: Option<BoundFn<'a>>,
556 domain: Domain,
557 _k: PhantomData<fn() -> K>,
558}
559
560impl<'a, K> std::fmt::Debug for IndexedVarBuilder<'a, K> {
561 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
562 f.debug_struct("IndexedVarBuilder")
563 .field("base_name", &self.base_name)
564 .field("keys", &self.keys.len())
565 .field("lb", &self.lb)
566 .field("ub", &self.ub)
567 .field("per_key_lb", &self.lb_by.is_some())
568 .field("per_key_ub", &self.ub_by.is_some())
569 .field("domain", &self.domain)
570 .finish()
571 }
572}
573
574impl<'a, K> IndexedVarBuilder<'a, K> {
575 pub fn lb(mut self, v: f64) -> Self {
576 self.lb = v;
577 self
578 }
579 pub fn ub(mut self, v: f64) -> Self {
580 self.ub = v;
581 self
582 }
583 pub fn bounds(mut self, lb: f64, ub: f64) -> Self {
584 self.lb = lb;
585 self.ub = ub;
586 self
587 }
588 pub fn lb_by<F>(mut self, f: F) -> Self
597 where
598 K: FromIndexKey,
599 F: Fn(K) -> f64 + 'a,
600 {
601 self.lb_by = Some(Box::new(move |k: &IndexKey| f(K::from_index_key(k))));
602 self
603 }
604 pub fn ub_by<F>(mut self, f: F) -> Self
613 where
614 K: FromIndexKey,
615 F: Fn(K) -> f64 + 'a,
616 {
617 self.ub_by = Some(Box::new(move |k: &IndexKey| f(K::from_index_key(k))));
618 self
619 }
620 pub fn domain(mut self, d: Domain) -> Self {
621 self.domain = d;
622 self
623 }
624 pub fn integer(mut self) -> Self {
625 self.domain = Domain::Integer;
626 self
627 }
628 pub fn binary(mut self) -> Self {
629 self.domain = Domain::Binary;
630 self.lb = 0.0;
631 self.ub = 1.0;
632 self
633 }
634
635 pub fn build(self) -> IndexedVar<'a, K> {
640 let Self { model, base_name, keys, axes, lb, ub, lb_by, ub_by, domain, _k } = self;
641
642 let make = |key: &IndexKey| -> Expr<'a> {
643 let scalar_name: SmolStr = format_index_name(&base_name, key).into();
644 let lo = lb_by.as_ref().map_or(lb, |f| f(key));
645 let hi = ub_by.as_ref().map_or(ub, |f| f(key));
646 model.__var(scalar_name).lb(lo).ub(hi).domain(domain).build()
647 };
648
649 let storage = if let Some(axes) = axes {
650 let total = keys.len();
654 let mut data: Vec<Option<Expr<'a>>> = vec![None; total];
655 let mut kept: Vec<Option<IndexKey>> = vec![None; total];
656 for key in keys {
657 let expr = make(&key);
658 let off = grid_offset(&axes, &key).expect("dense grid key out of range");
659 data[off] = Some(expr);
660 kept[off] = Some(key);
661 }
662 let data = data.into_iter().map(|o| o.expect("dense grid had a gap")).collect();
663 let kept = kept.into_iter().map(|o| o.expect("dense grid had a gap")).collect();
664 Storage::Dense { data, keys: kept, axes }
665 } else {
666 let mut entries = FxHashMap::default();
667 for key in keys {
668 let expr = make(&key);
669 entries.insert(key, expr);
670 }
671 Storage::Sparse(entries)
672 };
673 IndexedVar { storage, _k: PhantomData }
674 }
675}
676
677fn format_index_name(base: &str, key: &IndexKey) -> String {
678 let mut out = String::with_capacity(base.len() + 4);
679 out.push_str(base);
680 out.push('[');
681 write_key_parts(&mut out, key);
682 out.push(']');
683 out
684}
685
686fn write_key_parts(out: &mut String, key: &IndexKey) {
687 use std::fmt::Write;
688 match key {
689 IndexKey::Int(i) => write!(out, "{i}").unwrap(),
690 IndexKey::Str(s) => out.push_str(s),
691 IndexKey::Tuple(parts) => {
692 for (i, p) in parts.iter().enumerate() {
693 if i > 0 {
694 out.push(',');
695 }
696 write_key_parts(out, p);
697 }
698 }
699 }
700}
701
702pub fn display_index_key(key: &IndexKey) -> String {
705 let mut out = String::new();
706 write_key_parts(&mut out, key);
707 out
708}
709
710#[cfg(test)]
711#[allow(deprecated)]
713mod tests {
714 use oximo_expr::extract_linear;
715
716 use super::*;
717 use crate::constraint::Relate;
718
719 #[test]
720 fn param_times_var_keeps_model_linear() {
721 let m = Model::new("p");
722 let param = m.param("param", 4.0);
723 let x = m.var("x").lb(0.0).build();
724 m.minimize(param * x);
725 assert_eq!(m.kind(), ModelKind::LP);
726 }
727
728 #[test]
729 fn param_coeff_resolves_and_rebinds() {
730 let m = Model::new("p");
731 let param = m.param("param", 4.0);
732 let x = m.var("x").lb(0.0).build();
733 let obj = param * x;
734
735 let coeff = |m: &Model| {
736 let arena = m.arena();
737 extract_linear(&arena, obj.id).expect("linear").coeffs[0].1
738 };
739 assert!((coeff(&m) - 4.0).abs() < f64::EPSILON);
740
741 m.set_param(param, 9.0);
742 assert!((coeff(&m) - 9.0).abs() < f64::EPSILON);
743 assert_eq!(m.parameter_id("param"), Some(param.param_id().unwrap()));
744 }
745
746 #[test]
747 fn param_value_reads_live_arena_value() {
748 let m = Model::new("p");
749 let param = m.param("param", 4.0);
750 let id = param.param_id().unwrap();
751 assert!((m.param_value(id) - 4.0).abs() < f64::EPSILON);
752 assert!((m.param_value_of(param).unwrap() - 4.0).abs() < f64::EPSILON);
753
754 m.set_param(param, 7.5);
755 assert!((m.param_value(id) - 7.5).abs() < f64::EPSILON);
756
757 let x = m.var("x").build();
758 assert!(m.param_value_of(x).is_none());
759 }
760
761 #[test]
762 fn set_param_invalidates_kind_cache() {
763 let m = Model::new("p");
764 let p = m.param("p", 1.0);
765 let x = m.var("x").lb(0.0).build();
766 m.constraint("c", (p * x).le(10.0));
767 assert_eq!(m.kind(), ModelKind::LP);
768 m.set_param(p, 2.0);
769 assert_eq!(m.kind(), ModelKind::LP);
770 }
771
772 #[test]
773 #[should_panic(expected = "parameter name \"dup\" already registered")]
774 fn duplicate_param_name_panics() {
775 let m = Model::new("p");
776 let _a = m.param("dup", 1.0);
777 let _b = m.param("dup", 2.0);
778 }
779}