1pub mod builtin;
2pub mod evaluation;
3pub mod process;
4
5use crate::integrands::evaluation::{
6 EvaluationMetaData, EvaluationResult, RawBatchEvaluationResult, StabilityResult,
7 StabilityStatus,
8};
9use crate::integrands::builtin::h_function::{HFunctionTestIntegrand, HFunctionTestSettings};
11use crate::integrands::process::ProcessIntegrand;
12use crate::integrands::process::{amplitude, cross_section};
13use crate::model::Model;
14use crate::momentum::FourMomentum;
15use crate::observables::{
16 ObservableAccumulatorBundle, ObservableFileFormat, ObservableSnapshotBundle,
17};
18use crate::utils::{F, FloatLike};
19use crate::{
20 is_interrupted,
21 settings::{
22 RuntimeSettings,
23 runtime::{IntegratorSettings, Precision},
24 },
25 utils,
26};
27
28use bincode_trait_derive::{Decode, Encode};
29use color_eyre::Result;
30use enum_dispatch::enum_dispatch;
31use schemars::JsonSchema;
32use serde::{Deserialize, Serialize};
33use spenso::algebra::complex::Complex;
34use std::fmt::{Display, Formatter};
35use std::time::Duration;
36use symbolica::numerical_integration::{ContinuousGrid, Grid, Sample};
37#[allow(unused_imports)]
38use tracing::{debug, error, info, instrument, trace, warn};
39
40#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
41#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
42#[allow(non_snake_case)]
44#[serde(tag = "type")]
45pub enum IntegrandSettings {
46 #[serde(rename = "unit_surface")]
47 UnitSurface(UnitSurfaceSettings),
48 #[serde(rename = "unit_volume")]
49 UnitVolume(UnitVolumeSettings),
50 #[serde(rename = "h_function_test")]
51 HFunctionTest(HFunctionTestSettings),
52}
53
54impl Display for IntegrandSettings {
55 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
56 match self {
57 IntegrandSettings::UnitSurface(_) => write!(f, "unit_surface"),
58 IntegrandSettings::UnitVolume(_) => write!(f, "unit_volume"),
59 IntegrandSettings::HFunctionTest(_) => {
60 write!(f, "h_function_test")
61 }
62 }
63 }
64}
65
66impl Default for IntegrandSettings {
67 fn default() -> IntegrandSettings {
68 IntegrandSettings::UnitSurface(UnitSurfaceSettings { n_3d_momenta: 11 })
69 }
70}
71
72#[enum_dispatch]
73pub trait HasIntegrand {
74 fn create_grid(&self) -> Grid<F<f64>>;
76
77 fn name(&self) -> String;
79
80 fn evaluate_sample(
82 &mut self,
83 sample: &Sample<F<f64>>,
84 model: &Model,
85 wgt: F<f64>,
86 iter: usize,
87 use_arb_prec: bool,
88 max_eval: Complex<F<f64>>,
89 ) -> Result<EvaluationResult>;
90
91 fn get_n_dim(&self) -> usize;
93
94 fn get_integrator_settings(&self) -> IntegratorSettings {
96 IntegratorSettings::default()
97 }
98
99 fn merge_results<I: HasIntegrand>(&mut self, _other: &mut I, _iter: usize) {}
102
103 fn update_results(&mut self, _iter: usize) {}
106}
107
108#[derive(Clone)]
109pub enum Integrand {
110 UnitSurface(UnitSurfaceIntegrand),
112 UnitVolume(UnitVolumeIntegrand),
114 HFunctionTest(HFunctionTestIntegrand),
116 ProcessIntegrand(Box<ProcessIntegrand>),
119}
120
121impl Integrand {
122 pub fn evaluate_samples_raw(
123 &mut self,
124 samples: &[Sample<F<f64>>],
125 model: &Model,
126 iter: usize,
127 use_arb_prec: bool,
128 stop_on_interrupt: bool,
129 max_eval: Complex<F<f64>>,
130 ) -> Result<RawBatchEvaluationResult> {
131 match self {
132 Integrand::ProcessIntegrand(integrand) => integrand.evaluate_samples_raw(
133 model,
134 samples,
135 iter,
136 use_arb_prec,
137 stop_on_interrupt,
138 max_eval,
139 ),
140 _ => {
141 let mut results = Vec::with_capacity(samples.len());
142 for sample in samples {
143 if stop_on_interrupt && is_interrupted() {
144 break;
145 }
146 results.push(self.evaluate_sample(
147 sample,
148 model,
149 sample.get_weight(),
150 iter,
151 use_arb_prec,
152 max_eval,
153 )?);
154 if stop_on_interrupt && is_interrupted() {
155 break;
156 }
157 }
158 Ok(RawBatchEvaluationResult {
159 statistics: evaluation::StatisticsCounter::from_evaluation_results(&results),
160 samples: results,
161 })
162 }
163 }
164 }
165
166 pub fn process_evaluation_result(&mut self, result: &EvaluationResult) {
167 if let Integrand::ProcessIntegrand(integrand) = self {
168 integrand.process_evaluation_result(result);
169 }
170 }
171
172 pub fn merge_runtime_results(&mut self, other: &mut Integrand) -> Result<()> {
173 match (self, other) {
174 (Integrand::ProcessIntegrand(lhs), Integrand::ProcessIntegrand(rhs)) => {
175 lhs.merge_event_processing_runtime(rhs)
176 }
177 _ => Ok(()),
178 }
179 }
180
181 pub fn update_runtime_results(&mut self, iter: usize) {
182 if let Integrand::ProcessIntegrand(integrand) = self {
183 integrand.update_event_processing_runtime(iter);
184 }
185 }
186
187 pub fn observable_accumulator_bundle(&self) -> Option<ObservableAccumulatorBundle> {
188 match self {
189 Integrand::ProcessIntegrand(integrand) => integrand.observable_accumulator_bundle(),
190 _ => None,
191 }
192 }
193
194 pub fn has_observables(&self) -> bool {
195 match self {
196 Integrand::ProcessIntegrand(integrand) => integrand.has_observables(),
197 _ => false,
198 }
199 }
200
201 pub fn observable_snapshot_bundle(&self) -> Option<ObservableSnapshotBundle> {
202 match self {
203 Integrand::ProcessIntegrand(integrand) => integrand.observable_snapshot_bundle(),
204 _ => None,
205 }
206 }
207
208 pub fn build_observable_snapshots_for_result(
209 &self,
210 result: &EvaluationResult,
211 ) -> Option<ObservableSnapshotBundle> {
212 match self {
213 Integrand::ProcessIntegrand(integrand) => {
214 integrand.build_observable_snapshots_for_result(result)
215 }
216 _ => None,
217 }
218 }
219
220 pub fn write_observable_snapshots(
221 &self,
222 path: impl AsRef<std::path::Path>,
223 format: ObservableFileFormat,
224 ) -> Result<()> {
225 match self {
226 Integrand::ProcessIntegrand(integrand) => {
227 integrand.write_observable_snapshots(path, format)
228 }
229 _ => Ok(()),
230 }
231 }
232}
233
234impl HasIntegrand for Integrand {
235 fn name(&self) -> String {
236 match self {
237 Integrand::UnitSurface(_) => "UnitSurface".to_string(),
238 Integrand::UnitVolume(_) => "UnitVolume".to_string(),
239 Integrand::HFunctionTest(_) => "HFunctionTest".to_string(),
240 Integrand::ProcessIntegrand(i) => i.name(),
242 }
243 }
244
245 fn create_grid(&self) -> Grid<F<f64>> {
246 match self {
247 Integrand::UnitSurface(integrand) => integrand.create_grid(),
248 Integrand::UnitVolume(integrand) => integrand.create_grid(),
249 Integrand::HFunctionTest(integrand) => integrand.create_grid(),
250 Integrand::ProcessIntegrand(integrand) => integrand.create_grid(),
252 }
253 }
254
255 fn evaluate_sample(
256 &mut self,
257 sample: &Sample<F<f64>>,
258 model: &Model,
259 wgt: F<f64>,
260 iter: usize,
261 use_arb_prec: bool,
262 max_eval: Complex<F<f64>>,
263 ) -> Result<EvaluationResult> {
264 match self {
265 Integrand::UnitSurface(integrand) => {
266 integrand.evaluate_sample(sample, model, wgt, iter, use_arb_prec, max_eval)
267 }
268 Integrand::UnitVolume(integrand) => {
269 integrand.evaluate_sample(sample, model, wgt, iter, use_arb_prec, max_eval)
270 }
271 Integrand::HFunctionTest(integrand) => {
272 integrand.evaluate_sample(sample, model, wgt, iter, use_arb_prec, max_eval)
273 }
274 Integrand::ProcessIntegrand(integrand) => {
278 integrand.evaluate_sample(sample, model, wgt, iter, use_arb_prec, max_eval)
279 }
280 }
281 }
282
283 fn get_n_dim(&self) -> usize {
284 match self {
285 Integrand::UnitSurface(integrand) => integrand.get_n_dim(),
286 Integrand::UnitVolume(integrand) => integrand.get_n_dim(),
287 Integrand::HFunctionTest(integrand) => integrand.get_n_dim(),
288 Integrand::ProcessIntegrand(integrand) => integrand.get_n_dim(),
290 }
291 }
292
293 fn get_integrator_settings(&self) -> IntegratorSettings {
294 match self {
295 Integrand::UnitSurface(integrand) => integrand.get_integrator_settings(),
296 Integrand::UnitVolume(integrand) => integrand.get_integrator_settings(),
297 Integrand::HFunctionTest(integrand) => integrand.get_integrator_settings(),
298 Integrand::ProcessIntegrand(integrand) => integrand.get_integrator_settings(),
300 }
301 }
302}
303
304pub(crate) fn integrand_factory(settings: &RuntimeSettings) -> Integrand {
305 match settings.hard_coded_integrand.as_ref().unwrap().clone() {
306 IntegrandSettings::UnitSurface(integrand_settings) => Integrand::UnitSurface(
307 UnitSurfaceIntegrand::new(settings.clone(), integrand_settings),
308 ),
309 IntegrandSettings::UnitVolume(integrand_settings) => Integrand::UnitVolume(
310 UnitVolumeIntegrand::new(settings.clone(), integrand_settings),
311 ),
312 IntegrandSettings::HFunctionTest(integrand_settings) => Integrand::HFunctionTest(
313 HFunctionTestIntegrand::new(settings.clone(), integrand_settings),
314 ),
315 }
316}
317
318#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
319#[derive(Debug, Clone, Default, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
320pub struct UnitSurfaceSettings {
322 pub n_3d_momenta: usize,
324}
325
326#[derive(Clone)]
327pub struct UnitSurfaceIntegrand {
328 pub settings: RuntimeSettings,
329 pub n_dim: usize,
330 pub n_3d_momenta: usize,
331 pub surface: F<f64>,
332}
333
334#[allow(unused)]
335impl UnitSurfaceIntegrand {
336 pub(crate) fn new(
337 settings: RuntimeSettings,
338 integrand_settings: UnitSurfaceSettings,
339 ) -> UnitSurfaceIntegrand {
340 let n_dim = integrand_settings.n_3d_momenta * 3 - 1;
341 let surface = utils::compute_surface_and_volume(
342 integrand_settings.n_3d_momenta * 3 - 1,
343 F(settings.kinematics.e_cm),
344 )
345 .0;
346 UnitSurfaceIntegrand {
347 settings,
348 n_3d_momenta: integrand_settings.n_3d_momenta,
349 n_dim,
350 surface,
351 }
352 }
353
354 fn evaluate_numerator<T: FloatLike>(&self, loop_momenta: &[FourMomentum<F<T>>]) -> F<T> {
355 loop_momenta[0].temporal.value.one()
356 }
357
358 fn parameterize<T: FloatLike>(&self, xs: &[F<T>]) -> (Vec<[F<T>; 3]>, F<T>) {
359 let zero = xs[0].zero();
360 utils::global_parameterize(
361 xs,
362 F::<T>::from_f64(self.settings.kinematics.e_cm * self.settings.kinematics.e_cm),
363 &self
364 .settings
365 .sampling
366 .get_parameterization_settings()
367 .unwrap(),
368 )
369 }
370}
371
372#[allow(unused)]
373impl HasIntegrand for UnitSurfaceIntegrand {
374 fn name(&self) -> String {
375 "UnitSurfaceIntegrand".to_string()
376 }
377
378 fn create_grid(&self) -> Grid<F<f64>> {
379 Grid::Continuous(ContinuousGrid::new(
380 self.n_dim,
381 self.settings.integrator.n_bins,
382 self.settings.integrator.min_samples_for_update,
383 self.settings.integrator.bin_number_evolution.clone(),
384 self.settings.integrator.train_on_avg,
385 ))
386 }
387
388 fn get_n_dim(&self) -> usize {
389 self.n_dim
390 }
391
392 fn evaluate_sample(
393 &mut self,
394 sample: &Sample<F<f64>>,
395 model: &Model,
396 wgt: F<f64>,
397 iter: usize,
398 use_arb_prec: bool,
399 max_eval: Complex<F<f64>>,
400 ) -> Result<EvaluationResult> {
401 let start_evaluate_sample = std::time::Instant::now();
402
403 let xs = match sample {
404 Sample::Continuous(_w, v) => v,
405 _ => panic!("Wrong sample type"),
406 };
407 let mut sample_xs = vec![F(self.settings.kinematics.e_cm)];
408 sample_xs.extend(xs);
409
410 let before_parameterization = std::time::Instant::now();
411 let (moms, jac) = self.parameterize(sample_xs.as_slice());
412 let mut loop_momenta = vec![];
413 for m in &moms {
414 loop_momenta.push(FourMomentum::from_args(
415 ((m[0] + m[1] + m[2]) * (m[0] + m[1] + m[2])).sqrt(),
416 m[0],
417 m[1],
418 m[2],
419 ));
420 }
421
422 let parameterization_time = before_parameterization.elapsed();
423
424 let before_evaluation = std::time::Instant::now();
425 let mut itg_wgt = self.evaluate_numerator(loop_momenta.as_slice());
426 itg_wgt /= self.surface;
428
429 info!("Sampled loop momenta:");
430 for (i, l) in loop_momenta.iter().enumerate() {
431 info!("k{} = ( {:-23})", i, format!("{:+.16e}", l),);
432 }
433 info!("Integrator weight : {:+.16e}", wgt);
434 info!("Integrand weight : {:+.16e}", itg_wgt);
435 info!("Sampling jacobian : {:+.16e}", jac);
436 info!("Final contribution: {:+.16e}", itg_wgt * jac);
437
438 let is_nan = itg_wgt.is_nan();
439
440 let evaluation_time = before_evaluation.elapsed();
441
442 let evaluation_metadata = EvaluationMetaData {
443 total_timing: start_evaluate_sample.elapsed(),
444 integrand_evaluation_time: evaluation_time,
445 evaluator_evaluation_time: Duration::ZERO,
446 parameterization_time,
447 event_processing_time: Duration::ZERO,
448 generated_event_count: 0,
449 accepted_event_count: 0,
450 relative_instability_error: Complex::new_zero(),
451 is_nan,
452 loop_momenta_escalation: None,
453 stability_results: vec![StabilityResult {
454 precision: Precision::Double,
455 estimated_relative_accuracy: None,
456 status: StabilityStatus::Unknown,
457 total_time: start_evaluate_sample.elapsed(),
458 }],
459 threshold_counterterm_error: None,
460 radial_root_diagnostics: Default::default(),
461 };
462
463 Ok(EvaluationResult {
464 integrand_result: Complex::new(itg_wgt, F(0.)),
465 parameterization_jacobian: Some(jac),
466 integrator_weight: wgt,
467 event_groups: Default::default(),
468 evaluation_metadata,
469 })
470 }
471}
472
473#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
474#[derive(Debug, Clone, Default, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
475pub struct UnitVolumeSettings {
477 pub n_3d_momenta: usize,
479}
480
481#[derive(Clone)]
482pub struct UnitVolumeIntegrand {
483 pub settings: RuntimeSettings,
484 pub n_dim: usize,
485 pub n_3d_momenta: usize,
486 pub volume: F<f64>,
487}
488
489#[allow(unused)]
490impl UnitVolumeIntegrand {
491 pub(crate) fn new(
492 settings: RuntimeSettings,
493 integrand_settings: UnitVolumeSettings,
494 ) -> UnitVolumeIntegrand {
495 let n_dim = utils::get_n_dim_for_n_loop_momenta(
496 &settings.sampling,
497 integrand_settings.n_3d_momenta,
498 None,
499 );
500 let volume = utils::compute_surface_and_volume(
501 integrand_settings.n_3d_momenta * 3,
502 F(settings.kinematics.e_cm),
503 )
504 .1;
505 UnitVolumeIntegrand {
506 settings,
507 n_3d_momenta: integrand_settings.n_3d_momenta,
508 n_dim,
509 volume,
510 }
511 }
512
513 fn evaluate_numerator<T: FloatLike>(&self, loop_momenta: &[FourMomentum<F<T>>]) -> F<T> {
514 let zero = loop_momenta[0].temporal.value.zero();
515 if loop_momenta
516 .iter()
517 .map(|l| l.spatial.norm_squared())
518 .reduce(|acc, e| acc + &e)
519 .unwrap_or(zero.clone())
520 .sqrt()
521 > F::<T>::from_f64(self.settings.kinematics.e_cm)
522 {
523 zero
524 } else {
525 zero.one()
526 }
527 }
528
529 fn parameterize<T: FloatLike>(&self, xs: &[F<T>]) -> (Vec<[F<T>; 3]>, F<T>) {
530 let zero = xs[0].zero();
531 utils::global_parameterize(
532 xs,
533 F::<T>::from_f64(self.settings.kinematics.e_cm * self.settings.kinematics.e_cm),
534 &self
535 .settings
536 .sampling
537 .get_parameterization_settings()
538 .unwrap(),
539 )
540 }
541}
542
543#[allow(unused)]
544impl HasIntegrand for UnitVolumeIntegrand {
545 fn name(&self) -> String {
546 "UnitVolumeIntegrand".to_string()
547 }
548 fn create_grid(&self) -> Grid<F<f64>> {
549 Grid::Continuous(ContinuousGrid::new(
550 self.n_dim,
551 self.settings.integrator.n_bins,
552 self.settings.integrator.min_samples_for_update,
553 self.settings.integrator.bin_number_evolution.clone(),
554 self.settings.integrator.train_on_avg,
555 ))
556 }
557
558 fn get_n_dim(&self) -> usize {
559 self.n_dim
560 }
561
562 fn evaluate_sample(
563 &mut self,
564 sample: &Sample<F<f64>>,
565 model: &Model,
566 wgt: F<f64>,
567 iter: usize,
568 use_arb_prec: bool,
569 max_eval: Complex<F<f64>>,
570 ) -> Result<EvaluationResult> {
571 let start_evaluate_sample = std::time::Instant::now();
572
573 let xs = match sample {
574 Sample::Continuous(_w, v) => v,
575 _ => panic!("Wrong sample type"),
576 };
577
578 let before_parameterization = std::time::Instant::now();
579
580 let (moms, jac) = self.parameterize(xs);
581 let mut loop_momenta = vec![];
582 for m in &moms {
583 loop_momenta.push(FourMomentum::new(F(0.).into(), (*m).into()));
584 }
585
586 let parameterization_time = before_parameterization.elapsed();
587
588 let before_evaluation = std::time::Instant::now();
589 let mut itg_wgt = self.evaluate_numerator(loop_momenta.as_slice());
590 itg_wgt /= self.volume;
592 info!("Sampled loop momenta:");
593 for (i, l) in loop_momenta.iter().enumerate() {
594 info!("k{} = ( {:-23})", i, format!("{:+.16e}", l),);
595 }
596 info!("Integrator weight : {:+.16e}", wgt);
597 info!("Integrand weight : {:+.16e}", itg_wgt);
598 info!("Sampling jacobian : {:+.16e}", jac);
599 info!("Final contribution: {:+.16e}", itg_wgt * jac);
600
601 let is_nan = itg_wgt.is_nan();
602
603 let evaluation_time = before_evaluation.elapsed();
604
605 let evaluation_metadata = EvaluationMetaData {
606 total_timing: start_evaluate_sample.elapsed(),
607 integrand_evaluation_time: evaluation_time,
608 evaluator_evaluation_time: Duration::ZERO,
609 parameterization_time,
610 event_processing_time: Duration::ZERO,
611 generated_event_count: 0,
612 accepted_event_count: 0,
613 relative_instability_error: Complex::new_zero(),
614 is_nan,
615 loop_momenta_escalation: None,
616 stability_results: vec![StabilityResult {
617 precision: Precision::Double,
618 estimated_relative_accuracy: None,
619 status: StabilityStatus::Unknown,
620 total_time: start_evaluate_sample.elapsed(),
621 }],
622 threshold_counterterm_error: None,
623 radial_root_diagnostics: Default::default(),
624 };
625
626 Ok(EvaluationResult {
627 integrand_result: Complex::new(itg_wgt, F(0.)),
628 parameterization_jacobian: Some(jac),
629 integrator_weight: wgt,
630 event_groups: Default::default(),
631 evaluation_metadata,
632 })
633 }
634}