1use crate::traits::Problem;
4use serde::de::DeserializeOwned;
5use serde::Serialize;
6use std::any::Any;
7use std::marker::PhantomData;
8
9#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
11pub enum ReductionError {
12 #[error("{source_problem} -> {target_problem}: target construction failed: {cause}")]
13 Construction {
14 source_problem: &'static str,
15 target_problem: &'static str,
16 #[source]
17 cause: crate::registry::ConstructionError,
18 },
19 #[error("{source_problem} -> {target_problem}: integer overflow while {operation}")]
20 IntegerOverflow {
21 source_problem: &'static str,
22 target_problem: &'static str,
23 operation: String,
24 },
25 #[error("{source_problem} -> {target_problem}: non-finite value while {operation}")]
26 NonFiniteResult {
27 source_problem: &'static str,
28 target_problem: &'static str,
29 operation: String,
30 },
31 #[error("{source_problem} -> {target_problem}: {cause}")]
32 InexactFloatConversion {
33 source_problem: &'static str,
34 target_problem: &'static str,
35 #[source]
36 cause: crate::types::ExactI64ToF64Error,
37 },
38 #[error("{source_problem} -> {target_problem}: {message}")]
39 InvalidTarget {
40 source_problem: &'static str,
41 target_problem: &'static str,
42 message: String,
43 },
44 #[error(
45 "{source_problem} -> {target_problem}: reduction executor expected source type `{expected}`"
46 )]
47 SourceTypeMismatch {
48 source_problem: &'static str,
49 target_problem: &'static str,
50 expected: &'static str,
51 },
52}
53
54impl ReductionError {
55 pub(crate) fn for_reduction<S: Problem, T: Problem>(self) -> Self {
56 match self {
57 Self::Construction { cause, .. } => Self::construction::<S, T>(cause),
58 Self::IntegerOverflow { operation, .. } => Self::integer_overflow::<S, T>(operation),
59 Self::NonFiniteResult { operation, .. } => Self::non_finite_result::<S, T>(operation),
60 Self::InexactFloatConversion { cause, .. } => {
61 Self::inexact_float_conversion::<S, T>(cause)
62 }
63 Self::InvalidTarget { message, .. } => Self::invalid_target::<S, T>(message),
64 Self::SourceTypeMismatch { expected, .. } => Self::SourceTypeMismatch {
65 source_problem: S::NAME,
66 target_problem: T::NAME,
67 expected,
68 },
69 }
70 }
71
72 pub fn source_type_mismatch<S: Problem, T: Problem>() -> Self {
74 Self::SourceTypeMismatch {
75 source_problem: S::NAME,
76 target_problem: T::NAME,
77 expected: std::any::type_name::<S>(),
78 }
79 }
80
81 pub fn integer_overflow<S: Problem, T: Problem>(operation: impl Into<String>) -> Self {
83 Self::IntegerOverflow {
84 source_problem: S::NAME,
85 target_problem: T::NAME,
86 operation: operation.into(),
87 }
88 }
89
90 pub fn inexact_float_conversion<S: Problem, T: Problem>(
92 cause: crate::types::ExactI64ToF64Error,
93 ) -> Self {
94 Self::InexactFloatConversion {
95 source_problem: S::NAME,
96 target_problem: T::NAME,
97 cause,
98 }
99 }
100
101 pub fn non_finite_result<S: Problem, T: Problem>(operation: impl Into<String>) -> Self {
103 Self::NonFiniteResult {
104 source_problem: S::NAME,
105 target_problem: T::NAME,
106 operation: operation.into(),
107 }
108 }
109
110 pub fn invalid_target<S: Problem, T: Problem>(message: impl Into<String>) -> Self {
112 Self::InvalidTarget {
113 source_problem: S::NAME,
114 target_problem: T::NAME,
115 message: message.into(),
116 }
117 }
118
119 pub fn construction<S: Problem, T: Problem>(cause: crate::registry::ConstructionError) -> Self {
121 Self::Construction {
122 source_problem: S::NAME,
123 target_problem: T::NAME,
124 cause,
125 }
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
131pub enum ExtractionError {
132 #[error("{0}")]
133 InvalidTargetSolution(String),
134 #[error("{source_problem} -> {target_problem}: {message}")]
135 Reduction {
136 source_problem: &'static str,
137 target_problem: &'static str,
138 message: String,
139 },
140 #[error("target evaluation failed during extraction: {0}")]
141 Evaluation(#[from] crate::traits::EvaluationError),
142}
143
144impl ExtractionError {
145 pub fn invalid(message: impl Into<String>) -> Self {
146 Self::InvalidTargetSolution(message.into())
147 }
148
149 fn for_reduction<S: Problem, T: Problem>(self) -> Self {
150 match self {
151 Self::InvalidTargetSolution(message) => Self::Reduction {
152 source_problem: S::NAME,
153 target_problem: T::NAME,
154 message,
155 },
156 error => error,
157 }
158 }
159}
160
161pub type ExtractionResult<T> = std::result::Result<T, ExtractionError>;
162
163pub(crate) fn validate_target_solution<P: Problem>(
165 target: &P,
166 solution: &P::Solution,
167) -> ExtractionResult<P::Value> {
168 Ok(target.evaluate(solution)?)
169}
170
171pub trait ReductionResult {
176 type Source: Problem;
178 type Target: Problem;
180
181 fn target_problem(&self) -> &Self::Target;
183
184 fn extract_solution(
192 &self,
193 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
194 ) -> ExtractionResult<<Self::Source as crate::traits::Problem>::Solution>;
195}
196
197pub trait ReduceTo<T: Problem>: Problem {
225 type Result: ReductionResult<Source = Self, Target = T>;
227
228 fn target_construction(error: crate::registry::ConstructionError) -> ReductionError
230 where
231 Self: Sized,
232 {
233 ReductionError::construction::<Self, T>(error)
234 }
235
236 fn exact_i64(value: usize, operation: impl Into<String>) -> Result<i64, ReductionError>
238 where
239 Self: Sized,
240 {
241 i64::try_from(value).map_err(|_| ReductionError::integer_overflow::<Self, T>(operation))
242 }
243
244 fn reduce_to(&self) -> Result<Self::Result, ReductionError>;
246}
247
248pub trait AggregateReductionResult {
253 type Source: Problem;
255 type Target: Problem;
257
258 fn target_problem(&self) -> &Self::Target;
260
261 fn extract_value(
263 &self,
264 target_value: <Self::Target as crate::traits::Problem>::Value,
265 ) -> <Self::Source as crate::traits::Problem>::Value;
266}
267
268pub trait ReduceToAggregate<T: Problem>: Problem {
271 type Result: AggregateReductionResult<Source = Self, Target = T>;
273
274 fn reduce_to_aggregate(&self) -> Result<Self::Result, ReductionError>;
276}
277
278#[derive(Debug, Clone)]
282pub struct VariantReductionResult<S: Problem, T: Problem> {
283 target: T,
284 _phantom: PhantomData<S>,
285}
286
287impl<S: Problem, T: Problem> VariantReductionResult<S, T> {
288 pub fn new(target: T) -> Self {
290 Self {
291 target,
292 _phantom: PhantomData,
293 }
294 }
295}
296
297impl<S, T> ReductionResult for VariantReductionResult<S, T>
298where
299 S: Problem,
300 T: Problem<Solution = S::Solution>,
301 S::Solution: Clone,
302{
303 type Source = S;
304 type Target = T;
305
306 fn target_problem(&self) -> &Self::Target {
307 &self.target
308 }
309
310 fn extract_solution(&self, target_solution: &T::Solution) -> ExtractionResult<S::Solution> {
311 validate_target_solution(self.target_problem(), target_solution)?;
312 Ok(target_solution.clone())
313 }
314}
315
316impl<S: Problem, T: Problem<Value = S::Value>> AggregateReductionResult
317 for VariantReductionResult<S, T>
318{
319 type Source = S;
320 type Target = T;
321
322 fn target_problem(&self) -> &Self::Target {
323 &self.target
324 }
325
326 fn extract_value(&self, target_value: T::Value) -> S::Value {
327 target_value
328 }
329}
330
331pub trait DynReductionResult {
336 fn target_problem_any(&self) -> &dyn Any;
338 fn extract_solution_dyn(&self, target_solution: &dyn Any) -> ExtractionResult<Box<dyn Any>>;
340 fn source_solution_json(
342 &self,
343 source_solution: &dyn Any,
344 ) -> ExtractionResult<serde_json::Value>;
345 fn target_solution_from_json(
347 &self,
348 target_solution: serde_json::Value,
349 ) -> ExtractionResult<Box<dyn Any>>;
350}
351
352impl<R: ReductionResult + 'static> DynReductionResult for R
353where
354 R::Target: 'static,
355 <R::Target as Problem>::Solution: 'static,
356 <R::Target as Problem>::Solution: serde::de::DeserializeOwned,
357 <R::Source as Problem>::Solution: 'static,
358 <R::Source as Problem>::Solution: serde::Serialize,
359{
360 fn target_problem_any(&self) -> &dyn Any {
361 self.target_problem() as &dyn Any
362 }
363 fn extract_solution_dyn(&self, target_solution: &dyn Any) -> ExtractionResult<Box<dyn Any>> {
364 let target_solution = target_solution
365 .downcast_ref::<<R::Target as Problem>::Solution>()
366 .ok_or_else(|| {
367 ExtractionError::invalid(format!(
368 "target solution type mismatch: expected {}",
369 std::any::type_name::<<R::Target as Problem>::Solution>()
370 ))
371 })?;
372 self.extract_solution(target_solution)
373 .map(|solution| Box::new(solution) as Box<dyn Any>)
374 .map_err(|error| error.for_reduction::<R::Source, R::Target>())
375 }
376
377 fn source_solution_json(
378 &self,
379 source_solution: &dyn Any,
380 ) -> ExtractionResult<serde_json::Value> {
381 let source_solution = source_solution
382 .downcast_ref::<<R::Source as Problem>::Solution>()
383 .ok_or_else(|| ExtractionError::invalid("source solution type mismatch"))?;
384 serde_json::to_value(source_solution).map_err(|error| {
385 ExtractionError::invalid(format!("source solution serialization failed: {error}"))
386 })
387 }
388
389 fn target_solution_from_json(
390 &self,
391 target_solution: serde_json::Value,
392 ) -> ExtractionResult<Box<dyn Any>> {
393 serde_json::from_value::<<R::Target as Problem>::Solution>(target_solution)
394 .map(|solution| Box::new(solution) as Box<dyn Any>)
395 .map_err(|error| {
396 ExtractionError::invalid(format!("target solution deserialization failed: {error}"))
397 })
398 }
399}
400
401pub trait DynAggregateReductionResult {
403 fn target_problem_any(&self) -> &dyn Any;
405 fn extract_value_dyn(&self, target_value: serde_json::Value) -> serde_json::Value;
407 fn extract_value_from_solution_dyn(
411 &self,
412 target_solution: &dyn Any,
413 ) -> ExtractionResult<Box<dyn Any>>;
414}
415
416impl<R: AggregateReductionResult + 'static> DynAggregateReductionResult for R
417where
418 R::Target: 'static,
419 <R::Target as Problem>::Solution: 'static,
420 <R::Target as Problem>::Value: Serialize + DeserializeOwned,
421 <R::Source as Problem>::Value: Serialize + 'static,
422{
423 fn target_problem_any(&self) -> &dyn Any {
424 self.target_problem() as &dyn Any
425 }
426
427 fn extract_value_dyn(&self, target_value: serde_json::Value) -> serde_json::Value {
428 let target_value = serde_json::from_value(target_value)
429 .expect("DynAggregateReductionResult target value deserialize failed");
430 let source_value = self.extract_value(target_value);
431 serde_json::to_value(source_value)
432 .expect("DynAggregateReductionResult source value serialize failed")
433 }
434
435 fn extract_value_from_solution_dyn(
436 &self,
437 target_solution: &dyn Any,
438 ) -> ExtractionResult<Box<dyn Any>> {
439 let target_solution = target_solution
440 .downcast_ref::<<R::Target as Problem>::Solution>()
441 .ok_or_else(|| {
442 ExtractionError::invalid(format!(
443 "target solution type mismatch: expected {}",
444 std::any::type_name::<<R::Target as Problem>::Solution>()
445 ))
446 })?;
447 let target_value = self.target_problem().evaluate(target_solution)?;
448 Ok(Box::new(self.extract_value(target_value)))
449 }
450}
451
452#[cfg(test)]
453#[path = "../unit_tests/rules/traits.rs"]
454mod tests;