1use std::{
2 collections::BTreeSet,
3 fmt::{Display, Formatter},
4};
5
6use crate::utils::{
7 GS,
8 serde_utils::{
9 IsDefault, is_default_form_path, is_default_pysecdec_relative_precision,
10 is_default_python_path, is_default_vakint_evaluation_methods,
11 is_default_vakint_normalization, is_false, is_minus_one_string, is_true, is_usize,
12 },
13};
14use bincode_trait_derive::{Decode, Encode};
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17use vakint::{AlphaLoopOptions, EvaluationMethod, FMFTOptions, MATADOptions, PySecDecOptions};
18
19#[derive(
20 Debug,
21 Clone,
22 Copy,
23 Serialize,
24 Deserialize,
25 Encode,
26 Decode,
27 PartialEq,
28 Eq,
29 Hash,
30 JsonSchema,
31 Default,
32)]
33#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
34#[serde(deny_unknown_fields)]
35pub enum ApproximationType {
36 #[default]
37 #[serde(rename = "MUV", alias = "muv")]
38 MUV,
39 #[serde(rename = "PolePart", alias = "pole_part")]
40 PolePart,
41 #[serde(rename = "OS", alias = "os")]
42 OS,
43 #[serde(rename = "IR", alias = "ir")]
44 IR,
45 #[serde(rename = "Unsubtracted", alias = "unsubtracted")]
46 Unsubtracted,
47 #[serde(rename = "VaccuumLimit", alias = "vaccuum_limit")]
48 VaccuumLimit,
49}
50
51impl Display for ApproximationType {
52 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53 match self {
54 ApproximationType::MUV => write!(f, "MUV"),
55 ApproximationType::PolePart => write!(f, "PolePart"),
56 ApproximationType::OS => write!(f, "OS"),
57 ApproximationType::IR => write!(f, "IR"),
58 ApproximationType::Unsubtracted => write!(f, "Unsubtracted"),
59 ApproximationType::VaccuumLimit => write!(f, "VaccuumLimit"),
60 }
61 }
62}
63
64#[derive(
65 Debug,
66 Clone,
67 Copy,
68 Serialize,
69 Deserialize,
70 Encode,
71 Decode,
72 PartialEq,
73 Eq,
74 Hash,
75 JsonSchema,
76 Default,
77)]
78#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
79#[serde(deny_unknown_fields)]
80pub enum UVOrchestrator {
81 #[serde(rename = "legacy_dag_forest", alias = "LegacyDagForest")]
82 LegacyDagForest,
83 #[default]
84 #[serde(rename = "hedge_poset", alias = "HedgePoset")]
85 HedgePoset,
86 #[serde(rename = "compare", alias = "Compare")]
87 Compare,
88}
89
90impl Display for UVOrchestrator {
91 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
92 match self {
93 UVOrchestrator::LegacyDagForest => write!(f, "legacy_dag_forest"),
94 UVOrchestrator::HedgePoset => write!(f, "hedge_poset"),
95 UVOrchestrator::Compare => write!(f, "compare"),
96 }
97 }
98}
99
100#[cfg_attr(
101 feature = "python_api",
102 pyo3::pyclass(from_py_object, get_all, set_all)
103)]
104#[derive(
105 Debug,
106 Clone,
107 Serialize,
108 Deserialize,
109 Encode,
110 Decode,
111 Default,
112 PartialEq,
113 Eq,
114 PartialOrd,
115 Ord,
116 Hash,
117 JsonSchema,
118)]
119#[serde(default, deny_unknown_fields)]
120pub struct CTIdentifier {
121 #[serde(skip_serializing_if = "BTreeSet::is_empty")]
123 pub external_pdg_set: BTreeSet<isize>,
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub internal_pdg_set: Option<BTreeSet<isize>>,
127}
128
129impl CTIdentifier {
130 pub fn new(
131 external_pdg_set: BTreeSet<isize>,
132 internal_pdg_set: Option<BTreeSet<isize>>,
133 ) -> Self {
134 Self {
135 external_pdg_set,
136 internal_pdg_set,
137 }
138 }
139
140 pub fn matches(&self, candidate: &Self) -> bool {
141 self.external_pdg_set == candidate.external_pdg_set
142 && self
143 .internal_pdg_set
144 .as_ref()
145 .is_none_or(|internal| candidate.internal_pdg_set.as_ref() == Some(internal))
146 }
147}
148
149#[cfg_attr(
150 feature = "python_api",
151 pyo3::pyclass(from_py_object, get_all, set_all)
152)]
153#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, JsonSchema)]
154#[serde(deny_unknown_fields)]
155pub struct CTRenormalizationRule {
156 pub ct_identifier: CTIdentifier,
158 pub prescription: ApproximationType,
160}
161
162impl CTRenormalizationRule {
163 pub fn new(ct_identifier: CTIdentifier, prescription: ApproximationType) -> Self {
164 Self {
165 ct_identifier,
166 prescription,
167 }
168 }
169}
170
171#[cfg_attr(
172 feature = "python_api",
173 pyo3::pyclass(from_py_object, get_all, set_all)
174)]
175#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
176#[serde(default, deny_unknown_fields)]
177pub struct RenormalizationPrescriptionSettings {
178 #[serde(skip_serializing_if = "IsDefault::is_default")]
180 pub log_divergent: ApproximationType,
181 #[serde(skip_serializing_if = "IsDefault::is_default")]
183 pub massive_power_divergent: ApproximationType,
184 #[serde(skip_serializing_if = "IsDefault::is_default")]
186 pub massless_power_divergent: ApproximationType,
187 #[serde(
189 default,
190 skip_serializing_if = "IsDefault::is_default",
191 with = "ct_renormalization_overrides_serde"
192 )]
193 #[schemars(with = "Vec<CTRenormalizationRule>")]
194 pub overrides: Vec<CTRenormalizationRule>,
195}
196
197impl Default for RenormalizationPrescriptionSettings {
198 fn default() -> Self {
199 Self {
200 log_divergent: ApproximationType::MUV,
201 massive_power_divergent: ApproximationType::MUV,
202 massless_power_divergent: ApproximationType::MUV,
203 overrides: Vec::new(),
204 }
205 }
206}
207
208impl RenormalizationPrescriptionSettings {
209 pub fn approximation_scheme_for(
210 &self,
211 ct_identifier: &CTIdentifier,
212 dod: i32,
213 has_massive_externals: bool,
214 ) -> ApproximationType {
215 if let Some(rule) = self
216 .overrides
217 .iter()
218 .find(|rule| &rule.ct_identifier == ct_identifier)
219 {
220 return rule.prescription;
221 }
222
223 if let Some(rule) = self.overrides.iter().find(|rule| {
224 rule.ct_identifier.internal_pdg_set.is_none()
225 && rule.ct_identifier.matches(ct_identifier)
226 }) {
227 return rule.prescription;
228 }
229
230 if dod == 0 {
231 self.log_divergent
232 } else if has_massive_externals {
233 self.massive_power_divergent
234 } else {
235 self.massless_power_divergent
236 }
237 }
238}
239
240#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
241#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
242#[serde(default, deny_unknown_fields)]
243pub struct MATADSettings {
244 #[serde(skip_serializing_if = "is_true")]
246 pub expand_masters: bool,
247 #[serde(skip_serializing_if = "is_true")]
249 pub susbstitute_masters: bool,
250 #[serde(skip_serializing_if = "is_true")]
252 pub substitute_hpls: bool,
253 #[serde(skip_serializing_if = "is_true")]
255 pub direct_numerical_substition: bool,
256}
257
258impl Default for MATADSettings {
259 fn default() -> Self {
260 Self {
261 expand_masters: true,
262 susbstitute_masters: true,
263 substitute_hpls: true,
264 direct_numerical_substition: true,
265 }
266 }
267}
268
269#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
270#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
271#[serde(default, deny_unknown_fields)]
272pub struct AlphaLoopSettings {
273 #[serde(skip_serializing_if = "is_true")]
275 pub susbstitute_masters: bool,
276}
277
278impl Default for AlphaLoopSettings {
279 fn default() -> Self {
280 Self {
281 susbstitute_masters: true,
282 }
283 }
284}
285
286#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
287#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
288#[serde(default, deny_unknown_fields)]
289pub struct FMFTSettings {
290 #[serde(skip_serializing_if = "is_true")]
292 pub expand_masters: bool,
293 #[serde(skip_serializing_if = "is_true")]
295 pub susbstitute_masters: bool,
296}
297
298impl Default for FMFTSettings {
299 fn default() -> Self {
300 Self {
301 expand_masters: true,
302 susbstitute_masters: true,
303 }
304 }
305}
306#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
307#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
308#[serde(default, deny_unknown_fields)]
309pub struct PySecDecSettings {
310 #[serde(skip_serializing_if = "is_true")]
312 pub quiet: bool,
313 #[serde(skip_serializing_if = "is_default_pysecdec_relative_precision")]
315 pub relative_precision: f64,
316 #[serde(skip_serializing_if = "is_usize::<10_000>")]
318 pub min_n_evals: usize,
319 #[serde(skip_serializing_if = "is_usize::<1_000_000_000_000>")]
321 pub max_n_evals: usize,
322 #[serde(skip_serializing_if = "IsDefault::is_default")]
324 pub reuse_existing_output: Option<String>,
325}
326
327impl Default for PySecDecSettings {
328 fn default() -> Self {
329 Self {
330 quiet: true,
331 relative_precision: 1.0e-7,
332 min_n_evals: 10_000,
333 max_n_evals: 1_000_000_000_000,
334 reuse_existing_output: None,
335 }
336 }
337}
338
339#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
340#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
341#[serde(default, deny_unknown_fields)]
342pub struct VakintSettings {
343 #[serde(skip_serializing_if = "is_default_form_path")]
345 pub form_exe_path: String,
346 #[serde(skip_serializing_if = "is_default_python_path")]
348 pub python_exe_path: String,
349 #[serde(skip_serializing_if = "is_default_vakint_evaluation_methods")]
351 pub evaluation_methods: Vec<String>,
352 #[serde(skip_serializing_if = "IsDefault::is_default")]
354 pub matad: MATADSettings,
355 #[serde(skip_serializing_if = "IsDefault::is_default")]
357 pub alphaloop: AlphaLoopSettings,
358 #[serde(skip_serializing_if = "IsDefault::is_default")]
360 pub fmft: FMFTSettings,
361 #[serde(skip_serializing_if = "IsDefault::is_default")]
363 pub pysecdec: PySecDecSettings,
364 #[serde(skip_serializing_if = "is_usize::<16>")]
366 pub run_time_decimal_precision: usize,
367 #[serde(skip_serializing_if = "is_true")]
369 pub clean_tmp_dir: bool,
370 #[serde(skip_serializing_if = "IsDefault::is_default")]
372 pub temporary_directory: Option<String>,
373 #[serde(skip_serializing_if = "is_default_vakint_normalization")]
375 pub normalization: String,
376 #[serde(skip_serializing_if = "is_minus_one_string")]
378 pub additional_normalization: String,
379}
380
381impl VakintSettings {
382 pub fn true_settings(&self) -> vakint::VakintSettings {
383 vakint::VakintSettings {
384 form_exe_path: self.form_exe_path.clone(),
385 python_exe_path: self.python_exe_path.clone(),
386 verify_numerator_identification: false,
387 run_time_decimal_precision: self.run_time_decimal_precision as u32,
388 allow_unknown_integrals: false,
389 clean_tmp_dir: self.clean_tmp_dir,
390 precision_for_input_float_rationalization:
391 vakint::InputFloatRationalizationPrecision::FullPrecision,
392 evaluation_order: vakint::EvaluationOrder(
393 self.evaluation_methods
394 .iter()
395 .map(|a| match a.as_str() {
396 "alphaloop" => EvaluationMethod::AlphaLoop(AlphaLoopOptions {
397 susbstitute_masters: self.alphaloop.susbstitute_masters,
398 }),
399 "matad" => EvaluationMethod::MATAD(MATADOptions {
400 expand_masters: self.matad.expand_masters,
401 susbstitute_masters: self.matad.susbstitute_masters,
402 substitute_hpls: self.matad.substitute_hpls,
403 direct_numerical_substition: self.matad.direct_numerical_substition,
404 }),
405 "fmft" => EvaluationMethod::FMFT(FMFTOptions {
406 expand_masters: self.fmft.expand_masters,
407 susbstitute_masters: self.fmft.susbstitute_masters,
408 }),
409 "pysecdec" => EvaluationMethod::PySecDec(PySecDecOptions {
410 quiet: self.pysecdec.quiet,
411 relative_precision: self.pysecdec.relative_precision,
412 min_n_evals: self.pysecdec.min_n_evals as u64,
413 max_n_evals: self.pysecdec.max_n_evals as u64,
414 reuse_existing_output: self.pysecdec.reuse_existing_output.clone(),
415 ..Default::default()
416 }),
417 _ => panic!("Unknown vakint evaluation method: {}", a),
418 })
419 .collect(),
420 ),
421 use_dot_product_notation: false,
422 temporary_directory: self.temporary_directory.clone(),
423 epsilon_symbol: GS.dim_epsilon.get_name().into(),
424 mu_r_sq_symbol: GS.mu_r_sq.get_name().into(),
425 integral_normalization_factor: match self.normalization.as_str() {
426 "MSbar" => vakint::LoopNormalizationFactor::MSbar,
427 "FMFTandMATAD" => vakint::LoopNormalizationFactor::FMFTandMATAD,
428 "pySecDec" => vakint::LoopNormalizationFactor::pySecDec,
429 _ => vakint::LoopNormalizationFactor::Custom(self.normalization.clone()),
430 },
431 number_of_terms_in_epsilon_expansion: 5,
433 }
435 }
436}
437
438impl Default for VakintSettings {
439 fn default() -> Self {
440 Self {
441 form_exe_path: "form".to_string(),
442 python_exe_path: "python3".to_string(),
443 evaluation_methods: vec![
445 "alphaloop".to_string(),
446 "matad".to_string(),
447 "fmft".to_string(),
448 ],
449 matad: MATADSettings::default(),
450 alphaloop: AlphaLoopSettings::default(),
451 fmft: FMFTSettings::default(),
452 pysecdec: PySecDecSettings::default(),
453 run_time_decimal_precision: 100,
454 clean_tmp_dir: true,
455 temporary_directory: None,
456 normalization: "MSbar".to_string(),
457 additional_normalization: "-1".to_string(),
458 }
459 }
460}
461
462#[derive(Debug, Clone, Default, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
463#[cfg_attr(
464 feature = "python_api",
465 pyo3::pyclass(from_py_object, get_all, set_all)
466)]
467pub enum FinalIntegrandDimension {
468 FourD,
469 #[default]
470 ThreeD,
471}
472
473impl Display for FinalIntegrandDimension {
474 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
475 match self {
476 FinalIntegrandDimension::FourD => write!(f, "4D"),
477 FinalIntegrandDimension::ThreeD => write!(f, "3D"),
478 }
479 }
480}
481
482#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
483#[cfg_attr(
484 feature = "python_api",
485 pyo3::pyclass(from_py_object, get_all, set_all)
486)]
487#[serde(default, deny_unknown_fields)]
488pub struct UVgenerationSettings {
489 #[serde(skip_serializing_if = "is_true")]
491 pub softct: bool,
492 #[serde(skip_serializing_if = "is_true")]
494 pub generate_integrated: bool,
495 #[serde(skip_serializing_if = "is_true")]
497 pub subtract_uv: bool,
498 #[serde(skip_serializing_if = "IsDefault::is_default")]
500 pub final_integrand: FinalIntegrandDimension,
501 #[serde(skip_serializing_if = "is_false")]
503 pub add_marker: bool,
504 #[serde(skip_serializing_if = "is_true")]
506 pub keep_marker: bool,
507 #[serde(skip_serializing_if = "is_true")]
509 pub inner_products: bool,
510 #[serde(skip_serializing_if = "IsDefault::is_default")]
512 pub orchestrator: UVOrchestrator,
513 #[serde(skip_serializing_if = "IsDefault::is_default")]
515 pub renormalization_prescription: RenormalizationPrescriptionSettings,
516 #[serde(skip_serializing_if = "IsDefault::is_default")]
518 pub vakint: VakintSettings,
519}
520
521impl Default for UVgenerationSettings {
522 fn default() -> Self {
523 UVgenerationSettings {
524 softct: true,
525 generate_integrated: true,
526 subtract_uv: true,
527 final_integrand: FinalIntegrandDimension::default(),
528 inner_products: true,
529 orchestrator: UVOrchestrator::default(),
530 add_marker: false,
531 keep_marker: true,
532 renormalization_prescription: RenormalizationPrescriptionSettings::default(),
533 vakint: VakintSettings::default(),
534 }
535 }
536}
537
538impl UVgenerationSettings {
539 pub fn approximation_scheme_for(
540 &self,
541 ct_identifier: &CTIdentifier,
542 dod: i32,
543 has_massive_externals: bool,
544 ) -> ApproximationType {
545 self.renormalization_prescription.approximation_scheme_for(
546 ct_identifier,
547 dod,
548 has_massive_externals,
549 )
550 }
551}
552
553mod ct_renormalization_overrides_serde {
554 use super::CTRenormalizationRule;
555 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
556 use std::collections::BTreeSet;
557
558 pub fn serialize<S>(
559 overrides: &Vec<CTRenormalizationRule>,
560 serializer: S,
561 ) -> Result<S::Ok, S::Error>
562 where
563 S: Serializer,
564 {
565 overrides.serialize(serializer)
566 }
567
568 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<CTRenormalizationRule>, D::Error>
569 where
570 D: Deserializer<'de>,
571 {
572 let rules = Vec::<CTRenormalizationRule>::deserialize(deserializer)?;
573 let mut seen = BTreeSet::new();
574
575 for rule in &rules {
576 if !seen.insert(rule.ct_identifier.clone()) {
577 return Err(D::Error::custom(format!(
578 "duplicate CTIdentifier in renormalization_prescription.overrides: {:?}",
579 rule.ct_identifier
580 )));
581 }
582 }
583
584 Ok(rules)
585 }
586}
587
588#[cfg(test)]
589mod tests {
590 use super::{
591 ApproximationType, CTIdentifier, CTRenormalizationRule,
592 RenormalizationPrescriptionSettings, UVOrchestrator, UVgenerationSettings,
593 };
594 use std::collections::BTreeSet;
595
596 fn pdg_set(values: impl IntoIterator<Item = isize>) -> BTreeSet<isize> {
597 values.into_iter().collect()
598 }
599
600 #[test]
601 fn renormalization_prescription_prefers_exact_internal_match_over_wildcard() {
602 let exact_identifier = CTIdentifier::new(pdg_set([1]), Some(pdg_set([1, 22])));
603 let wildcard_identifier = CTIdentifier::new(pdg_set([1]), None);
604 let settings = UVgenerationSettings {
605 renormalization_prescription: RenormalizationPrescriptionSettings {
606 overrides: vec![
607 CTRenormalizationRule::new(
608 wildcard_identifier,
609 ApproximationType::Unsubtracted,
610 ),
611 CTRenormalizationRule::new(exact_identifier.clone(), ApproximationType::OS),
612 ],
613 ..Default::default()
614 },
615 ..Default::default()
616 };
617
618 assert_eq!(
619 settings.approximation_scheme_for(&exact_identifier, 0, false),
620 ApproximationType::OS
621 );
622 }
623
624 #[test]
625 fn renormalization_prescription_matches_wildcard_internal_rule() {
626 let candidate_identifier = CTIdentifier::new(pdg_set([1]), Some(pdg_set([1, 22])));
627 let settings = UVgenerationSettings {
628 renormalization_prescription: RenormalizationPrescriptionSettings {
629 overrides: vec![CTRenormalizationRule::new(
630 CTIdentifier::new(pdg_set([1]), None),
631 ApproximationType::OS,
632 )],
633 ..Default::default()
634 },
635 ..Default::default()
636 };
637
638 assert_eq!(
639 settings.approximation_scheme_for(&candidate_identifier, 0, false),
640 ApproximationType::OS
641 );
642 }
643
644 #[test]
645 fn renormalization_prescription_can_select_unsubtracted() {
646 let candidate_identifier = CTIdentifier::new(pdg_set([1]), Some(pdg_set([1, 22])));
647 let settings = UVgenerationSettings {
648 renormalization_prescription: RenormalizationPrescriptionSettings {
649 overrides: vec![CTRenormalizationRule::new(
650 CTIdentifier::new(pdg_set([1]), None),
651 ApproximationType::Unsubtracted,
652 )],
653 ..Default::default()
654 },
655 ..Default::default()
656 };
657
658 assert_eq!(
659 settings.approximation_scheme_for(&candidate_identifier, 0, false),
660 ApproximationType::Unsubtracted
661 );
662 }
663
664 #[test]
665 fn renormalization_prescription_defaults_to_msbar() {
666 let settings = UVgenerationSettings::default();
667
668 assert_eq!(
669 settings.approximation_scheme_for(
670 &CTIdentifier::new(pdg_set([1]), Some(pdg_set([1, 22]))),
671 1,
672 false
673 ),
674 ApproximationType::MUV
675 );
676 }
677
678 #[test]
679 fn renormalization_prescription_accepts_blanket_pole_part() {
680 let settings: RenormalizationPrescriptionSettings = toml::from_str(
681 r#"
682log_divergent = "PolePart"
683massive_power_divergent = "PolePart"
684massless_power_divergent = "PolePart"
685"#,
686 )
687 .unwrap();
688
689 assert_eq!(settings.log_divergent, ApproximationType::PolePart);
690 assert_eq!(
691 settings.massive_power_divergent,
692 ApproximationType::PolePart
693 );
694 assert_eq!(
695 settings.massless_power_divergent,
696 ApproximationType::PolePart
697 );
698 }
699
700 #[test]
701 fn renormalization_prescription_uses_log_divergent_bucket() {
702 let settings = UVgenerationSettings {
703 renormalization_prescription: RenormalizationPrescriptionSettings {
704 log_divergent: ApproximationType::OS,
705 massive_power_divergent: ApproximationType::IR,
706 massless_power_divergent: ApproximationType::Unsubtracted,
707 ..Default::default()
708 },
709 ..Default::default()
710 };
711
712 assert_eq!(
713 settings.approximation_scheme_for(
714 &CTIdentifier::new(pdg_set([1]), Some(pdg_set([1, 22]))),
715 0,
716 false
717 ),
718 ApproximationType::OS
719 );
720 }
721
722 #[test]
723 fn renormalization_prescription_uses_massive_power_divergent_bucket() {
724 let settings = UVgenerationSettings {
725 renormalization_prescription: RenormalizationPrescriptionSettings {
726 massive_power_divergent: ApproximationType::OS,
727 massless_power_divergent: ApproximationType::IR,
728 ..Default::default()
729 },
730 ..Default::default()
731 };
732
733 assert_eq!(
734 settings.approximation_scheme_for(
735 &CTIdentifier::new(pdg_set([1]), Some(pdg_set([1, 22]))),
736 1,
737 true
738 ),
739 ApproximationType::OS
740 );
741 }
742
743 #[test]
744 fn renormalization_prescription_uses_massless_power_divergent_bucket() {
745 let settings = UVgenerationSettings {
746 renormalization_prescription: RenormalizationPrescriptionSettings {
747 massive_power_divergent: ApproximationType::OS,
748 massless_power_divergent: ApproximationType::IR,
749 ..Default::default()
750 },
751 ..Default::default()
752 };
753
754 assert_eq!(
755 settings.approximation_scheme_for(
756 &CTIdentifier::new(pdg_set([1]), Some(pdg_set([1, 22]))),
757 1,
758 false
759 ),
760 ApproximationType::IR
761 );
762 }
763
764 #[test]
765 fn renormalization_prescription_rejects_duplicate_overrides() {
766 let toml = r#"
767[[overrides]]
768prescription = "OS"
769
770[overrides.ct_identifier]
771external_pdg_set = [1]
772
773[[overrides]]
774prescription = "IR"
775
776[overrides.ct_identifier]
777external_pdg_set = [1]
778"#;
779
780 let err = toml::from_str::<RenormalizationPrescriptionSettings>(toml).unwrap_err();
781 assert!(
782 err.to_string()
783 .contains("duplicate CTIdentifier in renormalization_prescription.overrides"),
784 "{err}"
785 );
786 }
787
788 #[test]
789 fn orchestrator_defaults_to_hedge_poset_and_accepts_explicit_modes() {
790 assert_eq!(UVOrchestrator::default(), UVOrchestrator::HedgePoset);
791 assert_eq!(
792 UVgenerationSettings::default().orchestrator,
793 UVOrchestrator::HedgePoset
794 );
795 assert_eq!(
796 toml::from_str::<UVgenerationSettings>("")
797 .unwrap()
798 .orchestrator,
799 UVOrchestrator::HedgePoset
800 );
801
802 let legacy = UVgenerationSettings {
803 orchestrator: UVOrchestrator::LegacyDagForest,
804 ..Default::default()
805 };
806 assert_eq!(legacy.orchestrator, UVOrchestrator::LegacyDagForest);
807 let legacy_toml = toml::to_string(&legacy).unwrap();
808 assert!(legacy_toml.contains("orchestrator = \"legacy_dag_forest\""));
809 assert_eq!(
810 toml::from_str::<UVgenerationSettings>(&legacy_toml).unwrap(),
811 legacy
812 );
813
814 let compare = UVgenerationSettings {
815 orchestrator: UVOrchestrator::Compare,
816 ..Default::default()
817 };
818 assert_eq!(compare.orchestrator, UVOrchestrator::Compare);
819 }
820}