1use serde::de::{self, DeserializeOwned, Visitor};
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use std::fmt;
6
7pub const MAX_EXACT_F64_INTEGER: i64 = (1_i64 << 53) - 1;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
12#[error(
13 "integer {value} is outside the exactly representable f64 range [{min}, {max}]",
14 min = -MAX_EXACT_F64_INTEGER,
15 max = MAX_EXACT_F64_INTEGER
16)]
17pub struct ExactI64ToF64Error {
18 pub value: i64,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
23pub enum NumericArithmeticError {
24 #[error("integer overflow")]
26 IntegerOverflow,
27 #[error("non-finite floating-point result")]
29 NonFiniteResult,
30}
31
32pub fn i64_to_exact_f64(value: i64) -> Result<f64, ExactI64ToF64Error> {
34 if (-MAX_EXACT_F64_INTEGER..=MAX_EXACT_F64_INTEGER).contains(&value) {
35 Ok(value as f64)
36 } else {
37 Err(ExactI64ToF64Error { value })
38 }
39}
40
41pub trait NumericSize:
43 Clone
44 + Default
45 + PartialOrd
46 + num_traits::Num
47 + num_traits::Zero
48 + num_traits::Bounded
49 + std::ops::AddAssign
50 + 'static
51{
52 fn checked_add_value(self, other: Self) -> Result<Self, NumericArithmeticError>;
54 fn checked_mul_value(self, other: Self) -> Result<Self, NumericArithmeticError>;
56}
57
58pub(crate) fn max_numeric_magnitude_bits<C: NumericSize>(
62 values: impl IntoIterator<Item = C>,
63) -> u64 {
64 fn bits<C: NumericSize>(mut value: C) -> u64 {
65 let one = C::one();
66 let two = one.clone() + one.clone();
67 let negative = value < C::zero();
68 let unit = if negative { C::zero() - one } else { one };
69 let mut bits = 0;
70 while if negative {
71 value <= unit
72 } else {
73 value >= unit
74 } {
75 value = value / two.clone();
76 bits += 1;
77 }
78 bits.max(1)
79 }
80
81 let (mut minimum, mut maximum) = (C::zero(), C::zero());
83 for value in values {
84 if value < minimum {
85 minimum = value;
86 } else if value > maximum {
87 maximum = value;
88 }
89 }
90 bits(minimum).max(bits(maximum))
91}
92
93macro_rules! impl_integer_numeric_size {
94 ($($type:ty),* $(,)?) => {
95 $(
96 impl NumericSize for $type {
97 fn checked_add_value(self, other: Self) -> Result<Self, NumericArithmeticError> {
98 self.checked_add(other).ok_or(NumericArithmeticError::IntegerOverflow)
99 }
100
101 fn checked_mul_value(self, other: Self) -> Result<Self, NumericArithmeticError> {
102 self.checked_mul(other).ok_or(NumericArithmeticError::IntegerOverflow)
103 }
104 }
105 )*
106 };
107}
108
109impl_integer_numeric_size!(i64, u64, usize);
110
111impl NumericSize for f64 {
112 fn checked_add_value(self, other: Self) -> Result<Self, NumericArithmeticError> {
113 let result = self + other;
114 result
115 .is_finite()
116 .then_some(result)
117 .ok_or(NumericArithmeticError::NonFiniteResult)
118 }
119
120 fn checked_mul_value(self, other: Self) -> Result<Self, NumericArithmeticError> {
121 let result = self * other;
122 result
123 .is_finite()
124 .then_some(result)
125 .ok_or(NumericArithmeticError::NonFiniteResult)
126 }
127}
128
129fn evaluation_arithmetic_error(
130 error: NumericArithmeticError,
131 context: &str,
132) -> crate::traits::EvaluationError {
133 match error {
134 NumericArithmeticError::IntegerOverflow => {
135 crate::traits::EvaluationError::IntegerOverflow(context.to_string())
136 }
137 NumericArithmeticError::NonFiniteResult => {
138 crate::traits::EvaluationError::NonFiniteResult(context.to_string())
139 }
140 }
141}
142
143pub trait WeightElement: Clone + Default + 'static {
150 type Sum: NumericSize;
152 const IS_UNIT: bool;
154 fn unit() -> Self;
156 fn validate_element(&self, context: &str) -> Result<(), crate::registry::ConstructionError>;
158 fn to_sum(&self) -> Self::Sum;
160 fn checked_add_to_sum(
162 total: Self::Sum,
163 value: Self::Sum,
164 context: &str,
165 ) -> Result<Self::Sum, crate::traits::EvaluationError> {
166 total
167 .checked_add_value(value)
168 .map_err(|error| evaluation_arithmetic_error(error, context))
169 }
170 fn checked_mul_sum(
172 left: Self::Sum,
173 right: Self::Sum,
174 context: &str,
175 ) -> Result<Self::Sum, crate::traits::EvaluationError> {
176 left.checked_mul_value(right)
177 .map_err(|error| evaluation_arithmetic_error(error, context))
178 }
179}
180
181impl WeightElement for i64 {
182 type Sum = i64;
183 const IS_UNIT: bool = false;
184 fn unit() -> Self {
185 1
186 }
187 fn validate_element(&self, _context: &str) -> Result<(), crate::registry::ConstructionError> {
188 Ok(())
189 }
190 fn to_sum(&self) -> i64 {
191 *self
192 }
193}
194
195impl WeightElement for f64 {
196 type Sum = f64;
197 const IS_UNIT: bool = false;
198 fn unit() -> Self {
199 1.0
200 }
201 fn validate_element(&self, context: &str) -> Result<(), crate::registry::ConstructionError> {
202 if self.is_finite() {
203 Ok(())
204 } else {
205 Err(crate::registry::ConstructionError::NonFiniteFloat(format!(
206 "{context} must be finite"
207 )))
208 }
209 }
210 fn to_sum(&self) -> f64 {
211 *self
212 }
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
220pub struct One;
221
222impl Serialize for One {
223 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
224 where
225 S: Serializer,
226 {
227 serializer.serialize_i64(1)
228 }
229}
230
231impl<'de> Deserialize<'de> for One {
232 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
233 where
234 D: Deserializer<'de>,
235 {
236 struct OneVisitor;
237
238 impl<'de> Visitor<'de> for OneVisitor {
239 type Value = One;
240
241 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242 formatter.write_str("the unit weight `One` encoded as 1 or unit/null")
243 }
244
245 fn visit_i64<E>(self, value: i64) -> Result<One, E>
246 where
247 E: de::Error,
248 {
249 if value == 1 {
250 Ok(One)
251 } else {
252 Err(E::custom(format!("expected 1 for One, got {value}")))
253 }
254 }
255
256 fn visit_u64<E>(self, value: u64) -> Result<One, E>
257 where
258 E: de::Error,
259 {
260 if value == 1 {
261 Ok(One)
262 } else {
263 Err(E::custom(format!("expected 1 for One, got {value}")))
264 }
265 }
266
267 fn visit_unit<E>(self) -> Result<One, E>
268 where
269 E: de::Error,
270 {
271 Ok(One)
272 }
273
274 fn visit_none<E>(self) -> Result<One, E>
275 where
276 E: de::Error,
277 {
278 Ok(One)
279 }
280
281 fn visit_str<E>(self, value: &str) -> Result<One, E>
282 where
283 E: de::Error,
284 {
285 if value == "One" {
286 Ok(One)
287 } else {
288 Err(E::custom(format!("expected \"One\" for One, got {value}")))
289 }
290 }
291 }
292
293 deserializer.deserialize_any(OneVisitor)
294 }
295}
296
297impl WeightElement for One {
298 type Sum = i64;
299 const IS_UNIT: bool = true;
300 fn unit() -> Self {
301 One
302 }
303 fn validate_element(&self, _context: &str) -> Result<(), crate::registry::ConstructionError> {
304 Ok(())
305 }
306 fn to_sum(&self) -> i64 {
307 1
308 }
309}
310
311impl std::fmt::Display for One {
312 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313 write!(f, "One")
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
319pub enum AggregationError {
320 #[error("aggregate arithmetic overflow or non-finite result")]
321 ArithmeticOverflow,
322 #[error("aggregate values are not comparable")]
323 UnorderedComparison,
324 #[error("cannot combine extrema with different optimization senses")]
325 IncompatibleExtremumSense,
326}
327
328pub trait Aggregate: Clone + fmt::Debug + Serialize + DeserializeOwned {
330 fn identity() -> Self;
332
333 fn combine(self, other: Self) -> Result<Self, AggregationError>;
335
336 fn is_absorbing(&self) -> bool {
338 false
339 }
340}
341
342pub trait SolutionAggregate: Aggregate {
344 fn contributes_to_solution(value: &Self, total: &Self) -> bool;
346}
347
348#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
350pub struct Max<V>(pub Option<V>);
351
352impl<V: fmt::Debug + PartialOrd + Clone + Serialize + DeserializeOwned> Aggregate for Max<V> {
353 fn identity() -> Self {
354 Max(None)
355 }
356
357 fn combine(self, other: Self) -> Result<Self, AggregationError> {
358 use std::cmp::Ordering;
359
360 Ok(match (self.0, other.0) {
361 (None, rhs) => Max(rhs),
362 (lhs, None) => Max(lhs),
363 (Some(lhs), Some(rhs)) => {
364 let ord = lhs
365 .partial_cmp(&rhs)
366 .ok_or(AggregationError::UnorderedComparison)?;
367 match ord {
368 Ordering::Less => Max(Some(rhs)),
369 Ordering::Equal | Ordering::Greater => Max(Some(lhs)),
370 }
371 }
372 })
373 }
374}
375
376impl<V: fmt::Debug + PartialOrd + Clone + Serialize + DeserializeOwned> SolutionAggregate
377 for Max<V>
378{
379 fn contributes_to_solution(value: &Self, total: &Self) -> bool {
380 matches!((value, total), (Max(Some(value)), Max(Some(best))) if value == best)
381 }
382}
383
384impl<V: fmt::Display> fmt::Display for Max<V> {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 match &self.0 {
387 Some(value) => write!(f, "Max({value})"),
388 None => write!(f, "Max(None)"),
389 }
390 }
391}
392
393impl<V> Max<V> {
394 pub fn is_valid(&self) -> bool {
395 self.0.is_some()
396 }
397
398 pub fn size(&self) -> Option<&V> {
399 self.0.as_ref()
400 }
401
402 pub fn unwrap(self) -> V {
403 self.0.expect("called unwrap on invalid Max value")
404 }
405}
406
407#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
409pub struct Min<V>(pub Option<V>);
410
411impl<V: fmt::Debug + PartialOrd + Clone + Serialize + DeserializeOwned> Aggregate for Min<V> {
412 fn identity() -> Self {
413 Min(None)
414 }
415
416 fn combine(self, other: Self) -> Result<Self, AggregationError> {
417 use std::cmp::Ordering;
418
419 Ok(match (self.0, other.0) {
420 (None, rhs) => Min(rhs),
421 (lhs, None) => Min(lhs),
422 (Some(lhs), Some(rhs)) => {
423 let ord = lhs
424 .partial_cmp(&rhs)
425 .ok_or(AggregationError::UnorderedComparison)?;
426 match ord {
427 Ordering::Greater => Min(Some(rhs)),
428 Ordering::Equal | Ordering::Less => Min(Some(lhs)),
429 }
430 }
431 })
432 }
433}
434
435impl<V: fmt::Debug + PartialOrd + Clone + Serialize + DeserializeOwned> SolutionAggregate
436 for Min<V>
437{
438 fn contributes_to_solution(value: &Self, total: &Self) -> bool {
439 matches!((value, total), (Min(Some(value)), Min(Some(best))) if value == best)
440 }
441}
442
443impl<V: fmt::Display> fmt::Display for Min<V> {
444 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445 match &self.0 {
446 Some(value) => write!(f, "Min({value})"),
447 None => write!(f, "Min(None)"),
448 }
449 }
450}
451
452impl<V> Min<V> {
453 pub fn is_valid(&self) -> bool {
454 self.0.is_some()
455 }
456
457 pub fn size(&self) -> Option<&V> {
458 self.0.as_ref()
459 }
460
461 pub fn unwrap(self) -> V {
462 self.0.expect("called unwrap on invalid Min value")
463 }
464}
465
466pub trait OptimizationValue: Aggregate {
468 type Inner: Clone + PartialOrd + fmt::Debug + Serialize + DeserializeOwned;
470
471 fn meets_bound(value: &Self, bound: &Self::Inner) -> bool;
473}
474
475impl<V: fmt::Debug + PartialOrd + Clone + Serialize + DeserializeOwned> OptimizationValue
476 for Min<V>
477{
478 type Inner = V;
479
480 fn meets_bound(value: &Self, bound: &V) -> bool {
481 matches!(&value.0, Some(v) if *v <= *bound)
482 }
483}
484
485impl<V: fmt::Debug + PartialOrd + Clone + Serialize + DeserializeOwned> OptimizationValue
486 for Max<V>
487{
488 type Inner = V;
489
490 fn meets_bound(value: &Self, bound: &V) -> bool {
491 matches!(&value.0, Some(v) if *v >= *bound)
492 }
493}
494
495#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
497pub struct Sum<W>(pub W);
498
499impl<W: fmt::Debug + NumericSize + Serialize + DeserializeOwned> Aggregate for Sum<W> {
500 fn identity() -> Self {
501 Sum(W::zero())
502 }
503
504 fn combine(self, other: Self) -> Result<Self, AggregationError> {
505 self.0
506 .checked_add_value(other.0)
507 .map(Sum)
508 .map_err(|_| AggregationError::ArithmeticOverflow)
509 }
510}
511
512impl<W: fmt::Display> fmt::Display for Sum<W> {
513 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
514 write!(f, "Sum({})", self.0)
515 }
516}
517
518#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
520pub struct Or(pub bool);
521
522impl Or {
523 pub fn is_valid(&self) -> bool {
524 self.0
525 }
526
527 pub fn unwrap(self) -> bool {
528 self.0
529 }
530}
531
532impl Aggregate for Or {
533 fn identity() -> Self {
534 Or(false)
535 }
536
537 fn combine(self, other: Self) -> Result<Self, AggregationError> {
538 Ok(Or(self.0 || other.0))
539 }
540
541 fn is_absorbing(&self) -> bool {
542 self.0
543 }
544}
545
546impl SolutionAggregate for Or {
547 fn contributes_to_solution(value: &Self, total: &Self) -> bool {
548 value.0 && total.0
549 }
550}
551
552impl fmt::Display for Or {
553 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
554 write!(f, "Or({})", self.0)
555 }
556}
557
558impl std::ops::Not for Or {
559 type Output = bool;
560
561 fn not(self) -> Self::Output {
562 !self.0
563 }
564}
565
566impl PartialEq<bool> for Or {
567 fn eq(&self, other: &bool) -> bool {
568 self.0 == *other
569 }
570}
571
572impl PartialEq<Or> for bool {
573 fn eq(&self, other: &Or) -> bool {
574 *self == other.0
575 }
576}
577
578#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
580pub struct And(pub bool);
581
582impl Aggregate for And {
583 fn identity() -> Self {
584 And(true)
585 }
586
587 fn combine(self, other: Self) -> Result<Self, AggregationError> {
588 Ok(And(self.0 && other.0))
589 }
590
591 fn is_absorbing(&self) -> bool {
592 !self.0
593 }
594}
595
596impl fmt::Display for And {
597 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
598 write!(f, "And({})", self.0)
599 }
600}
601
602#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
603pub enum ExtremumSense {
604 Maximize,
605 Minimize,
606}
607
608#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
609pub struct Extremum<V> {
610 pub sense: ExtremumSense,
611 pub value: Option<V>,
612}
613
614impl<V> Extremum<V> {
615 pub fn maximize(value: Option<V>) -> Self {
616 Self {
617 sense: ExtremumSense::Maximize,
618 value,
619 }
620 }
621
622 pub fn minimize(value: Option<V>) -> Self {
623 Self {
624 sense: ExtremumSense::Minimize,
625 value,
626 }
627 }
628
629 pub fn is_valid(&self) -> bool {
630 self.value.is_some()
631 }
632
633 pub fn size(&self) -> Option<&V> {
634 self.value.as_ref()
635 }
636
637 pub fn unwrap(self) -> V {
638 self.value.expect("called unwrap on invalid Extremum value")
639 }
640}
641
642impl<V: fmt::Debug + PartialOrd + Clone + Serialize + DeserializeOwned> Aggregate for Extremum<V> {
643 fn identity() -> Self {
644 Self::maximize(None)
645 }
646
647 fn combine(self, other: Self) -> Result<Self, AggregationError> {
648 use std::cmp::Ordering;
649
650 Ok(match (self.value, other.value) {
651 (None, rhs) => Self {
652 sense: other.sense,
653 value: rhs,
654 },
655 (lhs, None) => Self {
656 sense: self.sense,
657 value: lhs,
658 },
659 (Some(lhs), Some(rhs)) => {
660 if self.sense != other.sense {
661 return Err(AggregationError::IncompatibleExtremumSense);
662 }
663 let ord = lhs
664 .partial_cmp(&rhs)
665 .ok_or(AggregationError::UnorderedComparison)?;
666 let keep_self = match self.sense {
667 ExtremumSense::Maximize => matches!(ord, Ordering::Equal | Ordering::Greater),
668 ExtremumSense::Minimize => matches!(ord, Ordering::Equal | Ordering::Less),
669 };
670 if keep_self {
671 Self {
672 sense: self.sense,
673 value: Some(lhs),
674 }
675 } else {
676 Self {
677 sense: other.sense,
678 value: Some(rhs),
679 }
680 }
681 }
682 })
683 }
684}
685
686impl<V: fmt::Debug + PartialOrd + Clone + Serialize + DeserializeOwned> SolutionAggregate
687 for Extremum<V>
688{
689 fn contributes_to_solution(candidate: &Self, total: &Self) -> bool {
690 matches!(
691 (candidate.value.as_ref(), total.value.as_ref()),
692 (Some(value), Some(best)) if candidate.sense == total.sense && value == best
693 )
694 }
695}
696
697impl<V: fmt::Display> fmt::Display for Extremum<V> {
698 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
699 match (&self.sense, &self.value) {
700 (ExtremumSense::Maximize, Some(value)) => write!(f, "Max({value})"),
701 (ExtremumSense::Maximize, None) => write!(f, "Max(None)"),
702 (ExtremumSense::Minimize, Some(value)) => write!(f, "Min({value})"),
703 (ExtremumSense::Minimize, None) => write!(f, "Min(None)"),
704 }
705 }
706}
707
708#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
710pub struct ProblemParameters {
711 #[serde(deserialize_with = "deserialize_parameter_components")]
713 pub(crate) components: Vec<(String, u64)>,
714}
715
716impl ProblemParameters {
717 pub fn new(components: Vec<(&str, u64)>) -> Self {
722 Self::from_owned(
723 components
724 .into_iter()
725 .map(|(name, value)| (name.to_string(), value))
726 .collect(),
727 )
728 }
729
730 pub fn from_owned(components: Vec<(String, u64)>) -> Self {
735 if let Some(name) = duplicate_parameter_name(&components) {
736 panic!("duplicate problem parameter `{name}`");
737 }
738 Self { components }
739 }
740
741 pub fn iter(&self) -> impl Iterator<Item = (&str, u64)> {
743 self.components
744 .iter()
745 .map(|(name, value)| (name.as_str(), *value))
746 }
747
748 pub fn get(&self, name: &str) -> Option<u64> {
750 self.components
751 .iter()
752 .find(|(k, _)| k == name)
753 .map(|(_, v)| *v)
754 }
755}
756
757fn duplicate_parameter_name(components: &[(String, u64)]) -> Option<&str> {
758 components
759 .iter()
760 .enumerate()
761 .find_map(|(index, (name, _))| {
762 components[..index]
763 .iter()
764 .any(|(previous, _)| previous == name)
765 .then_some(name.as_str())
766 })
767}
768
769fn deserialize_parameter_components<'de, D>(deserializer: D) -> Result<Vec<(String, u64)>, D::Error>
770where
771 D: serde::Deserializer<'de>,
772{
773 let components = Vec::<(String, u64)>::deserialize(deserializer)?;
774 if let Some(name) = duplicate_parameter_name(&components) {
775 return Err(serde::de::Error::custom(format!(
776 "duplicate problem parameter `{name}`"
777 )));
778 }
779 Ok(components)
780}
781
782impl fmt::Display for ProblemParameters {
783 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
784 write!(f, "ProblemParameters{{")?;
785 for (i, (name, value)) in self.components.iter().enumerate() {
786 if i > 0 {
787 write!(f, ", ")?;
788 }
789 write!(f, "{}: {}", name, value)?;
790 }
791 write!(f, "}}")
792 }
793}
794
795use crate::impl_variant_param;
796
797impl_variant_param!(f64, "weight");
798impl_variant_param!(i64, "weight");
799impl_variant_param!(One, "weight");
800
801#[cfg(test)]
802#[path = "unit_tests/types.rs"]
803mod tests;
804
805#[cfg(test)]
806#[path = "unit_tests/types_optimization_value.rs"]
807mod optimization_value_tests;