Skip to main content

gammalooprs/model/
mod.rs

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;
14// use linnet::half_edge::drawing::Decoration;
15use 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
30// use log::{info, trace};
31use 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    /// Create empty card
126    pub fn new() -> Self {
127        Self {
128            data: HashMap::new(),
129        }
130    }
131
132    /// Insert a parameter
133    pub fn insert(&mut self, sym: UFOSymbol, value: Complex<T>) -> Option<Complex<T>> {
134        self.data.insert(sym, value)
135    }
136
137    /// Get immutable reference
138    pub fn get(&self, sym: &UFOSymbol) -> Option<&Complex<T>> {
139        self.data.get(sym)
140    }
141
142    /// Get mutable reference
143    pub fn get_mut(&mut self, sym: &UFOSymbol) -> Option<&mut Complex<T>> {
144        self.data.get_mut(sym)
145    }
146
147    /// Remove a parameter
148    pub fn remove(&mut self, sym: &UFOSymbol) -> Option<Complex<T>> {
149        self.data.remove(sym)
150    }
151
152    /// Iterate over all parameters
153    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
224/// Let it behave like a map if desired
225impl<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    // assert_eq!(UFOSymbol::zero().namespaceless_string(), "ZERO");
277}
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    // #[allow(clippy::complexity)]
605    // pub fn get_coupling_orders(&self) -> Vec<Vec<Option<CouplingName>>> {
606    //     self.couplings
607    //         .iter()
608    //         .map(|row| {
609    //             row.iter()
610    //                 .map(|co| co.clone().map(|c| c.clone()))
611    //                 .collect::<Vec<_>>()
612    //         })
613    //         .collect::<Vec<_>>()
614    // }
615}
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 = normalise_complex(&self.expression);
798        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 decoration(&self) -> Decoration {
1011    //     match self.spin {
1012    //         0 => Decoration::Dashed,
1013    //         1 => Decoration::None,
1014    //         2 => Decoration::Arrow,
1015    //         3 => {
1016    //             if self.pdg_code.abs() == 9 || self.pdg_code.abs() == 21 {
1017    //                 Decoration::Coil
1018    //             } else {
1019    //                 Decoration::Wave
1020    //             }
1021    //         }
1022    //         _ => Decoration::None,
1023    //     }
1024    // }
1025
1026    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            // The checks below are too slow
1081            // if self.name != other.name {
1082            //     panic!(
1083            //         "Particle with same pdg code but different names: {} and {}",
1084            //         self.name, other.name
1085            //     );
1086            // }
1087            // if self.spin != other.spin {
1088            //     panic!(
1089            //         "Particle with same pdg code but different spins: {} and {}",
1090            //         self.spin, other.spin
1091            //     );
1092            // }
1093            // if self.color != other.color {
1094            //     panic!(
1095            //         "Particle with same pdg code but different colors: {} and {}",
1096            //         self.color, other.color
1097            //     );
1098            // }
1099            // if self.mass != other.mass {
1100            //     panic!(
1101            //         "Particle with same pdg code but different masses: {} and {}",
1102            //         self.mass, other.mass
1103            //     );
1104            // }
1105            // if self.width != other.width {
1106            //     panic!(
1107            //         "Particle with same pdg code but different widths: {} and {}",
1108            //         self.width, other.width
1109            //     );
1110            // }
1111            // if self.texname != other.texname {
1112            //     panic!(
1113            //         "Particle with same pdg code but different texnames: {} and {}",
1114            //         self.texname, other.texname
1115            //     );
1116            // }
1117            // if self.antitexname != other.antitexname {
1118            //     panic!(
1119            //         "Particle with same pdg code but different antitexnames: {} and {}",
1120            //         self.antitexname, other.antitexname
1121            //     );
1122            // }
1123            // if self.charge != other.charge {
1124            //     panic!(
1125            //         "Particle with same pdg code but different charges: {} and {}",
1126            //         self.charge, other.charge
1127            //     );
1128            // }
1129            // if self.ghost_number != other.ghost_number {
1130            //     panic!(
1131            //         "Particle with same pdg code but different ghost_numbers: {} and {}",
1132            //         self.ghost_number, other.ghost_number
1133            //     );
1134            // }
1135            // if self.lepton_number != other.lepton_number {
1136            //     panic!(
1137            //         "Particle with same pdg code but different lepton_numbers: {} and {}",
1138            //         self.lepton_number, other.lepton_number
1139            //     );
1140            // }
1141            // if self.y_charge != other.y_charge {
1142            //     panic!(
1143            //         "Particle with same pdg code but different y_charges: {} and {}",
1144            //         self.y_charge, other.y_charge
1145            //     );
1146            // }
1147            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    /// Generate edge styles for visualization based on particle properties
1252    pub fn generate_edge_typst_dict(&self) -> String {
1253        let label = format!("mi(`{}`)", self.texname);
1254        // Determine line thickness based on mass
1255        let thickness = if self.is_massive() {
1256            "massive"
1257        } else {
1258            "massless"
1259        };
1260
1261        // Determine color based on charge
1262        let color = if self.charge.abs() > 0.0 {
1263            "blue"
1264        } else {
1265            "black"
1266        };
1267
1268        // Generate base styles
1269        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            // Vector bosons: differentiate based on charge and color properties
1281            if self.charge == 0.0 && self.color == 1 {
1282                // Neutral color singlet (photon): wavy line
1283                (
1284                    format!("{} + wave", base_source),
1285                    format!("{} + wave", base_sink),
1286                )
1287            } else if self.charge == 0.0 && self.color == 8 {
1288                // Neutral color octet (gluon): coiled line
1289                (
1290                    format!("{} + coil", base_source),
1291                    format!("{} + coil", base_sink),
1292                )
1293            } else {
1294                // Charged vector bosons (W+/W-/Z with mass): zigzag line
1295                (
1296                    format!("{} + zigzag", base_source),
1297                    format!("{} + zigzag", base_sink),
1298                )
1299            }
1300        } else if self.is_scalar() {
1301            // Scalar particles: dashed lines
1302            (
1303                format!("source-stroke(c: {color}, thickness: {thickness}, dash: dashed)",),
1304                format!("sink-stroke(c: {color}, thickness: {thickness}, dash: dashed)"),
1305            )
1306        } else {
1307            // Default: solid line
1308            (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.value == other.value
1474            && 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: (&param.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, normalise_complex(&rhs?)])
1503        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) = &param.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 {
1775            //     vertex_table.push_record(&[
1776            //         vr.name.to_string().green().to_string(),
1777            //         vr.particles
1778            //             .iter()
1779            //             .map(|p| format!("{:<6}", p.name.to_string()).blue().to_string())
1780            //             .collect::<Vec<_>>()
1781            //             .join(", "),
1782            //     ]);
1783            // }
1784            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                // Pad the record with empty strings if necessary
1790                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                        // keep only the split after column 0 (between col 0 and 1)
1802                        .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    /// Generate edge-style.typ template file with styles for all particles in the model
1853    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        // Create the directory if it doesn't exist
1860        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        // Generate styles for all particles in the model
1876        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        // Remove zero parameters from the input card
1922        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        // Set all external parameters with value 0 to constant internal parameters with expression zero.
1930        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        // Remove all vertices with zero couplings
1958        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        // Now remove all couplings that are zero
2032        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, &params)
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(&param_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        //initialize the UFO and ETS symbols
2359
2360        // let _ = *UFO;
2361        let _ = *ETS;
2362
2363        let mut model: Model = Model::default();
2364        model.name = serializable_model.name;
2365        model.restriction = serializable_model.restriction;
2366
2367        // Extract coupling orders
2368        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        // Extract parameters
2386        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        // Extract particles
2397        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        // Extract propagators
2415
2416        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        // Extract Lorentz structures
2433        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        // Extract couplings
2450        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        // Extract vertex rules
2461        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        // Set propagator mapping
2477        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); //Some(F(8.66025)));
2765
2766        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 vals = [0.01, 0., 1.].into_iter().map(F).collect::<Vec<_>>();
2772        // let mom = ThreeMomentum::new(vals[0], vals[1], vals[2]).into_on_shell_four_momentum(None);
2773
2774        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}