1use crate::core::math::Scalar;
2
3#[cfg(test)]
4#[path = "model/integer_domain_api_prototype.rs"]
5mod integer_domain_api_prototype;
6
7pub trait Constraints {
22 type Set: ConstraintDescription;
24 fn constraints(&self)
26 -> <Self::Set as ConstraintDescription>::Borrowed<'_>;
27}
28
29mod sealed {
30 pub trait Set {}
31}
32
33pub trait ConstraintDescription: sealed::Set {
35 type Borrowed<'a>
37 where
38 Self: 'a;
39 fn borrowed(&self) -> Self::Borrowed<'_>;
41}
42pub trait BorrowConstraint {
44 type Borrowed<'a>
46 where
47 Self: 'a;
48 fn borrowed(&self) -> Self::Borrowed<'_>;
50}
51
52#[derive(Clone, Debug)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
58pub struct ConstraintSet<
59 B = NoBounds,
60 L = NoLinearConstraints,
61 N = NoNonlinearConstraints,
62> {
63 pub(crate) bounds: B,
64 pub(crate) linear: L,
65 pub(crate) nonlinear: N,
66}
67impl<B, L, N> sealed::Set for ConstraintSet<B, L, N> {}
68impl<B: BorrowConstraint, L: BorrowConstraint, N: BorrowConstraint>
69 ConstraintDescription for ConstraintSet<B, L, N>
70{
71 type Borrowed<'a>
72 = ConstraintSet<B::Borrowed<'a>, L::Borrowed<'a>, N::Borrowed<'a>>
73 where
74 Self: 'a;
75 fn borrowed(&self) -> Self::Borrowed<'_> {
76 ConstraintSet {
77 bounds: self.bounds.borrowed(),
78 linear: self.linear.borrowed(),
79 nonlinear: self.nonlinear.borrowed(),
80 }
81 }
82}
83
84pub type NoConstraints = ConstraintSet;
86#[derive(Clone, Copy, Debug, Default)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89pub struct NoBounds;
90#[derive(Clone, Copy, Debug, Default)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93pub struct NoLinearBlock;
94#[derive(Clone, Copy, Debug, Default)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97pub struct NoNonlinearBlock;
98pub type NoLinearConstraints = LinearBlocks<NoLinearBlock, NoLinearBlock>;
100pub type NoNonlinearConstraints =
102 NonlinearBlocks<NoNonlinearBlock, NoNonlinearBlock>;
103
104#[derive(Clone, Debug)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110pub struct BoxBounds<V> {
111 lower: Option<V>,
112 upper: Option<V>,
113}
114impl<V> BoxBounds<V> {
115 pub fn new(lower: Option<V>, upper: Option<V>) -> Self {
117 Self { lower, upper }
118 }
119 pub fn lower(&self) -> Option<&V> {
121 self.lower.as_ref()
122 }
123 pub fn upper(&self) -> Option<&V> {
125 self.upper.as_ref()
126 }
127}
128
129#[derive(Clone, Debug)]
134#[cfg_attr(feature = "serde", derive(serde::Serialize))]
135pub struct LinearBlock<M, R> {
136 pub(crate) matrix: Option<M>,
137 pub(crate) rhs: Option<R>,
138}
139impl<M, R> LinearBlock<M, R> {
140 pub fn new(matrix: M, rhs: R) -> Self {
142 Self {
143 matrix: Some(matrix),
144 rhs: Some(rhs),
145 }
146 }
147 pub fn optional(data: Option<(M, R)>) -> Self {
149 match data {
150 Some((m, r)) => Self::new(m, r),
151 None => Self {
152 matrix: None,
153 rhs: None,
154 },
155 }
156 }
157 pub fn matrix(&self) -> Option<&M> {
159 self.matrix.as_ref()
160 }
161 pub fn rhs(&self) -> Option<&R> {
163 self.rhs.as_ref()
164 }
165}
166
167#[cfg(feature = "serde")]
168impl<'de, M, R> serde::Deserialize<'de> for LinearBlock<M, R>
169where
170 M: serde::Deserialize<'de>,
171 R: serde::Deserialize<'de>,
172{
173 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
174 where
175 D: serde::Deserializer<'de>,
176 {
177 #[derive(serde::Deserialize)]
178 #[serde(rename = "LinearBlock")]
179 struct StoredLinearBlock<M, R> {
180 matrix: Option<M>,
181 rhs: Option<R>,
182 }
183
184 let stored = StoredLinearBlock::<M, R>::deserialize(deserializer)?;
185 if stored.matrix.is_some() != stored.rhs.is_some() {
186 return Err(serde::de::Error::custom(
187 "linear block matrix and rhs must both be present or both be absent",
188 ));
189 }
190 Ok(Self {
191 matrix: stored.matrix,
192 rhs: stored.rhs,
193 })
194 }
195}
196
197#[derive(Clone, Debug)]
199#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
200pub struct LinearBlocks<E = NoLinearBlock, I = NoLinearBlock> {
201 pub(crate) equalities: E,
202 pub(crate) inequalities: I,
203}
204impl<E, I> LinearBlocks<E, I> {
205 pub fn equalities(&self) -> &E {
207 &self.equalities
208 }
209 pub fn inequalities(&self) -> &I {
211 &self.inequalities
212 }
213}
214
215#[derive(Clone, Copy, Debug)]
217#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
218pub struct NonlinearBlock {
219 pub(crate) count: usize,
220}
221impl NonlinearBlock {
222 pub fn count(&self) -> usize {
224 self.count
225 }
226}
227#[derive(Clone, Debug)]
229#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
230pub struct NonlinearBlocks<E = NoNonlinearBlock, I = NoNonlinearBlock> {
231 pub(crate) equalities: E,
232 pub(crate) inequalities: I,
233}
234impl<E, I> NonlinearBlocks<E, I> {
235 pub fn equalities(&self) -> &E {
237 &self.equalities
238 }
239 pub fn inequalities(&self) -> &I {
241 &self.inequalities
242 }
243}
244
245impl NoConstraints {
246 pub const EMPTY: Self = Self {
248 bounds: NoBounds,
249 linear: LinearBlocks {
250 equalities: NoLinearBlock,
251 inequalities: NoLinearBlock,
252 },
253 nonlinear: NonlinearBlocks {
254 equalities: NoNonlinearBlock,
255 inequalities: NoNonlinearBlock,
256 },
257 };
258 pub const fn new() -> Self {
260 Self::EMPTY
261 }
262}
263impl Default for NoConstraints {
264 fn default() -> Self {
265 Self::new()
266 }
267}
268impl<B, L, N> ConstraintSet<B, L, N> {
269 pub fn bounds(&self) -> &B {
271 &self.bounds
272 }
273 pub fn linear(&self) -> &L {
275 &self.linear
276 }
277 pub fn nonlinear(&self) -> &N {
279 &self.nonlinear
280 }
281 pub fn with_bounds<V>(
283 self,
284 bounds: BoxBounds<V>,
285 ) -> ConstraintSet<BoxBounds<V>, L, N> {
286 ConstraintSet {
287 bounds,
288 linear: self.linear,
289 nonlinear: self.nonlinear,
290 }
291 }
292}
293impl<B, E, I, N> ConstraintSet<B, LinearBlocks<E, I>, N> {
294 pub fn with_linear_equalities<M, R>(
296 self,
297 block: LinearBlock<M, R>,
298 ) -> ConstraintSet<B, LinearBlocks<LinearBlock<M, R>, I>, N> {
299 ConstraintSet {
300 bounds: self.bounds,
301 linear: LinearBlocks {
302 equalities: block,
303 inequalities: self.linear.inequalities,
304 },
305 nonlinear: self.nonlinear,
306 }
307 }
308 pub fn with_linear_inequalities<M, R>(
310 self,
311 block: LinearBlock<M, R>,
312 ) -> ConstraintSet<B, LinearBlocks<E, LinearBlock<M, R>>, N> {
313 ConstraintSet {
314 bounds: self.bounds,
315 linear: LinearBlocks {
316 equalities: self.linear.equalities,
317 inequalities: block,
318 },
319 nonlinear: self.nonlinear,
320 }
321 }
322}
323impl<B, L, E, I> ConstraintSet<B, L, NonlinearBlocks<E, I>> {
324 pub fn with_nonlinear_equalities(
326 self,
327 count: usize,
328 ) -> ConstraintSet<B, L, NonlinearBlocks<NonlinearBlock, I>> {
329 ConstraintSet {
330 bounds: self.bounds,
331 linear: self.linear,
332 nonlinear: NonlinearBlocks {
333 equalities: NonlinearBlock { count },
334 inequalities: self.nonlinear.inequalities,
335 },
336 }
337 }
338 pub fn with_nonlinear_inequalities(
340 self,
341 count: usize,
342 ) -> ConstraintSet<B, L, NonlinearBlocks<E, NonlinearBlock>> {
343 ConstraintSet {
344 bounds: self.bounds,
345 linear: self.linear,
346 nonlinear: NonlinearBlocks {
347 equalities: self.nonlinear.equalities,
348 inequalities: NonlinearBlock { count },
349 },
350 }
351 }
352}
353
354#[derive(Clone, Copy, Debug, PartialEq, Eq)]
356#[non_exhaustive]
357pub enum ConstraintBlock {
358 Bounds,
360 LinearEqualities,
362 LinearInequalities,
364 NonlinearEqualities,
366 NonlinearInequalities,
368 Jacobian,
370}
371#[derive(Clone, Copy, Debug, PartialEq, Eq)]
373pub enum BoundSide {
374 Lower,
376 Upper,
378}
379#[derive(Clone, Copy, Debug, PartialEq, Eq)]
381pub enum ConstraintAxis {
382 Length,
384 Rows,
386 Columns,
388}
389#[derive(Clone, Debug, PartialEq, Eq)]
391#[non_exhaustive]
392pub enum ConstraintError {
393 NaNBound {
395 side: BoundSide,
397 coordinate: usize,
399 },
400 InvalidInfinity {
402 side: BoundSide,
404 coordinate: usize,
406 },
407 InvertedBounds {
409 coordinate: usize,
411 },
412 DimensionMismatch {
414 block: ConstraintBlock,
416 axis: ConstraintAxis,
418 expected: usize,
420 actual: usize,
422 },
423 MissingBound {
425 side: BoundSide,
427 },
428 NonFiniteInterval {
430 coordinate: usize,
432 },
433 NonFiniteLinearData {
435 block: ConstraintBlock,
437 },
438 LayoutChanged,
440 CountOverflow,
442}
443impl std::fmt::Display for ConstraintError {
444 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
445 write!(f, "invalid constraints: {self:?}")
446 }
447}
448impl std::error::Error for ConstraintError {}
449#[derive(Debug, PartialEq, Eq)]
451#[non_exhaustive]
452pub enum ConstraintSolveError<E> {
453 InvalidConstraints(ConstraintError),
455 Evaluation(E),
457}
458impl<E> From<E> for ConstraintSolveError<E> {
459 fn from(error: E) -> Self {
460 Self::Evaluation(error)
461 }
462}
463impl<E: std::fmt::Display> std::fmt::Display for ConstraintSolveError<E> {
464 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
465 match self {
466 Self::InvalidConstraints(e) => e.fmt(f),
467 Self::Evaluation(e) => write!(f, "problem evaluation failed: {e}"),
468 }
469 }
470}
471impl<E: std::error::Error + 'static> std::error::Error
472 for ConstraintSolveError<E>
473{
474 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
475 match self {
476 Self::InvalidConstraints(e) => Some(e),
477 Self::Evaluation(e) => Some(e),
478 }
479 }
480}
481
482pub trait NonlinearInequalityValues {
489 type Param;
491 type Values;
493 type Error;
495 fn nonlinear_inequalities(
497 &self,
498 x: &Self::Param,
499 ) -> Result<Self::Values, ConstraintSolveError<Self::Error>>;
500}
501pub trait NonlinearEqualityValues {
503 type Param;
505 type Values;
507 type Error;
509 fn nonlinear_equalities(
511 &self,
512 x: &Self::Param,
513 ) -> Result<Self::Values, ConstraintSolveError<Self::Error>>;
514}
515
516pub(crate) fn dimension(
517 block: ConstraintBlock,
518 axis: ConstraintAxis,
519 expected: usize,
520 actual: usize,
521) -> Result<(), ConstraintError> {
522 if expected == actual {
523 Ok(())
524 } else {
525 Err(ConstraintError::DimensionMismatch {
526 block,
527 axis,
528 expected,
529 actual,
530 })
531 }
532}
533pub(crate) fn validate_bound<F: Scalar>(
534 value: F,
535 side: BoundSide,
536 coordinate: usize,
537) -> Result<(), ConstraintError> {
538 if value.is_nan() {
539 return Err(ConstraintError::NaNBound { side, coordinate });
540 }
541 if (side == BoundSide::Lower && value == F::infinity())
542 || (side == BoundSide::Upper && value == F::neg_infinity())
543 {
544 return Err(ConstraintError::InvalidInfinity { side, coordinate });
545 }
546 Ok(())
547}
548macro_rules! borrow_copy {
549 ($($ty:ty),* $(,)?) => {$ (
550 impl BorrowConstraint for $ty {
551 type Borrowed<'a> = Self where Self: 'a;
552 fn borrowed(&self) -> Self { *self }
553 }
554 )*};
555}
556borrow_copy!(NoBounds, NoLinearBlock, NoNonlinearBlock, NonlinearBlock);
557impl<V> BorrowConstraint for BoxBounds<V> {
558 type Borrowed<'a>
559 = BoxBounds<&'a V>
560 where
561 Self: 'a;
562 fn borrowed(&self) -> Self::Borrowed<'_> {
563 BoxBounds::new(self.lower(), self.upper())
564 }
565}
566impl<M, R> BorrowConstraint for LinearBlock<M, R> {
567 type Borrowed<'a>
568 = LinearBlock<&'a M, &'a R>
569 where
570 Self: 'a;
571 fn borrowed(&self) -> Self::Borrowed<'_> {
572 LinearBlock::optional(self.matrix.as_ref().zip(self.rhs.as_ref()))
573 }
574}
575impl<E: BorrowConstraint, I: BorrowConstraint> BorrowConstraint
576 for LinearBlocks<E, I>
577{
578 type Borrowed<'a>
579 = LinearBlocks<E::Borrowed<'a>, I::Borrowed<'a>>
580 where
581 Self: 'a;
582 fn borrowed(&self) -> Self::Borrowed<'_> {
583 LinearBlocks {
584 equalities: self.equalities.borrowed(),
585 inequalities: self.inequalities.borrowed(),
586 }
587 }
588}
589impl<E: BorrowConstraint, I: BorrowConstraint> BorrowConstraint
590 for NonlinearBlocks<E, I>
591{
592 type Borrowed<'a>
593 = NonlinearBlocks<E::Borrowed<'a>, I::Borrowed<'a>>
594 where
595 Self: 'a;
596 fn borrowed(&self) -> Self::Borrowed<'_> {
597 NonlinearBlocks {
598 equalities: self.equalities.borrowed(),
599 inequalities: self.inequalities.borrowed(),
600 }
601 }
602}