1use crate::HasModel;
2use crate::momentum::Helicity;
3use crate::numerator::aind::Aind;
4use crate::utils::serde_utils::SmartSerde;
5use crate::utils::symbolica_ext::DOD;
6use crate::utils::{self, F, W_};
7use ahash::{AHashMap, HashSet, RandomState};
8use bincode::{Decode, Encode};
9use color_eyre::Report;
10use color_eyre::owo_colors::OwoColorize;
11use colored::Colorize;
12use eyre::eyre;
13use itertools::Itertools;
14use linnet::half_edge::involution::{EdgeIndex, Flow};
16use rand::rngs::SmallRng;
17use rand::{Rng, SeedableRng};
18use serde::de::DeserializeOwned;
19#[cfg(test)]
20use spenso::shadowing::symbolica_utils::SpensoPrintSettings;
21use spenso::structure::{IndexLess, PermutedStructure};
22use symbolica_utils::{PrintSettingsExt, Replaces};
23use tabled::settings::Modify;
24use tabled::{
25 builder::Builder,
26 settings::{Span, object::Cell},
27 settings::{Style, style::VerticalLine},
28};
29
30use idenso::{
32 dirac::AGS,
33 representations::{Bispinor, ColorAdjoint, ColorFundamental, ColorSextet},
34};
35use serde::{Deserialize, Serialize};
36use smartstring::{LazyCompact, SmartString};
37use spenso::algebra::complex::Complex;
38use spenso::network::library::symbolic::ETS;
39use spenso::structure::OrderedStructure;
40use spenso::structure::representation::Euclidean;
41use spenso::structure::representation::{LibraryRep, Minkowski};
42use spenso::structure::{
43 representation::BaseRepName, representation::Lorentz, representation::RepName, slot::DummyAind,
44 slot::IsAbstractSlot, slot::Slot,
45};
46use spenso::tensors::data::{DataTensor, DenseTensor, SetTensorData, SparseTensor};
47use spenso::tensors::parametric::ParamTensor;
48use std::collections::BTreeMap;
49use std::fmt::{Display, Formatter};
50use std::fs;
51use symbolica::{domains::rational::Fraction, prelude::*, printer::PrintUserData};
52use tracing::info;
53
54use color_eyre::Result;
55use std::collections::HashMap;
56use std::ops::Deref;
57use std::path::Path;
58use std::sync::Arc;
59
60use crate::settings::global::VectorPolarizationSumGauge;
61use crate::utils::GS;
62
63#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
64pub struct SerializableInputParamCard<T> {
65 pub data: BTreeMap<String, (T, T)>,
66}
67
68impl<T> SerializableInputParamCard<T>
69where
70 T: From<f64> + Clone + Serialize + DeserializeOwned,
71{
72 pub(crate) fn from_file(file_path: impl AsRef<Path>) -> Result<Self, Report> {
73 let hashmap_card: BTreeMap<String, (T, T)> = SmartSerde::from_file(file_path, "model")?;
74 Ok(SerializableInputParamCard { data: hashmap_card })
75 }
76
77 pub fn from_str(s: String, format: &str) -> Result<Self, Report> {
78 let hashmap_card: BTreeMap<String, (T, T)> =
79 SmartSerde::from_str(s, format, "model_parameters")?;
80 Ok(SerializableInputParamCard { data: hashmap_card })
81 }
82
83 pub fn from_input_param_card(card: &InputParamCard<T>) -> Self {
84 let serializeable_card: BTreeMap<String, (T, T)> = card
85 .data
86 .iter()
87 .map(|(k, v)| {
88 (
89 k.namespaceless_string().to_string(),
90 (v.re.clone(), v.im.clone()),
91 )
92 })
93 .collect();
94 SerializableInputParamCard {
95 data: serializeable_card,
96 }
97 }
98
99 pub fn to_file<P: AsRef<Path>>(&self, path: P, overwrite: bool) -> Result<(), Report> {
100 SmartSerde::to_file(&self.data, path, overwrite)
101 }
102}
103
104#[derive(Debug, Clone)]
105pub struct InputParamCard<T>
106where
107 T: From<f64> + Clone + Serialize + DeserializeOwned,
108{
109 data: HashMap<UFOSymbol, Complex<T>>,
110}
111
112impl<T> Default for InputParamCard<T>
113where
114 T: From<f64> + Clone + Serialize + DeserializeOwned,
115{
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121impl<T> InputParamCard<T>
122where
123 T: From<f64> + Clone + Serialize + DeserializeOwned,
124{
125 pub fn new() -> Self {
127 Self {
128 data: HashMap::new(),
129 }
130 }
131
132 pub fn insert(&mut self, sym: UFOSymbol, value: Complex<T>) -> Option<Complex<T>> {
134 self.data.insert(sym, value)
135 }
136
137 pub fn get(&self, sym: &UFOSymbol) -> Option<&Complex<T>> {
139 self.data.get(sym)
140 }
141
142 pub fn get_mut(&mut self, sym: &UFOSymbol) -> Option<&mut Complex<T>> {
144 self.data.get_mut(sym)
145 }
146
147 pub fn remove(&mut self, sym: &UFOSymbol) -> Option<Complex<T>> {
149 self.data.remove(sym)
150 }
151
152 pub fn iter(&self) -> impl Iterator<Item = (&UFOSymbol, &Complex<T>)> {
154 self.data.iter()
155 }
156
157 pub fn to_serializable(&self) -> SerializableInputParamCard<T> {
158 SerializableInputParamCard::from_input_param_card(self)
159 }
160
161 pub fn from_serializable(
162 serializable_input_param_card: &SerializableInputParamCard<T>,
163 ) -> Self {
164 let data: HashMap<UFOSymbol, Complex<T>> = serializable_input_param_card
165 .data
166 .iter()
167 .map(|(k, v)| {
168 (
169 UFOSymbol::from(k.as_str()),
170 Complex::new(v.0.clone(), v.1.clone()),
171 )
172 })
173 .collect();
174 InputParamCard { data }
175 }
176
177 pub fn from_file(file_path: impl AsRef<Path>) -> Result<Self, Report> {
178 let serializable_input_param_card = SerializableInputParamCard::from_file(file_path)?;
179 Ok(Self::from_serializable(&serializable_input_param_card))
180 }
181 pub fn from_str(s: String, format: &str) -> Result<Self, Report> {
182 let serializable_input_param_card = SerializableInputParamCard::from_str(s, format)?;
183 Ok(Self::from_serializable(&serializable_input_param_card))
184 }
185
186 pub fn to_file<P: AsRef<Path>>(&self, path: P, overwrite: bool) -> Result<(), Report> {
187 let serializable_card = self.to_serializable();
188 serializable_card.to_file(path, overwrite)?;
189 Ok(())
190 }
191}
192
193impl InputParamCard<F<f64>> {
194 pub fn apply_to_model(&self, model: &mut Model) -> Result<(), Report> {
195 for (param, value) in &self.data {
196 if let Some(model_param) = model.get_parameter_mut_opt(param.namespaceless_string()) {
197 model_param.value = Some(*value);
198 } else {
199 return Err(eyre!(
200 "Parameter {} not found in model when applying input parameter card",
201 param
202 ));
203 }
204 }
205 model.recompute_dependents()
206 }
207}
208
209impl InputParamCard<F<f64>> {
210 pub fn default_from_model(model: &Model) -> Self {
211 let mut card = InputParamCard::new();
212 for param in model.parameters.values() {
213 if param.nature == ParameterNature::External
214 && !param.name.is_zero()
215 && let Some(value) = param.value
216 {
217 card.insert(param.name, value);
218 }
219 }
220 card
221 }
222}
223
224impl<T> std::ops::Deref for InputParamCard<T>
226where
227 T: From<f64> + Clone + Serialize + DeserializeOwned,
228{
229 type Target = HashMap<UFOSymbol, Complex<T>>;
230 fn deref(&self) -> &Self::Target {
231 &self.data
232 }
233}
234impl<T> std::ops::DerefMut for InputParamCard<T>
235where
236 T: From<f64> + Clone + Serialize + DeserializeOwned,
237{
238 fn deref_mut(&mut self) -> &mut Self::Target {
239 &mut self.data
240 }
241}
242
243#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
244pub struct UFOSymbol(pub Symbol);
245
246impl Display for UFOSymbol {
247 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
248 write!(f, "{}", self.0)
249 }
250}
251
252impl From<UFOSymbol> for Atom {
253 fn from(value: UFOSymbol) -> Self {
254 Atom::var(value.0)
255 }
256}
257
258impl UFOSymbol {
259 pub fn zero() -> Self {
260 UFOSymbol(GS.ufozero)
261 }
262
263 pub fn is_zero(&self) -> bool {
264 self.0 == Self::zero().0
265 }
266
267 pub fn namespaceless_string(&self) -> &str {
268 self.0.get_stripped_name()
269 }
270}
271
272#[test]
273fn zerosym() {
274 println!("{}", Atom::num(1) * Atom::from(UFOSymbol::zero()));
275 println!("{}", Atom::from(UFOSymbol::zero()));
276 }
278
279#[test]
280fn ufo_symbol_typst_quoting_requires_typst_mode() {
281 let atom = Atom::from(UFOSymbol::from("typst_mode_probe"));
282
283 assert_eq!(
284 atom.printer(SpensoPrintSettings::typst_options())
285 .to_string(),
286 r#""typst_mode_probe""#
287 );
288
289 let mut symbolica = SpensoPrintSettings::typst().nice_symbolica();
290 symbolica.color_builtin_symbols = false;
291 assert_eq!(atom.printer(symbolica).to_string(), "typst_mode_probe");
292}
293
294impl<T> From<T> for UFOSymbol
295where
296 T: AsRef<str>,
297{
298 fn from(s: T) -> Self {
299 let is_zero = s.as_ref() == "ZERO";
300 if is_zero {
301 UFOSymbol::zero()
302 } else {
303 let name = format!("UFO::{}", s.as_ref());
304 if let Some(a) = get_symbol!(&name) {
305 UFOSymbol(a)
306 } else {
307 UFOSymbol(symbol!(
308 &name,
309 print = |a, opt, _state| {
310 let AtomView::Var(a) = a else {
311 return None;
312 };
313 match opt.custom_print_mode.get("spenso") {
314 Some(PrintUserData::Integer(_)) if opt.typst_mode().is_some() => {
315 Some(format!("\"{}\"", a.get_symbol().get_stripped_name()))
316 }
317 _ => None,
318 }
319 }
320 ))
321 }
322 }
323 }
324}
325
326#[derive(Debug, Clone)]
327pub struct ArcPropagator(pub Arc<Propagator>);
328impl Deref for ArcPropagator {
329 type Target = Propagator;
330 fn deref(&self) -> &Self::Target {
331 self.0.deref()
332 }
333}
334
335impl Encode for ArcPropagator {
336 fn encode<E: bincode::enc::Encoder>(
337 &self,
338 encoder: &mut E,
339 ) -> std::result::Result<(), bincode::error::EncodeError> {
340 Encode::encode(&self.0.name.to_string(), encoder)?;
341 Ok(())
342 }
343}
344
345impl<T: HasModel> Decode<T> for ArcPropagator {
346 fn decode<D: bincode::de::Decoder<Context = T>>(
347 decoder: &mut D,
348 ) -> std::result::Result<Self, bincode::error::DecodeError> {
349 let name: String = Decode::decode(decoder)?;
350 let context = decoder.context();
351 let model = context.get_model();
352 let prop = model.get_propagator(&name);
353 Ok(prop)
354 }
355}
356
357#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd, Hash)]
358pub struct ArcParticle(pub Arc<Particle>);
359
360impl Deref for ArcParticle {
361 type Target = Particle;
362 fn deref(&self) -> &Self::Target {
363 self.0.deref()
364 }
365}
366
367#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd, Hash)]
368pub struct ArcVertexRule(pub Arc<VertexRule>);
369impl Deref for ArcVertexRule {
370 type Target = VertexRule;
371 fn deref(&self) -> &Self::Target {
372 self.0.deref()
373 }
374}
375
376impl Encode for ArcParticle {
377 fn encode<E: bincode::enc::Encoder>(
378 &self,
379 encoder: &mut E,
380 ) -> std::result::Result<(), bincode::error::EncodeError> {
381 Encode::encode(&self.0.pdg_code, encoder)?;
382 Ok(())
383 }
384}
385
386impl<T: HasModel> Decode<T> for ArcParticle {
387 fn decode<D: bincode::de::Decoder<Context = T>>(
388 decoder: &mut D,
389 ) -> std::result::Result<Self, bincode::error::DecodeError> {
390 let pdg_code: isize = Decode::decode(decoder)?;
391 let context = decoder.context();
392 let model = context.get_model();
393 let particle = model.get_particle_from_pdg(pdg_code);
394 Ok(particle)
395 }
396}
397
398impl Encode for ArcVertexRule {
399 fn encode<E: bincode::enc::Encoder>(
400 &self,
401 encoder: &mut E,
402 ) -> std::result::Result<(), bincode::error::EncodeError> {
403 Encode::encode(&self.0.name.to_string(), encoder)?;
404 Ok(())
405 }
406}
407
408impl<T: HasModel> Decode<T> for ArcVertexRule {
409 fn decode<D: bincode::de::Decoder<Context = T>>(
410 decoder: &mut D,
411 ) -> std::result::Result<Self, bincode::error::DecodeError> {
412 let vertex_rule_name: String = Decode::decode(decoder)?;
413 let context = decoder.context();
414 let model = context.get_model();
415 let vertex_rule = model.get_vertex_rule(vertex_rule_name);
416 Ok(vertex_rule)
417 }
418}
419
420#[allow(unused)]
421pub(crate) fn normalise_complex(atom: &Atom) -> Atom {
422 let re = parse!("re_");
423 let im = parse!("im_");
424
425 let comp_id = symbol!("complex");
426
427 let complexfn = function!(comp_id, re, im).to_pattern();
428
429 let i = Atom::i();
430 let complexpanded = &re + i * &im;
431
432 atom.replace(&complexfn).with(complexpanded.to_pattern())
433}
434
435#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
436pub enum ParameterNature {
437 #[default]
438 #[serde(rename = "external")]
439 External,
440 #[serde(rename = "internal")]
441 Internal,
442}
443
444#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
445pub enum ParameterType {
446 #[default]
447 #[serde(rename = "real")]
448 Real,
449 #[serde(rename = "complex")]
450 Imaginary,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
454pub struct SerializableVertexRule {
455 pub name: SmartString<LazyCompact>,
456 pub particles: Vec<SmartString<LazyCompact>>,
457 pub color_structures: Vec<SmartString<LazyCompact>>,
458 pub lorentz_structures: Vec<SmartString<LazyCompact>>,
459 pub couplings: Vec<Vec<Option<SmartString<LazyCompact>>>>,
460}
461
462impl SerializableVertexRule {
463 pub(crate) fn from_vertex_rule(vertex_rule: &VertexRule) -> SerializableVertexRule {
464 SerializableVertexRule {
465 name: vertex_rule.name.clone(),
466 particles: vertex_rule
467 .particles
468 .iter()
469 .map(|particle| particle.0.name.clone())
470 .collect(),
471 color_structures: vertex_rule
472 .color_structures
473 .iter()
474 .map(|a| a.to_canonical_string())
475 .map(SmartString::from)
476 .collect(),
477 lorentz_structures: vertex_rule
478 .lorentz_structures
479 .iter()
480 .map(|lorentz_structure| lorentz_structure.name.clone())
481 .collect(),
482 couplings: vertex_rule
483 .couplings
484 .iter()
485 .map(|couplings| {
486 couplings
487 .iter()
488 .map(|coupling| {
489 coupling
490 .as_ref()
491 .map(|cpl| cpl.namespaceless_string().into())
492 })
493 .collect()
494 })
495 .collect(),
496 }
497 }
498}
499
500#[derive(Debug, Clone, PartialEq, Eq)]
501pub struct ColorStructure {
502 pub color_structure: Vec<Atom>,
503}
504
505impl ColorStructure {
506 pub(crate) fn iter(&'_ self) -> std::slice::Iter<'_, Atom> {
507 self.color_structure.iter()
508 }
509}
510
511impl FromIterator<Atom> for ColorStructure {
512 fn from_iter<T: IntoIterator<Item = Atom>>(iter: T) -> Self {
513 ColorStructure {
514 color_structure: iter.into_iter().collect(),
515 }
516 }
517}
518
519#[derive(Debug, Clone)]
520pub struct VertexRule {
521 pub name: SmartString<LazyCompact>,
522 pub particles: Vec<ArcParticle>,
523 pub color_structures: ColorStructure,
524 pub lorentz_structures: Vec<Arc<LorentzStructure>>,
525 pub couplings: Vec<Vec<Option<CouplingName>>>,
526 pub dod: i32,
527}
528
529impl Eq for VertexRule {}
530
531impl PartialEq for VertexRule {
532 fn eq(&self, other: &Self) -> bool {
533 self.name == other.name
534 }
535}
536
537impl PartialOrd for VertexRule {
538 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
539 Some(self.cmp(other))
540 }
541}
542
543impl Ord for VertexRule {
544 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
545 self.name.cmp(&other.name)
546 }
547}
548
549impl std::hash::Hash for VertexRule {
550 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
551 self.name.hash(state);
552 }
553}
554
555impl VertexRule {
556 pub(crate) fn tensors(
557 &self,
558 i: Aind,
559 j: Aind,
560 ) -> [ParamTensor<OrderedStructure<Euclidean, Aind>>; 3] {
561 let spin_structure = self
562 .lorentz_structures
563 .iter()
564 .map(|ls| ls.structure.clone())
565 .collect_vec();
566
567 let color_structure: Vec<Atom> = self.color_structures.color_structure.clone();
568
569 let i = Euclidean {}.new_slot(color_structure.len(), i);
570 let j = Euclidean {}.new_slot(spin_structure.len(), j);
571
572 let color_structure: ParamTensor<OrderedStructure<Euclidean, Aind>> =
573 ParamTensor::composite(DataTensor::Dense(
574 DenseTensor::from_data(
575 color_structure,
576 PermutedStructure::from_iter([i]).structure,
577 )
578 .unwrap(),
579 ));
580
581 let spin_structure: ParamTensor<OrderedStructure<Euclidean, Aind>> =
582 ParamTensor::composite(DataTensor::Dense(
583 DenseTensor::from_data(spin_structure, PermutedStructure::from_iter([j]).structure)
584 .unwrap(),
585 ));
586
587 let mut couplings: ParamTensor<OrderedStructure<Euclidean, Aind>> =
588 ParamTensor::composite(DataTensor::Sparse(SparseTensor::empty(
589 PermutedStructure::from_iter([i, j]).structure,
590 Atom::Zero,
591 )));
592
593 for (i, row) in self.couplings.iter().enumerate() {
594 for (j, col) in row.iter().enumerate() {
595 if let Some(atom) = col {
596 couplings.set(&[i, j], atom.0.into()).unwrap();
597 }
598 }
599 }
600
601 [color_structure, couplings, spin_structure]
602 }
603
604 }
616
617impl VertexRule {
618 pub(crate) fn coupling_orders(
619 &self,
620 model: &Model,
621 ) -> AHashMap<SmartString<LazyCompact>, usize> {
622 let mut node_coupling_orders = AHashMap::default();
623 self.couplings.iter().for_each(|cs| {
624 cs.iter().for_each(|c_opt| {
625 if let Some(c) = c_opt {
626 model.couplings[c]
627 .orders
628 .iter()
629 .for_each(|(coupling_order, &weight)| {
630 let w = node_coupling_orders
631 .entry(coupling_order.clone())
632 .or_insert(weight);
633 if *w < weight {
634 *w = weight;
635 }
636 });
637 }
638 })
639 });
640 node_coupling_orders
641 }
642
643 pub(crate) fn from_serializable_vertex_rule(
644 model: &Model,
645 vertex_rule: &SerializableVertexRule,
646 ) -> VertexRule {
647 let lorentz_structures: Vec<Arc<LorentzStructure>> = vertex_rule
648 .lorentz_structures
649 .iter()
650 .map(|lorentz_structure_name| {
651 model.get_lorentz_structure(lorentz_structure_name).clone()
652 })
653 .collect();
654
655 let dod = lorentz_structures
656 .iter()
657 .map(|a| a.dod())
658 .reduce(|a, b| a.max(b))
659 .unwrap();
660 VertexRule {
661 name: vertex_rule.name.clone(),
662 particles: vertex_rule
663 .particles
664 .iter()
665 .map(|particle_name| model.get_particle(particle_name).clone())
666 .collect(),
667 color_structures: vertex_rule
668 .color_structures
669 .iter()
670 .map(|color_structure_name| {
671 utils::parse_python_expression(color_structure_name.as_str())
672 })
673 .collect(),
674 lorentz_structures: vertex_rule
675 .lorentz_structures
676 .iter()
677 .map(|lorentz_structure_name| {
678 model.get_lorentz_structure(lorentz_structure_name).clone()
679 })
680 .collect(),
681 couplings: vertex_rule
682 .couplings
683 .iter()
684 .map(|coupling_names| {
685 coupling_names
686 .iter()
687 .map(|coupling_name| {
688 coupling_name
689 .as_ref()
690 .map(|cpl_name| CouplingName(UFOSymbol::from(cpl_name)))
691 })
692 .collect()
693 })
694 .collect(),
695 dod,
696 }
697 }
698}
699
700#[derive(Debug, Clone, Serialize, Deserialize)]
701pub struct SerializablePropagator {
702 pub name: SmartString<LazyCompact>,
703 pub particle: SmartString<LazyCompact>,
704 pub numerator: SmartString<LazyCompact>,
705 pub denominator: SmartString<LazyCompact>,
706}
707
708impl SerializablePropagator {
709 pub(crate) fn from_propagator(propagator: &Propagator) -> SerializablePropagator {
710 SerializablePropagator {
711 name: propagator.name.clone(),
712 particle: propagator.particle.0.name.clone(),
713 numerator: propagator.numerator.to_canonical_string().into(),
714 denominator: propagator.denominator.to_canonical_string().into(),
715 }
716 }
717}
718
719#[derive(Debug, Clone, Encode)]
720pub struct Propagator {
721 #[bincode(with_serde)]
722 pub name: SmartString<LazyCompact>,
723 pub particle: ArcParticle,
724 pub numerator: Atom,
725 pub denominator: Atom,
726 pub dod: i32,
727}
728
729impl Propagator {
730 pub(crate) fn from_serializable_propagator(
731 model: &Model,
732 propagator: &SerializablePropagator,
733 ) -> Propagator {
734 let numerator = utils::parse_python_expression(propagator.numerator.as_str());
735 let denominator = utils::parse_python_expression(propagator.denominator.as_str());
736 let dod = (&numerator / &denominator).all_dod();
737 Propagator {
738 name: propagator.name.clone(),
739 particle: model.get_particle(&propagator.particle).clone(),
740 numerator,
741 denominator,
742 dod,
743 }
744 }
745}
746
747#[derive(Debug, Clone, Serialize, Deserialize)]
748pub struct SerializableCoupling {
749 name: SmartString<LazyCompact>,
750 expression: SmartString<LazyCompact>,
751 #[serde(with = "vectorize")]
752 orders: BTreeMap<SmartString<LazyCompact>, usize>,
753 value: Option<(f64, f64)>,
754}
755
756impl SerializableCoupling {
757 pub(crate) fn from_coupling(coupling: &Coupling) -> SerializableCoupling {
758 SerializableCoupling {
759 name: coupling.name.namespaceless_string().into(),
760 expression: coupling.expression.to_canonical_string().into(),
761 orders: coupling.orders.clone(),
762 value: coupling.value.map(|value| (value.re, value.im)),
763 }
764 }
765}
766
767#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
768pub struct CouplingName(pub UFOSymbol);
769
770impl Deref for CouplingName {
771 type Target = UFOSymbol;
772 fn deref(&self) -> &Self::Target {
773 &self.0
774 }
775}
776
777#[derive(Debug, Clone)]
778pub struct Coupling {
779 pub name: UFOSymbol,
780 pub expression: Atom,
781 pub orders: BTreeMap<SmartString<LazyCompact>, usize>,
782 pub value: Option<Complex<f64>>,
783}
784
785impl Coupling {
786 pub(crate) fn from_serializable_coupling(coupling: &SerializableCoupling) -> Coupling {
787 Coupling {
788 name: (&coupling.name).into(),
789 expression: utils::parse_python_expression(coupling.expression.as_str()),
790 orders: coupling.orders.clone(),
791 value: coupling.value.map(|value| Complex::new(value.0, value.1)),
792 }
793 }
794
795 pub(crate) fn rep_rule(&self) -> [Atom; 2] {
796 let lhs = self.name.into();
797 let rhs = self.expression.clone();
799
800 [lhs, rhs]
801 }
802}
803
804#[derive(Debug, Clone, Serialize, Deserialize)]
805pub struct SerializableParticle {
806 pdg_code: isize,
807 name: SmartString<LazyCompact>,
808 antiname: SmartString<LazyCompact>,
809 spin: isize,
810 color: isize,
811 mass: SmartString<LazyCompact>,
812 width: SmartString<LazyCompact>,
813 texname: SmartString<LazyCompact>,
814 antitexname: SmartString<LazyCompact>,
815 charge: f64,
816 ghost_number: isize,
817 lepton_number: isize,
818 y_charge: isize,
819 #[serde(default, alias = "goldstoneboson", alias = "GoldstoneBoson")]
820 goldstone: bool,
821}
822
823impl SerializableParticle {
824 pub(crate) fn from_particle(particle: &Particle) -> SerializableParticle {
825 SerializableParticle {
826 pdg_code: particle.pdg_code,
827 name: particle.name.clone(),
828 antiname: particle.antiname.clone(),
829 spin: particle.spin,
830 color: particle.color,
831 mass: particle.mass.namespaceless_string().into(),
832 width: particle.width.namespaceless_string().into(),
833 texname: particle.texname.clone(),
834 antitexname: particle.antitexname.clone(),
835 charge: particle.charge,
836 ghost_number: particle.ghost_number,
837 lepton_number: particle.lepton_number,
838 y_charge: particle.y_charge,
839 goldstone: particle.goldstone,
840 }
841 }
842}
843
844#[derive(Debug, Clone)]
845pub struct Particle {
846 pub pdg_code: isize,
847 pub name: SmartString<LazyCompact>,
848 pub antiname: SmartString<LazyCompact>,
849 pub spin: isize,
850 pub color: isize,
851 pub mass: ParameterName,
852 pub width: ParameterName,
853 pub texname: SmartString<LazyCompact>,
854 pub antitexname: SmartString<LazyCompact>,
855 pub charge: f64,
856 pub ghost_number: isize,
857 pub lepton_number: isize,
858 pub y_charge: isize,
859 pub goldstone: bool,
860}
861
862impl Particle {
863 fn fermion_polarization_sum_rhs(&self, eid: EdgeIndex) -> Atom {
864 assert!(self.is_spinor());
865 let mu: Slot<Minkowski, Aind> = Minkowski {}.new_rep(4).slot(Aind::new_dummy());
866 let mass_sign = if self.is_antiparticle() { -1 } else { 1 };
867
868 GS.emr_mom(eid, mu.to_atom()) * function!(AGS.gamma, W_.a_, W_.b_, mu.to_atom())
869 + Atom::num(mass_sign) * Atom::from(self.mass.0) * function!(ETS.metric, W_.a_, W_.b_)
870 }
871
872 fn polarization_average_factor(&self) -> Result<Atom> {
873 match self.spin {
874 1 => Ok(Atom::one()),
875 2 => Ok(Atom::num(1) / Atom::num(2)),
876 3 => Ok(if self.is_massive() {
877 Atom::num(1) / Atom::num(3)
878 } else {
879 Atom::num(1) / Atom::num(2)
880 }),
881 4 => Ok(Atom::num(1) / Atom::num(4)),
882 5 => Ok(if self.is_massive() {
883 Atom::num(1) / Atom::num(5)
884 } else {
885 Atom::num(1) / Atom::num(4)
886 }),
887 spin => Err(eyre!(
888 "Polarization averaging for particle '{}' (PDG {}, spin {}) is not supported yet.",
889 self.name,
890 self.pdg_code,
891 spin
892 )),
893 }
894 }
895
896 fn vector_polarization_sum_rhs(
897 &self,
898 eid: EdgeIndex,
899 gauge: VectorPolarizationSumGauge,
900 ) -> Atom {
901 assert!(self.is_vector());
902
903 let minus_metric = -function!(ETS.metric, W_.a_, W_.b_);
904
905 match gauge {
906 VectorPolarizationSumGauge::Feynman => minus_metric,
907 VectorPolarizationSumGauge::LightLikeAxial => {
908 if self.is_massive() {
909 let mass_squared = Atom::from(self.mass.0).pow(2);
910
911 minus_metric + GS.emr_mom(eid, W_.a_) * GS.emr_mom(eid, W_.b_) / mass_squared
912 } else {
913 let temporal_component = GS.emr_mom(eid, GS.cind(0));
914 let n_a = temporal_component.clone() * GS.energy_delta(W_.a_)
915 - GS.emr_vec_index(eid, W_.a_);
916 let n_b = temporal_component.clone() * GS.energy_delta(W_.b_)
917 - GS.emr_vec_index(eid, W_.b_);
918 let q_dot_n = temporal_component.pow(2)
919 + Euclidean {}
920 .new_rep(4)
921 .inner_product(GS.emr_vec(eid), GS.emr_vec(eid));
922
923 minus_metric
924 + (GS.emr_mom(eid, W_.a_) * n_b + n_a * GS.emr_mom(eid, W_.b_)) / q_dot_n
925 }
926 }
927 }
928 }
929
930 pub(crate) fn polarization_sum(
931 &self,
932 eid: EdgeIndex,
933 average: bool,
934 vector_polarization_sum_gauge: VectorPolarizationSumGauge,
935 ) -> Result<Option<Replacement>> {
936 let average_factor = if average {
937 self.polarization_average_factor()?
938 } else {
939 Atom::one()
940 };
941 Ok(match self.spin {
942 1 => None,
943 2 => Some(if !self.is_antiparticle() {
944 (function!(GS.u, eid.0, W_.a_) * function!(GS.ubar, eid.0, W_.b_))
945 .replace_with(self.fermion_polarization_sum_rhs(eid) * average_factor)
946 } else {
947 (function!(GS.v, eid.0, W_.a_) * function!(GS.vbar, eid.0, W_.b_))
948 .replace_with(self.fermion_polarization_sum_rhs(eid) * average_factor)
949 }),
950 3 => Some(
951 (function!(GS.epsilon, eid.0, W_.a_) * function!(GS.epsilonbar, eid.0, W_.b_))
952 .replace_with(
953 self.vector_polarization_sum_rhs(eid, vector_polarization_sum_gauge)
954 * average_factor,
955 ),
956 ),
957 spin => {
958 return Err(eyre!(
959 "Polarization sum replacement for particle '{}' (PDG {}, spin {}) is not implemented yet.",
960 self.name,
961 self.pdg_code,
962 spin
963 ));
964 }
965 })
966 }
967 pub(crate) fn random_helicity(&self, seed: u64) -> Helicity {
968 let mut rng = SmallRng::seed_from_u64(seed);
969 if self.is_spinor() {
970 if rng.random_bool(0.5) {
971 Helicity::PLUS
972 } else {
973 Helicity::MINUS
974 }
975 } else if self.is_vector() {
976 Helicity::try_from(rng.random_range(1..=1)).unwrap()
977 } else {
978 Helicity::ZERO
979 }
980 }
981
982 pub(crate) fn is_massless(&self) -> bool {
983 !self.is_massive()
984 }
985
986 pub(crate) fn resolved_mass_value(&self, model: &Model) -> Result<Complex<F<f64>>> {
987 model
988 .parameters
989 .get(&self.mass)
990 .and_then(|parameter| parameter.value)
991 .ok_or_else(|| {
992 eyre!(
993 "Particle '{}' (PDG {}) requires a resolved value for mass parameter '{}' in model '{}'.",
994 self.name,
995 self.pdg_code,
996 self.mass.0,
997 model.name
998 )
999 })
1000 }
1001
1002 pub(crate) fn has_zero_resolved_mass(&self, model: &Model) -> Result<bool> {
1003 let mass_value = self.resolved_mass_value(model)?;
1004 Ok(mass_value.re == mass_value.re.zero() && mass_value.im == mass_value.im.zero())
1005 }
1006
1007 pub(crate) fn is_qcd_charged(&self) -> bool {
1008 self.color != 1
1009 }
1010 pub fn is_fermion(&self) -> bool {
1027 self.spin % 2 == 0
1028 }
1029
1030 pub fn is_vector(&self) -> bool {
1031 self.spin == 3
1032 }
1033
1034 pub fn is_tensor(&self) -> bool {
1035 self.spin == 5
1036 }
1037
1038 pub fn is_scalar(&self) -> bool {
1039 self.spin == 1
1040 }
1041
1042 pub fn is_spinor(&self) -> bool {
1043 self.spin == 2
1044 }
1045
1046 pub fn is_ghost(&self) -> bool {
1047 self.ghost_number != 0
1048 }
1049
1050 pub fn is_goldstone(&self) -> bool {
1051 self.goldstone
1052 }
1053
1054 pub fn symbolic_mass(&self) -> Atom {
1055 self.mass.0.into()
1056 }
1057}
1058
1059impl Ord for Particle {
1060 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1061 self.pdg_code.cmp(&other.pdg_code)
1062 }
1063}
1064
1065impl PartialOrd for Particle {
1066 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1067 Some(self.cmp(other))
1068 }
1069}
1070
1071impl std::hash::Hash for Particle {
1072 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1073 self.pdg_code.hash(state);
1074 }
1075}
1076impl Eq for Particle {}
1077impl PartialEq for Particle {
1078 fn eq(&self, other: &Self) -> bool {
1079 if self.pdg_code == other.pdg_code {
1080 true
1148 } else {
1149 false
1150 }
1151 }
1152}
1153
1154#[derive(Debug, Clone, Serialize, Deserialize)]
1155pub struct InOutIndex {
1156 incoming: Slot<LibraryRep>,
1157 outgoing: Slot<LibraryRep>,
1158}
1159
1160#[derive(Debug, Clone, bincode_trait_derive::Encode, bincode_trait_derive::Decode)]
1161#[trait_decode(trait = symbolica::state::HasStateMap)]
1162pub struct EdgeSlots<LorRep: RepName> {
1163 pub lorentz: Vec<Slot<LorRep>>,
1164 spin: Vec<Slot<Bispinor>>,
1165 pub color: Vec<Slot<LibraryRep>>,
1166}
1167
1168impl From<EdgeSlots<Minkowski>> for OrderedStructure {
1169 fn from(value: EdgeSlots<Minkowski>) -> Self {
1170 PermutedStructure::<OrderedStructure>::from(
1171 value
1172 .lorentz
1173 .into_iter()
1174 .map(|x| x.to_lib())
1175 .chain(value.spin.into_iter().map(|x| x.to_lib()))
1176 .chain(value.color)
1177 .collect_vec(),
1178 )
1179 .structure
1180 }
1181}
1182
1183impl<LorRep: BaseRepName> Display for EdgeSlots<LorRep> {
1184 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1185 write!(f, "Lorentz: ")?;
1186 for l in &self.lorentz {
1187 write!(f, "{} ", l)?;
1188 }
1189 write!(f, "Spin: ")?;
1190 for s in &self.spin {
1191 write!(f, "{} ", s)?;
1192 }
1193 write!(f, "Color: ")?;
1194 for c in &self.color {
1195 write!(f, "{} ", c)?;
1196 }
1197 Ok(())
1198 }
1199}
1200
1201impl From<EdgeSlots<Lorentz>> for OrderedStructure {
1202 fn from(value: EdgeSlots<Lorentz>) -> Self {
1203 PermutedStructure::<OrderedStructure>::from(
1204 value
1205 .lorentz
1206 .into_iter()
1207 .map(|x| x.to_lib())
1208 .chain(value.spin.into_iter().map(|a| a.to_lib()))
1209 .chain(value.color)
1210 .collect_vec(),
1211 )
1212 .structure
1213 }
1214}
1215
1216impl Particle {
1217 pub fn is_antiparticle(&self) -> bool {
1218 self.pdg_code < 0
1219 }
1220
1221 pub(crate) fn get_anti_particle(&self, model: &Model) -> ArcParticle {
1222 model.get_particle(&self.antiname)
1223 }
1224
1225 pub fn is_self_antiparticle(&self) -> bool {
1226 self.name == self.antiname
1227 }
1228
1229 pub(crate) fn spin_reps(&self) -> IndexLess<LibraryRep, Aind> {
1230 PermutedStructure::<IndexLess<LibraryRep, Aind>>::from_iter(match self.spin {
1231 -1..=1 => vec![],
1232 a => {
1233 if a > 0 {
1234 if a % 2 == 0 {
1235 vec![Bispinor {}.new_rep(4).cast(); a.div_euclid(2) as usize]
1236 } else {
1237 vec![Minkowski {}.new_rep(4).cast(); a.div_euclid(2) as usize]
1238 }
1239 } else {
1240 vec![]
1241 }
1242 }
1243 })
1244 .structure
1245 }
1246
1247 pub(crate) fn is_massive(&self) -> bool {
1248 !self.mass.is_zero()
1249 }
1250
1251 pub fn generate_edge_typst_dict(&self) -> String {
1253 let label = format!("mi(`{}`)", self.texname);
1254 let thickness = if self.is_massive() {
1256 "massive"
1257 } else {
1258 "massless"
1259 };
1260
1261 let color = if self.charge.abs() > 0.0 {
1263 "blue"
1264 } else {
1265 "black"
1266 };
1267
1268 let base_source = format!("source-stroke(c: {}, thickness: {})", color, thickness);
1270 let base_sink = format!("sink-stroke(c: {}, thickness: {})", color, thickness);
1271
1272 let (source, sink) = if self.is_ghost() {
1273 (
1274 format!("source-stroke(c: {color}, thickness: {thickness}, dash: dotted)",),
1275 format!("sink-stroke(c: {color}, thickness: {thickness}, dash: dotted)"),
1276 )
1277 } else if self.is_fermion() {
1278 (base_source, base_sink)
1279 } else if self.is_vector() {
1280 if self.charge == 0.0 && self.color == 1 {
1282 (
1284 format!("{} + wave", base_source),
1285 format!("{} + wave", base_sink),
1286 )
1287 } else if self.charge == 0.0 && self.color == 8 {
1288 (
1290 format!("{} + coil", base_source),
1291 format!("{} + coil", base_sink),
1292 )
1293 } else {
1294 (
1296 format!("{} + zigzag", base_source),
1297 format!("{} + zigzag", base_sink),
1298 )
1299 }
1300 } else if self.is_scalar() {
1301 (
1303 format!("source-stroke(c: {color}, thickness: {thickness}, dash: dashed)",),
1304 format!("sink-stroke(c: {color}, thickness: {thickness}, dash: dashed)"),
1305 )
1306 } else {
1307 (base_source, base_sink)
1309 };
1310
1311 let flow_marker = if self.is_fermion() && !self.is_ghost() {
1312 " + fermion-flow"
1313 } else {
1314 ""
1315 };
1316
1317 format!("(source:{source}, sink:{sink}, label:{label}){flow_marker}")
1318 }
1319
1320 pub(crate) fn color_reps(&self, flow: Flow) -> IndexLess {
1321 let reps = match flow {
1322 Flow::Source => match self.color {
1323 3 => vec![ColorFundamental {}.new_rep(3).cast()],
1324
1325 -3 => vec![ColorFundamental {}.dual().new_rep(3).cast()],
1326 6 => vec![ColorSextet {}.new_rep(6).cast()],
1327 -6 => vec![ColorSextet {}.dual().new_rep(6).cast()],
1328 8 => vec![ColorAdjoint {}.new_rep(8).cast()],
1329 _ => vec![],
1330 },
1331 Flow::Sink => match self.color {
1332 -3 => vec![ColorFundamental {}.new_rep(3).cast()],
1333 3 => vec![ColorFundamental {}.dual().new_rep(3).cast()],
1334 -6 => vec![ColorSextet {}.new_rep(6).cast()],
1335 6 => vec![ColorSextet {}.dual().new_rep(6).cast()],
1336 8 => vec![ColorAdjoint {}.new_rep(8).cast()],
1337 _ => vec![],
1338 },
1339 };
1340 PermutedStructure::<IndexLess>::from_iter(reps).structure
1341 }
1342
1343 pub(crate) fn from_serializable_particle(particle: &SerializableParticle) -> Particle {
1344 Particle {
1345 pdg_code: particle.pdg_code,
1346 name: particle.name.clone(),
1347 antiname: particle.antiname.clone(),
1348 spin: particle.spin,
1349 color: particle.color,
1350 mass: ParameterName((&particle.mass).into()),
1351 width: ParameterName((&particle.width).into()),
1352 texname: particle.texname.clone(),
1353 antitexname: particle.antitexname.clone(),
1354 charge: particle.charge,
1355 ghost_number: particle.ghost_number,
1356 lepton_number: particle.lepton_number,
1357 y_charge: particle.y_charge,
1358 goldstone: particle.goldstone,
1359 }
1360 }
1361}
1362
1363#[derive(Debug, Clone, Serialize, Deserialize)]
1364pub struct SerializableLorentzStructure {
1365 name: SmartString<LazyCompact>,
1366 spins: Vec<isize>,
1367 structure: SmartString<LazyCompact>,
1368}
1369
1370impl SerializableLorentzStructure {
1371 pub(crate) fn from_lorentz_structure(ls: &LorentzStructure) -> SerializableLorentzStructure {
1372 SerializableLorentzStructure {
1373 name: ls.name.clone(),
1374 spins: ls.spins.clone(),
1375 structure: ls.structure.to_canonical_string().into(),
1376 }
1377 }
1378}
1379
1380#[derive(Debug, Clone, PartialEq, Eq)]
1381pub struct LorentzStructure {
1382 pub name: SmartString<LazyCompact>,
1383 pub spins: Vec<isize>,
1384 pub structure: Atom,
1385}
1386
1387impl LorentzStructure {
1388 pub(crate) fn from_serializable_lorentz_structure(
1389 ls: &SerializableLorentzStructure,
1390 ) -> LorentzStructure {
1391 LorentzStructure {
1392 name: ls.name.clone(),
1393 spins: ls.spins.clone(),
1394 structure: utils::parse_python_expression(ls.structure.as_str()),
1395 }
1396 }
1397
1398 pub(crate) fn dod(&self) -> i32 {
1399 self.structure.all_dod()
1400 }
1401}
1402
1403#[derive(Debug, Clone, Serialize, Deserialize)]
1404pub struct SerializableParameter {
1405 name: SmartString<LazyCompact>,
1406 lhablock: Option<SmartString<LazyCompact>>,
1407 lhacode: Option<Vec<usize>>,
1408 nature: ParameterNature,
1409 parameter_type: ParameterType,
1410 value: Option<(F<f64>, F<f64>)>,
1411 expression: Option<SmartString<LazyCompact>>,
1412}
1413
1414impl SerializableParameter {
1415 pub(crate) fn from_parameter(param: &Parameter) -> SerializableParameter {
1416 SerializableParameter {
1417 name: param.name.namespaceless_string().into(),
1418 lhablock: param.lhablock.clone(),
1419 lhacode: param.lhacode.clone(),
1420 nature: param.nature.clone(),
1421 parameter_type: param.parameter_type.clone(),
1422 value: param.value.map(|value| (value.re, value.im)),
1423 expression: param
1424 .expression
1425 .as_ref()
1426 .map(|a| a.to_canonical_string())
1427 .map(SmartString::from),
1428 }
1429 }
1430}
1431
1432#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1433pub struct ParameterName(pub UFOSymbol);
1434
1435impl Deref for ParameterName {
1436 type Target = UFOSymbol;
1437
1438 fn deref(&self) -> &Self::Target {
1439 &self.0
1440 }
1441}
1442
1443#[derive(Debug, Clone)]
1444pub struct Parameter {
1445 pub name: UFOSymbol,
1446 pub lhablock: Option<SmartString<LazyCompact>>,
1447 pub lhacode: Option<Vec<usize>>,
1448 pub nature: ParameterNature,
1449 pub parameter_type: ParameterType,
1450 pub value: Option<Complex<F<f64>>>,
1451 pub expression: Option<Atom>,
1452}
1453
1454impl std::fmt::Display for Parameter {
1455 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1456 write!(f, "{}", self.name)
1457 }
1458}
1459
1460fn parameter_display_sort_key(parameter: &Parameter) -> (u8, String) {
1461 let nature_rank = match parameter.nature {
1462 ParameterNature::External => 0,
1463 ParameterNature::Internal => 1,
1464 };
1465 (nature_rank, parameter.name.to_string())
1466}
1467
1468impl PartialEq for Parameter {
1469 fn eq(&self, other: &Self) -> bool {
1470 self.name == other.name
1471 && self.nature == other.nature
1472 && self.parameter_type == other.parameter_type
1473 && self.expression == other.expression
1475 && self.lhablock == other.lhablock
1476 && self.lhacode == other.lhacode
1477 }
1478}
1479
1480impl Eq for Parameter {}
1481
1482impl Parameter {
1483 pub(crate) fn from_serializable_parameter(param: &SerializableParameter) -> Parameter {
1484 Parameter {
1485 name: (¶m.name).into(),
1486 lhablock: param.lhablock.clone(),
1487 lhacode: param.lhacode.clone(),
1488 nature: param.nature.clone(),
1489 parameter_type: param.parameter_type.clone(),
1490 value: param.value.map(|value| Complex::new(value.0, value.1)),
1491 expression: param
1492 .expression
1493 .as_ref()
1494 .map(|expr| utils::parse_python_expression(expr.as_str())),
1495 }
1496 }
1497
1498 pub(crate) fn rep_rule(&self) -> Option<[Atom; 2]> {
1499 let lhs = self.name.into();
1500 let rhs = self.expression.clone();
1501
1502 Some([lhs, rhs?])
1504 }
1505}
1506#[derive(Debug, Clone, Serialize, Deserialize)]
1507pub struct Order {
1508 pub name: SmartString<LazyCompact>,
1509 pub expansion_order: isize,
1510 pub hierarchy: isize,
1511}
1512
1513#[derive(Debug, Clone, Serialize, Deserialize)]
1514pub struct SerializableModel {
1515 pub name: SmartString<LazyCompact>,
1516 pub restriction: Option<SmartString<LazyCompact>>,
1517 orders: Vec<Order>,
1518 parameters: Vec<SerializableParameter>,
1519 particles: Vec<SerializableParticle>,
1520 propagators: Vec<SerializablePropagator>,
1521 lorentz_structures: Vec<SerializableLorentzStructure>,
1522 couplings: Vec<SerializableCoupling>,
1523 vertex_rules: Vec<SerializableVertexRule>,
1524}
1525
1526impl SerializableModel {
1527 pub(crate) fn from_file(file_path: impl AsRef<Path>) -> Result<SerializableModel, Report> {
1528 SmartSerde::from_file(file_path, "model")
1529 }
1530 pub(crate) fn from_str(s: String, format: &str) -> Result<SerializableModel, Report> {
1531 SmartSerde::from_str(s, format, "model")
1532 }
1533
1534 pub(crate) fn from_model(model: &Model) -> SerializableModel {
1535 SerializableModel {
1536 name: model.name.clone(),
1537 restriction: model.restriction.clone(),
1538 orders: model
1539 .orders
1540 .iter()
1541 .map(|order| order.as_ref().clone())
1542 .collect(),
1543 parameters: model
1544 .parameters
1545 .values()
1546 .map(SerializableParameter::from_parameter)
1547 .collect(),
1548 particles: model
1549 .particles
1550 .iter()
1551 .map(|particle| SerializableParticle::from_particle(particle.0.as_ref()))
1552 .collect(),
1553 propagators: model
1554 .propagators
1555 .iter()
1556 .map(|propagator| SerializablePropagator::from_propagator(propagator.as_ref()))
1557 .collect(),
1558 lorentz_structures: model
1559 .lorentz_structures
1560 .iter()
1561 .map(|lorentz_structure| {
1562 SerializableLorentzStructure::from_lorentz_structure(lorentz_structure.as_ref())
1563 })
1564 .collect(),
1565 couplings: model
1566 .couplings
1567 .values()
1568 .map(SerializableCoupling::from_coupling)
1569 .collect(),
1570 vertex_rules: model
1571 .vertex_rules
1572 .iter()
1573 .map(|vertex_rule| SerializableVertexRule::from_vertex_rule(vertex_rule.0.as_ref()))
1574 .collect(),
1575 }
1576 }
1577}
1578
1579#[derive(Debug, Clone)]
1580pub struct Model {
1581 pub name: SmartString<LazyCompact>,
1582 pub restriction: Option<SmartString<LazyCompact>>,
1583 pub orders: Vec<Arc<Order>>,
1584 pub parameters: BTreeMap<ParameterName, Parameter>,
1585 pub particles: Vec<ArcParticle>,
1586 pub propagators: Vec<Arc<Propagator>>,
1587 pub lorentz_structures: Vec<Arc<LorentzStructure>>,
1588 pub couplings: BTreeMap<CouplingName, Coupling>,
1589 pub vertex_rules: Vec<ArcVertexRule>,
1590 pub unresolved_particles: HashMap<SmartString<LazyCompact>, HashSet<ArcParticle>>,
1591 pub particle_set_to_vertex_rules_map: HashMap<Vec<ArcParticle>, Vec<ArcVertexRule>>,
1592 pub order_name_to_position: HashMap<SmartString<LazyCompact>, usize, RandomState>,
1593 pub lorentz_structure_name_to_position: HashMap<SmartString<LazyCompact>, usize, RandomState>,
1594 pub particle_name_to_position: HashMap<SmartString<LazyCompact>, usize, RandomState>,
1595 pub particle_pdg_to_position: HashMap<isize, usize, RandomState>,
1596 pub propagator_name_to_position: HashMap<SmartString<LazyCompact>, usize, RandomState>,
1597 pub vertex_rule_name_to_position: HashMap<SmartString<LazyCompact>, usize, RandomState>,
1598 pub particle_name_to_propagator_position: HashMap<SmartString<LazyCompact>, usize, RandomState>,
1599}
1600
1601impl Default for Model {
1602 fn default() -> Self {
1603 Model {
1604 name: SmartString::<LazyCompact>::from("ModelNotLoaded"),
1605 restriction: None,
1606 orders: vec![],
1607 parameters: BTreeMap::new(),
1608 particles: vec![],
1609 propagators: vec![],
1610 lorentz_structures: vec![],
1611 couplings: BTreeMap::new(),
1612 vertex_rules: vec![],
1613 order_name_to_position:
1614 HashMap::<SmartString<LazyCompact>, usize, RandomState>::default(),
1615 lorentz_structure_name_to_position: HashMap::<
1616 SmartString<LazyCompact>,
1617 usize,
1618 RandomState,
1619 >::default(),
1620 unresolved_particles: HashMap::new(),
1621 particle_set_to_vertex_rules_map: HashMap::new(),
1622 particle_name_to_position:
1623 HashMap::<SmartString<LazyCompact>, usize, RandomState>::default(),
1624 particle_pdg_to_position: HashMap::<isize, usize, RandomState>::default(),
1625 propagator_name_to_position:
1626 HashMap::<SmartString<LazyCompact>, usize, RandomState>::default(),
1627 vertex_rule_name_to_position:
1628 HashMap::<SmartString<LazyCompact>, usize, RandomState>::default(),
1629 particle_name_to_propagator_position: HashMap::<
1630 SmartString<LazyCompact>,
1631 usize,
1632 RandomState,
1633 >::default(),
1634 }
1635 }
1636}
1637impl Model {
1638 pub fn apply_param_card(
1639 &mut self,
1640 input_param_card: &InputParamCard<F<f64>>,
1641 ) -> Result<(), Report> {
1642 input_param_card.apply_to_model(self)?;
1643 Ok(())
1644 }
1645
1646 pub fn get_description(
1647 &self,
1648 show_particles: bool,
1649 show_parameters: bool,
1650 show_vertices: bool,
1651 show_couplings: bool,
1652 ) -> String {
1653 let name = self.name.clone().green().to_string();
1654 let restriction = match &self.restriction {
1655 Some(r) => r.clone().blue().to_string(),
1656 None => "None".into(),
1657 };
1658 let coupling_orders_value = if self.orders.is_empty() {
1659 "None".into()
1660 } else {
1661 self.orders
1662 .iter()
1663 .map(|order| {
1664 format!(
1665 "{} (expansion order: {}, hierarchy: {})",
1666 order.name.green(),
1667 order.expansion_order,
1668 order.hierarchy
1669 )
1670 })
1671 .collect::<Vec<_>>()
1672 .join(", ")
1673 };
1674
1675 let particle_list = if !show_particles {
1676 "[ hiddem ]".blue().to_string()
1677 } else {
1678 let mut particle_table = Builder::new();
1679
1680 particle_table.push_record([
1681 "Name".green().to_string(),
1682 "PDG code".normal().to_string(),
1683 "Mass name".blue().to_string(),
1684 "Mass value".normal().to_string(),
1685 "Width name".blue().to_string(),
1686 "Width value".normal().to_string(),
1687 ]);
1688 for p in &self.particles {
1689 let mass_value = if let Some(value) = self.get_parameter(p.mass.0.to_string()).value
1690 {
1691 format!("{:.6}", value.re)
1692 } else {
1693 "None".into()
1694 };
1695 let width_value =
1696 if let Some(value) = self.get_parameter(p.width.0.to_string()).value {
1697 format!("{:.6}", value.re)
1698 } else {
1699 "None".into()
1700 };
1701
1702 particle_table.push_record(&[
1703 p.name.to_string().green().to_string(),
1704 format!("{:+}", p.pdg_code).normal().to_string(),
1705 p.mass.0.to_string().blue().to_string(),
1706 mass_value.normal().to_string(),
1707 p.width.0.to_string().blue().to_string(),
1708 width_value.normal().to_string(),
1709 ]);
1710 }
1711 let mut particle_table_built = particle_table.build();
1712
1713 format!("\n{}\n", particle_table_built.with(Style::rounded()))
1714 };
1715
1716 let parameter_list = if !show_parameters {
1717 "[ hidden ]".blue().to_string()
1718 } else {
1719 let mut parameter_table = Builder::new();
1720 parameter_table.push_record([
1721 "Name".green().to_string(),
1722 "Nature".normal().to_string(),
1723 "Type".normal().to_string(),
1724 "Value".normal().to_string(),
1725 "Expression".to_string(),
1726 ]);
1727 let mut parameters = self.parameters.values().collect_vec();
1728 parameters.sort_by_key(|parameter| parameter_display_sort_key(parameter));
1729
1730 for param in parameters {
1731 parameter_table.push_record(&[
1732 param.name.to_string().green().to_string(),
1733 if param.nature == ParameterNature::External {
1734 format!("{:?}", param.nature).green().to_string()
1735 } else {
1736 format!("{:?}", param.nature).yellow().to_string()
1737 },
1738 if param.parameter_type == ParameterType::Real {
1739 format!("{:?}", param.parameter_type).green().to_string()
1740 } else {
1741 format!("{:?}", param.parameter_type).yellow().to_string()
1742 },
1743 if let Some(value) = param.value {
1744 format!("{:.6}", value.re)
1745 } else {
1746 "".into()
1747 },
1748 if let Some(expr) = ¶m.expression {
1749 expr.to_string()
1750 } else {
1751 "".into()
1752 },
1753 ]);
1754 }
1755 format!("\n{}\n", parameter_table.build().with(Style::rounded()))
1756 };
1757
1758 let vertex_list = if !show_vertices {
1759 "[ hidden ]".blue().to_string()
1760 } else {
1761 let mut vertex_table = Builder::new();
1762
1763 let max_n_particles = self
1764 .vertex_rules
1765 .iter()
1766 .map(|vr| vr.particles.len())
1767 .max()
1768 .unwrap_or(3);
1769 let mut header = vec!["Name".green().to_string(), "Particles".blue().to_string()];
1770 for _ in 0..max_n_particles - 1 {
1771 header.push("".to_string());
1772 }
1773 vertex_table.push_record(header);
1774 for vr in &self.vertex_rules {
1785 let mut record = vec![vr.name.to_string().green().to_string()];
1786 for p in &vr.particles {
1787 record.push(p.name.to_string().blue().to_string());
1788 }
1789 while record.len() < max_n_particles + 1 {
1791 record.push("".to_string());
1792 }
1793 vertex_table.push_record(&record);
1794 }
1795 let mut vertex_table_built = vertex_table.build();
1796
1797 vertex_table_built
1798 .with(
1799 Style::rounded()
1800 .remove_vertical()
1801 .verticals([(1, VerticalLine::inherit(Style::rounded()))]),
1803 )
1804 .with(Modify::new(Cell::new(0, 1)).with(Span::column(max_n_particles as isize)));
1805 format!("\n{}\n", vertex_table_built)
1806 };
1807
1808 let coupling_list = if !show_couplings {
1809 "[ hidden ]".blue().to_string()
1810 } else {
1811 let mut coupling_table = Builder::new();
1812 coupling_table.push_record([
1813 "Name".green().to_string(),
1814 "Orders".blue().to_string(),
1815 "Expression".normal().to_string(),
1816 ]);
1817 for c in self.couplings.values() {
1818 coupling_table.push_record(&[
1819 c.name.to_string().green().to_string(),
1820 c.orders
1821 .iter()
1822 .map(|(k, v)| format!("{}={}", k.blue(), v))
1823 .collect::<Vec<_>>()
1824 .join(" "),
1825 c.expression.to_string(),
1826 ]);
1827 }
1828 format!("\n{}\n", coupling_table.build().with(Style::rounded()),)
1829 };
1830
1831 #[rustfmt::skip]
1832 return format!("
1833{model_name_label:<30}: {name}
1834{restriction_label:<30}: {restriction}
1835{coupling_orders_label:<30}: {coupling_orders_value}
1836
1837{n_particles} particles : {particle_list}
1838{n_parameters} parameters :{parameter_list}
1839{n_vertices} vertices : {vertex_list}
1840{n_couplings} couplings : {coupling_list}
1841",
1842model_name_label = "Model name",
1843restriction_label = "Restriction",
1844coupling_orders_label = "Coupling orders",
1845n_particles = format!("{}", self.particles.len()).green(),
1846n_parameters = format!("{}", self.parameters.len()).green(),
1847n_vertices = format!("{}", self.vertex_rules.len()).green(),
1848n_couplings = format!("{}", self.couplings.len()).green(),
1849);
1850 }
1851
1852 pub fn generate_edge_style_template(
1854 &self,
1855 template_path: impl AsRef<std::path::Path>,
1856 ) -> Result<(), std::io::Error> {
1857 use std::fs;
1858
1859 if let Some(parent) = template_path.as_ref().parent() {
1861 fs::create_dir_all(parent)?;
1862 }
1863
1864 let mut edge_style_content = String::new();
1865 edge_style_content.push_str(
1866 r#"#import "crates/linnest/typst/src/physics-edge-style.typ": mi, massive, massless, dashed, dotted, stroke-style, source-stroke, sink-stroke, fermion-flow, wave, coil, zigzag, default-edge, style
1867
1868// Auto-generated particle styles from model (computed in Rust). The reusable
1869// physics drawing callbacks live in physics-edge-style.typ; this file only
1870// supplies the model-specific particle map and GammaLoop-compatible wrappers.
1871#let map = (
1872"#,
1873 );
1874
1875 for particle in self.particles.iter() {
1877 edge_style_content.push_str(&format!(
1878 r#" "{}": {},
1879"#,
1880 particle.name,
1881 particle.generate_edge_typst_dict()
1882 ));
1883 }
1884
1885 edge_style_content.push_str(
1886 r#")
1887
1888#let source-style(edge, typst-fields: "plain", ..options) = {
1889 let callbacks = style(map: map, typst-fields: typst-fields, ..options.named())
1890 (callbacks.source-style)(edge)
1891}
1892
1893#let sink-style(edge, typst-fields: "plain", ..options) = {
1894 let callbacks = style(map: map, typst-fields: typst-fields, ..options.named())
1895 (callbacks.sink-style)(edge)
1896}
1897
1898#let edge-label(edge, typst-fields: "plain", ..options) = {
1899 let callbacks = style(map: map, typst-fields: typst-fields, ..options.named())
1900 (callbacks.edge-label)(edge)
1901}
1902"#,
1903 );
1904
1905 fs::write(&template_path, edge_style_content)?;
1906 info!(
1907 "Generated dynamic edge styles for {} particles",
1908 self.particles.len()
1909 );
1910
1911 Ok(())
1912 }
1913
1914 pub fn simplify(
1915 &mut self,
1916 model_parameters: &mut InputParamCard<F<f64>>,
1917 ) -> Result<(), Report> {
1918 self.apply_param_card(model_parameters)?;
1919 self.recompute_dependents()?;
1920
1921 model_parameters.data = model_parameters
1923 .data
1924 .iter()
1925 .filter(|(_, v)| v.re != F::<f64>::from_f64(0.0) || v.im != F::<f64>::from_f64(0.0))
1926 .map(|(k, v)| (*k, *v))
1927 .collect::<HashMap<UFOSymbol, Complex<F<f64>>>>();
1928
1929 let mut removed_parameters = vec![];
1931 for param in self.parameters.values_mut() {
1932 if param.nature == ParameterNature::External
1933 && let Some(value) = param.value
1934 && value == Complex::new(F(0.0), F(0.0))
1935 {
1936 param.value = Some(Complex::new(F(0.0), F(0.0)));
1937 param.expression = Some(parse!("UFO::ZERO"));
1938 param.nature = ParameterNature::Internal;
1939 removed_parameters.push(param.name);
1940 }
1941 }
1942
1943 if !removed_parameters.is_empty() {
1944 info!(
1945 "The following {} external parameters were forced to zero by the restriction card:\n{}",
1946 format!("{}", removed_parameters.len()).green(),
1947 removed_parameters
1948 .iter()
1949 .map(|p| p.to_string())
1950 .collect::<Vec<_>>()
1951 .join(", ")
1952 .to_string()
1953 .blue(),
1954 );
1955 }
1956
1957 let mut retained_vertex_rules = vec![];
1959 let mut removed_vertex_rules = vec![];
1960 for v in self.vertex_rules.iter() {
1961 let mut new_vr = v.0.as_ref().clone();
1962 for row in new_vr.couplings.iter_mut() {
1963 *row = row
1964 .iter()
1965 .map(|c_opt| {
1966 if let Some(c) = c_opt {
1967 if let Some(cpl) = self.couplings.get(c) {
1968 if let Some(value) = cpl.value {
1969 if value == Complex::new(0.0, 0.0) {
1970 None
1971 } else {
1972 Some(*c)
1973 }
1974 } else {
1975 Some(*c)
1976 }
1977 } else {
1978 Some(*c)
1979 }
1980 } else {
1981 None
1982 }
1983 })
1984 .collect::<Vec<_>>();
1985 }
1986 if new_vr
1987 .couplings
1988 .iter()
1989 .all(|row| row.iter().all(|c| c.is_none()))
1990 {
1991 removed_vertex_rules.push(new_vr);
1992 } else {
1993 retained_vertex_rules.push(new_vr);
1994 }
1995 }
1996
1997 self.vertex_rules = retained_vertex_rules
1998 .into_iter()
1999 .map(|vr| ArcVertexRule(Arc::new(vr)))
2000 .collect::<Vec<_>>();
2001
2002 if !removed_vertex_rules.is_empty() {
2003 info!(
2004 "The following {} vertex rules were removed by the restriction card:\n{}",
2005 format!("{}", removed_vertex_rules.len()).green(),
2006 removed_vertex_rules
2007 .iter()
2008 .map(|v| format!(
2009 "{} -> ({})",
2010 v.name,
2011 v.particles
2012 .iter()
2013 .map(|p| p.name.to_string())
2014 .collect::<Vec<_>>()
2015 .join(", ")
2016 ))
2017 .collect::<Vec<_>>()
2018 .join(" | ")
2019 .to_string()
2020 .blue(),
2021 );
2022 }
2023
2024 let removed_couplings = self
2025 .couplings
2026 .iter()
2027 .filter(|(_, cpl)| cpl.value == Some(Complex::new(0.0, 0.0)))
2028 .map(|(name, _)| *name)
2029 .collect::<Vec<_>>();
2030
2031 self.couplings
2033 .retain(|_, cpl| cpl.value != Some(Complex::new(0.0, 0.0)));
2034
2035 if !removed_couplings.is_empty() {
2036 info!(
2037 "The following {} couplings were removed by the restriction card:\n{}",
2038 format!("{}", removed_couplings.len()).green(),
2039 removed_couplings
2040 .iter()
2041 .map(|c| c.to_string())
2042 .collect::<Vec<_>>()
2043 .join(", ")
2044 .to_string()
2045 .blue(),
2046 );
2047 }
2048
2049 self.update_name_dictionaries();
2050
2051 Ok(())
2052 }
2053
2054 pub fn default_param_card(&self) -> InputParamCard<F<f64>> {
2055 InputParamCard::default_from_model(self)
2056 }
2057
2058 pub fn contains_symbol(&self, symbol: &UFOSymbol) -> bool {
2059 self.couplings.contains_key(&CouplingName(*symbol))
2060 || self.parameters.contains_key(&ParameterName(*symbol))
2061 }
2062 pub fn get_symbol_value(&self, symbol: UFOSymbol) -> Option<Complex<F<f64>>> {
2063 if let Some(cpl) = self.couplings.get(&CouplingName(symbol)) {
2064 return cpl.value.map(|a| a.map(F));
2065 }
2066 if let Some(param) = self.parameters.get(&ParameterName(symbol))
2067 && let Some(value) = param.value
2068 {
2069 return Some(value);
2070 }
2071 None
2072 }
2073
2074 fn parameters_to_empty_fns(&self) -> Vec<Replacement> {
2075 let mut reps = vec![];
2076 for n in self.couplings.keys() {
2077 reps.push(Replacement::new(
2078 Atom::from(n.0).to_pattern(),
2079 function!(n.0.0),
2080 ))
2081 }
2082 for n in self.parameters.keys() {
2083 reps.push(Replacement::new(
2084 Atom::from(n.0).to_pattern(),
2085 function!(n.0.0),
2086 ))
2087 }
2088
2089 reps
2090 }
2091
2092 pub fn recompute_dependents(&mut self) -> Result<()> {
2093 let mut fn_map = FunctionMap::new();
2094 let reps = self.parameters_to_empty_fns();
2095
2096 let mut expr = vec![];
2097 let mut new_values_len = 0;
2098 let mut dependent_parameter_names = vec![];
2099
2100 for (n, c) in &self.couplings {
2101 let key = n.0.0;
2102 expr.push(function!(key));
2103
2104 fn_map
2105 .add_function::<Symbol, Symbol>(
2106 key,
2107 Vec::new(),
2108 c.expression.replace_multiple(&reps),
2109 )
2110 .map_err(|e| eyre!(" {}", e))?;
2111 new_values_len += 1;
2112 }
2113
2114 let mut params = vec![];
2115 let mut param_values = vec![];
2116
2117 for (n, p) in &self.parameters {
2118 let key = function!(n.0.0);
2119 match p.nature {
2120 ParameterNature::External => {
2121 params.push(key);
2122 if let Some(value) = p.value {
2123 param_values.push(value);
2124 } else {
2125 return Err(eyre!("External parameter {} has no value", p.name));
2126 }
2127 }
2128 ParameterNature::Internal => {
2129 if p.name.is_zero() {
2130 continue;
2131 }
2132
2133 new_values_len += 1;
2134 expr.push(key.clone());
2135 dependent_parameter_names.push(*n);
2136 if let Some(body) = p.expression.clone() {
2137 fn_map
2138 .add_function::<Symbol, Symbol>(
2139 n.0.0,
2140 Vec::new(),
2141 body.replace_multiple(&reps),
2142 )
2143 .map_err(|e| eyre!(" {}", e))?;
2144 } else {
2145 let value = p
2146 .value
2147 .ok_or(eyre!("internal param {} has no expression or value", key))?;
2148 let value_rat = symbolica::domains::float::Complex::new(
2149 Fraction::<IntegerRing>::try_from(value.re.0).unwrap(),
2150 Fraction::<IntegerRing>::try_from(value.im.0).unwrap(),
2151 );
2152 fn_map
2153 .add_aliases([(key, Atom::num(value_rat))])
2154 .map_err(|e| eyre!(" {}", e))?;
2155 }
2156 }
2157 }
2158 }
2159
2160 fn_map
2161 .add_aliases([(
2162 Atom::var(Symbol::PI),
2163 Atom::num(symbolica::domains::float::Complex::new(
2164 Rational::try_from(0.0.pi()).unwrap(),
2165 Rational::zero(),
2166 )),
2167 )])
2168 .map_err(|e| eyre!(" {}", e))?;
2169
2170 let evaluator = AtomView::to_eval_tree_multiple(&expr, &fn_map, ¶ms)
2171 .unwrap()
2172 .linearize(
2173 &OptimizationSettings::new()
2174 .cpe_iterations(Some(1))
2175 .verbose(false),
2176 );
2177 let mut evaluator =
2178 evaluator.map_coeff(&|f| Complex::new(F(f.re.to_f64()), F(f.im.to_f64())));
2179
2180 let mut new_values = vec![Complex::new(F(0.0), F(0.0)); new_values_len];
2181 evaluator.evaluate(¶m_values, &mut new_values);
2182
2183 for (i, c) in self.couplings.values_mut().enumerate() {
2184 c.value = Some(new_values[i].map(|f| f.0));
2185 }
2186 for (i, name) in (self.couplings.len()..).zip(dependent_parameter_names) {
2187 if let Some(c) = self.parameters.get_mut(&name) {
2188 c.value = Some(new_values[i]);
2189 }
2190 }
2191
2192 Ok(())
2193 }
2194
2195 pub(crate) fn generate_params(&self) -> Vec<Atom> {
2196 let mut params = vec![];
2197
2198 for cpl in self.couplings.values().filter(|c| c.value.is_some()) {
2199 if cpl.value.is_some() {
2200 params.push(cpl.name.into());
2201 }
2202 }
2203 for param in self.parameters.values().filter(|p| p.value.is_some()) {
2204 if param.value.is_some() {
2205 let name = param.name.into();
2206 params.push(name);
2207 }
2208 }
2209
2210 params
2211 }
2212
2213 pub fn is_empty(&self) -> bool {
2214 self.name == "ModelNotLoaded" || self.particles.is_empty()
2215 }
2216
2217 pub fn apply_coupling_replacement_rules(&self, a: &Atom) -> Atom {
2218 let mut reps = vec![];
2219 for cpl in self.couplings.values() {
2220 let [a, b] = cpl.rep_rule();
2221 reps.push(Replacement::new(a.to_pattern(), b));
2222 }
2223
2224 a.replace_multiple(&reps)
2225 }
2226 pub fn apply_parameter_replacement_rules(&self, a: &Atom) -> Atom {
2227 let mut reps = vec![];
2228 for p in self.parameters.values() {
2229 let Some([a, b]) = p.rep_rule() else {
2230 continue;
2231 };
2232 reps.push(Replacement::new(a.to_pattern(), b));
2233 }
2234
2235 a.replace_multiple(&reps)
2236 }
2237
2238 pub fn export_coupling_replacement_rules(
2239 &self,
2240 export_root: &str,
2241 print_ops: PrintOptions,
2242 ) -> Result<(), Report> {
2243 let path = Path::new(export_root).join("sources").join("model");
2244
2245 if !path.exists() {
2246 fs::create_dir_all(&path)?;
2247 }
2248 let mut reps = Vec::new();
2249
2250 for cpl in self.couplings.values() {
2251 reps.push(cpl.rep_rule().map(|a| {
2252 format!(
2253 "{}",
2254 AtomPrinter::new_with_options(a.as_view(), print_ops.clone())
2255 )
2256 }));
2257 }
2258
2259 for para in self.parameters.values() {
2260 if let Some(rule) = para.rep_rule() {
2261 reps.push(rule.map(|a| {
2262 format!(
2263 "{}",
2264 AtomPrinter::new_with_options(a.as_view(), print_ops.clone())
2265 )
2266 }));
2267 }
2268 }
2269
2270 fs::write(
2271 path.join("model_replacements.json"),
2272 serde_json::to_string_pretty(&reps)?,
2273 )?;
2274
2275 Ok(())
2276 }
2277
2278 fn generate_particle_set_to_vertex_rules_map(&mut self) {
2279 let mut map = HashMap::new();
2280
2281 for vertex in self.vertex_rules.iter() {
2282 let mut vertex_particles = vertex.0.particles.clone();
2283 vertex_particles.sort();
2284 map.entry(vertex_particles)
2285 .and_modify(|l: &mut Vec<ArcVertexRule>| l.push(vertex.clone()))
2286 .or_insert(vec![vertex.clone()]);
2287 }
2288 self.particle_set_to_vertex_rules_map = map;
2289 }
2290
2291 fn generate_unresolved_particles(&mut self) {
2292 let mut map = HashMap::new();
2293
2294 for v in &self.vertex_rules {
2295 let mut set = HashSet::default();
2296 for p in &v.0.particles {
2297 if p.0.is_massless() {
2298 set.insert(p.clone());
2299 }
2300 }
2301 for (k, _) in v.0.coupling_orders(self) {
2302 let current_set = map.entry(k).or_insert(HashSet::<ArcParticle>::default());
2303
2304 set.iter().for_each(|d| {
2305 current_set.insert(d.clone());
2306 });
2307 }
2308 }
2309
2310 self.unresolved_particles = map;
2311 }
2312
2313 pub(crate) fn update_name_dictionaries(&mut self) {
2314 self.order_name_to_position = self
2315 .orders
2316 .iter()
2317 .enumerate()
2318 .map(|(i, o)| (o.name.clone(), i))
2319 .collect();
2320
2321 self.lorentz_structure_name_to_position = self
2322 .lorentz_structures
2323 .iter()
2324 .enumerate()
2325 .map(|(i, ls)| (ls.name.clone(), i))
2326 .collect();
2327
2328 self.particle_name_to_position = self
2329 .particles
2330 .iter()
2331 .enumerate()
2332 .map(|(i, p)| (p.0.name.clone(), i))
2333 .collect();
2334
2335 self.particle_pdg_to_position = self
2336 .particles
2337 .iter()
2338 .enumerate()
2339 .map(|(i, p)| (p.0.pdg_code, i))
2340 .collect();
2341
2342 self.propagator_name_to_position = self
2343 .propagators
2344 .iter()
2345 .enumerate()
2346 .map(|(i, pr)| (pr.name.clone(), i))
2347 .collect();
2348
2349 self.vertex_rule_name_to_position = self
2350 .vertex_rules
2351 .iter()
2352 .enumerate()
2353 .map(|(i, vr)| (vr.0.name.clone(), i))
2354 .collect();
2355 }
2356
2357 pub(crate) fn from_serializable_model(serializable_model: SerializableModel) -> Model {
2358 let _ = *ETS;
2362
2363 let mut model: Model = Model::default();
2364 model.name = serializable_model.name;
2365 model.restriction = serializable_model.restriction;
2366
2367 model.orders = serializable_model
2369 .orders
2370 .iter()
2371 .enumerate()
2372 .map(|(i_order, serializable_order)| {
2373 let order = Arc::new(Order {
2374 name: serializable_order.name.clone(),
2375 expansion_order: serializable_order.expansion_order,
2376 hierarchy: serializable_order.hierarchy,
2377 });
2378 model
2379 .order_name_to_position
2380 .insert(order.name.clone(), i_order);
2381 order
2382 })
2383 .collect();
2384
2385 model.parameters = serializable_model
2387 .parameters
2388 .iter()
2389 .map(|serializable_param| {
2390 let parameter = Parameter::from_serializable_parameter(serializable_param);
2391
2392 (ParameterName(parameter.name), parameter)
2393 })
2394 .collect();
2395
2396 model.particles = serializable_model
2398 .particles
2399 .iter()
2400 .enumerate()
2401 .map(|(i_part, serializable_particle)| {
2402 let particle =
2403 Arc::new(Particle::from_serializable_particle(serializable_particle));
2404 model
2405 .particle_name_to_position
2406 .insert(particle.name.clone(), i_part);
2407 model
2408 .particle_pdg_to_position
2409 .insert(particle.pdg_code, i_part);
2410 ArcParticle(particle)
2411 })
2412 .collect();
2413
2414 model.propagators = serializable_model
2417 .propagators
2418 .iter()
2419 .enumerate()
2420 .map(|(i_prop, serializable_propagator)| {
2421 let propagator = Arc::new(Propagator::from_serializable_propagator(
2422 &model,
2423 serializable_propagator,
2424 ));
2425 model
2426 .propagator_name_to_position
2427 .insert(propagator.name.clone(), i_prop);
2428 propagator
2429 })
2430 .collect();
2431
2432 model.lorentz_structures = serializable_model
2434 .lorentz_structures
2435 .iter()
2436 .enumerate()
2437 .map(|(i_lor, serializable_lorentz_structure)| {
2438 let lorentz_structure =
2439 Arc::new(LorentzStructure::from_serializable_lorentz_structure(
2440 serializable_lorentz_structure,
2441 ));
2442 model
2443 .lorentz_structure_name_to_position
2444 .insert(lorentz_structure.name.clone(), i_lor);
2445 lorentz_structure
2446 })
2447 .collect();
2448
2449 model.couplings = serializable_model
2451 .couplings
2452 .iter()
2453 .map(|serializable_coupling| {
2454 let coupling = Coupling::from_serializable_coupling(serializable_coupling);
2455
2456 (CouplingName(coupling.name), coupling)
2457 })
2458 .collect();
2459
2460 model.vertex_rules = serializable_model
2462 .vertex_rules
2463 .iter()
2464 .enumerate()
2465 .map(|(i_vr, serializable_vertex_rule)| {
2466 let vertex_rule = ArcVertexRule(Arc::new(
2467 VertexRule::from_serializable_vertex_rule(&model, serializable_vertex_rule),
2468 ));
2469 model
2470 .vertex_rule_name_to_position
2471 .insert(vertex_rule.0.name.clone(), i_vr);
2472 vertex_rule
2473 })
2474 .collect();
2475
2476 model.particle_name_to_propagator_position =
2478 serializable_model.propagators.iter().enumerate().fold(
2479 HashMap::<SmartString<LazyCompact>, usize, RandomState>::default(),
2480 |mut map, (i_prop, serializable_propagator)| {
2481 map.insert(serializable_propagator.particle.clone(), i_prop);
2482 map
2483 },
2484 );
2485
2486 model.generate_unresolved_particles();
2487 model.generate_particle_set_to_vertex_rules_map();
2488
2489 model
2490 }
2491
2492 pub fn to_serializable(&self) -> SerializableModel {
2493 SerializableModel::from_model(self)
2494 }
2495
2496 pub fn from_file(file_path: impl AsRef<Path>) -> Result<Model, Report> {
2497 let mut model =
2498 SerializableModel::from_file(file_path).map(Model::from_serializable_model)?;
2499
2500 model.recompute_dependents()?;
2501 Ok(model)
2502 }
2503
2504 pub fn from_str(s: String, format: &str) -> Result<Model, Report> {
2505 let mut model =
2506 SerializableModel::from_str(s, format).map(Model::from_serializable_model)?;
2507
2508 model.recompute_dependents()?;
2509 Ok(model)
2510 }
2511
2512 #[inline]
2513 pub(crate) fn get_propagator_for_particle<S: AsRef<str>>(&self, name: S) -> Arc<Propagator> {
2514 if let Some(position) = self.particle_name_to_propagator_position.get(name.as_ref()) {
2515 self.propagators[*position].clone()
2516 } else {
2517 panic!(
2518 "Propagator for particle '{}' not found in model '{}'. Valid entries are:\n{}",
2519 name.as_ref(),
2520 self.name,
2521 self.particle_name_to_propagator_position.keys().join(", ")
2522 );
2523 }
2524 }
2525
2526 #[inline]
2527 pub fn get_particle<S: AsRef<str>>(&self, name: S) -> ArcParticle {
2528 if let Some(position) = self.particle_name_to_position.get(name.as_ref()) {
2529 self.particles[*position].clone()
2530 } else {
2531 panic!(
2532 "Particle '{}' not found in model '{}'. Valid entries are:\n{}",
2533 name.as_ref(),
2534 self.name,
2535 self.particle_name_to_position.keys().join(", ")
2536 );
2537 }
2538 }
2539
2540 #[inline]
2541 pub fn try_get_particle<S: AsRef<str>>(&self, name: S) -> Result<ArcParticle> {
2542 if let Some(position) = self.particle_name_to_position.get(name.as_ref()) {
2543 Ok(self.particles[*position].clone())
2544 } else {
2545 Err(eyre!(
2546 "Particle '{}' not found in model '{}'. Valid entries are:\n{}",
2547 name.as_ref(),
2548 self.name,
2549 self.particle_name_to_position.keys().join(", ")
2550 ))
2551 }
2552 }
2553 #[inline]
2554 pub(crate) fn get_particle_from_pdg(&self, pdg: isize) -> ArcParticle {
2555 if let Some(position) = self.particle_pdg_to_position.get(&pdg) {
2556 self.particles[*position].clone()
2557 } else {
2558 panic!(
2559 "Particle with PDG {} not found in model '{}'.",
2560 pdg, self.name
2561 );
2562 }
2563 }
2564
2565 #[inline]
2566 pub(crate) fn try_get_particle_from_pdg(&self, pdg: isize) -> Result<ArcParticle> {
2567 if let Some(position) = self.particle_pdg_to_position.get(&pdg) {
2568 Ok(self.particles[*position].clone())
2569 } else {
2570 Err(eyre!(
2571 "Particle with PDG {} not found in model '{}'.",
2572 pdg,
2573 self.name
2574 ))
2575 }
2576 }
2577
2578 #[inline]
2579 pub fn get_propagator<S: AsRef<str>>(&self, name: S) -> ArcPropagator {
2580 if let Some(position) = self.propagator_name_to_position.get(name.as_ref()) {
2581 ArcPropagator(self.propagators[*position].clone())
2582 } else {
2583 panic!(
2584 "Propagator '{}' not found in model '{}'.",
2585 name.as_ref(),
2586 self.name
2587 );
2588 }
2589 }
2590
2591 #[inline]
2592 pub fn get_parameter_opt<S: AsRef<str>>(&self, name: S) -> Option<&Parameter> {
2593 if let Some(position) = self
2594 .parameters
2595 .get(&ParameterName(UFOSymbol::from(name.as_ref())))
2596 {
2597 Some(position)
2598 } else {
2599 None
2600 }
2601 }
2602
2603 #[inline]
2604 pub fn get_parameter<S: AsRef<str>>(&self, name: S) -> &Parameter {
2605 if let Some(position) = self
2606 .parameters
2607 .get(&ParameterName(UFOSymbol::from(name.as_ref())))
2608 {
2609 position
2610 } else {
2611 panic!(
2612 "Parameter '{}' not found in model '{}'.",
2613 name.as_ref(),
2614 self.name
2615 );
2616 }
2617 }
2618
2619 #[inline]
2620 pub fn get_parameter_mut_opt<S: AsRef<str>>(&mut self, name: S) -> Option<&mut Parameter> {
2621 if let Some(position) = self
2622 .parameters
2623 .get_mut(&ParameterName(UFOSymbol::from(name.as_ref())))
2624 {
2625 Some(position)
2626 } else {
2627 None
2628 }
2629 }
2630
2631 #[inline]
2632 pub fn get_parameter_mut<S: AsRef<str>>(&mut self, name: S) -> Result<&mut Parameter> {
2633 if let Some(position) = self
2634 .parameters
2635 .get_mut(&ParameterName(UFOSymbol::from(name.as_ref())))
2636 {
2637 Ok(position)
2638 } else {
2639 Err(eyre!(
2640 "Parameter '{}' not found in model '{}'.",
2641 name.as_ref(),
2642 self.name
2643 ))
2644 }
2645 }
2646
2647 #[inline]
2648 pub fn get_order<S: AsRef<str>>(&self, name: S) -> Arc<Order> {
2649 if let Some(position) = self.order_name_to_position.get(name.as_ref()) {
2650 self.orders[*position].clone()
2651 } else {
2652 panic!(
2653 "Coupling order '{}' not found in model '{}'.",
2654 name.as_ref(),
2655 self.name
2656 );
2657 }
2658 }
2659 #[inline]
2660 pub fn get_lorentz_structure<S: AsRef<str>>(&self, name: S) -> Arc<LorentzStructure> {
2661 if let Some(position) = self.lorentz_structure_name_to_position.get(name.as_ref()) {
2662 self.lorentz_structures[*position].clone()
2663 } else {
2664 panic!(
2665 "Lorentz structure '{}' not found in model '{}'.",
2666 name.as_ref(),
2667 self.name
2668 );
2669 }
2670 }
2671 #[inline]
2672 pub fn get_coupling<S: AsRef<str>>(&self, name: S) -> &Coupling {
2673 if let Some(coupling) = self
2674 .couplings
2675 .get(&CouplingName(UFOSymbol::from(name.as_ref())))
2676 {
2677 coupling
2678 } else {
2679 panic!(
2680 "Coupling '{}' not found in model '{}'.",
2681 name.as_ref(),
2682 self.name
2683 );
2684 }
2685 }
2686 #[inline]
2687 pub fn get_vertex_rule<S: AsRef<str>>(&self, name: S) -> ArcVertexRule {
2688 if let Some(position) = self.vertex_rule_name_to_position.get(name.as_ref()) {
2689 self.vertex_rules[*position].clone()
2690 } else {
2691 panic!(
2692 "Vertex rule '{}' not found in model '{}'.",
2693 name.as_ref(),
2694 self.name
2695 );
2696 }
2697 }
2698}
2699
2700#[cfg(test)]
2701#[path = "test_polarization_sums.rs"]
2702mod test_polarization_sums;
2703
2704#[cfg(test)]
2705mod tests {
2706 use crate::{
2707 model::{
2708 ArcPropagator, ArcVertexRule, Parameter, ParameterName, ParameterNature, ParameterType,
2709 },
2710 momentum::{Helicity, ThreeMomentum},
2711 utils::{F, load_generic_model},
2712 };
2713
2714 use super::{ArcParticle, Model, UFOSymbol, parameter_display_sort_key};
2715
2716 #[test]
2717 fn test_encode_decode_arc_particle() {
2718 let model = load_generic_model("scalars");
2719 let particle = model.get_particle("scalar_0");
2720 let particle_encoded =
2721 bincode::encode_to_vec(&particle, bincode::config::standard()).unwrap();
2722
2723 let particle_decoded: ArcParticle = bincode::decode_from_slice_with_context(
2724 &particle_encoded,
2725 bincode::config::standard(),
2726 model,
2727 )
2728 .unwrap()
2729 .0;
2730
2731 assert_eq!(particle, particle_decoded);
2732 }
2733
2734 #[test]
2735 fn test_encode_decode_vertex_rule() {
2736 let model = load_generic_model("sm");
2737 let vertex = model.get_vertex_rule("V_141");
2738 let vertex_encoded = bincode::encode_to_vec(&vertex, bincode::config::standard()).unwrap();
2739
2740 let vertex_decoded: ArcVertexRule = bincode::decode_from_slice_with_context(
2741 &vertex_encoded,
2742 bincode::config::standard(),
2743 model,
2744 )
2745 .unwrap()
2746 .0;
2747 assert_eq!(vertex, vertex_decoded);
2748 }
2749
2750 #[test]
2751 fn autogenerated_model_dod_counts_raw_ufo_momenta() {
2752 let model = load_generic_model("sm");
2753
2754 assert_eq!(model.get_vertex_rule("V_35").dod, 1);
2755 assert_eq!(model.get_vertex_rule("V_36").dod, 1);
2756 assert_eq!(model.get_propagator_for_particle("ghG").dod, -2);
2757 assert_eq!(model.get_propagator_for_particle("g").dod, -2);
2758 assert_eq!(model.get_propagator_for_particle("d").dod, -1);
2759 }
2760
2761 #[test]
2762 fn test_pol_limit_vector() {
2763 let vals = [0., 3., 4.].into_iter().map(F).collect::<Vec<_>>();
2764 let mom = ThreeMomentum::new(vals[0], vals[1], vals[2]).into_on_shell_four_momentum(None); let hel = Helicity::MINUS;
2767 println!("mom:{mom}");
2768 println!("hel{hel}");
2769 println!("{}", mom.eps_pol(hel));
2770 println!("{}", mom.eps_pol(hel).bar());
2771 let hel = Helicity::PLUS;
2775 println!("mom:{mom}");
2776 println!("hel{hel}");
2777 println!("{}", mom.eps_pol(hel));
2778 println!("{}", mom.eps_pol(hel).bar());
2779 let hel = Helicity::MINUS;
2780
2781 println!("hel{hel}");
2782 println!("{}", mom.eps_pol(hel));
2783 println!("{}", mom.eps_pol(hel).bar());
2784
2785 let vals = [0., 0., -1.].into_iter().map(F).collect::<Vec<_>>();
2786 let mom = ThreeMomentum::new(vals[0], vals[1], vals[2]).into_on_shell_four_momentum(None);
2787
2788 let hel = Helicity::PLUS;
2789
2790 println!("mom:{mom}");
2791 println!("hel{hel}");
2792
2793 println!("{}", mom.eps_pol(hel));
2794 println!("{}", mom.eps_pol(hel).bar());
2795
2796 let hel = Helicity::MINUS;
2797
2798 println!("hel{hel}");
2799 println!("{}", mom.eps_pol(hel));
2800 println!("{}", mom.eps_pol(hel).bar());
2801 }
2802
2803 #[test]
2804 fn display_orders_external_parameters_first() {
2805 let mut model = Model::default();
2806 let external = Parameter {
2807 name: UFOSymbol::from("zeta"),
2808 lhablock: None,
2809 lhacode: None,
2810 nature: ParameterNature::External,
2811 parameter_type: ParameterType::Real,
2812 value: None,
2813 expression: None,
2814 };
2815 let internal = Parameter {
2816 name: UFOSymbol::from("alpha"),
2817 lhablock: None,
2818 lhacode: None,
2819 nature: ParameterNature::Internal,
2820 parameter_type: ParameterType::Real,
2821 value: None,
2822 expression: None,
2823 };
2824 model
2825 .parameters
2826 .insert(ParameterName(external.name), external.clone());
2827 model
2828 .parameters
2829 .insert(ParameterName(internal.name), internal.clone());
2830
2831 assert!(parameter_display_sort_key(&external) < parameter_display_sort_key(&internal));
2832
2833 let description = model.get_description(false, true, false, false);
2834 let external_index = description.find("zeta").unwrap();
2835 let internal_index = description.find("alpha").unwrap();
2836 assert!(external_index < internal_index);
2837 }
2838
2839 mod failing {
2840 use super::*;
2841
2842 #[test]
2843 fn test_encode_decode_arc_propagator() {
2844 let model = load_generic_model(
2845 "sm
2846 ",
2847 );
2848 let propagator = model.get_propagator("t_propFeynman");
2849 let propagator_encoded =
2850 bincode::encode_to_vec(&propagator, bincode::config::standard()).unwrap();
2851
2852 let propagator_decoded: ArcPropagator = bincode::decode_from_slice_with_context(
2853 &propagator_encoded,
2854 bincode::config::standard(),
2855 model,
2856 )
2857 .unwrap()
2858 .0;
2859
2860 assert_eq!(propagator.name, propagator_decoded.name);
2861 }
2862 }
2863}