Skip to main content

gammalooprs/settings/runtime/
kinematic.rs

1pub mod improvement;
2
3use bincode_trait_derive::{Decode, Encode};
4use eyre::eyre;
5use schemars::JsonSchema;
6use serde::{Deserialize, Deserializer, Serialize};
7use spenso::algebra::{algebraic_traits::IsZero, complex::Complex};
8use tabled::{builder::Builder, settings::Style};
9use tracing::debug;
10use typed_index_collections::TiVec;
11
12use crate::{
13    DependentMomentaConstructor, GammaLoopContext,
14    graph::Graph,
15    momentum::sample::{ExternalFourMomenta, ExternalIndex},
16    momentum::signature::ExternalSignature,
17    momentum::{
18        self, Dep, ExternalMomenta, FourMomentum, Helicity, Polarization, Rotatable, SignOrZero,
19    },
20    settings::runtime::kinematic::improvement::{PhaseSpaceImprovementSettings, improve_ps},
21    utils::{
22        F, FloatLike, f128,
23        serde_utils::{IsDefault, is_float},
24    },
25};
26use color_eyre::{Result, Section};
27
28#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
29#[derive(Debug, Clone, Serialize, Encode, Decode, PartialEq, JsonSchema)]
30#[trait_decode(trait= GammaLoopContext)]
31#[serde(default, deny_unknown_fields)]
32pub struct KinematicsSettings {
33    /// Center-of-mass energy used to normalize and construct external momenta.
34    #[serde(skip_serializing_if = "is_float::<64>")]
35    pub e_cm: f64,
36    /// External momentum and helicity configuration evaluated by the integrand.
37    #[serde(skip_serializing_if = "IsDefault::is_default")]
38    pub externals: Externals,
39}
40
41#[derive(Debug, Default, Deserialize)]
42#[serde(default, deny_unknown_fields)]
43struct KinematicsSettingsParser {
44    pub e_cm: Option<f64>,
45    pub externals: Externals,
46}
47
48impl<'de> Deserialize<'de> for KinematicsSettings {
49    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
50    where
51        D: Deserializer<'de>,
52    {
53        let KinematicsSettingsParser { e_cm, externals } =
54            KinematicsSettingsParser::deserialize(deserializer)?;
55
56        let e_cm = e_cm.unwrap_or_else(|| externals.sane_e_cm_value().0);
57
58        Ok(Self { e_cm, externals })
59    }
60}
61
62impl KinematicsSettings {
63    pub fn random(graph: &Graph, seed: u64) -> Self {
64        Self {
65            e_cm: 64.,
66            externals: graph.random_externals(seed),
67        }
68    }
69}
70
71impl Default for KinematicsSettings {
72    fn default() -> Self {
73        Self {
74            e_cm: 64.,
75            externals: Externals::default(),
76        }
77    }
78}
79
80#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Encode, Decode, JsonSchema)]
81// #[trait_decode(trait= GammaLoopContext)]
82#[serde(tag = "type", content = "data")]
83#[serde(deny_unknown_fields)]
84pub enum Externals {
85    /// Use one fixed list of external momenta and helicities for every evaluation.
86    #[serde(rename = "constant")]
87    Constant {
88        /// External four-momenta; exactly one entry may be marked dependent.
89        momenta: Vec<ExternalMomenta<F<f64>>>,
90        /// Helicity assigned to each external particle in process order.
91        helicities: Vec<Helicity>,
92        /// Optional on-shell phase-space improvement applied before evaluation.
93        #[serde(default, skip_serializing_if = "IsDefault::is_default")]
94        improvement_settings: PhaseSpaceImprovementSettings,
95        // must be set in warmup, but is tied to the constant
96        #[serde(skip)]
97        f_64_cache: Option<TiVec<ExternalIndex, FourMomentum<F<f64>>>>,
98        #[serde(skip)]
99        f_128_cache: Option<TiVec<ExternalIndex, FourMomentum<F<f128>>>>,
100    },
101    // add different type of pdfs here when needed
102}
103
104impl Externals {
105    pub fn sane_e_cm_value(&self) -> F<f64> {
106        match self {
107            Externals::Constant { momenta, .. } => {
108                let mut sum = F(0.0);
109                let mut num_non_zero = 0;
110
111                for m in momenta {
112                    if let ExternalMomenta::Independent(components) = m {
113                        for c in components {
114                            if !c.is_zero() {
115                                sum += c.abs();
116                                num_non_zero += 1;
117                            }
118                        }
119                    }
120                }
121
122                if num_non_zero > 0 {
123                    sum / F(num_non_zero as f64)
124                } else {
125                    F(64.0) // default value for vacuum amplitudes
126                }
127            }
128        }
129    }
130}
131
132impl Rotatable for Externals {
133    fn rotate(&self, rotation: &momentum::Rotation) -> Self {
134        match self {
135            Externals::Constant {
136                momenta,
137                helicities,
138                f_64_cache,
139                f_128_cache,
140                improvement_settings,
141            } => {
142                let momenta = momenta.iter().map(|m| m.rotate(rotation)).collect();
143                Externals::Constant {
144                    momenta,
145                    helicities: helicities.clone(),
146                    improvement_settings: improvement_settings.clone(),
147                    f_64_cache: f_64_cache
148                        .as_ref()
149                        .map(|cache| cache.iter().map(|m| m.rotate(rotation)).collect()),
150                    f_128_cache: f_128_cache
151                        .as_ref()
152                        .map(|cache| cache.iter().map(|m| m.rotate(rotation)).collect()),
153                }
154            }
155        }
156    }
157}
158
159#[derive(Debug, Clone, PartialEq, Encode, Decode)]
160#[trait_decode(trait= GammaLoopContext)]
161pub enum Polarizations {
162    Constant {
163        polarizations: Vec<Polarization<Complex<F<f64>>>>,
164    },
165    None,
166}
167
168impl Rotatable for Polarizations {
169    fn rotate(&self, rotation: &momentum::Rotation) -> Self {
170        match self {
171            Polarizations::Constant { polarizations } => {
172                let polarizations = polarizations.iter().map(|p| p.rotate(rotation)).collect();
173                Polarizations::Constant { polarizations }
174            }
175            Polarizations::None => Polarizations::None,
176        }
177    }
178}
179
180use thiserror::Error;
181
182#[derive(Error, Debug)]
183pub enum ExternalsValidationError {
184    #[error("There should be exactly one dependent external momentum")]
185    WrongNumberOfDependentMomenta,
186    #[error("Found {0} momenta, expected {1}")]
187    WrongNumberOfMomentaExpected(usize, usize),
188    #[error("Found {0} helicities, expected {1}")]
189    WrongNumberOfHelicities(usize, usize),
190    #[error("Massless vector cannot have zero helicity: pos {0}")]
191    MasslessVectorZeroHelicity(usize),
192    #[error("Spinors cannot have zero helicity at pos {0}")]
193    SpinorZeroHelicity(usize),
194    #[error("Scalars cannot have non-zero helicity at pos {0}")]
195    ScalarNonZeroHelicity(usize),
196    #[error("{0} is an Unsuported external spin for pos {0}")]
197    UnsupportedSpin(isize, usize),
198}
199
200impl Externals {
201    pub fn validate_helicities(
202        &self,
203        spins: &[(isize, bool)],
204    ) -> Result<(), ExternalsValidationError> {
205        match self {
206            Externals::Constant { helicities, .. } => {
207                if helicities.len() == spins.len() {
208                    for (i, (h, (s, is_massless))) in
209                        helicities.iter().zip(spins.iter()).enumerate()
210                    {
211                        let Helicity::Signed(h) = h else {
212                            continue;
213                        };
214                        match *s {
215                            1 => {
216                                if !h.is_zero() {
217                                    return Err(ExternalsValidationError::ScalarNonZeroHelicity(i));
218                                }
219                            }
220                            2 => {
221                                if h.is_zero() {
222                                    return Err(ExternalsValidationError::SpinorZeroHelicity(i));
223                                }
224                            }
225                            3 => {
226                                if h.is_zero() && *is_massless {
227                                    return Err(
228                                        ExternalsValidationError::MasslessVectorZeroHelicity(i),
229                                    );
230                                }
231                            }
232                            s => return Err(ExternalsValidationError::UnsupportedSpin(s, i)),
233                        }
234                    }
235                    Ok(())
236                } else {
237                    Err(ExternalsValidationError::WrongNumberOfHelicities(
238                        helicities.len(),
239                        spins.len(),
240                    ))
241                }
242            }
243        }
244    }
245
246    pub fn set_dependent_at_end(
247        &mut self,
248        signature: &ExternalSignature,
249    ) -> Result<(), ExternalsValidationError> {
250        match self {
251            Externals::Constant { momenta, .. } => {
252                let mut sum: FourMomentum<F<f128>> = FourMomentum::from([F(0.0); 4]).higher();
253                let mut pos_dep = 0;
254                let mut n_dep = 0;
255
256                let mut dependent_sign = SignOrZero::Plus;
257
258                for ((i, m), s) in momenta.iter().enumerate().zip(signature.iter()) {
259                    if let Ok(a) = FourMomentum::try_from(*m) {
260                        sum -= *s * a.higher();
261                    } else {
262                        pos_dep = i;
263                        n_dep += 1;
264                        dependent_sign = *s;
265                    }
266                }
267                if n_dep == 1 {
268                    momenta[pos_dep] = (dependent_sign * sum.lower()).into();
269                    let len = momenta.len();
270                    momenta[len - 1] = ExternalMomenta::Dependent(Dep::Dep);
271                } else if n_dep == 0 {
272                    debug!("No dependent momentum found, adding the sum at the end");
273                    momenta.push(ExternalMomenta::Dependent(Dep::Dep));
274                } else {
275                    return Err(ExternalsValidationError::WrongNumberOfDependentMomenta);
276                }
277
278                let len = momenta.len();
279                if len == signature.len() {
280                    Ok(())
281                } else {
282                    Err(ExternalsValidationError::WrongNumberOfMomentaExpected(
283                        len - 1,
284                        signature.len() - 1,
285                    ))
286                }
287            }
288        }
289    }
290
291    #[inline(never)]
292    // #[comemo::memoize]
293    pub fn get_dependent_externals<T: FloatLike>(
294        &self,
295        dependent_momenta_constructor: DependentMomentaConstructor,
296    ) -> Result<ExternalFourMomenta<F<T>>>
297// where
298    //     T::Higher: PrecisionUpgradable<Lower = T> + FloatLike,
299    {
300        if let Some(cached) = T::try_extract_externals_from_cache(self) {
301            return Ok(cached.clone());
302        }
303
304        match self {
305            Externals::Constant { momenta, .. } => {
306                match dependent_momenta_constructor {
307                    DependentMomentaConstructor::Amplitude(external_signature) => {
308                        if external_signature.is_empty() {
309                            return Ok(vec![].into());
310                        }
311
312                        let mut sum: FourMomentum<F<T>> = FourMomentum::from([
313                            F::<T>::from_f64(0.0),
314                            F::from_f64(0.0),
315                            F::from_f64(0.0),
316                            F::from_f64(0.0),
317                        ]);
318                        // .higher();
319                        let mut pos_dep = external_signature.len() - 1;
320
321                        let mut dependent_sign = SignOrZero::Plus;
322
323                        let mut dependent_momenta = vec![];
324
325                        if momenta.len() != external_signature.len() {
326                            return Err(eyre!(
327                                "External Momentum in input do not match the number of externals"
328                            ))
329                            .with_note(|| {
330                                let mut table = Builder::new();
331                                for m in momenta {
332                                    match m {
333                                        ExternalMomenta::Dependent(_) => {
334                                            table.push_record(["Dependent"])
335                                        }
336                                        ExternalMomenta::Independent([e, x, y, z]) => table
337                                            .push_record([
338                                                e.to_string(),
339                                                x.to_string(),
340                                                y.to_string(),
341                                                z.to_string(),
342                                            ]),
343                                    }
344                                }
345                                format!(
346                                    "External momenta: \n{}\n{}",
347                                    table.build().with(Style::rounded()),
348                                    external_signature
349                                )
350                            });
351                        }
352
353                        for ((i, m), s) in momenta.iter().enumerate().zip(external_signature.iter())
354                        {
355                            if let Ok(a) = FourMomentum::try_from(*m) {
356                                // println!("external{i}: {}", a);
357                                let a = FourMomentum::<F<T>>::from_ff64(&a);
358                                sum -= *s * a.clone(); //.higher();
359                                dependent_momenta.push(a);
360                            } else {
361                                pos_dep = i;
362                                dependent_sign = *s;
363                                dependent_momenta.push(sum.clone()); //.lower());
364                            }
365                        }
366
367                        dependent_momenta[pos_dep] = dependent_sign * sum; //.lower();
368                        let res = dependent_momenta.into();
369
370                        Ok(res)
371                    }
372                    //DependentMomentaConstructor::CrossSection {
373                    //    external_connections,
374                    //} => {
375                    //    assert_eq!(external_connections.len(), momenta.len());
376
377                    //    // set all_to_zero
378                    //    let mut dependent_momenta = (0..momenta.len() * 2)
379                    //        .map(|_| {
380                    //            FourMomentum::<F<T>>::from_ff64(&FourMomentum::from([F(0.0); 4]))
381                    //        })
382                    //        .collect::<ExternalFourMomenta<F<T>>>();
383
384                    //    let incoming_momenta = momenta
385                    //        .iter()
386                    //        .map(|m| {
387                    //            FourMomentum::<F<T>>::from_ff64(
388                    //                &FourMomentum::try_from(*m)
389                    //                    .expect("dependent momenta in cross section not allowed"),
390                    //            )
391                    //        })
392                    //        .collect_vec();
393
394                    //    for (external_connection, incoming_momentum) in
395                    //        external_connections.iter().zip(incoming_momenta)
396                    //    {
397                    //        dependent_momenta[external_connection.incoming_index] =
398                    //            incoming_momentum.clone();
399                    //        dependent_momenta[external_connection.outgoing_index] =
400                    //            incoming_momentum;
401                    //    }
402
403                    //    Ok(dependent_momenta)
404                    //}
405                    DependentMomentaConstructor::CrossSection => {
406                        let dependent_momenta = momenta
407                            .iter()
408                            .map(|m| {
409                                FourMomentum::<F<T>>::from_ff64(
410                                    &FourMomentum::try_from(*m)
411                                        .expect("dependent momenta in None not allowed"),
412                                )
413                            })
414                            .collect::<ExternalFourMomenta<F<T>>>();
415                        Ok(dependent_momenta)
416                    }
417                }
418            }
419        }
420    }
421
422    #[allow(unused_variables)]
423    #[inline]
424    pub fn get_indep_externals(&self) -> Vec<FourMomentum<F<f64>>> {
425        match self {
426            Externals::Constant {
427                momenta,
428                helicities,
429                ..
430            } => {
431                let momenta: Vec<FourMomentum<_>> = momenta
432                    .iter()
433                    .flat_map(|e| FourMomentum::try_from(*e))
434                    .collect();
435                momenta
436            }
437        }
438    }
439
440    pub(crate) fn get_helicities(&self) -> &[Helicity] {
441        match self {
442            Externals::Constant { helicities, .. } => helicities,
443        }
444    }
445
446    pub(crate) fn _pdf(&self, _x_space_point: &[F<f64>]) -> F<f64> {
447        match self {
448            Externals::Constant { .. } => F(1.0),
449        }
450    }
451
452    pub fn improve_and_cache(
453        &mut self,
454        constructor: DependentMomentaConstructor,
455        masses: &TiVec<ExternalIndex, F<f64>>,
456        e_cm: &F<f64>,
457    ) -> Result<()> {
458        let dep_momenta_f64 = self.get_dependent_externals::<f64>(constructor)?;
459        let dep_momenta_f128 = self.get_dependent_externals::<f128>(constructor)?;
460
461        match constructor {
462            DependentMomentaConstructor::Amplitude(signature) => match self {
463                Externals::Constant {
464                    improvement_settings,
465                    f_64_cache,
466                    f_128_cache,
467                    ..
468                } => {
469                    let improved_f64 = improve_ps(
470                        &dep_momenta_f64,
471                        masses,
472                        signature,
473                        e_cm,
474                        improvement_settings,
475                    )?;
476
477                    let upcasted_masses = masses.iter().map(|m| F::<f128>::from_ff64(*m)).collect();
478
479                    let improved_f128 = improve_ps(
480                        &dep_momenta_f128,
481                        &upcasted_masses,
482                        signature,
483                        &F::<f128>::from_ff64(*e_cm),
484                        improvement_settings,
485                    )?;
486                    *f_64_cache = Some(improved_f64);
487                    *f_128_cache = Some(improved_f128);
488                    Ok(())
489                }
490            },
491            // todo, improve the on-shellness
492            DependentMomentaConstructor::CrossSection { .. } => Ok(()),
493        }
494    }
495}
496
497#[test]
498fn external_inv() {
499    let mut ext = Externals::Constant {
500        momenta: vec![[F(1.), F(2.), F(3.), F(4.)].into(); 3],
501        helicities: vec![Helicity::PLUS; 4],
502        f_64_cache: None,
503        f_128_cache: None,
504        improvement_settings: PhaseSpaceImprovementSettings::default(),
505    };
506
507    let signs: ExternalSignature = [1i8, 1, 1, 1].into_iter().collect();
508    ext.set_dependent_at_end(&signs).unwrap();
509
510    let momenta = vec![
511        ExternalMomenta::Dependent(Dep::Dep),
512        [F(1.), F(2.), F(3.), F(4.)].into(),
513        [F(1.), F(2.), F(3.), F(4.)].into(),
514        [F(-3.), F(-6.), F(-9.), F(-12.)].into(),
515    ];
516    let mut ext2 = Externals::Constant {
517        momenta,
518        helicities: vec![Helicity::PLUS; 4],
519        f_64_cache: None,
520        f_128_cache: None,
521        improvement_settings: PhaseSpaceImprovementSettings::default(),
522    };
523
524    ext2.set_dependent_at_end(&signs).unwrap();
525
526    assert_eq!(ext, ext2);
527}
528
529impl Default for Externals {
530    fn default() -> Self {
531        Externals::Constant {
532            momenta: vec![],
533            helicities: vec![],
534            f_64_cache: None,
535            f_128_cache: None,
536            improvement_settings: PhaseSpaceImprovementSettings::default(),
537        }
538    }
539}