Skip to main content

gammalooprs/settings/
mod.rs

1use bincode_trait_derive::{Decode, Encode};
2use global::{GenerationSettings, Parallelisation};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use crate::{
7    GammaLoopContext,
8    observables::{ObservablesSettings, QuantitiesSettings, SelectorsSettings},
9    settings::runtime::HFunctionSettings,
10    utils::{
11        F, FloatLike,
12        serde_utils::{IsDefault, show_defaults_helper},
13        tracing::LogStyle,
14    },
15};
16
17#[cfg_attr(feature = "python", pyo3::pyclass(from_py_object, get_all, set_all))]
18#[derive(Debug, Clone, Deserialize, Serialize, Encode, Decode, JsonSchema, PartialEq)]
19#[trait_decode(trait= GammaLoopContext)]
20#[serde(default, deny_unknown_fields)]
21pub struct GlobalSettings {
22    /// Tracing filter applied to the optional log-file sink; `off` disables that sink.
23    #[serde(skip_serializing_if = "is_default_logfile_directive")]
24    #[serde(default = "default_logfile_directive")]
25    pub logfile_directive: String,
26    /// Tracing filter applied to terminal output shown by the interactive session.
27    #[serde(skip_serializing_if = "is_default_display_directive")]
28    #[serde(default = "default_display_directive")]
29    pub display_directive: String,
30    /// Formatting choices shared by terminal and file tracing subscribers.
31    #[serde(skip_serializing_if = "IsDefault::is_default")]
32    pub log_style: LogStyle,
33    /// Diagram, numerator, counterterm, evaluator, and compilation settings used during generation.
34    #[serde(skip_serializing_if = "IsDefault::is_default")]
35    pub generation: GenerationSettings,
36    /// Per-stage worker counts for diagram generation, evaluator construction, compilation, and integration.
37    #[serde(skip_serializing_if = "IsDefault::is_default")]
38    pub n_cores: Parallelisation,
39}
40
41#[cfg_attr(feature = "python", pyo3::pyclass(from_py_object, get_all, set_all))]
42#[derive(Debug, Clone, Default, Deserialize, Serialize, Encode, Decode, JsonSchema, PartialEq)]
43#[trait_decode(trait= GammaLoopContext)]
44#[serde(default, deny_unknown_fields)]
45pub struct RuntimeSettings {
46    // Runtime settings
47    /// Evaluation scales, backend, caching, event generation, and output-unit controls.
48    #[serde(rename = "general", skip_serializing_if = "IsDefault::is_default")]
49    pub general: GeneralSettings,
50    /// Per-integrand overrides for external model parameters.
51    #[serde(rename = "model", skip_serializing_if = "IsDefault::is_default")]
52    pub model: RuntimeModelSettings,
53    /// Center-of-mass energy, external momenta, helicities, and phase-space improvement.
54    #[serde(rename = "kinematics", skip_serializing_if = "IsDefault::is_default")]
55    pub kinematics: KinematicsSettings,
56    /// Adaptive Monte Carlo iteration, accuracy, learning, and output controls.
57    #[serde(rename = "integrator", skip_serializing_if = "IsDefault::is_default")]
58    pub integrator: IntegratorSettings,
59    /// Named derived quantities available to selectors and observables.
60    #[serde(rename = "quantities", skip_serializing_if = "IsDefault::is_default")]
61    pub quantities: QuantitiesSettings,
62    /// Named histograms and other observable accumulators.
63    #[serde(rename = "observables", skip_serializing_if = "IsDefault::is_default")]
64    pub observables: ObservablesSettings,
65    /// Named event-selection predicates applied before observable accumulation.
66    #[serde(rename = "selectors", skip_serializing_if = "IsDefault::is_default")]
67    pub selectors: SelectorsSettings,
68    /// Precision escalation, rotated checks, and optional stability diagnostics.
69    #[serde(rename = "stability")]
70    #[serde(skip_serializing_if = "IsDefault::is_default")]
71    pub stability: StabilitySettings,
72    /// Continuous parameterization and discrete graph/orientation/channel sampling strategy.
73    #[serde(rename = "sampling", skip_serializing_if = "IsDefault::is_default")]
74    pub sampling: SamplingSettings,
75    /// Local and integrated threshold/ultraviolet subtraction controls.
76    #[serde(rename = "subtraction", skip_serializing_if = "IsDefault::is_default")]
77    pub subtraction: SubtractionSettings,
78    /// Damping profile shared by Local Unitarity threshold terms; it also localizes
79    /// zero-temperature Fermi-surface distributions.
80    #[serde(rename = "h_function", skip_serializing_if = "IsDefault::is_default")]
81    pub lu_h_function: HFunctionSettings,
82}
83
84impl RuntimeSettings {
85    pub(crate) fn additional_params<T: FloatLike>(&self) -> Vec<F<T>> {
86        self.general
87            .additional_param_values
88            .iter()
89            .map(|a| F(T::from_f64(*a)))
90            .collect()
91    }
92
93    pub(crate) fn should_generate_events(&self) -> bool {
94        self.general.generate_events
95            || self.selectors.values().any(|selector| selector.active)
96            || !self.observables.is_empty()
97    }
98
99    pub(crate) fn should_buffer_generated_events(&self) -> bool {
100        self.general.generate_events || !self.observables.is_empty()
101    }
102
103    pub(crate) fn should_return_generated_events(&self) -> bool {
104        self.general.generate_events
105    }
106}
107
108fn default_logfile_directive() -> String {
109    "off".to_string()
110}
111
112fn default_display_directive() -> String {
113    "info".to_string()
114}
115
116fn is_default_logfile_directive(val: &String) -> bool {
117    show_defaults_helper(val == &default_logfile_directive())
118}
119
120fn is_default_display_directive(val: &String) -> bool {
121    show_defaults_helper(val == &default_display_directive())
122}
123
124impl Default for GlobalSettings {
125    fn default() -> Self {
126        Self {
127            logfile_directive: default_logfile_directive(),
128            display_directive: default_display_directive(),
129            log_style: LogStyle::default(),
130            generation: GenerationSettings::default(),
131            n_cores: Parallelisation::default(),
132        }
133    }
134}
135
136pub mod global;
137pub use runtime::{
138    GeneralSettings, IntegratorSettings, ObservablesOutputSettings, RuntimeModelSettings,
139    SamplingSettings, StabilitySettings, SubtractionSettings, kinematic::KinematicsSettings,
140};
141pub mod runtime;
142
143#[cfg(test)]
144mod tests {
145    use serde::{Deserialize, Serialize};
146
147    use crate::{
148        momentum::{Dep, ExternalMomenta, Helicity},
149        settings::{
150            GlobalSettings, RuntimeSettings, SamplingSettings, SubtractionSettings,
151            global::{GammaloopCompileOptions, GenerationSettings, ThresholdSubtractionSettings},
152            runtime::{
153                DiscreteGraphSamplingSettings, DiscreteGraphSamplingType,
154                GammaloopTropicalSamplingSettings, SamplingChannelWeight,
155                kinematic::{Externals, improvement::PhaseSpaceImprovementSettings},
156            },
157        },
158        utils::{
159            DEFAULT_ESURFACE_EXISTENCE_THRESHOLD, F,
160            serde_utils::{SHOWDEFAULTS, ShowDefaultsGuard},
161        },
162    };
163    use std::fmt::Debug;
164
165    fn generic_test_settings<T>()
166    where
167        T: Serialize + for<'de> Deserialize<'de> + Default + PartialEq + Debug,
168    {
169        {
170            let default = T::default();
171            let serialized = serde_yaml::to_string(&default).unwrap();
172            assert_eq!(serialized, "{}\n");
173            let deserialized: T = serde_yaml::from_str(&serialized).unwrap();
174            assert_eq!(default, deserialized);
175
176            let deserialized_from_empty: T = serde_yaml::from_str("").unwrap();
177            assert_eq!(default, deserialized_from_empty);
178        }
179        {
180            let default = T::default();
181            let serialized = toml::to_string_pretty(&default).unwrap();
182            assert_eq!(serialized, "");
183            let deserialized: T = toml::from_str(&serialized).unwrap();
184            assert_eq!(default, deserialized);
185
186            let deserialized_from_empty: T = toml::from_str("").unwrap();
187            assert_eq!(default, deserialized_from_empty);
188        }
189        {
190            let default = T::default();
191            let serialized = serde_json::to_string(&default).unwrap();
192            assert_eq!(serialized, "{}");
193            let deserialized: T = serde_json::from_str(&serialized).unwrap();
194            assert_eq!(default, deserialized);
195
196            let deserialized_from_empty: T = serde_yaml::from_str("").unwrap();
197            assert_eq!(default, deserialized_from_empty);
198        }
199    }
200
201    #[test]
202    fn global_test_serialize_deserialize() {
203        generic_test_settings::<GlobalSettings>();
204    }
205
206    #[test]
207    fn generation_test_serialize_deserialize() {
208        generic_test_settings::<GenerationSettings>();
209    }
210
211    #[test]
212    fn esurface_existence_threshold_defaults_and_overrides() {
213        assert_eq!(
214            ThresholdSubtractionSettings::default().esurface_existence_threshold,
215            DEFAULT_ESURFACE_EXISTENCE_THRESHOLD,
216        );
217        assert_eq!(
218            SubtractionSettings::default().esurface_existence_threshold,
219            DEFAULT_ESURFACE_EXISTENCE_THRESHOLD,
220        );
221
222        let generation: GenerationSettings =
223            toml::from_str("[threshold_subtraction]\nesurface_existence_threshold = 2.5e-8\n")
224                .unwrap();
225        assert_eq!(
226            generation
227                .threshold_subtraction
228                .esurface_existence_threshold,
229            2.5e-8,
230        );
231
232        let runtime: RuntimeSettings =
233            toml::from_str("[subtraction]\nesurface_existence_threshold = 4.0e-9\n").unwrap();
234        assert_eq!(runtime.subtraction.esurface_existence_threshold, 4.0e-9,);
235
236        let zero_generation: GenerationSettings =
237            toml::from_str("[threshold_subtraction]\nesurface_existence_threshold = 0.0\n")
238                .unwrap();
239        assert_eq!(
240            zero_generation
241                .threshold_subtraction
242                .esurface_existence_threshold,
243            0.0,
244        );
245
246        for invalid in ["-1.0e-7", "nan", "inf", "-inf"] {
247            let generation = toml::from_str::<GenerationSettings>(&format!(
248                "[threshold_subtraction]\nesurface_existence_threshold = {invalid}\n"
249            ));
250            assert!(
251                generation.is_err(),
252                "generation-time E-surface tolerance {invalid} must be rejected"
253            );
254
255            let runtime = toml::from_str::<RuntimeSettings>(&format!(
256                "[subtraction]\nesurface_existence_threshold = {invalid}\n"
257            ));
258            assert!(
259                runtime.is_err(),
260                "runtime E-surface tolerance {invalid} must be rejected"
261            );
262        }
263    }
264
265    #[test]
266    fn radial_root_residual_tolerance_defaults_and_overrides() {
267        assert_eq!(
268            SubtractionSettings::default().radial_root_residual_tolerance,
269            64.0,
270        );
271        assert!(
272            !toml::to_string(&RuntimeSettings::default())
273                .unwrap()
274                .contains("radial_root_residual_tolerance")
275        );
276
277        let runtime: RuntimeSettings =
278            toml::from_str("[subtraction]\nradial_root_residual_tolerance = 128.0\n").unwrap();
279        assert_eq!(runtime.subtraction.radial_root_residual_tolerance, 128.0);
280
281        for invalid in ["-1.0", "nan", "inf", "-inf"] {
282            let runtime = toml::from_str::<RuntimeSettings>(&format!(
283                "[subtraction]\nradial_root_residual_tolerance = {invalid}\n"
284            ));
285            assert!(
286                runtime.is_err(),
287                "radial-root residual tolerance {invalid} must be rejected"
288            );
289        }
290    }
291
292    #[test]
293    fn evaluator_settings_partial_tables_use_evaluator_defaults() {
294        use crate::processes::EvaluatorSettings;
295
296        let parsed: EvaluatorSettings =
297            toml::from_str("horner_iterations = 0\ncpe_iterations = 0\n").unwrap();
298
299        assert_eq!(
300            parsed,
301            EvaluatorSettings {
302                horner_iterations: 0,
303                cpe_iterations: Some(0),
304                ..EvaluatorSettings::default()
305            }
306        );
307
308        let serialized = toml::to_string(&parsed).unwrap();
309        assert!(serialized.contains("horner_iterations = 0"));
310        assert!(serialized.contains("cpe_iterations = 0"));
311        assert!(!serialized.contains("iterative_orientation_optimization"));
312        assert!(!serialized.contains("n_cores"));
313        assert!(!serialized.contains("max_horner_scheme_variables"));
314        assert!(!serialized.contains("max_common_pair_cache_entries"));
315        assert!(!serialized.contains("max_common_pair_distance"));
316    }
317
318    #[test]
319    fn evaluator_contraction_order_defaults_and_explicit_presets_roundtrip() {
320        use crate::processes::{EvaluatorSettings, TensorNetworkContractionOrder};
321
322        let defaults_guard = ShowDefaultsGuard::new(false);
323        let defaults: EvaluatorSettings = toml::from_str("").unwrap();
324        assert_eq!(
325            defaults.tensor_network_contraction_order,
326            TensorNetworkContractionOrder::IntermediateCost
327        );
328        assert_eq!(defaults, EvaluatorSettings::default());
329        assert!(
330            !toml::to_string(&defaults)
331                .unwrap()
332                .contains("tensor_network_contraction_order")
333        );
334        for (name, expected) in [
335            (
336                "intermediate_cost",
337                TensorNetworkContractionOrder::IntermediateCost,
338            ),
339            (
340                "sparse_atom_aware",
341                TensorNetworkContractionOrder::SparseAtomAware,
342            ),
343            ("atom_aware", TensorNetworkContractionOrder::AtomAware),
344            (
345                "result_rank_only",
346                TensorNetworkContractionOrder::ResultRankOnly,
347            ),
348            ("entry_aware", TensorNetworkContractionOrder::EntryAware),
349        ] {
350            let settings: EvaluatorSettings =
351                toml::from_str(&format!("tensor_network_contraction_order = \"{name}\"\n"))
352                    .unwrap();
353            assert_eq!(settings.tensor_network_contraction_order, expected);
354            let serialized = toml::to_string(&settings).unwrap();
355            assert_eq!(
356                toml::from_str::<EvaluatorSettings>(&serialized).unwrap(),
357                settings
358            );
359            if expected != TensorNetworkContractionOrder::IntermediateCost {
360                assert!(serialized.contains(name));
361            }
362        }
363        drop(defaults_guard);
364        let _defaults_guard = ShowDefaultsGuard::new(true);
365        assert!(
366            toml::to_string(&defaults)
367                .unwrap()
368                .contains("tensor_network_contraction_order = \"intermediate_cost\"")
369        );
370    }
371
372    #[test]
373    fn compile_jit_direct_translation_defaults_and_overrides() {
374        use crate::settings::global::GammaloopCompileOptions;
375
376        let defaults_guard = ShowDefaultsGuard::new(false);
377        let defaults: GammaloopCompileOptions = toml::from_str("").unwrap();
378        assert!(defaults.jit_direct_translation);
379        assert_eq!(defaults, GammaloopCompileOptions::default());
380        assert!(
381            !toml::to_string(&defaults)
382                .unwrap()
383                .contains("jit_direct_translation")
384        );
385
386        let settings: GammaloopCompileOptions =
387            toml::from_str("jit_direct_translation = false").unwrap();
388        assert!(!settings.jit_direct_translation);
389        let serialized = toml::to_string(&settings).unwrap();
390        assert!(serialized.contains("jit_direct_translation = false"));
391        assert_eq!(
392            toml::from_str::<GammaloopCompileOptions>(&serialized).unwrap(),
393            settings
394        );
395
396        drop(defaults_guard);
397        let _defaults_guard = ShowDefaultsGuard::new(true);
398        assert!(
399            toml::to_string(&defaults)
400                .unwrap()
401                .contains("jit_direct_translation = true")
402        );
403    }
404
405    #[test]
406    fn compile_test_serialize_deserialize() {
407        use crate::settings::global::{CompilationOptionsSnapshot, GammaloopCompileOptions};
408        generic_test_settings::<GammaloopCompileOptions>();
409        generic_test_settings::<CompilationOptionsSnapshot>();
410    }
411
412    #[test]
413    fn compile_settings_default_to_symjit_o2() {
414        use crate::{
415            processes::EvaluatorSettings,
416            settings::global::{
417                CompilationMode, CompilationOptimizationLevel, CompilationOptionsSnapshot,
418                FrozenCompilationMode, GammaloopCompileOptions,
419            },
420        };
421
422        let options = GammaloopCompileOptions::default();
423        assert_eq!(options.compilation_mode, CompilationMode::Symjit);
424        assert_eq!(options.optimization_level, CompilationOptimizationLevel::O2);
425        assert_eq!(
426            options.frozen_mode(&EvaluatorSettings {
427                compile: true,
428                ..Default::default()
429            }),
430            FrozenCompilationMode::Symjit(CompilationOptionsSnapshot {
431                optimization_level: CompilationOptimizationLevel::O2,
432                jit_direct_translation: true,
433                ..Default::default()
434            })
435        );
436    }
437
438    #[test]
439    fn compile_settings_forward_symjit_options() {
440        use crate::{
441            processes::EvaluatorSettings,
442            settings::global::{
443                CompilationOptimizationLevel, CompilationOptionsSnapshot, FrozenCompilationMode,
444                GammaloopCompileOptions,
445            },
446        };
447
448        let options = GammaloopCompileOptions {
449            optimization_level: CompilationOptimizationLevel::O1,
450            jit_direct_translation: false,
451            ..Default::default()
452        };
453        assert!(!options.options_snapshot().jit_direct_translation);
454
455        assert_eq!(
456            options.frozen_mode(&EvaluatorSettings {
457                compile: true,
458                ..Default::default()
459            }),
460            FrozenCompilationMode::Symjit(CompilationOptionsSnapshot {
461                optimization_level: CompilationOptimizationLevel::O1,
462                jit_direct_translation: false,
463                ..Default::default()
464            })
465        );
466    }
467
468    #[test]
469    fn compile_settings_resolve_frozen_mode_from_compile_flag() {
470        use crate::{
471            processes::EvaluatorSettings,
472            settings::global::{CompilationMode, FrozenCompilationMode, GammaloopCompileOptions},
473        };
474
475        let options = GammaloopCompileOptions {
476            compilation_mode: CompilationMode::Assembly,
477            ..Default::default()
478        };
479
480        assert_eq!(
481            options.frozen_mode(&EvaluatorSettings {
482                compile: false,
483                ..Default::default()
484            }),
485            FrozenCompilationMode::Eager
486        );
487        assert!(matches!(
488            options.frozen_mode(&EvaluatorSettings {
489                compile: true,
490                ..Default::default()
491            }),
492            FrozenCompilationMode::Assembly(_)
493        ));
494    }
495
496    #[test]
497    fn compile_settings_map_custom_args_into_symbolica_compile_options() {
498        use crate::settings::global::{
499            CompilationMode, CompilationOptimizationLevel, GammaloopCompileOptions,
500        };
501
502        let options = GammaloopCompileOptions {
503            compilation_mode: CompilationMode::Cpp,
504            optimization_level: CompilationOptimizationLevel::O1,
505            jit_direct_translation: true,
506            fast_math: false,
507            unsafe_math: false,
508            compiler: "clang++".to_string(),
509            custom: vec!["-g".to_string(), "-Winvalid".to_string()],
510        };
511        let symbolica = options.to_symbolica_compile_options();
512
513        assert_eq!(
514            symbolica.to_string(),
515            "clang++ -shared -O1 -fPIC -march=native -g -Winvalid"
516        );
517    }
518
519    #[test]
520    fn tropical_subgraph_table_test_serialize_deserialize() {
521        use crate::settings::global::TropicalSubgraphTableSettings;
522        generic_test_settings::<TropicalSubgraphTableSettings>();
523    }
524
525    #[test]
526    fn runtime_test_serialize_deserialize() {
527        generic_test_settings::<RuntimeSettings>();
528    }
529
530    #[test]
531    fn runtime_model_settings_serialize_deserialize() {
532        use crate::settings::runtime::RuntimeModelSettings;
533        generic_test_settings::<RuntimeModelSettings>();
534    }
535
536    #[test]
537    fn runtime_settings_serializes_model_overrides_under_model_block() {
538        let mut settings = RuntimeSettings::default();
539        settings
540            .model
541            .external_parameters
542            .insert("mass_scalar_2".to_string(), (F(2.0), F(0.0)));
543
544        let serialized = toml::to_string_pretty(&settings).unwrap();
545        assert!(serialized.contains("[model]"));
546        assert!(serialized.contains("mass_scalar_2 = ["));
547
548        let deserialized: RuntimeSettings = toml::from_str(&serialized).unwrap();
549        assert_eq!(settings, deserialized);
550    }
551
552    #[test]
553    fn subtraction_settings_test_serialize_deserialize() {
554        use crate::settings::runtime::SubtractionSettings;
555        generic_test_settings::<SubtractionSettings>();
556    }
557
558    #[test]
559    fn test_general_settings_serialize_deserialize() {
560        use crate::settings::runtime::GeneralSettings;
561        generic_test_settings::<GeneralSettings>();
562    }
563
564    #[test]
565    fn runtime_event_generation_policy() {
566        let mut settings = RuntimeSettings::default();
567        assert!(!settings.should_generate_events());
568        assert!(!settings.should_buffer_generated_events());
569        assert!(!settings.should_return_generated_events());
570
571        settings.observables.insert(
572            "observable".to_string(),
573            crate::observables::ObservableSettings {
574                quantity: "pt".to_string(),
575                selections: Vec::new(),
576                entry_selection: crate::observables::EntrySelection::All,
577                entry_index: 0,
578                value_transform: crate::observables::ObservableValueTransform::Identity,
579                phase: crate::observables::ObservablePhase::Real,
580                misbinning_max_normalized_distance: None,
581                histogram: crate::observables::HistogramSettings::Continuous(
582                    crate::observables::ContinuousHistogramSettings {
583                        x_min: 0.0,
584                        x_max: 1.0,
585                        n_bins: 1,
586                        log_x_axis: false,
587                        log_y_axis: true,
588                        title: None,
589                        type_description: "AL".to_string(),
590                    },
591                ),
592            },
593        );
594        assert!(settings.should_generate_events());
595        assert!(settings.should_buffer_generated_events());
596        assert!(!settings.should_return_generated_events());
597
598        settings.observables.clear();
599
600        settings.selectors.insert(
601            "selector".to_string(),
602            crate::observables::SelectorSettings {
603                quantity: "pt".to_string(),
604                active: true,
605                entry_selection: crate::observables::EntrySelection::All,
606                entry_index: 0,
607                selector: crate::observables::SelectorDefinitionSettings::CountRange(
608                    crate::observables::CountRangeSelectorSettings {
609                        min_count: 1,
610                        max_count: None,
611                    },
612                ),
613            },
614        );
615        assert!(settings.should_generate_events());
616        assert!(!settings.should_buffer_generated_events());
617        assert!(!settings.should_return_generated_events());
618
619        settings.observables.insert(
620            "observable".to_string(),
621            crate::observables::ObservableSettings {
622                quantity: "pt".to_string(),
623                selections: Vec::new(),
624                entry_selection: crate::observables::EntrySelection::All,
625                entry_index: 0,
626                value_transform: crate::observables::ObservableValueTransform::Identity,
627                phase: crate::observables::ObservablePhase::Real,
628                misbinning_max_normalized_distance: None,
629                histogram: crate::observables::HistogramSettings::Continuous(
630                    crate::observables::ContinuousHistogramSettings {
631                        x_min: 0.0,
632                        x_max: 1.0,
633                        n_bins: 1,
634                        log_x_axis: false,
635                        log_y_axis: true,
636                        title: None,
637                        type_description: "AL".to_string(),
638                    },
639                ),
640            },
641        );
642        assert!(settings.should_generate_events());
643        assert!(settings.should_buffer_generated_events());
644        assert!(!settings.should_return_generated_events());
645
646        settings.general.generate_events = true;
647        assert!(settings.should_generate_events());
648        assert!(settings.should_buffer_generated_events());
649        assert!(settings.should_return_generated_events());
650    }
651
652    #[test]
653    fn test_integrator_settings_serialize_deserialize() {
654        use crate::settings::runtime::IntegratorSettings;
655        generic_test_settings::<IntegratorSettings>();
656    }
657
658    #[test]
659    fn test_observables_output_settings_serialize_deserialize() {
660        use crate::settings::runtime::ObservablesOutputSettings;
661        generic_test_settings::<ObservablesOutputSettings>();
662    }
663
664    #[test]
665    fn observables_output_settings_accept_single_entry_format_lists() {
666        use crate::{
667            observables::ObservableFileFormat, settings::runtime::ObservablesOutputSettings,
668        };
669
670        let parsed: ObservablesOutputSettings = toml::from_str("format = [\"hwu\"]").unwrap();
671        assert_eq!(parsed.format, vec![ObservableFileFormat::Hwu]);
672
673        let serialized = toml::to_string_pretty(&parsed).unwrap();
674        assert_eq!(serialized, "format = [\"hwu\"]\n");
675    }
676
677    #[test]
678    fn observables_output_settings_accept_multiple_formats() {
679        use crate::{
680            observables::ObservableFileFormat, settings::runtime::ObservablesOutputSettings,
681        };
682
683        let parsed: ObservablesOutputSettings =
684            toml::from_str("format = [\"hwu\", \"json\"]").unwrap();
685        assert_eq!(
686            parsed.format,
687            vec![ObservableFileFormat::Hwu, ObservableFileFormat::Json]
688        );
689
690        let serialized = toml::to_string_pretty(&parsed).unwrap();
691        let reparsed: ObservablesOutputSettings = toml::from_str(&serialized).unwrap();
692        assert_eq!(parsed, reparsed);
693    }
694
695    #[test]
696    fn test_parameterization_settings_serialize_deserialize() {
697        use crate::settings::runtime::ParameterizationSettings;
698        generic_test_settings::<ParameterizationSettings>();
699    }
700
701    #[test]
702    fn test_multi_channeling_settings_serialize_deserialize() {
703        use crate::settings::runtime::MultiChannelingSettings;
704        generic_test_settings::<MultiChannelingSettings>();
705    }
706
707    #[test]
708    fn test_gammaloop_tropical_sampling_settings_serialize_deserialize() {
709        use crate::settings::runtime::GammaloopTropicalSamplingSettings;
710        generic_test_settings::<GammaloopTropicalSamplingSettings>();
711    }
712
713    #[test]
714    fn test_discrete_graph_sampling_settings_serialize_deserialize() {
715        use crate::settings::runtime::DiscreteGraphSamplingSettings;
716        generic_test_settings::<DiscreteGraphSamplingSettings>();
717    }
718
719    #[test]
720    fn test_local_counter_term_settings_serialize_deserialize() {
721        use crate::settings::runtime::LocalCounterTermSettings;
722        generic_test_settings::<LocalCounterTermSettings>();
723    }
724
725    #[test]
726    fn test_uv_localisation_settings_serialize_deserialize() {
727        use crate::settings::runtime::UVLocalisationSettings;
728        generic_test_settings::<UVLocalisationSettings>();
729    }
730
731    #[test]
732    fn test_integrated_counterterm_settings_serialize_deserialize() {
733        use crate::settings::runtime::IntegratedCounterTermSettings;
734        generic_test_settings::<IntegratedCounterTermSettings>();
735    }
736
737    #[test]
738    fn test_uv_generation_settings_serialize_deserialize() {
739        use crate::uv::UVgenerationSettings;
740        generic_test_settings::<UVgenerationSettings>();
741    }
742
743    #[test]
744    fn test_overlap_settings_serialize_deserialize() {
745        use crate::settings::runtime::OverlapSettings;
746        generic_test_settings::<OverlapSettings>();
747    }
748    #[test]
749    fn test_h_function_settings_serialize_deserialize() {
750        use crate::settings::runtime::HFunctionSettings;
751        generic_test_settings::<HFunctionSettings>();
752    }
753
754    #[test]
755    fn test_kinematics_settings_serialize_deserialize() {
756        use crate::settings::KinematicsSettings;
757        generic_test_settings::<KinematicsSettings>();
758
759        let kinematics_settings = KinematicsSettings {
760            e_cm: 100.0,
761            externals: Externals::Constant {
762                momenta: vec![
763                    ExternalMomenta::Independent([F(1.), F(2.), F(3.), F(4.)]),
764                    ExternalMomenta::Dependent(Dep::Dep),
765                ],
766                helicities: vec![Helicity::PLUS, Helicity::MINUS],
767                improvement_settings: PhaseSpaceImprovementSettings::default(),
768                f_64_cache: None,
769                f_128_cache: None,
770                arb_cache: Default::default(),
771            },
772        };
773
774        let toml = toml::to_string_pretty(&kinematics_settings).unwrap();
775        let deserialized: KinematicsSettings = toml::from_str(&toml).unwrap();
776        assert_eq!(kinematics_settings, deserialized);
777
778        let toml_without_ecm = format!(
779            "{}\n",
780            toml.lines()
781                .filter(|line| !line.trim_start().starts_with("e_cm = "))
782                .collect::<Vec<_>>()
783                .join("\n")
784        );
785        let deserialized_without_ecm: KinematicsSettings =
786            toml::from_str(&toml_without_ecm).unwrap();
787        assert_eq!(
788            deserialized_without_ecm.externals,
789            kinematics_settings.externals
790        );
791        assert_eq!(deserialized_without_ecm.e_cm, 2.5);
792    }
793
794    #[test]
795    fn sampling_settings_serializes_to_parser_shape() {
796        let sampling_settings = SamplingSettings::DiscreteGraphs(DiscreteGraphSamplingSettings {
797            graph_names: Vec::new(),
798            sample_orientations: true,
799            sampling_type: DiscreteGraphSamplingType::SamplingMultiChanneling(
800                crate::settings::runtime::MultiChannelingSettings::default(),
801            ),
802        });
803
804        let _guard = ShowDefaultsGuard::new(true);
805        let toml = toml::to_string_pretty(&sampling_settings).unwrap();
806        assert!(toml.contains("graphs = \"monte_carlo\""));
807        assert!(toml.contains("orientations = \"monte_carlo\""));
808        assert!(toml.contains("sampling_multichanneling = true"));
809        assert!(toml.contains("sampling_channels = \"monte_carlo\""));
810        assert!(toml.contains("alpha = 3.0"));
811        assert!(toml.contains("sampling_channel_weight = \"map_density\""));
812        assert!(!toml.contains("lmb_multichanneling"));
813        assert!(!toml.contains("lmb_channels"));
814        assert!(!toml.contains("lmb_channel_weight"));
815        assert!(toml.contains("coordinate_system = \"spherical\""));
816        assert!(toml.contains("power = 1.0"));
817        assert!(toml.contains("graph_names = []"));
818        assert!(!toml.contains("type = \"discrete_graph_sampling\""));
819        assert!(!toml.contains("subtype = \"discrete_multi_channeling\""));
820    }
821
822    #[test]
823    fn sampling_settings_accepts_advanced_channel_selection() {
824        let toml = r#"
825sampling_multichanneling = true
826sampling_channel_weight = "singularity_proxy"
827default_channel_selection = ["auto:surfaces"]
828channel_selection = { GL638 = ["auto:optimized_lmb", "corner"] }
829
830[channel_definitions.GL638.corner]
831around = "intersect(surface(2,4,12), surface(3,10,13))"
832parent_lmb = [3, 4, 7, 10]
833on_cut = [2, 6, 10]
834"#;
835
836        let settings: SamplingSettings = toml::from_str(toml).unwrap();
837        let parameterization = settings.get_parameterization_settings().unwrap();
838        assert_eq!(
839            parameterization.sampling_channels.weight,
840            SamplingChannelWeight::SingularityProxy
841        );
842        assert_eq!(
843            parameterization.sampling_channels.default_channel_selection,
844            ["auto:surfaces"]
845        );
846        assert_eq!(
847            parameterization
848                .sampling_channels
849                .channel_selection
850                .get("GL638")
851                .unwrap(),
852            &["auto:optimized_lmb", "corner"]
853        );
854        let reparsed: SamplingSettings =
855            toml::from_str(&toml::to_string_pretty(&settings).unwrap()).unwrap();
856        assert_eq!(settings, reparsed);
857    }
858
859    #[test]
860    fn sampling_settings_accepts_old_sampling_aliases() {
861        let settings: SamplingSettings = toml::from_str(
862            "lmb_multichanneling = true\nlmb_channels = 'summed'\nlmb_channel_weight = 'ose'",
863        )
864        .unwrap();
865        assert!(matches!(settings, SamplingSettings::MultiChanneling(_)));
866    }
867
868    #[test]
869    fn sampling_settings_requires_parent_lmb_for_channel_definitions() {
870        let err = toml::from_str::<SamplingSettings>(
871            r#"
872channel_definitions = { G = { bad = { around = "surface(1)" } } }
873"#,
874        )
875        .unwrap_err();
876        assert!(err.to_string().contains("requires a non-empty parent_lmb"));
877    }
878
879    #[test]
880    fn sampling_settings_rejects_duplicate_channel_edge_metadata() {
881        let duplicate_subspace = toml::from_str::<SamplingSettings>(
882            r#"
883sampling_multichanneling = true
884channel_definitions = { G = { bad = { around = "surface(1)", parent_lmb = [1, 2], subspace_lmb = [1, 1] } } }
885"#,
886        )
887        .unwrap_err();
888        assert!(
889            duplicate_subspace
890                .to_string()
891                .contains("duplicate subspace_lmb edge ids")
892        );
893
894        let duplicate_on_cut = toml::from_str::<SamplingSettings>(
895            r#"
896sampling_multichanneling = true
897channel_definitions = { G = { bad = { around = "surface(1)", parent_lmb = [1, 2], subspace_lmb = [1], on_cut = [3, 3] } } }
898"#,
899        )
900        .unwrap_err();
901        assert!(
902            duplicate_on_cut
903                .to_string()
904                .contains("duplicate on_cut edge ids")
905        );
906    }
907
908    #[test]
909    fn sampling_settings_graph_names_round_trip() {
910        let sampling_settings = SamplingSettings::DiscreteGraphs(DiscreteGraphSamplingSettings {
911            graph_names: vec!["GL22".to_string(), "GL30".to_string()],
912            sample_orientations: false,
913            sampling_type: DiscreteGraphSamplingType::Default(Default::default()),
914        });
915
916        let toml = toml::to_string_pretty(&sampling_settings).unwrap();
917        assert!(toml.contains("graphs = \"monte_carlo\""));
918        assert!(toml.contains("graph_names = ["));
919        assert!(toml.contains("\"GL22\""));
920        assert!(toml.contains("\"GL30\""));
921        let reparsed: SamplingSettings = toml::from_str(&toml).unwrap();
922        assert_eq!(reparsed, sampling_settings);
923        assert_eq!(reparsed.selected_graph_names(), ["GL22", "GL30"]);
924    }
925
926    #[test]
927    fn sampling_settings_default_has_no_graph_name_filter() {
928        assert!(
929            SamplingSettings::default()
930                .selected_graph_names()
931                .is_empty()
932        );
933
934        let parsed: SamplingSettings = toml::from_str("graphs = \"monte_carlo\"").unwrap();
935        assert!(parsed.selected_graph_names().is_empty());
936        assert!(
937            !toml::to_string_pretty(&parsed)
938                .unwrap()
939                .contains("graph_names")
940        );
941    }
942
943    #[test]
944    fn sampling_settings_rejects_graph_names_with_summed_graphs() {
945        let err =
946            toml::from_str::<SamplingSettings>("graphs = \"summed\"\ngraph_names = [\"GL22\"]")
947                .unwrap_err();
948        assert!(
949            err.to_string()
950                .contains("graph_names requires graphs = 'monte_carlo'")
951        );
952    }
953
954    #[test]
955    fn sampling_settings_rejects_duplicate_graph_names() {
956        let err = toml::from_str::<SamplingSettings>(
957            "graphs = \"monte_carlo\"\ngraph_names = [\"GL22\", \"GL22\"]",
958        )
959        .unwrap_err();
960        assert!(err.to_string().contains("duplicate graph name 'GL22'"));
961    }
962
963    #[test]
964    fn sampling_settings_rejects_incompatible_orientation_sampling() {
965        let invalid_toml = r#"
966graphs = "summed"
967orientations = "monte_carlo"
968lmb_multichanneling = false
969lmb_channels = "summed"
970coordinate_system = "spherical"
971mapping = "linear"
972b = 1.0
973"#;
974
975        let err = toml::from_str::<SamplingSettings>(invalid_toml).unwrap_err();
976        assert!(err.to_string().contains(
977            "orientations can only be set to 'monte_carlo' when graphs is 'monte_carlo'"
978        ));
979    }
980
981    #[test]
982    fn sampling_settings_serializes_lmb_basis_ids_for_default_sampling() {
983        let sampling_settings =
984            SamplingSettings::Default(crate::settings::runtime::ParameterizationSettings {
985                lmb_basis_ids: std::collections::BTreeMap::from([
986                    ("GL02".to_string(), vec![0, 3]),
987                    ("GL05".to_string(), vec![1]),
988                ]),
989                ..Default::default()
990            });
991
992        let toml = toml::to_string_pretty(&sampling_settings).unwrap();
993        assert!(toml.contains("lmb_basis_ids"));
994        let reparsed: SamplingSettings = toml::from_str(&toml).unwrap();
995        assert_eq!(reparsed, sampling_settings);
996    }
997
998    #[test]
999    fn sampling_settings_deserializes_lmb_basis_ids_for_default_sampling() {
1000        let toml = r#"
1001graphs = "summed"
1002orientations = "summed"
1003lmb_multichanneling = false
1004lmb_channels = "summed"
1005coordinate_system = "spherical"
1006mapping = "linear"
1007lmb_basis_ids = { GL02 = [0, 3], GL05 = [1] }
1008"#;
1009
1010        let settings: SamplingSettings = toml::from_str(toml).unwrap();
1011        assert_eq!(
1012            settings,
1013            SamplingSettings::Default(crate::settings::runtime::ParameterizationSettings {
1014                lmb_basis_ids: std::collections::BTreeMap::from([
1015                    ("GL02".to_string(), vec![0, 3]),
1016                    ("GL05".to_string(), vec![1]),
1017                ]),
1018                ..Default::default()
1019            })
1020        );
1021    }
1022
1023    #[test]
1024    fn sampling_settings_allows_lmb_basis_ids_with_multichanneling() {
1025        let toml = r#"
1026graphs = "summed"
1027orientations = "summed"
1028lmb_multichanneling = true
1029lmb_channels = "summed"
1030coordinate_system = "spherical"
1031lmb_basis_ids = { GL02 = [1] }
1032"#;
1033
1034        let settings: SamplingSettings = toml::from_str(toml).unwrap();
1035        assert!(
1036            settings
1037                .get_parameterization_settings()
1038                .unwrap()
1039                .lmb_basis_ids
1040                .contains_key("GL02")
1041        );
1042    }
1043
1044    #[test]
1045    fn sampling_settings_rejects_scalar_lmb_basis_id_as_unknown() {
1046        let invalid_toml = r#"
1047graphs = "summed"
1048orientations = "summed"
1049lmb_multichanneling = false
1050lmb_channels = "summed"
1051coordinate_system = "spherical"
1052lmb_basis_id = 1
1053"#;
1054
1055        let err = toml::from_str::<SamplingSettings>(invalid_toml).unwrap_err();
1056        assert!(err.to_string().contains("unknown field `lmb_basis_id`"));
1057    }
1058
1059    #[test]
1060    fn sampling_settings_rejects_empty_lmb_basis_ids_list() {
1061        let invalid_toml = r#"
1062graphs = "summed"
1063orientations = "summed"
1064lmb_multichanneling = false
1065lmb_channels = "summed"
1066coordinate_system = "spherical"
1067lmb_basis_ids = { GL02 = [] }
1068"#;
1069
1070        let err = toml::from_str::<SamplingSettings>(invalid_toml).unwrap_err();
1071        assert!(err.to_string().contains("cannot be empty"));
1072    }
1073
1074    #[test]
1075    fn sampling_settings_rejects_duplicate_lmb_basis_ids() {
1076        let invalid_toml = r#"
1077graphs = "summed"
1078orientations = "summed"
1079lmb_multichanneling = false
1080lmb_channels = "summed"
1081coordinate_system = "spherical"
1082lmb_basis_ids = { GL02 = [1, 1] }
1083"#;
1084
1085        let err = toml::from_str::<SamplingSettings>(invalid_toml).unwrap_err();
1086        assert!(err.to_string().contains("duplicate basis id 1"));
1087    }
1088
1089    #[test]
1090    fn sampling_settings_rejects_lmb_basis_ids_with_tropical_sampling() {
1091        let invalid_toml = r#"
1092graphs = "monte_carlo"
1093orientations = "summed"
1094lmb_multichanneling = false
1095lmb_channels = "summed"
1096coordinate_system = "tropical"
1097lmb_basis_ids = { GL02 = [1] }
1098"#;
1099
1100        let err = toml::from_str::<SamplingSettings>(invalid_toml).unwrap_err();
1101        assert!(
1102            err.to_string()
1103                .contains("coordinate_system = 'tropical' is incompatible with lmb_basis_ids")
1104        );
1105    }
1106
1107    #[test]
1108    fn sampling_settings_deserializes_from_parser_shape() {
1109        let toml = r#"
1110graphs = "monte_carlo"
1111orientations = "summed"
1112lmb_multichanneling = true
1113lmb_channels = "summed"
1114alpha = 1.5
1115lmb_channel_weight = "inverse_jacobian"
1116coordinate_system = "momentum_space"
1117mapping = "log"
1118b = 5.0
1119power = 2.0
1120"#;
1121
1122        let settings: SamplingSettings = toml::from_str(toml).unwrap();
1123        assert_eq!(
1124            settings,
1125            SamplingSettings::DiscreteGraphs(DiscreteGraphSamplingSettings {
1126                graph_names: Vec::new(),
1127                sample_orientations: false,
1128                sampling_type: DiscreteGraphSamplingType::MultiChanneling(
1129                    crate::settings::runtime::MultiChannelingSettings {
1130                        parameterization_settings:
1131                            crate::settings::runtime::ParameterizationSettings {
1132                                mode: crate::settings::runtime::ParameterizationMode::MomentumSpace,
1133                                mapping: crate::settings::runtime::ParameterizationMapping::Log,
1134                                b: 5.0,
1135                                power: 2.0,
1136                                lmb_basis_ids: Default::default(),
1137                                sampling_channels: crate::settings::runtime::SamplingChannelSelection {
1138                                    weight: crate::settings::runtime::SamplingChannelWeight::InverseJacobian,
1139                                    alpha: 1.5,
1140                                    default_channel_selection: vec!["auto:optimized_lmb".to_owned()],
1141                                    ..Default::default()
1142                                },
1143                            },
1144                    },
1145                ),
1146            })
1147        );
1148    }
1149
1150    #[test]
1151    fn sampling_settings_deserializes_power_mapping() {
1152        let toml = r#"
1153graphs = "monte_carlo"
1154orientations = "summed"
1155lmb_multichanneling = true
1156lmb_channels = "summed"
1157lmb_channel_weight = "inverse_jacobian"
1158coordinate_system = "spherical"
1159mapping = "power"
1160b = 1.5
1161power = 4.0
1162"#;
1163
1164        let settings: SamplingSettings = toml::from_str(toml).unwrap();
1165        assert_eq!(
1166            settings,
1167            SamplingSettings::DiscreteGraphs(DiscreteGraphSamplingSettings {
1168                graph_names: Vec::new(),
1169                sample_orientations: false,
1170                sampling_type: DiscreteGraphSamplingType::MultiChanneling(
1171                    crate::settings::runtime::MultiChannelingSettings {
1172                        parameterization_settings:
1173                            crate::settings::runtime::ParameterizationSettings {
1174                                mode: crate::settings::runtime::ParameterizationMode::Spherical,
1175                                mapping: crate::settings::runtime::ParameterizationMapping::Power,
1176                                b: 1.5,
1177                                power: 4.0,
1178                                lmb_basis_ids: Default::default(),
1179                                sampling_channels: crate::settings::runtime::SamplingChannelSelection {
1180                                    weight: crate::settings::runtime::SamplingChannelWeight::InverseJacobian,
1181                                    default_channel_selection: vec!["auto:optimized_lmb".to_owned()],
1182                                    ..Default::default()
1183                                },
1184                            },
1185                    },
1186                ),
1187            })
1188        );
1189    }
1190
1191    #[test]
1192    fn sampling_settings_rejects_invalid_power_mapping_exponent() {
1193        let invalid_toml = r#"
1194graphs = "monte_carlo"
1195orientations = "summed"
1196lmb_multichanneling = true
1197lmb_channels = "summed"
1198coordinate_system = "spherical"
1199mapping = "power"
1200power = 0.0
1201"#;
1202
1203        let err = toml::from_str::<SamplingSettings>(invalid_toml).unwrap_err();
1204        assert!(err.to_string().contains("requires power >= 1"));
1205    }
1206
1207    #[test]
1208    fn sampling_settings_deserializes_relative_spherical_coordinates() {
1209        let toml = r#"
1210graphs = "monte_carlo"
1211orientations = "summed"
1212lmb_multichanneling = true
1213lmb_channels = "summed"
1214lmb_channel_weight = "inverse_jacobian"
1215coordinate_system = "relative_spherical"
1216mapping = "linear"
1217b = 1.0
1218"#;
1219
1220        let settings: SamplingSettings = toml::from_str(toml).unwrap();
1221        assert_eq!(
1222            settings,
1223            SamplingSettings::DiscreteGraphs(DiscreteGraphSamplingSettings {
1224                graph_names: Vec::new(),
1225                sample_orientations: false,
1226                sampling_type: DiscreteGraphSamplingType::MultiChanneling(
1227                    crate::settings::runtime::MultiChannelingSettings {
1228                        parameterization_settings:
1229                            crate::settings::runtime::ParameterizationSettings {
1230                                mode: crate::settings::runtime::ParameterizationMode::RelativeSpherical,
1231                                mapping: crate::settings::runtime::ParameterizationMapping::Linear,
1232                                b: 1.0,
1233                                power: 1.0,
1234                                lmb_basis_ids: Default::default(),
1235                                sampling_channels: crate::settings::runtime::SamplingChannelSelection {
1236                                    weight: crate::settings::runtime::SamplingChannelWeight::InverseJacobian,
1237                                    default_channel_selection: vec!["auto:optimized_lmb".to_owned()],
1238                                    ..Default::default()
1239                                },
1240                            },
1241                    },
1242                ),
1243            })
1244        );
1245    }
1246
1247    #[test]
1248    fn sampling_settings_rejects_inverse_jacobian_for_flat_hyperspherical() {
1249        let invalid_toml = r#"
1250graphs = "monte_carlo"
1251orientations = "summed"
1252lmb_multichanneling = true
1253lmb_channels = "summed"
1254alpha = 1.5
1255lmb_channel_weight = "inverse_jacobian"
1256coordinate_system = "hyperspherical_flat"
1257mapping = "linear"
1258b = 1.0
1259"#;
1260
1261        let err = toml::from_str::<SamplingSettings>(invalid_toml).unwrap_err();
1262        assert!(err.to_string().contains("inverse map is not available"));
1263    }
1264
1265    #[test]
1266    fn how_does_tropical_look() {
1267        SHOWDEFAULTS.store(true, std::sync::atomic::Ordering::Relaxed);
1268        let sampling_settings = SamplingSettings::DiscreteGraphs(DiscreteGraphSamplingSettings {
1269            graph_names: Vec::new(),
1270            sample_orientations: false,
1271            sampling_type: DiscreteGraphSamplingType::TropicalSampling(
1272                GammaloopTropicalSamplingSettings {
1273                    upcast_on_failure: false,
1274                    matrix_stability_test: Some(1e-5),
1275                },
1276            ),
1277        });
1278        let toml = toml::to_string_pretty(&sampling_settings).unwrap();
1279        println!("{toml}");
1280        SHOWDEFAULTS.store(false, std::sync::atomic::Ordering::Relaxed);
1281    }
1282
1283    mod failing {
1284        use super::*;
1285
1286        #[test]
1287        fn test_stability_settings_serialize_deserialize() {
1288            use crate::settings::runtime::StabilitySettings;
1289            generic_test_settings::<StabilitySettings>();
1290            let settings = StabilitySettings {
1291                escalate_if_exact_zero: true,
1292                ..Default::default()
1293            };
1294            let serialized = toml::to_string(&settings).unwrap();
1295            assert!(serialized.contains("escalate_if_exact_zero = true"));
1296            assert_eq!(
1297                toml::from_str::<StabilitySettings>(&serialized).unwrap(),
1298                settings
1299            );
1300        }
1301
1302        #[test]
1303        fn test_uv_generation_settings_serializes_renormalization_prescription() {
1304            use crate::uv::{
1305                ApproximationType, CTIdentifier, CTRenormalizationRule,
1306                RenormalizationPrescriptionSettings, UVgenerationSettings,
1307            };
1308            use std::collections::BTreeSet;
1309
1310            let settings = UVgenerationSettings {
1311                softct: true,
1312                renormalization_prescription: RenormalizationPrescriptionSettings {
1313                    log_divergent: ApproximationType::MUV,
1314                    massive_power_divergent: ApproximationType::OS,
1315                    massless_power_divergent: ApproximationType::IR,
1316                    overrides: vec![
1317                        CTRenormalizationRule::new(
1318                            CTIdentifier::new(BTreeSet::from([1]), Some(BTreeSet::from([1, 22]))),
1319                            ApproximationType::OS,
1320                        ),
1321                        CTRenormalizationRule::new(
1322                            CTIdentifier::new(BTreeSet::from([6]), None),
1323                            ApproximationType::Unsubtracted,
1324                        ),
1325                    ],
1326                },
1327                ..Default::default()
1328            };
1329
1330            let toml = toml::to_string_pretty(&GenerationSettings {
1331                compile: GammaloopCompileOptions {
1332                    compiler: "symjit".to_string(),
1333                    ..Default::default()
1334                },
1335                uv: settings.clone(),
1336                ..Default::default()
1337            })
1338            .unwrap();
1339            println!("{}", toml);
1340            assert!(toml.contains("[uv.renormalization_prescription]"));
1341            assert!(toml.contains("[[uv.renormalization_prescription.overrides]]"));
1342            assert!(toml.contains("prescription = \"Unsubtracted\""));
1343
1344            let deserialized_from_toml: GenerationSettings = toml::from_str(&toml).unwrap();
1345            assert_eq!(settings, deserialized_from_toml.uv);
1346
1347            let json = serde_json::to_string(&settings).unwrap();
1348            let deserialized_from_json: UVgenerationSettings = serde_json::from_str(&json).unwrap();
1349            assert_eq!(settings, deserialized_from_json);
1350        }
1351
1352        #[test]
1353        fn test_stability_level_settings_serialize_deserialize() {
1354            use crate::settings::runtime::StabilitySettings;
1355            generic_test_settings::<StabilitySettings>();
1356        }
1357    }
1358}