1use std::collections::BTreeSet;
4use std::error::Error;
5use std::fmt::{Display, Formatter};
6use std::sync::{Arc, Mutex};
7
8use crate::{
9 CancellationToken, CheckpointSink, Constraint, ConstraintError, ConstraintTransform,
10 DiagnosticValue, LatticeBounds, ParameterKey, RefinementEvent, RefinementEventKind,
11 RefinementEventSink, RefinementRuntime, RietveldCovarianceOptions, RietveldGeneralCheckpoint,
12 RietveldGeneralParameterError, RietveldGeneralRefinementError, RietveldGeneralRefinementResult,
13 RietveldInput, RietveldInstrumentParameter, RietveldParameterLayout,
14 RietveldParameterSelection, RietveldRefinementOptions, RietveldStructuralSelection,
15 TerminationReason, calculate_rietveld_pattern, refine_general_rietveld_with_runtime,
16};
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum RietveldRecipeMode {
21 Explicit,
23 Intelligent,
25}
26
27impl RietveldRecipeMode {
28 #[must_use]
30 pub const fn as_str(self) -> &'static str {
31 match self {
32 Self::Explicit => "explicit",
33 Self::Intelligent => "intelligent",
34 }
35 }
36}
37
38#[derive(Clone, Debug, PartialEq)]
40pub struct RietveldStage {
41 name: String,
42 selection: RietveldParameterSelection,
43 rationale: Vec<String>,
44 options: Option<RietveldRefinementOptions>,
45 covariance: Option<RietveldCovarianceOptions>,
46 accepted_terminations: Vec<TerminationReason>,
47}
48
49impl RietveldStage {
50 pub fn new(
56 name: impl Into<String>,
57 selection: RietveldParameterSelection,
58 rationale: Vec<String>,
59 ) -> Result<Self, RietveldRecipeError> {
60 let result = Self {
61 name: name.into(),
62 selection,
63 rationale,
64 options: None,
65 covariance: None,
66 accepted_terminations: vec![TerminationReason::Converged, TerminationReason::Stagnated],
67 };
68 result.validate()?;
69 Ok(result)
70 }
71
72 #[must_use]
74 pub fn with_options(
75 mut self,
76 options: RietveldRefinementOptions,
77 covariance: RietveldCovarianceOptions,
78 ) -> Self {
79 self.options = Some(options);
80 self.covariance = Some(covariance);
81 self
82 }
83
84 pub fn with_accepted_terminations(
91 mut self,
92 accepted: Vec<TerminationReason>,
93 ) -> Result<Self, RietveldRecipeError> {
94 self.accepted_terminations = accepted;
95 self.validate()?;
96 Ok(self)
97 }
98
99 #[must_use]
101 pub fn name(&self) -> &str {
102 &self.name
103 }
104
105 #[must_use]
107 pub const fn selection(&self) -> &RietveldParameterSelection {
108 &self.selection
109 }
110
111 #[must_use]
113 pub fn rationale(&self) -> &[String] {
114 &self.rationale
115 }
116
117 #[must_use]
119 pub const fn options(&self) -> Option<&RietveldRefinementOptions> {
120 self.options.as_ref()
121 }
122
123 #[must_use]
125 pub const fn covariance(&self) -> Option<RietveldCovarianceOptions> {
126 self.covariance
127 }
128
129 #[must_use]
131 pub fn accepted_terminations(&self) -> &[TerminationReason] {
132 &self.accepted_terminations
133 }
134
135 fn validate(&self) -> Result<(), RietveldRecipeError> {
136 if !valid_label(&self.name) {
137 return Err(RietveldRecipeError::InvalidStageName);
138 }
139 if self.rationale.is_empty() || self.rationale.iter().any(|value| !valid_label(value)) {
140 return Err(RietveldRecipeError::InvalidRationale);
141 }
142 if self.accepted_terminations.is_empty()
143 || self
144 .accepted_terminations
145 .iter()
146 .enumerate()
147 .any(|(index, value)| self.accepted_terminations[..index].contains(value))
148 {
149 return Err(RietveldRecipeError::InvalidAcceptedTerminations);
150 }
151 Ok(())
152 }
153}
154
155#[derive(Clone, Debug, PartialEq)]
157pub struct RietveldRecipe {
158 name: String,
159 stages: Vec<RietveldStage>,
160 mode: RietveldRecipeMode,
161 planner_notes: Vec<String>,
162}
163
164impl RietveldRecipe {
165 pub fn new(
172 name: impl Into<String>,
173 stages: Vec<RietveldStage>,
174 mode: RietveldRecipeMode,
175 planner_notes: Vec<String>,
176 ) -> Result<Self, RietveldRecipeError> {
177 let result = Self {
178 name: name.into(),
179 stages,
180 mode,
181 planner_notes,
182 };
183 result.validate()?;
184 Ok(result)
185 }
186
187 #[must_use]
189 pub fn name(&self) -> &str {
190 &self.name
191 }
192
193 #[must_use]
195 pub fn stages(&self) -> &[RietveldStage] {
196 &self.stages
197 }
198
199 #[must_use]
201 pub const fn mode(&self) -> RietveldRecipeMode {
202 self.mode
203 }
204
205 #[must_use]
207 pub fn planner_notes(&self) -> &[String] {
208 &self.planner_notes
209 }
210
211 fn validate(&self) -> Result<(), RietveldRecipeError> {
212 if !valid_label(&self.name) {
213 return Err(RietveldRecipeError::InvalidRecipeName);
214 }
215 if self.stages.is_empty() {
216 return Err(RietveldRecipeError::EmptyRecipe);
217 }
218 let mut names = BTreeSet::new();
219 for stage in &self.stages {
220 stage.validate()?;
221 if !names.insert(stage.name.clone()) {
222 return Err(RietveldRecipeError::DuplicateStageName {
223 name: stage.name.clone(),
224 });
225 }
226 }
227 if self.planner_notes.iter().any(|value| !valid_label(value)) {
228 return Err(RietveldRecipeError::InvalidPlannerNote);
229 }
230 Ok(())
231 }
232}
233
234#[derive(Clone, Debug, PartialEq)]
236pub struct RietveldStageResult {
237 pub stage: RietveldStage,
239 pub starting_rwp: f64,
241 pub result: RietveldGeneralRefinementResult,
243 pub accepted: bool,
245}
246
247#[derive(Clone, Debug, PartialEq)]
249pub struct RietveldWorkflowResult {
250 recipe: RietveldRecipe,
251 stages: Vec<RietveldStageResult>,
252 completed: bool,
253}
254
255impl RietveldWorkflowResult {
256 #[must_use]
258 pub const fn recipe(&self) -> &RietveldRecipe {
259 &self.recipe
260 }
261
262 #[must_use]
264 pub fn stages(&self) -> &[RietveldStageResult] {
265 &self.stages
266 }
267
268 #[must_use]
270 pub const fn completed(&self) -> bool {
271 self.completed
272 }
273
274 #[must_use]
276 pub fn final_result(&self) -> &RietveldGeneralRefinementResult {
277 &self.stages[self.stages.len() - 1].result
278 }
279
280 #[must_use]
282 pub fn last_accepted_stage(&self) -> Option<&RietveldStageResult> {
283 self.stages.iter().rev().find(|stage| stage.accepted)
284 }
285}
286
287#[derive(Clone, Default)]
289pub struct RietveldRecipeSinks {
290 event: Option<SharedEventSink>,
291 checkpoint: Option<SharedCheckpointSink>,
292}
293
294impl RietveldRecipeSinks {
295 #[must_use]
297 pub fn with_event_sink(mut self, sink: impl RefinementEventSink + 'static) -> Self {
298 self.event = Some(SharedEventSink(Arc::new(Mutex::new(Some(Box::new(sink))))));
299 self
300 }
301
302 #[must_use]
304 pub fn with_checkpoint_sink(
305 mut self,
306 sink: impl CheckpointSink<RietveldGeneralCheckpoint> + 'static,
307 ) -> Self {
308 self.checkpoint = Some(SharedCheckpointSink(Arc::new(Mutex::new(Box::new(sink)))));
309 self
310 }
311}
312
313type EventSinkBox = Box<dyn RefinementEventSink>;
314type CheckpointSinkBox = Box<dyn CheckpointSink<RietveldGeneralCheckpoint>>;
315
316#[derive(Clone)]
317struct SharedEventSink(Arc<Mutex<Option<EventSinkBox>>>);
318
319impl RefinementEventSink for SharedEventSink {
320 fn emit(&mut self, event: &RefinementEvent) -> Result<(), String> {
321 let mut sink = self
322 .0
323 .lock()
324 .map_err(|_| "Rietveld recipe event sink lock is poisoned".to_owned())?;
325 let Some(active) = sink.as_mut() else {
326 return Ok(());
327 };
328 if let Err(error) = active.emit(event) {
329 *sink = None;
330 return Err(error);
331 }
332 Ok(())
333 }
334}
335
336#[derive(Clone)]
337struct SharedCheckpointSink(Arc<Mutex<CheckpointSinkBox>>);
338
339impl CheckpointSink<RietveldGeneralCheckpoint> for SharedCheckpointSink {
340 fn checkpoint(&mut self, checkpoint: &RietveldGeneralCheckpoint) -> Result<(), String> {
341 self.0
342 .lock()
343 .map_err(|_| "Rietveld recipe checkpoint sink lock is poisoned".to_owned())?
344 .checkpoint(checkpoint)
345 }
346}
347
348#[allow(clippy::too_many_lines)]
356pub fn intelligent_rietveld_recipe(
357 input: &RietveldInput,
358 maximum: &RietveldParameterSelection,
359 name: impl Into<String>,
360) -> Result<RietveldRecipe, RietveldRecipeError> {
361 input.validate()?;
362 let mut stages = Vec::new();
363 let mut current = None;
364 append_stage(
365 &mut stages,
366 &mut current,
367 "scale_background",
368 selected(maximum, true, false, false, false, false, false, &[], true),
369 vec![
370 "Establish intensity scale and any differentiable background before correlated terms."
371 .to_owned(),
372 ],
373 )?;
374 let positions = [
375 RietveldInstrumentParameter::WavelengthAngstrom,
376 RietveldInstrumentParameter::ZeroShiftDeg,
377 RietveldInstrumentParameter::SampleDisplacementMm,
378 RietveldInstrumentParameter::DisplaceXMicrometre,
379 RietveldInstrumentParameter::DisplaceYMicrometre,
380 ];
381 append_stage(
382 &mut stages,
383 &mut current,
384 "positions",
385 selected(
386 maximum, true, true, false, false, false, false, &positions, true,
387 ),
388 vec![
389 "Align reflection positions before refining peak widths or structural intensities."
390 .to_owned(),
391 ],
392 )?;
393 append_stage(
394 &mut stages,
395 &mut current,
396 "structure",
397 selected(
398 maximum, true, true, true, true, true, false, &positions, true,
399 ),
400 vec![
401 "Stabilize relative structural intensities after peak centers are aligned.".to_owned(),
402 "Delay profile widths so they cannot initially mask intensity-model errors.".to_owned(),
403 ],
404 )?;
405 append_stage(
406 &mut stages,
407 &mut current,
408 "final_polish",
409 maximum.clone(),
410 vec![
411 "Release authorized profile and sample broadening after positions and intensities."
412 .to_owned(),
413 "Finish with every caller-authorized parameter active together.".to_owned(),
414 ],
415 )?;
416 if stages.is_empty() {
417 stages.push(RietveldStage::new(
418 "evaluate_only",
419 maximum.clone(),
420 vec![
421 "No refinable parameter family was authorized; evaluate the supplied state."
422 .to_owned(),
423 ],
424 )?);
425 }
426 let mut notes = vec![
427 "This plan is advisory workflow orchestration; the Rietveld solver remains general."
428 .to_owned(),
429 "Every stage is cumulative and limited to parameter families authorized by the maximum selection."
430 .to_owned(),
431 ];
432 if !maximum.background {
433 notes.push(
434 "No differentiable background was authorized; the supplied background stays fixed."
435 .to_owned(),
436 );
437 }
438 if !maximum.structural.lattice {
439 notes.push(
440 "Lattice refinement was unavailable or disabled and was not proposed.".to_owned(),
441 );
442 }
443 if maximum.instrument.iter().any(|value| {
444 matches!(
445 value,
446 RietveldInstrumentParameter::DisplaceXMicrometre
447 | RietveldInstrumentParameter::DisplaceYMicrometre
448 )
449 }) {
450 notes.push(
451 "Debye-Scherrer X/Y displacement was authorized and is proposed in the position-alignment stage before profile widths."
452 .to_owned(),
453 );
454 }
455 if maximum.structural.occupancy {
456 notes.push(
457 "Occupancy is delayed to the structural stage because it is strongly scale-correlated."
458 .to_owned(),
459 );
460 }
461 RietveldRecipe::new(name, stages, RietveldRecipeMode::Intelligent, notes)
462}
463
464#[allow(clippy::too_many_arguments)]
474pub fn run_rietveld_recipe(
475 input: &RietveldInput,
476 maximum: &RietveldParameterSelection,
477 lattice_bounds: &[Option<LatticeBounds>],
478 constraints: &[Constraint],
479 recipe: &RietveldRecipe,
480 options: &RietveldRefinementOptions,
481 covariance: RietveldCovarianceOptions,
482 cancellation: Option<&CancellationToken>,
483) -> Result<RietveldWorkflowResult, RietveldRecipeError> {
484 run_rietveld_recipe_with_sinks(
485 input,
486 maximum,
487 lattice_bounds,
488 constraints,
489 recipe,
490 options,
491 covariance,
492 cancellation,
493 None,
494 )
495}
496
497#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
508pub fn run_rietveld_recipe_with_sinks(
509 input: &RietveldInput,
510 maximum: &RietveldParameterSelection,
511 lattice_bounds: &[Option<LatticeBounds>],
512 constraints: &[Constraint],
513 recipe: &RietveldRecipe,
514 options: &RietveldRefinementOptions,
515 covariance: RietveldCovarianceOptions,
516 cancellation: Option<&CancellationToken>,
517 sinks: Option<&RietveldRecipeSinks>,
518) -> Result<RietveldWorkflowResult, RietveldRecipeError> {
519 input.validate()?;
520 recipe.validate()?;
521 let maximum_layout = RietveldParameterLayout::new(input, maximum, lattice_bounds)?;
522 validate_constraint_contract(&maximum_layout, constraints)?;
523 for stage in &recipe.stages {
524 if !selection_subset(&stage.selection, maximum) {
525 return Err(RietveldRecipeError::UnauthorizedSelection {
526 stage: stage.name.clone(),
527 });
528 }
529 }
530 let mut current = input.clone();
531 let mut current_rwp = calculate_rietveld_pattern(¤t, &options.calculation)?
532 .metrics
533 .rwp;
534 let mut results = Vec::with_capacity(recipe.stages.len());
535 for (index, stage) in recipe.stages.iter().enumerate() {
536 let layout = RietveldParameterLayout::new(¤t, &stage.selection, lattice_bounds)?;
537 let keys = layout
538 .parameters()
539 .specs()
540 .iter()
541 .map(|spec| spec.key().clone())
542 .collect::<Vec<_>>();
543 let stage_constraints = stage_constraints(constraints, &keys, &stage.name)?;
544 let stage_options = stage.options.as_ref().unwrap_or(options);
545 let mut stage_covariance = stage.covariance.unwrap_or(covariance);
546 if index + 1 < recipe.stages.len() {
547 stage_covariance.enabled = false;
548 }
549 let mut runtime = RefinementRuntime::new(stage_options.limits, cancellation.cloned())
550 .map_err(RietveldGeneralRefinementError::from)?;
551 if let Some(event) = sinks.and_then(|value| value.event.clone()) {
552 runtime.set_event_sink(event);
553 }
554 if let Some(checkpoint) = sinks.and_then(|value| value.checkpoint.clone()) {
555 runtime.set_checkpoint_sink(checkpoint);
556 }
557 runtime
558 .emit(
559 RefinementEventKind::Start,
560 &stage.name,
561 "native Rietveld recipe stage started",
562 vec![
563 (
564 "recipe".to_owned(),
565 DiagnosticValue::String(recipe.name.clone()),
566 ),
567 (
568 "stage_index".to_owned(),
569 DiagnosticValue::Unsigned(u64::try_from(index).unwrap_or(u64::MAX)),
570 ),
571 ],
572 )
573 .map_err(RietveldGeneralRefinementError::from)?;
574 let result = refine_general_rietveld_with_runtime(
575 ¤t,
576 &stage.selection,
577 lattice_bounds,
578 &stage_constraints,
579 stage_options,
580 stage_covariance,
581 None,
582 &mut runtime,
583 )?;
584 let accepted = stage
585 .accepted_terminations
586 .contains(&result.termination_reason);
587 let final_rwp = result.calculation.metrics.rwp;
588 runtime
589 .emit(
590 RefinementEventKind::Termination,
591 &stage.name,
592 if accepted {
593 "native Rietveld recipe stage accepted"
594 } else {
595 "native Rietveld recipe stage rejected"
596 },
597 vec![
598 ("accepted".to_owned(), DiagnosticValue::Bool(accepted)),
599 (
600 "reason".to_owned(),
601 DiagnosticValue::String(result.termination_reason.as_str().to_owned()),
602 ),
603 ],
604 )
605 .map_err(RietveldGeneralRefinementError::from)?;
606 results.push(RietveldStageResult {
607 stage: stage.clone(),
608 starting_rwp: current_rwp,
609 result,
610 accepted,
611 });
612 if !accepted {
613 break;
614 }
615 current = results[results.len() - 1].result.input.clone();
616 current_rwp = final_rwp;
617 }
618 let completed =
619 results.len() == recipe.stages.len() && results.last().is_some_and(|stage| stage.accepted);
620 Ok(RietveldWorkflowResult {
621 recipe: recipe.clone(),
622 stages: results,
623 completed,
624 })
625}
626
627#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
628fn selected(
629 maximum: &RietveldParameterSelection,
630 phase_scale: bool,
631 lattice: bool,
632 coordinates: bool,
633 occupancy: bool,
634 u_iso: bool,
635 sample_physics: bool,
636 instrument: &[RietveldInstrumentParameter],
637 background: bool,
638) -> RietveldParameterSelection {
639 RietveldParameterSelection {
640 structural: RietveldStructuralSelection {
641 phase_scale: phase_scale && maximum.structural.phase_scale,
642 lattice: lattice && maximum.structural.lattice,
643 coordinates: coordinates && maximum.structural.coordinates,
644 occupancy: occupancy && maximum.structural.occupancy,
645 u_iso: u_iso && maximum.structural.u_iso,
646 },
647 instrument: maximum
648 .instrument
649 .iter()
650 .copied()
651 .filter(|value| instrument.contains(value))
652 .collect(),
653 background: background && maximum.background,
654 sample_physics: sample_physics && maximum.sample_physics,
655 }
656}
657
658fn selection_has_parameters(selection: &RietveldParameterSelection) -> bool {
659 let structural = selection.structural;
660 structural.phase_scale
661 || structural.lattice
662 || structural.coordinates
663 || structural.occupancy
664 || structural.u_iso
665 || !selection.instrument.is_empty()
666 || selection.background
667 || selection.sample_physics
668}
669
670fn append_stage(
671 stages: &mut Vec<RietveldStage>,
672 current: &mut Option<RietveldParameterSelection>,
673 name: &str,
674 selection: RietveldParameterSelection,
675 rationale: Vec<String>,
676) -> Result<(), RietveldRecipeError> {
677 if current.as_ref() == Some(&selection) || !selection_has_parameters(&selection) {
678 return Ok(());
679 }
680 stages.push(RietveldStage::new(name, selection.clone(), rationale)?);
681 *current = Some(selection);
682 Ok(())
683}
684
685fn selection_subset(
686 selected: &RietveldParameterSelection,
687 maximum: &RietveldParameterSelection,
688) -> bool {
689 let selected_structural = selected.structural;
690 let maximum_structural = maximum.structural;
691 (!selected_structural.phase_scale || maximum_structural.phase_scale)
692 && (!selected_structural.lattice || maximum_structural.lattice)
693 && (!selected_structural.coordinates || maximum_structural.coordinates)
694 && (!selected_structural.occupancy || maximum_structural.occupancy)
695 && (!selected_structural.u_iso || maximum_structural.u_iso)
696 && (!selected.background || maximum.background)
697 && (!selected.sample_physics || maximum.sample_physics)
698 && selected
699 .instrument
700 .iter()
701 .all(|value| maximum.instrument.contains(value))
702}
703
704fn stage_constraints(
705 constraints: &[Constraint],
706 keys: &[ParameterKey],
707 stage: &str,
708) -> Result<Vec<Constraint>, RietveldRecipeError> {
709 let available = keys.iter().collect::<BTreeSet<_>>();
710 let mut selected = Vec::new();
711 for constraint in constraints {
712 if !available.contains(constraint.target()) {
713 continue;
714 }
715 let sources = match constraint {
716 Constraint::Fixed(_) => Vec::new(),
717 Constraint::Affine(value) => vec![value.source()],
718 Constraint::Linear(value) => value
719 .terms()
720 .iter()
721 .map(crate::LinearTerm::source)
722 .collect(),
723 };
724 if let Some(missing) = sources.into_iter().find(|key| !available.contains(key)) {
725 return Err(RietveldRecipeError::MissingConstraintDependency {
726 stage: stage.to_owned(),
727 target: Box::new(constraint.target().clone()),
728 missing: Box::new(missing.clone()),
729 });
730 }
731 selected.push(constraint.clone());
732 }
733 Ok(selected)
734}
735
736fn validate_constraint_contract(
737 layout: &RietveldParameterLayout,
738 constraints: &[Constraint],
739) -> Result<(), RietveldRecipeError> {
740 let transform = ConstraintTransform::new(layout.parameters().clone(), constraints.to_vec())?;
741 let constrained = transform.unpack(&transform.pack()?, false)?;
742 for spec in layout.parameters().specs() {
743 let value = constrained
744 .get(spec.key())
745 .copied()
746 .ok_or(RietveldRecipeError::InternalInvariant)?;
747 if (value - spec.value()).abs() > 2.0e-12 {
748 return Err(RietveldRecipeError::UnsatisfiedConstraint {
749 key: Box::new(spec.key().clone()),
750 });
751 }
752 }
753 Ok(())
754}
755
756fn valid_label(value: &str) -> bool {
757 !value.is_empty() && value.trim() == value
758}
759
760#[derive(Debug)]
762pub enum RietveldRecipeError {
763 InvalidRecipeName,
765 InvalidStageName,
767 InvalidRationale,
769 InvalidAcceptedTerminations,
771 InvalidPlannerNote,
773 EmptyRecipe,
775 DuplicateStageName {
777 name: String,
779 },
780 UnauthorizedSelection {
782 stage: String,
784 },
785 MissingConstraintDependency {
787 stage: String,
789 target: Box<ParameterKey>,
791 missing: Box<ParameterKey>,
793 },
794 UnsatisfiedConstraint {
796 key: Box<ParameterKey>,
798 },
799 InternalInvariant,
801 Constraint(Box<ConstraintError>),
803 Parameter(Box<RietveldGeneralParameterError>),
805 Rietveld(Box<crate::RietveldError>),
807 Refinement(Box<RietveldGeneralRefinementError>),
809}
810
811impl Display for RietveldRecipeError {
812 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
813 match self {
814 Self::InvalidRecipeName => formatter.write_str("Rietveld recipe name is invalid"),
815 Self::InvalidStageName => formatter.write_str("Rietveld stage name is invalid"),
816 Self::InvalidRationale => formatter.write_str("Rietveld stage rationale is invalid"),
817 Self::InvalidAcceptedTerminations => {
818 formatter.write_str("Rietveld stage accepted terminations are invalid")
819 }
820 Self::InvalidPlannerNote => formatter.write_str("Rietveld planner note is invalid"),
821 Self::EmptyRecipe => formatter.write_str("Rietveld recipe must contain a stage"),
822 Self::DuplicateStageName { name } => {
823 write!(
824 formatter,
825 "Rietveld recipe stage name {name:?} is duplicated"
826 )
827 }
828 Self::UnauthorizedSelection { stage } => write!(
829 formatter,
830 "Rietveld stage {stage:?} selects parameters outside the authorized maximum"
831 ),
832 Self::MissingConstraintDependency {
833 stage,
834 target,
835 missing,
836 } => write!(
837 formatter,
838 "Rietveld stage {stage:?} selects constraint target {} without dependency {}",
839 target.label(),
840 missing.label()
841 ),
842 Self::UnsatisfiedConstraint { key } => write!(
843 formatter,
844 "initial physical value does not satisfy the constraint for {}",
845 key.label()
846 ),
847 Self::InternalInvariant => {
848 formatter.write_str("native staged Rietveld invariant failed")
849 }
850 Self::Constraint(error) => Display::fmt(error, formatter),
851 Self::Parameter(error) => Display::fmt(error, formatter),
852 Self::Rietveld(error) => Display::fmt(error, formatter),
853 Self::Refinement(error) => Display::fmt(error, formatter),
854 }
855 }
856}
857
858impl Error for RietveldRecipeError {
859 fn source(&self) -> Option<&(dyn Error + 'static)> {
860 match self {
861 Self::Constraint(error) => Some(error),
862 Self::Parameter(error) => Some(error),
863 Self::Rietveld(error) => Some(error),
864 Self::Refinement(error) => Some(error),
865 Self::InvalidRecipeName
866 | Self::InvalidStageName
867 | Self::InvalidRationale
868 | Self::InvalidAcceptedTerminations
869 | Self::InvalidPlannerNote
870 | Self::EmptyRecipe
871 | Self::DuplicateStageName { .. }
872 | Self::UnauthorizedSelection { .. }
873 | Self::MissingConstraintDependency { .. }
874 | Self::UnsatisfiedConstraint { .. }
875 | Self::InternalInvariant => None,
876 }
877 }
878}
879
880impl From<RietveldGeneralParameterError> for RietveldRecipeError {
881 fn from(value: RietveldGeneralParameterError) -> Self {
882 Self::Parameter(Box::new(value))
883 }
884}
885
886impl From<ConstraintError> for RietveldRecipeError {
887 fn from(value: ConstraintError) -> Self {
888 Self::Constraint(Box::new(value))
889 }
890}
891
892impl From<crate::RietveldError> for RietveldRecipeError {
893 fn from(value: crate::RietveldError) -> Self {
894 Self::Rietveld(Box::new(value))
895 }
896}
897
898impl From<RietveldGeneralRefinementError> for RietveldRecipeError {
899 fn from(value: RietveldGeneralRefinementError) -> Self {
900 Self::Refinement(Box::new(value))
901 }
902}