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(crate) fn validate_target_witness<P: Problem>(
175 target: &P,
176 solution: &P::Solution,
177 certifies_source: impl FnOnce(P::Value) -> bool,
178 message: &str,
179) -> ExtractionResult<()> {
180 let value = validate_target_solution(target, solution)?;
181 if !certifies_source(value) {
182 return Err(ExtractionError::invalid(message));
183 }
184 Ok(())
185}
186
187pub trait ReductionResult {
195 type Source: Problem;
197 type Target: Problem;
199
200 fn target_problem(&self) -> &Self::Target;
202
203 fn extract_solution(
211 &self,
212 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
213 ) -> ExtractionResult<<Self::Source as crate::traits::Problem>::Solution>;
214}
215
216pub trait ReduceTo<T: Problem>: Problem {
244 type Result: ReductionResult<Source = Self, Target = T>;
246
247 fn target_construction(error: crate::registry::ConstructionError) -> ReductionError
249 where
250 Self: Sized,
251 {
252 ReductionError::construction::<Self, T>(error)
253 }
254
255 fn exact_i64(value: usize, operation: impl Into<String>) -> Result<i64, ReductionError>
257 where
258 Self: Sized,
259 {
260 i64::try_from(value).map_err(|_| ReductionError::integer_overflow::<Self, T>(operation))
261 }
262
263 fn reduce_to(&self) -> Result<Self::Result, ReductionError>;
265}
266
267pub trait AggregateReductionResult {
272 type Source: Problem;
274 type Target: Problem;
276
277 fn target_problem(&self) -> &Self::Target;
279
280 fn extract_value(
289 &self,
290 target_value: <Self::Target as crate::traits::Problem>::Value,
291 ) -> <Self::Source as crate::traits::Problem>::Value;
292}
293
294pub trait ReduceToAggregate<T: Problem>: Problem {
297 type Result: AggregateReductionResult<Source = Self, Target = T>;
299
300 fn reduce_to_aggregate(&self) -> Result<Self::Result, ReductionError>;
302}
303
304#[derive(Debug, Clone)]
308pub struct VariantReductionResult<S: Problem, T: Problem> {
309 target: T,
310 _phantom: PhantomData<S>,
311}
312
313impl<S: Problem, T: Problem> VariantReductionResult<S, T> {
314 pub fn new(target: T) -> Self {
316 Self {
317 target,
318 _phantom: PhantomData,
319 }
320 }
321}
322
323impl<S, T> ReductionResult for VariantReductionResult<S, T>
324where
325 S: Problem,
326 T: Problem<Solution = S::Solution>,
327 S::Solution: Clone,
328{
329 type Source = S;
330 type Target = T;
331
332 fn target_problem(&self) -> &Self::Target {
333 &self.target
334 }
335
336 fn extract_solution(&self, target_solution: &T::Solution) -> ExtractionResult<S::Solution> {
337 validate_target_solution(self.target_problem(), target_solution)?;
338 Ok(target_solution.clone())
339 }
340}
341
342impl<S: Problem, T: Problem<Value = S::Value>> AggregateReductionResult
343 for VariantReductionResult<S, T>
344{
345 type Source = S;
346 type Target = T;
347
348 fn target_problem(&self) -> &Self::Target {
349 &self.target
350 }
351
352 fn extract_value(&self, target_value: T::Value) -> S::Value {
353 target_value
354 }
355}
356
357pub trait DynReductionResult {
362 fn as_any(&self) -> &dyn Any;
364 fn target_problem_any(&self) -> &dyn Any;
366 fn extract_solution_dyn(&self, target_solution: &dyn Any) -> ExtractionResult<Box<dyn Any>>;
368 fn source_solution_json(
370 &self,
371 source_solution: &dyn Any,
372 ) -> ExtractionResult<serde_json::Value>;
373 fn target_solution_from_json(
375 &self,
376 target_solution: serde_json::Value,
377 ) -> ExtractionResult<Box<dyn Any>>;
378}
379
380impl<R: ReductionResult + 'static> DynReductionResult for R
381where
382 R::Target: 'static,
383 <R::Target as Problem>::Solution: 'static,
384 <R::Target as Problem>::Solution: serde::de::DeserializeOwned,
385 <R::Source as Problem>::Solution: 'static,
386 <R::Source as Problem>::Solution: serde::Serialize,
387{
388 fn as_any(&self) -> &dyn Any {
389 self
390 }
391 fn target_problem_any(&self) -> &dyn Any {
392 self.target_problem() as &dyn Any
393 }
394 fn extract_solution_dyn(&self, target_solution: &dyn Any) -> ExtractionResult<Box<dyn Any>> {
395 let target_solution = target_solution
396 .downcast_ref::<<R::Target as Problem>::Solution>()
397 .ok_or_else(|| {
398 ExtractionError::invalid(format!(
399 "target solution type mismatch: expected {}",
400 std::any::type_name::<<R::Target as Problem>::Solution>()
401 ))
402 })?;
403 self.extract_solution(target_solution)
404 .map(|solution| Box::new(solution) as Box<dyn Any>)
405 .map_err(|error| error.for_reduction::<R::Source, R::Target>())
406 }
407
408 fn source_solution_json(
409 &self,
410 source_solution: &dyn Any,
411 ) -> ExtractionResult<serde_json::Value> {
412 let source_solution = source_solution
413 .downcast_ref::<<R::Source as Problem>::Solution>()
414 .ok_or_else(|| ExtractionError::invalid("source solution type mismatch"))?;
415 serde_json::to_value(source_solution).map_err(|error| {
416 ExtractionError::invalid(format!("source solution serialization failed: {error}"))
417 })
418 }
419
420 fn target_solution_from_json(
421 &self,
422 target_solution: serde_json::Value,
423 ) -> ExtractionResult<Box<dyn Any>> {
424 serde_json::from_value::<<R::Target as Problem>::Solution>(target_solution)
425 .map(|solution| Box::new(solution) as Box<dyn Any>)
426 .map_err(|error| {
427 ExtractionError::invalid(format!("target solution deserialization failed: {error}"))
428 })
429 }
430}
431
432pub fn aggregate_view<R>(
434 result: &dyn DynReductionResult,
435) -> ExtractionResult<&dyn DynAggregateReductionResult>
436where
437 R: DynAggregateReductionResult + 'static,
438{
439 result
440 .as_any()
441 .downcast_ref::<R>()
442 .map(|result| result as &dyn DynAggregateReductionResult)
443 .ok_or_else(|| ExtractionError::invalid("executed reduction type mismatch"))
444}
445
446pub trait DynAggregateReductionResult {
448 fn target_problem_any(&self) -> &dyn Any;
450 fn extract_value_dyn(
452 &self,
453 target_value: serde_json::Value,
454 ) -> ExtractionResult<serde_json::Value>;
455 fn extract_value_from_solution_dyn(
459 &self,
460 target_solution: &dyn Any,
461 ) -> ExtractionResult<serde_json::Value>;
462}
463
464impl<R: AggregateReductionResult + 'static> DynAggregateReductionResult for R
465where
466 R::Target: 'static,
467 <R::Target as Problem>::Solution: 'static,
468 <R::Target as Problem>::Value: Serialize + DeserializeOwned,
469 <R::Source as Problem>::Value: Serialize + 'static,
470{
471 fn target_problem_any(&self) -> &dyn Any {
472 self.target_problem() as &dyn Any
473 }
474
475 fn extract_value_dyn(
476 &self,
477 target_value: serde_json::Value,
478 ) -> ExtractionResult<serde_json::Value> {
479 let target_value = serde_json::from_value(target_value).map_err(|error| {
480 ExtractionError::invalid(format!("target aggregate deserialization failed: {error}"))
481 })?;
482 let source_value = self.extract_value(target_value);
483 serde_json::to_value(source_value).map_err(|error| {
484 ExtractionError::invalid(format!("source aggregate serialization failed: {error}"))
485 })
486 }
487
488 fn extract_value_from_solution_dyn(
489 &self,
490 target_solution: &dyn Any,
491 ) -> ExtractionResult<serde_json::Value> {
492 let target_solution = target_solution
493 .downcast_ref::<<R::Target as Problem>::Solution>()
494 .ok_or_else(|| {
495 ExtractionError::invalid(format!(
496 "target solution type mismatch: expected {}",
497 std::any::type_name::<<R::Target as Problem>::Solution>()
498 ))
499 })?;
500 let target_value = self.target_problem().evaluate(target_solution)?;
501 serde_json::to_value(self.extract_value(target_value)).map_err(|error| {
502 ExtractionError::invalid(format!("source aggregate serialization failed: {error}"))
503 })
504 }
505}
506
507#[cfg(test)]
508#[path = "../unit_tests/rules/traits.rs"]
509pub(crate) mod tests;