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 ArbPrec, F, FloatLike, RuntimeCache, SamplingPrecision, f128,
23 serde_utils::{IsDefault, is_float},
24 },
25};
26use color_eyre::{Result, Section};
27
28#[cfg_attr(feature = "python", 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 #[serde(skip_serializing_if = "is_float::<64>")]
35 pub e_cm: f64,
36 #[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#[serde(tag = "type", content = "data")]
83#[serde(deny_unknown_fields)]
84pub enum Externals {
85 #[serde(rename = "constant")]
87 Constant {
88 momenta: Vec<ExternalMomenta<F<f64>>>,
90 helicities: Vec<Helicity>,
92 #[serde(default, skip_serializing_if = "IsDefault::is_default")]
94 improvement_settings: PhaseSpaceImprovementSettings,
95 #[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 #[serde(skip)]
103 arb_cache: RuntimeCache<TiVec<ExternalIndex, FourMomentum<F<ArbPrec>>>>,
104 },
105 }
107
108impl Externals {
109 pub fn sane_e_cm_value(&self) -> F<f64> {
110 match self {
111 Externals::Constant { momenta, .. } => {
112 let mut sum = F(0.0);
113 let mut num_non_zero = 0;
114
115 for m in momenta {
116 if let ExternalMomenta::Independent(components) = m {
117 for c in components {
118 if !c.is_zero() {
119 sum += c.abs();
120 num_non_zero += 1;
121 }
122 }
123 }
124 }
125
126 if num_non_zero > 0 {
127 sum / F(num_non_zero as f64)
128 } else {
129 F(64.0) }
131 }
132 }
133 }
134}
135
136impl Rotatable for Externals {
137 fn rotate(&self, rotation: &momentum::Rotation) -> Self {
138 match self {
139 Externals::Constant {
140 momenta,
141 helicities,
142 f_64_cache,
143 f_128_cache,
144 arb_cache,
145 improvement_settings,
146 } => {
147 let momenta = momenta.iter().map(|m| m.rotate(rotation)).collect();
148 let mut rotated_arb = RuntimeCache::default();
149 if let Some(cache) = arb_cache.as_ref() {
150 rotated_arb.set(cache.iter().map(|m| m.rotate(rotation)).collect());
151 }
152 Externals::Constant {
153 momenta,
154 helicities: helicities.clone(),
155 improvement_settings: improvement_settings.clone(),
156 arb_cache: rotated_arb,
157 f_64_cache: f_64_cache
158 .as_ref()
159 .map(|cache| cache.iter().map(|m| m.rotate(rotation)).collect()),
160 f_128_cache: f_128_cache
161 .as_ref()
162 .map(|cache| cache.iter().map(|m| m.rotate(rotation)).collect()),
163 }
164 }
165 }
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Encode, Decode)]
170#[trait_decode(trait= GammaLoopContext)]
171pub enum Polarizations {
172 Constant {
173 polarizations: Vec<Polarization<Complex<F<f64>>>>,
174 },
175 None,
176}
177
178impl Rotatable for Polarizations {
179 fn rotate(&self, rotation: &momentum::Rotation) -> Self {
180 match self {
181 Polarizations::Constant { polarizations } => {
182 let polarizations = polarizations.iter().map(|p| p.rotate(rotation)).collect();
183 Polarizations::Constant { polarizations }
184 }
185 Polarizations::None => Polarizations::None,
186 }
187 }
188}
189
190use thiserror::Error;
191
192#[derive(Error, Debug)]
193pub enum ExternalsValidationError {
194 #[error("There should be exactly one dependent external momentum")]
195 WrongNumberOfDependentMomenta,
196 #[error("Found {0} momenta, expected {1}")]
197 WrongNumberOfMomentaExpected(usize, usize),
198 #[error("Found {0} helicities, expected {1}")]
199 WrongNumberOfHelicities(usize, usize),
200 #[error("Massless vector cannot have zero helicity: pos {0}")]
201 MasslessVectorZeroHelicity(usize),
202 #[error("Spinors cannot have zero helicity at pos {0}")]
203 SpinorZeroHelicity(usize),
204 #[error("Scalars cannot have non-zero helicity at pos {0}")]
205 ScalarNonZeroHelicity(usize),
206 #[error("{0} is an Unsuported external spin for pos {0}")]
207 UnsupportedSpin(isize, usize),
208}
209
210impl Externals {
211 pub fn validate_helicities(
212 &self,
213 spins: &[(isize, bool)],
214 ) -> Result<(), ExternalsValidationError> {
215 match self {
216 Externals::Constant { helicities, .. } => {
217 if helicities.len() == spins.len() {
218 for (i, (h, (s, is_massless))) in
219 helicities.iter().zip(spins.iter()).enumerate()
220 {
221 let Helicity::Signed(h) = h else {
222 continue;
223 };
224 match *s {
225 1 => {
226 if !h.is_zero() {
227 return Err(ExternalsValidationError::ScalarNonZeroHelicity(i));
228 }
229 }
230 2 => {
231 if h.is_zero() {
232 return Err(ExternalsValidationError::SpinorZeroHelicity(i));
233 }
234 }
235 3 => {
236 if h.is_zero() && *is_massless {
237 return Err(
238 ExternalsValidationError::MasslessVectorZeroHelicity(i),
239 );
240 }
241 }
242 s => return Err(ExternalsValidationError::UnsupportedSpin(s, i)),
243 }
244 }
245 Ok(())
246 } else {
247 Err(ExternalsValidationError::WrongNumberOfHelicities(
248 helicities.len(),
249 spins.len(),
250 ))
251 }
252 }
253 }
254 }
255
256 pub fn set_dependent_at_end(
257 &mut self,
258 signature: &ExternalSignature,
259 ) -> Result<(), ExternalsValidationError> {
260 match self {
261 Externals::Constant { momenta, .. } => {
262 let mut sum: FourMomentum<F<f128>> = FourMomentum::from([F(0.0); 4]).higher();
263 let mut pos_dep = 0;
264 let mut n_dep = 0;
265
266 let mut dependent_sign = SignOrZero::Plus;
267
268 for ((i, m), s) in momenta.iter().enumerate().zip(signature.iter()) {
269 if let Ok(a) = FourMomentum::try_from(*m) {
270 sum -= *s * a.higher();
271 } else {
272 pos_dep = i;
273 n_dep += 1;
274 dependent_sign = *s;
275 }
276 }
277 if n_dep == 1 {
278 momenta[pos_dep] = (dependent_sign * sum.lower()).into();
279 let len = momenta.len();
280 momenta[len - 1] = ExternalMomenta::Dependent(Dep::Dep);
281 } else if n_dep == 0 {
282 debug!("No dependent momentum found, adding the sum at the end");
283 momenta.push(ExternalMomenta::Dependent(Dep::Dep));
284 } else {
285 return Err(ExternalsValidationError::WrongNumberOfDependentMomenta);
286 }
287
288 let len = momenta.len();
289 if len == signature.len() {
290 Ok(())
291 } else {
292 Err(ExternalsValidationError::WrongNumberOfMomentaExpected(
293 len - 1,
294 signature.len() - 1,
295 ))
296 }
297 }
298 }
299 }
300
301 #[inline(never)]
302 pub fn get_dependent_externals<T: FloatLike>(
304 &self,
305 dependent_momenta_constructor: DependentMomentaConstructor,
306 ) -> Result<ExternalFourMomenta<F<T>>>
307{
310 if let Some(cached) = T::try_extract_externals_from_cache(self) {
311 return Ok(cached.clone());
312 }
313 if T::sampling_precision() == SamplingPrecision::Fixed256
317 && let Some(cached) = ArbPrec::try_extract_externals_from_cache(self)
318 {
319 return cached
320 .iter()
321 .map(|p| {
322 Ok(FourMomentum::from([
323 F::from_arb(&p.temporal.value.0)?,
324 F::from_arb(&p.spatial.px.0)?,
325 F::from_arb(&p.spatial.py.0)?,
326 F::from_arb(&p.spatial.pz.0)?,
327 ]))
328 })
329 .collect();
330 }
331
332 match self {
333 Externals::Constant { momenta, .. } => {
334 match dependent_momenta_constructor {
335 DependentMomentaConstructor::Amplitude(external_signature) => {
336 if external_signature.is_empty() {
337 return Ok(vec![].into());
338 }
339
340 let mut sum: FourMomentum<F<T>> = FourMomentum::from([
341 F::<T>::from_f64(0.0),
342 F::from_f64(0.0),
343 F::from_f64(0.0),
344 F::from_f64(0.0),
345 ]);
346 let mut pos_dep = external_signature.len() - 1;
348
349 let mut dependent_sign = SignOrZero::Plus;
350
351 let mut dependent_momenta = vec![];
352
353 if momenta.len() != external_signature.len() {
354 return Err(eyre!(
355 "External Momentum in input do not match the number of externals"
356 ))
357 .with_note(|| {
358 let mut table = Builder::new();
359 for m in momenta {
360 match m {
361 ExternalMomenta::Dependent(_) => {
362 table.push_record(["Dependent"])
363 }
364 ExternalMomenta::Independent([e, x, y, z]) => table
365 .push_record([
366 e.to_string(),
367 x.to_string(),
368 y.to_string(),
369 z.to_string(),
370 ]),
371 }
372 }
373 format!(
374 "External momenta: \n{}\n{}",
375 table.build().with(Style::rounded()),
376 external_signature
377 )
378 });
379 }
380
381 for ((i, m), s) in momenta.iter().enumerate().zip(external_signature.iter())
382 {
383 if let Ok(a) = FourMomentum::try_from(*m) {
384 let a = FourMomentum::<F<T>>::from_ff64(&a);
386 sum -= *s * a.clone(); dependent_momenta.push(a);
388 } else {
389 pos_dep = i;
390 dependent_sign = *s;
391 dependent_momenta.push(sum.clone()); }
393 }
394
395 dependent_momenta[pos_dep] = dependent_sign * sum; let res = dependent_momenta.into();
397
398 Ok(res)
399 }
400 DependentMomentaConstructor::CrossSection => {
434 let dependent_momenta = momenta
435 .iter()
436 .map(|m| {
437 FourMomentum::<F<T>>::from_ff64(
438 &FourMomentum::try_from(*m)
439 .expect("dependent momenta in None not allowed"),
440 )
441 })
442 .collect::<ExternalFourMomenta<F<T>>>();
443 Ok(dependent_momenta)
444 }
445 }
446 }
447 }
448 }
449
450 #[allow(unused_variables)]
451 #[inline]
452 pub fn get_indep_externals(&self) -> Vec<FourMomentum<F<f64>>> {
453 match self {
454 Externals::Constant {
455 momenta,
456 helicities,
457 ..
458 } => {
459 let momenta: Vec<FourMomentum<_>> = momenta
460 .iter()
461 .flat_map(|e| FourMomentum::try_from(*e))
462 .collect();
463 momenta
464 }
465 }
466 }
467
468 pub(crate) fn get_helicities(&self) -> &[Helicity] {
469 match self {
470 Externals::Constant { helicities, .. } => helicities,
471 }
472 }
473
474 pub(crate) fn _pdf(&self, _x_space_point: &[F<f64>]) -> F<f64> {
475 match self {
476 Externals::Constant { .. } => F(1.0),
477 }
478 }
479
480 pub fn improve_and_cache(
481 &mut self,
482 constructor: DependentMomentaConstructor,
483 masses: &TiVec<ExternalIndex, F<f64>>,
484 e_cm: &F<f64>,
485 ) -> Result<()> {
486 let Self::Constant {
489 f_64_cache,
490 f_128_cache,
491 arb_cache,
492 ..
493 } = self;
494 *f_64_cache = None;
495 *f_128_cache = None;
496 arb_cache.invalidate();
497 let dep_momenta_f64 = self.get_dependent_externals::<f64>(constructor)?;
498 let dep_momenta_f128 = self.get_dependent_externals::<f128>(constructor)?;
499 let dep_momenta_arb = self.get_dependent_externals::<ArbPrec>(constructor)?;
500
501 match constructor {
502 DependentMomentaConstructor::Amplitude(signature) => match self {
503 Externals::Constant {
504 improvement_settings,
505 f_64_cache,
506 f_128_cache,
507 arb_cache,
508 ..
509 } => {
510 let improved_f64 = improve_ps(
511 &dep_momenta_f64,
512 masses,
513 signature,
514 e_cm,
515 improvement_settings,
516 )?;
517
518 let upcasted_masses = masses.iter().map(|m| F::<f128>::from_ff64(*m)).collect();
519
520 let improved_f128 = improve_ps(
521 &dep_momenta_f128,
522 &upcasted_masses,
523 signature,
524 &F::<f128>::from_ff64(*e_cm),
525 improvement_settings,
526 )?;
527 let arb_masses = masses
528 .iter()
529 .map(|mass| F::<ArbPrec>::from_ff64(*mass))
530 .collect();
531 let improved_arb = improve_ps(
532 &dep_momenta_arb,
533 &arb_masses,
534 signature,
535 &F::<ArbPrec>::from_ff64(*e_cm),
536 improvement_settings,
537 )?;
538 *f_64_cache = Some(improved_f64);
539 *f_128_cache = Some(improved_f128);
540 arb_cache.set(improved_arb);
541 Ok(())
542 }
543 },
544 DependentMomentaConstructor::CrossSection { .. } => Ok(()),
546 }
547 }
548}
549
550#[test]
551fn external_inv() {
552 let mut ext = Externals::Constant {
553 momenta: vec![[F(1.), F(2.), F(3.), F(4.)].into(); 3],
554 helicities: vec![Helicity::PLUS; 4],
555 f_64_cache: None,
556 f_128_cache: None,
557 arb_cache: Default::default(),
558 improvement_settings: PhaseSpaceImprovementSettings::default(),
559 };
560
561 let signs: ExternalSignature = [1i8, 1, 1, 1].into_iter().collect();
562 ext.set_dependent_at_end(&signs).unwrap();
563
564 let momenta = vec![
565 ExternalMomenta::Dependent(Dep::Dep),
566 [F(1.), F(2.), F(3.), F(4.)].into(),
567 [F(1.), F(2.), F(3.), F(4.)].into(),
568 [F(-3.), F(-6.), F(-9.), F(-12.)].into(),
569 ];
570 let mut ext2 = Externals::Constant {
571 momenta,
572 helicities: vec![Helicity::PLUS; 4],
573 f_64_cache: None,
574 f_128_cache: None,
575 arb_cache: Default::default(),
576 improvement_settings: PhaseSpaceImprovementSettings::default(),
577 };
578
579 ext2.set_dependent_at_end(&signs).unwrap();
580
581 assert_eq!(ext, ext2);
582}
583
584impl Default for Externals {
585 fn default() -> Self {
586 Externals::Constant {
587 momenta: vec![],
588 helicities: vec![],
589 f_64_cache: None,
590 f_128_cache: None,
591 arb_cache: Default::default(),
592 improvement_settings: PhaseSpaceImprovementSettings::default(),
593 }
594 }
595}