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
497pub fn validate_rietveld_recipe(
508 input: &RietveldInput,
509 maximum: &RietveldParameterSelection,
510 lattice_bounds: &[Option<LatticeBounds>],
511 constraints: &[Constraint],
512 recipe: &RietveldRecipe,
513) -> Result<(), RietveldRecipeError> {
514 input.validate()?;
515 recipe.validate()?;
516 let maximum_layout = RietveldParameterLayout::new(input, maximum, lattice_bounds)?;
517 validate_constraint_contract(&maximum_layout, constraints)?;
518 for stage in &recipe.stages {
519 if !selection_subset(&stage.selection, maximum) {
520 return Err(RietveldRecipeError::UnauthorizedSelection {
521 stage: stage.name.clone(),
522 });
523 }
524 let layout = RietveldParameterLayout::new(input, &stage.selection, lattice_bounds)?;
525 let keys = layout
526 .parameters()
527 .specs()
528 .iter()
529 .map(|spec| spec.key().clone())
530 .collect::<Vec<_>>();
531 stage_constraints(constraints, &keys, &stage.name)?;
532 }
533 Ok(())
534}
535
536#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
547pub fn run_rietveld_recipe_with_sinks(
548 input: &RietveldInput,
549 maximum: &RietveldParameterSelection,
550 lattice_bounds: &[Option<LatticeBounds>],
551 constraints: &[Constraint],
552 recipe: &RietveldRecipe,
553 options: &RietveldRefinementOptions,
554 covariance: RietveldCovarianceOptions,
555 cancellation: Option<&CancellationToken>,
556 sinks: Option<&RietveldRecipeSinks>,
557) -> Result<RietveldWorkflowResult, RietveldRecipeError> {
558 validate_rietveld_recipe(input, maximum, lattice_bounds, constraints, recipe)?;
559 let mut current = input.clone();
560 let mut current_rwp = calculate_rietveld_pattern(¤t, &options.calculation)?
561 .metrics
562 .rwp;
563 let mut results = Vec::with_capacity(recipe.stages.len());
564 for (index, stage) in recipe.stages.iter().enumerate() {
565 let layout = RietveldParameterLayout::new(¤t, &stage.selection, lattice_bounds)?;
566 let keys = layout
567 .parameters()
568 .specs()
569 .iter()
570 .map(|spec| spec.key().clone())
571 .collect::<Vec<_>>();
572 let stage_constraints = stage_constraints(constraints, &keys, &stage.name)?;
573 let stage_options = stage.options.as_ref().unwrap_or(options);
574 let mut stage_covariance = stage.covariance.unwrap_or(covariance);
575 if index + 1 < recipe.stages.len() {
576 stage_covariance.enabled = false;
577 }
578 let mut runtime = RefinementRuntime::new(stage_options.limits, cancellation.cloned())
579 .map_err(RietveldGeneralRefinementError::from)?;
580 if let Some(event) = sinks.and_then(|value| value.event.clone()) {
581 runtime.set_event_sink(event);
582 }
583 if let Some(checkpoint) = sinks.and_then(|value| value.checkpoint.clone()) {
584 runtime.set_checkpoint_sink(checkpoint);
585 }
586 runtime
587 .emit(
588 RefinementEventKind::Start,
589 &stage.name,
590 "native Rietveld recipe stage started",
591 vec![
592 (
593 "recipe".to_owned(),
594 DiagnosticValue::String(recipe.name.clone()),
595 ),
596 (
597 "stage_index".to_owned(),
598 DiagnosticValue::Unsigned(u64::try_from(index).unwrap_or(u64::MAX)),
599 ),
600 ],
601 )
602 .map_err(RietveldGeneralRefinementError::from)?;
603 let result = refine_general_rietveld_with_runtime(
604 ¤t,
605 &stage.selection,
606 lattice_bounds,
607 &stage_constraints,
608 stage_options,
609 stage_covariance,
610 None,
611 &mut runtime,
612 )?;
613 let accepted = stage
614 .accepted_terminations
615 .contains(&result.termination_reason);
616 let final_rwp = result.calculation.metrics.rwp;
617 runtime
618 .emit(
619 RefinementEventKind::Termination,
620 &stage.name,
621 if accepted {
622 "native Rietveld recipe stage accepted"
623 } else {
624 "native Rietveld recipe stage rejected"
625 },
626 vec![
627 ("accepted".to_owned(), DiagnosticValue::Bool(accepted)),
628 (
629 "reason".to_owned(),
630 DiagnosticValue::String(result.termination_reason.as_str().to_owned()),
631 ),
632 ],
633 )
634 .map_err(RietveldGeneralRefinementError::from)?;
635 results.push(RietveldStageResult {
636 stage: stage.clone(),
637 starting_rwp: current_rwp,
638 result,
639 accepted,
640 });
641 if !accepted {
642 break;
643 }
644 current = results[results.len() - 1].result.input.clone();
645 current_rwp = final_rwp;
646 }
647 let completed =
648 results.len() == recipe.stages.len() && results.last().is_some_and(|stage| stage.accepted);
649 Ok(RietveldWorkflowResult {
650 recipe: recipe.clone(),
651 stages: results,
652 completed,
653 })
654}
655
656#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
657fn selected(
658 maximum: &RietveldParameterSelection,
659 phase_scale: bool,
660 lattice: bool,
661 coordinates: bool,
662 occupancy: bool,
663 u_iso: bool,
664 sample_physics: bool,
665 instrument: &[RietveldInstrumentParameter],
666 background: bool,
667) -> RietveldParameterSelection {
668 RietveldParameterSelection {
669 structural: RietveldStructuralSelection {
670 phase_scale: phase_scale && maximum.structural.phase_scale,
671 lattice: lattice && maximum.structural.lattice,
672 coordinates: coordinates && maximum.structural.coordinates,
673 occupancy: occupancy && maximum.structural.occupancy,
674 u_iso: u_iso && maximum.structural.u_iso,
675 },
676 instrument: maximum
677 .instrument
678 .iter()
679 .copied()
680 .filter(|value| instrument.contains(value))
681 .collect(),
682 background: background && maximum.background,
683 sample_physics: sample_physics && maximum.sample_physics,
684 }
685}
686
687fn selection_has_parameters(selection: &RietveldParameterSelection) -> bool {
688 let structural = selection.structural;
689 structural.phase_scale
690 || structural.lattice
691 || structural.coordinates
692 || structural.occupancy
693 || structural.u_iso
694 || !selection.instrument.is_empty()
695 || selection.background
696 || selection.sample_physics
697}
698
699fn append_stage(
700 stages: &mut Vec<RietveldStage>,
701 current: &mut Option<RietveldParameterSelection>,
702 name: &str,
703 selection: RietveldParameterSelection,
704 rationale: Vec<String>,
705) -> Result<(), RietveldRecipeError> {
706 if current.as_ref() == Some(&selection) || !selection_has_parameters(&selection) {
707 return Ok(());
708 }
709 stages.push(RietveldStage::new(name, selection.clone(), rationale)?);
710 *current = Some(selection);
711 Ok(())
712}
713
714fn selection_subset(
715 selected: &RietveldParameterSelection,
716 maximum: &RietveldParameterSelection,
717) -> bool {
718 let selected_structural = selected.structural;
719 let maximum_structural = maximum.structural;
720 (!selected_structural.phase_scale || maximum_structural.phase_scale)
721 && (!selected_structural.lattice || maximum_structural.lattice)
722 && (!selected_structural.coordinates || maximum_structural.coordinates)
723 && (!selected_structural.occupancy || maximum_structural.occupancy)
724 && (!selected_structural.u_iso || maximum_structural.u_iso)
725 && (!selected.background || maximum.background)
726 && (!selected.sample_physics || maximum.sample_physics)
727 && selected
728 .instrument
729 .iter()
730 .all(|value| maximum.instrument.contains(value))
731}
732
733fn stage_constraints(
734 constraints: &[Constraint],
735 keys: &[ParameterKey],
736 stage: &str,
737) -> Result<Vec<Constraint>, RietveldRecipeError> {
738 let available = keys.iter().collect::<BTreeSet<_>>();
739 let mut selected = Vec::new();
740 for constraint in constraints {
741 if !available.contains(constraint.target()) {
742 continue;
743 }
744 let sources = match constraint {
745 Constraint::Fixed(_) => Vec::new(),
746 Constraint::Affine(value) => vec![value.source()],
747 Constraint::Linear(value) => value
748 .terms()
749 .iter()
750 .map(crate::LinearTerm::source)
751 .collect(),
752 };
753 if let Some(missing) = sources.into_iter().find(|key| !available.contains(key)) {
754 return Err(RietveldRecipeError::MissingConstraintDependency {
755 stage: stage.to_owned(),
756 target: Box::new(constraint.target().clone()),
757 missing: Box::new(missing.clone()),
758 });
759 }
760 selected.push(constraint.clone());
761 }
762 Ok(selected)
763}
764
765fn validate_constraint_contract(
766 layout: &RietveldParameterLayout,
767 constraints: &[Constraint],
768) -> Result<(), RietveldRecipeError> {
769 let transform = ConstraintTransform::new(layout.parameters().clone(), constraints.to_vec())?;
770 let constrained = transform.unpack(&transform.pack()?, false)?;
771 for spec in layout.parameters().specs() {
772 let value = constrained
773 .get(spec.key())
774 .copied()
775 .ok_or(RietveldRecipeError::InternalInvariant)?;
776 if (value - spec.value()).abs() > 2.0e-12 {
777 return Err(RietveldRecipeError::UnsatisfiedConstraint {
778 key: Box::new(spec.key().clone()),
779 });
780 }
781 }
782 Ok(())
783}
784
785fn valid_label(value: &str) -> bool {
786 !value.is_empty() && value.trim() == value
787}
788
789#[derive(Debug)]
791pub enum RietveldRecipeError {
792 InvalidRecipeName,
794 InvalidStageName,
796 InvalidRationale,
798 InvalidAcceptedTerminations,
800 InvalidPlannerNote,
802 EmptyRecipe,
804 DuplicateStageName {
806 name: String,
808 },
809 UnauthorizedSelection {
811 stage: String,
813 },
814 MissingConstraintDependency {
816 stage: String,
818 target: Box<ParameterKey>,
820 missing: Box<ParameterKey>,
822 },
823 UnsatisfiedConstraint {
825 key: Box<ParameterKey>,
827 },
828 InternalInvariant,
830 Constraint(Box<ConstraintError>),
832 Parameter(Box<RietveldGeneralParameterError>),
834 Rietveld(Box<crate::RietveldError>),
836 Refinement(Box<RietveldGeneralRefinementError>),
838}
839
840impl Display for RietveldRecipeError {
841 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
842 match self {
843 Self::InvalidRecipeName => formatter.write_str("Rietveld recipe name is invalid"),
844 Self::InvalidStageName => formatter.write_str("Rietveld stage name is invalid"),
845 Self::InvalidRationale => formatter.write_str("Rietveld stage rationale is invalid"),
846 Self::InvalidAcceptedTerminations => {
847 formatter.write_str("Rietveld stage accepted terminations are invalid")
848 }
849 Self::InvalidPlannerNote => formatter.write_str("Rietveld planner note is invalid"),
850 Self::EmptyRecipe => formatter.write_str("Rietveld recipe must contain a stage"),
851 Self::DuplicateStageName { name } => {
852 write!(
853 formatter,
854 "Rietveld recipe stage name {name:?} is duplicated"
855 )
856 }
857 Self::UnauthorizedSelection { stage } => write!(
858 formatter,
859 "Rietveld stage {stage:?} selects parameters outside the authorized maximum"
860 ),
861 Self::MissingConstraintDependency {
862 stage,
863 target,
864 missing,
865 } => write!(
866 formatter,
867 "Rietveld stage {stage:?} selects constraint target {} without dependency {}",
868 target.label(),
869 missing.label()
870 ),
871 Self::UnsatisfiedConstraint { key } => write!(
872 formatter,
873 "initial physical value does not satisfy the constraint for {}",
874 key.label()
875 ),
876 Self::InternalInvariant => {
877 formatter.write_str("native staged Rietveld invariant failed")
878 }
879 Self::Constraint(error) => Display::fmt(error, formatter),
880 Self::Parameter(error) => Display::fmt(error, formatter),
881 Self::Rietveld(error) => Display::fmt(error, formatter),
882 Self::Refinement(error) => Display::fmt(error, formatter),
883 }
884 }
885}
886
887impl Error for RietveldRecipeError {
888 fn source(&self) -> Option<&(dyn Error + 'static)> {
889 match self {
890 Self::Constraint(error) => Some(error),
891 Self::Parameter(error) => Some(error),
892 Self::Rietveld(error) => Some(error),
893 Self::Refinement(error) => Some(error),
894 Self::InvalidRecipeName
895 | Self::InvalidStageName
896 | Self::InvalidRationale
897 | Self::InvalidAcceptedTerminations
898 | Self::InvalidPlannerNote
899 | Self::EmptyRecipe
900 | Self::DuplicateStageName { .. }
901 | Self::UnauthorizedSelection { .. }
902 | Self::MissingConstraintDependency { .. }
903 | Self::UnsatisfiedConstraint { .. }
904 | Self::InternalInvariant => None,
905 }
906 }
907}
908
909impl From<RietveldGeneralParameterError> for RietveldRecipeError {
910 fn from(value: RietveldGeneralParameterError) -> Self {
911 Self::Parameter(Box::new(value))
912 }
913}
914
915impl From<ConstraintError> for RietveldRecipeError {
916 fn from(value: ConstraintError) -> Self {
917 Self::Constraint(Box::new(value))
918 }
919}
920
921impl From<crate::RietveldError> for RietveldRecipeError {
922 fn from(value: crate::RietveldError) -> Self {
923 Self::Rietveld(Box::new(value))
924 }
925}
926
927impl From<RietveldGeneralRefinementError> for RietveldRecipeError {
928 fn from(value: RietveldGeneralRefinementError) -> Self {
929 Self::Refinement(Box::new(value))
930 }
931}