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