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 #[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 },
101 }
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) }
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 pub fn get_dependent_externals<T: FloatLike>(
294 &self,
295 dependent_momenta_constructor: DependentMomentaConstructor,
296 ) -> Result<ExternalFourMomenta<F<T>>>
297{
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 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 let a = FourMomentum::<F<T>>::from_ff64(&a);
358 sum -= *s * a.clone(); dependent_momenta.push(a);
360 } else {
361 pos_dep = i;
362 dependent_sign = *s;
363 dependent_momenta.push(sum.clone()); }
365 }
366
367 dependent_momenta[pos_dep] = dependent_sign * sum; let res = dependent_momenta.into();
369
370 Ok(res)
371 }
372 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 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}