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