Skip to main content

gammalooprs/utils/
symbols.rs

1use std::sync::LazyLock;
2
3use itertools::Itertools;
4use linnet::half_edge::involution::{EdgeIndex, Orientation};
5
6use spenso::{
7    network::{library::symbolic::ETS, tags::SPENSO_TAG},
8    shadowing::symbolica_utils::SpensoPrintSettings,
9    structure::{
10        abstract_index::AIND_SYMBOLS,
11        concrete_index::ExpandedIndex,
12        representation::{Minkowski, RepName, Representation},
13        slot::{DummyAind, IsAbstractSlot},
14    },
15    utils::{to_subscript, to_superscript},
16};
17use symbolica::{
18    atom::{Atom, AtomCore, AtomOrView, AtomView, FunctionBuilder, Symbol},
19    domains::rational::Rational,
20    function, get_symbol,
21    id::Replacement,
22    printer::{PrintState, PrintUserData},
23    symbol,
24};
25use symbolica_utils::{PrintSettingsExt, TypstMode};
26
27use crate::{cff::orientations::GraphOrientation, graph::LoopMomentumBasis, numerator::aind::Aind};
28
29pub struct WildCards {
30    pub edgeid_: Symbol,
31    pub mom_: Symbol,
32    pub mass_: Symbol,
33    pub prop_: Symbol,
34    pub a_: Symbol,
35    pub b_: Symbol,
36    pub c_: Symbol,
37    pub d_: Symbol,
38    pub e_: Symbol,
39    pub f_: Symbol,
40    pub g_: Symbol,
41    pub h_: Symbol,
42    pub i_: Symbol,
43    pub j_: Symbol,
44    pub k_: Symbol,
45    pub l_: Symbol,
46    pub m_: Symbol,
47    pub n_: Symbol,
48    pub o_: Symbol,
49    pub p_: Symbol,
50    pub q_: Symbol,
51    pub r_: Symbol,
52    pub s_: Symbol,
53    pub t_: Symbol,
54    pub u_: Symbol,
55    pub v_: Symbol,
56    pub w_: Symbol,
57    pub x_: Symbol,
58    pub y_: Symbol,
59    pub z_: Symbol,
60    pub a__: Symbol,
61    pub b__: Symbol,
62    pub c__: Symbol,
63    pub d__: Symbol,
64    pub e__: Symbol,
65    pub f__: Symbol,
66    pub g__: Symbol,
67    pub h__: Symbol,
68    pub i__: Symbol,
69    pub j__: Symbol,
70    pub k__: Symbol,
71    pub l__: Symbol,
72    pub m__: Symbol,
73    pub n__: Symbol,
74    pub o__: Symbol,
75    pub p__: Symbol,
76    pub q__: Symbol,
77    pub r__: Symbol,
78    pub s__: Symbol,
79    pub t__: Symbol,
80    pub u__: Symbol,
81    pub v__: Symbol,
82    pub w__: Symbol,
83    pub x__: Symbol,
84    pub y__: Symbol,
85    pub z__: Symbol,
86    pub a___: Symbol,
87    pub b___: Symbol,
88    pub c___: Symbol,
89    pub d___: Symbol,
90    pub e___: Symbol,
91    pub f___: Symbol,
92    pub g___: Symbol,
93    pub h___: Symbol,
94    pub i___: Symbol,
95    pub j___: Symbol,
96    pub k___: Symbol,
97    pub l___: Symbol,
98    pub m___: Symbol,
99    pub n___: Symbol,
100    pub o___: Symbol,
101    pub p___: Symbol,
102    pub q___: Symbol,
103    pub r___: Symbol,
104    pub s___: Symbol,
105    pub t___: Symbol,
106    pub u___: Symbol,
107    pub v___: Symbol,
108    pub w___: Symbol,
109    pub x___: Symbol,
110    pub y___: Symbol,
111    pub z___: Symbol,
112}
113
114pub struct GammaloopSymbols {
115    pub integrand: Symbol,
116    /// wrapper function for the 4d bridge denominators
117    pub tree_denom_wrapper: Symbol,
118
119    pub killing_func: Symbol,
120    pub is_function: Symbol,
121    pub is_symbol: Symbol,
122    pub ufozero: Symbol,
123    pub _linear: Symbol,
124    pub linearize: Symbol,
125    pub loop_mom: Symbol,
126    pub edgeid: Symbol,
127    pub uvaind: Symbol,
128    pub edgeaind: Symbol,
129    pub dummyaind: Symbol,
130    pub hedgeaind: Symbol,
131    pub vertexaind: Symbol,
132    pub vertexid: Symbol,
133    pub source_id: Symbol,
134    pub sink_id: Symbol,
135    pub orientation_delta: Symbol,
136    pub ubar: Symbol,
137    pub hfunction_lu_cut: Symbol,
138    pub hfunction_left_th: Symbol,
139    pub hfunction_right_th: Symbol,
140    pub eta: Symbol,
141    pub eta_left: Symbol,
142    pub eta_right: Symbol,
143    pub deta_lu_cut: Symbol,
144    pub deta_left_th: Symbol,
145    pub deta_right_th: Symbol,
146    pub vbar: Symbol,
147    pub ose: Symbol,
148    pub energy: Symbol,
149    pub v: Symbol,
150    pub u: Symbol,
151    pub color_wrap: Symbol,
152    /// Epsilon for dimensional regularization
153    pub dim_epsilon: Symbol,
154
155    pub epsilon: Symbol,
156    pub epsilonbar: Symbol,
157    pub rescale: Symbol,
158    /// Bookkeeping scale for integrated vacuum masses and consumed loop measures.
159    pub integrated_loop_scale: Symbol,
160    pub rescale_mass: Symbol,
161    pub rescale_star: Symbol,
162    pub pi: Symbol,
163
164    //Parameters for UV renormalization and localization
165    /// UV mass used only as the auxiliary expansion/deformation scale in local UV series.
166    ///
167    /// This symbol prints as `mUVexp`. Public runtime output should not depend on this
168    /// name after the endpoint mass-role collapse.
169    pub m_uv_expansion: Symbol,
170    /// UV vacuum mass used by integrated counterterms and the vacuum-integral basis.
171    ///
172    /// This symbol prints as `mUV` and is the public symbolic representative of the
173    /// `general.m_uv` runtime setting.
174    pub m_uv_vacuum: Symbol,
175    /// UV localization scale factor
176    pub renormalization_localization_scale: Symbol,
177    pub mu_r_sq: Symbol,
178    pub sign: Symbol,
179    pub theta: Symbol,
180    pub broadcasting_sqrt: Symbol,
181    ///for selecting orientations at generation
182    pub selected: Symbol,
183
184    /// For analytic UV profiling
185    pub expansion: Symbol,
186    ///For selecting a concete index.
187    pub delta_vec: Symbol,
188    /// `Q(<edgeid>, index___)`
189    pub emr_mom: Symbol,
190    pub emr_vec: Symbol,
191    pub dot: Symbol,
192    pub external_mom: Symbol,
193    pub dim: Symbol,
194    pub coeff: Symbol,
195
196    pub localizing_integrand: Symbol,
197
198    pub uv_subgraph: Symbol,
199    pub uv_approx: Symbol,
200    pub uv_integrate: Symbol,
201    pub uv_series: Symbol,
202    pub uv_truncate: Symbol,
203    /// Marker for the ct term in the UV integrand
204    pub ct_marker: Symbol,
205
206    pub nc2_1: Symbol,
207    pub expr: Symbol,
208    pub num: Symbol,
209    /// Denominator wrapper, `den(<edge_id>, <momentum>, <mass>, <full_expr>)`
210    /// (no power and should not be multiplied in but divided!).
211    pub den: Symbol,
212    pub radius_left: Symbol,
213    pub radius_star_left: Symbol,
214    pub uv_damp_plus_left: Symbol,
215    pub uv_damp_minus_left: Symbol,
216    pub radius_right: Symbol,
217    pub radius_star_right: Symbol,
218    pub uv_damp_plus_right: Symbol,
219    pub uv_damp_minus_right: Symbol,
220}
221
222impl GammaloopSymbols {
223    pub fn collect_orientation_if<'a>(&self, arg: impl Into<AtomOrView<'a>>) -> Atom {
224        arg.into()
225            .replace(self.sign_theta(W_.a_))
226            .with(Symbol::IF.call(Atom::var(W_.a_) + 1))
227            .replace(Symbol::IF.call(W_.a_) * Symbol::IF.call(W_.b_))
228            .repeat()
229            .with(Symbol::IF.call(W_.a_ * W_.b_))
230            .replace(Symbol::IF.call(W_.a_) * W_.b___)
231            .with(Symbol::IF.call_args([Atom::var(W_.a_), Atom::var(W_.b___), Atom::Zero]))
232            .replace(Symbol::IF.call_args([Atom::var(W_.a_), Atom::Zero]))
233            .with(Symbol::IF.call_args([Atom::var(W_.a_), Atom::one(), Atom::Zero]))
234            .replace(Symbol::IF.call_args([Atom::var(W_.a_)]))
235            .with(Symbol::IF.call_args([Atom::var(W_.a_), Atom::one(), Atom::Zero]))
236    }
237
238    pub fn den<'a>(
239        &self,
240        eid: impl Into<AtomOrView<'a>>,
241        mom: impl Into<AtomOrView<'a>>,
242        mass: impl Into<AtomOrView<'a>>,
243        full_expr: impl Into<AtomOrView<'a>>,
244    ) -> Atom {
245        self.den.call_args([
246            eid.into().as_view(),
247            mom.into().as_view(),
248            mass.into().as_view(),
249            full_expr.into().as_view(),
250        ])
251    }
252
253    pub(crate) fn orientation_delta<O: GraphOrientation>(&self, orientation: &O) -> Atom {
254        let args: Vec<i32> = orientation
255            .orientation()
256            .iter()
257            .map(|(_, t)| match t {
258                Orientation::Default => 1,
259                Orientation::Reversed => -1,
260                Orientation::Undirected => 0,
261            })
262            .collect_vec();
263        FunctionBuilder::new(self.orientation_delta)
264            .add_args(&args)
265            .finish()
266    }
267
268    pub(crate) fn sign_theta<'a>(&self, arg: impl Into<AtomOrView<'a>>) -> Atom {
269        let arg = arg.into();
270
271        function!(self.theta, arg.as_view())
272    }
273
274    pub(crate) fn sign(&self, edge: EdgeIndex) -> Atom {
275        function!(self.sign, Atom::num(edge.0 as i64))
276    }
277}
278
279pub static W_: LazyLock<WildCards> = LazyLock::new(|| WildCards {
280    edgeid_: symbol!("eid_"),
281    mom_: symbol!("mom_"),
282    mass_: symbol!("mass_"),
283    prop_: symbol!("prop_"),
284    a_: symbol!("a_"),
285    b_: symbol!("b_"),
286    c_: symbol!("c_"),
287    d_: symbol!("d_"),
288    e_: symbol!("e_"),
289    f_: symbol!("f_"),
290    g_: symbol!("g_"),
291    h_: symbol!("h_"),
292    i_: symbol!("i_"),
293    j_: symbol!("j_"),
294    k_: symbol!("k_"),
295    l_: symbol!("l_"),
296    m_: symbol!("m_"),
297    n_: symbol!("n_"),
298    o_: symbol!("o_"),
299    p_: symbol!("p_"),
300    q_: symbol!("q_"),
301    r_: symbol!("r_"),
302    s_: symbol!("s_"),
303    t_: symbol!("t_"),
304    u_: symbol!("u_"),
305    v_: symbol!("v_"),
306    w_: symbol!("w_"),
307    x_: symbol!("x_"),
308    y_: symbol!("y_"),
309    z_: symbol!("z_"),
310    a__: symbol!("a__"),
311    b__: symbol!("b__"),
312    c__: symbol!("c__"),
313    d__: symbol!("d__"),
314    e__: symbol!("e__"),
315    f__: symbol!("f__"),
316    g__: symbol!("g__"),
317    h__: symbol!("h__"),
318    i__: symbol!("i__"),
319    j__: symbol!("j__"),
320    k__: symbol!("k__"),
321    l__: symbol!("l__"),
322    m__: symbol!("m__"),
323    n__: symbol!("n__"),
324    o__: symbol!("o__"),
325    p__: symbol!("p__"),
326    q__: symbol!("q__"),
327    r__: symbol!("r__"),
328    s__: symbol!("s__"),
329    t__: symbol!("t__"),
330    u__: symbol!("u__"),
331    v__: symbol!("v__"),
332    w__: symbol!("w__"),
333    x__: symbol!("x__"),
334    y__: symbol!("y__"),
335    z__: symbol!("z__"),
336    a___: symbol!("a___"),
337    b___: symbol!("b___"),
338    c___: symbol!("c___"),
339    d___: symbol!("d___"),
340    e___: symbol!("e___"),
341    f___: symbol!("f___"),
342    g___: symbol!("g___"),
343    h___: symbol!("h___"),
344    i___: symbol!("i___"),
345    j___: symbol!("j___"),
346    k___: symbol!("k___"),
347    l___: symbol!("l___"),
348    m___: symbol!("m___"),
349    n___: symbol!("n___"),
350    o___: symbol!("o___"),
351    p___: symbol!("p___"),
352    q___: symbol!("q___"),
353    r___: symbol!("r___"),
354    s___: symbol!("s___"),
355    t___: symbol!("t___"),
356    u___: symbol!("u___"),
357    v___: symbol!("v___"),
358    w___: symbol!("w___"),
359    x___: symbol!("x___"),
360    y___: symbol!("y___"),
361    z___: symbol!("z___"),
362});
363
364macro_rules! spenso_print_scripted_indexed {
365    ($a:ident, $opt:ident, $symbol:expr) => {
366        spenso_print_scripted_indexed!($a, $opt, $symbol, $symbol)
367    };
368    ($a:ident, $opt:ident, $symbol:expr, $typst_symbol:expr) => {{
369        match $opt.custom_print_mode.get("spenso") {
370            Some(PrintUserData::Integer(i)) => {
371                let SpensoPrintSettings {
372                    parens,
373                    symbol_scripts,
374                    commas,
375                    with_dim,
376                    ..
377                } = SpensoPrintSettings::from(*i as usize);
378
379                let AtomView::Fun(f) = $a else {
380                    return None;
381                };
382
383                let mut argiter = f.iter();
384                let id = argiter.next().unwrap();
385                let Ok(i) = usize::try_from(id) else {
386                    return None;
387                };
388
389                let is_typst = $opt.typst_mode().is_some();
390                let mut out = if is_typst {
391                    $typst_symbol.to_string()
392                } else {
393                    $symbol.to_string()
394                };
395                if is_typst {
396                    out.push('_');
397                    out.push_str(&i.to_string());
398                } else {
399                    out.push_str(&to_subscript(i as isize));
400                }
401                if $opt.color_builtin_symbols && !is_typst {
402                    out = nu_ansi_term::Color::Magenta.paint(out).to_string();
403                }
404
405                let mut printed_args = false;
406                for arg in argiter {
407                    let hidden_representation = matches!(
408                        arg,
409                        AtomView::Fun(a)
410                            if a.get_symbol().has_tag(&SPENSO_TAG.representation)
411                                && a.get_nargs() == 1
412                                && !with_dim
413                    );
414                    if hidden_representation {
415                        continue;
416                    }
417
418                    if printed_args {
419                        out.push(if commas { ',' } else { ' ' });
420                    } else {
421                        if symbol_scripts {
422                            out.push('^');
423                        }
424                        if parens {
425                            out.push('(');
426                        }
427                        printed_args = true;
428                    }
429
430                    arg.format(&mut out, $opt, PrintState::new()).unwrap();
431                }
432                if printed_args && parens {
433                    out.push(')');
434                }
435                Some(out)
436            }
437            _ => None,
438        }
439    }};
440}
441
442macro_rules! spenso_print_simple_indexed {
443    ($a:ident, $opt:ident, $symbol:expr) => {
444        spenso_print_simple_indexed!($a, $opt, $symbol, $symbol)
445    };
446    ($a:ident, $opt:ident, $symbol:expr, $typst_symbol:expr) => {{
447        match $opt.custom_print_mode.get("spenso") {
448            Some(PrintUserData::Integer(_)) => {
449                let AtomView::Fun(f) = $a else {
450                    return None;
451                };
452
453                let mut out = $symbol.to_string();
454                let mut args = f.iter();
455
456                let id = args.next().unwrap();
457                let Ok(i) = usize::try_from(id) else {
458                    return None;
459                };
460
461                if $opt.typst_mode().is_some() {
462                    out = $typst_symbol.to_string();
463                    out.push('_');
464                    out.push_str(&i.to_string());
465                } else {
466                    out.push_str(&to_subscript(i as isize));
467                }
468                let mut first = true;
469                for arg in args {
470                    if first {
471                        first = false;
472                        out.push('(');
473                    } else {
474                        out.push(',');
475                    }
476                    arg.format(&mut out, $opt, PrintState::new()).unwrap();
477                }
478                if !first {
479                    out.push(')');
480                }
481                Some(out)
482            }
483            _ => None,
484        }
485    }};
486}
487
488macro_rules! spenso_print_uv_unary {
489    ($a:ident, $opt:ident, $prefix:expr, $suffix:expr) => {{
490        match $opt.custom_print_mode.get("spenso") {
491            Some(PrintUserData::Integer(_)) => {
492                let AtomView::Fun(f) = $a else {
493                    return None;
494                };
495                if f.get_nargs() != 1 {
496                    return None;
497                }
498
499                let mut out = $prefix.to_string();
500                f.iter()
501                    .next()
502                    .unwrap()
503                    .format(&mut out, $opt, PrintState::new())
504                    .unwrap();
505                out.push_str($suffix);
506                Some(out)
507            }
508            _ => None,
509        }
510    }};
511}
512
513spenso::symbolica_init_lazy_static! {
514pub static GS, GS_INNER: GammaloopSymbols = || GammaloopSymbols {
515    renormalization_localization_scale: symbol!("rls"),
516    integrand: symbol!("integrand"),
517    tree_denom_wrapper: symbol!("tree_denoms"),
518    dim_epsilon: symbol!("ε"),
519    killing_func: symbol!(
520        "killing_func",
521        norm = |f, out| {
522            if let AtomView::Fun(_f) = f {
523                **out = Atom::one()
524            }
525        }
526    ),
527    ufozero: symbol!(
528        "UFO::ZERO",
529        norm = |_, out| {
530            **out = Atom::Zero;
531        }
532    ),
533
534    localizing_integrand: symbol!("int_loc"),
535    uvaind: symbol!(
536        "uvind",
537        print = |a, opt, _state| {
538            match opt.custom_print_mode.get("spenso") {
539                Some(PrintUserData::Integer(_i)) => {
540                    let AtomView::Fun(f) = a else {
541                        return None;
542                    };
543
544                    let mut out = "ᵘ".to_string();
545                    let mut first = true;
546                    for arg in f.iter() {
547                        let Ok(i) = isize::try_from(arg) else {
548                            return None;
549                        };
550
551                        if !first {
552                            out.push('.');
553                        } else {
554                            first = false;
555                        }
556                        out.push_str(&to_superscript(i));
557                    }
558                    Some(out)
559                }
560                _ => None,
561            }
562        },
563        tags = [SPENSO_TAG.index.clone()]
564    ),
565    edgeaind: symbol!(
566        "edge",
567        print = |a, opt, _state| {
568            match opt.custom_print_mode.get("spenso") {
569                Some(PrintUserData::Integer(_i)) => {
570                    let AtomView::Fun(f) = a else {
571                        return None;
572                    };
573
574                    let mut out = "ᵉ".to_string();
575                    let mut first = true;
576                    for arg in f.iter() {
577                        let Ok(i) = isize::try_from(arg) else {
578                            return None;
579                        };
580
581                        if !first {
582                            out.push('.');
583                        }
584                        first = false;
585
586                        out.push_str(&to_superscript(i));
587                    }
588                    Some(out)
589                }
590                _ => None,
591            }
592        },
593        tags = [SPENSO_TAG.index.clone()]
594    ),
595    vertexaind: symbol!(
596        "vertex",
597        print = |a, opt, _state| {
598            match opt.custom_print_mode.get("spenso") {
599                Some(PrintUserData::Integer(_i)) => {
600                    let AtomView::Fun(f) = a else {
601                        return None;
602                    };
603
604                    let mut out = "ᵛ".to_string();
605
606                    let mut first = true;
607                    for arg in f.iter() {
608                        let Ok(i) = isize::try_from(arg) else {
609                            return None;
610                        };
611
612                        if !first {
613                            out.push('.');
614                        }
615                        first = false;
616
617                        out.push_str(&to_superscript(i));
618                    }
619                    Some(out)
620                }
621                _ => None,
622            }
623        },
624        tags = [SPENSO_TAG.index.clone()]
625    ),
626    dummyaind: symbol!(
627        "dummy",
628        print = |a, opt, _state| {
629            match opt.custom_print_mode.get("spenso") {
630                Some(PrintUserData::Integer(_i)) => {
631                    let AtomView::Fun(f) = a else {
632                        return None;
633                    };
634
635                    let mut out = "ᵈ".to_string();
636                    let mut first = true;
637                    for arg in f.iter() {
638                        let Ok(i) = isize::try_from(arg) else {
639                            return None;
640                        };
641
642                        if !first {
643                            out.push('.');
644                        }
645                        first = false;
646
647                        out.push_str(&to_superscript(i));
648                    }
649                    Some(out)
650                }
651                _ => None,
652            }
653        },
654        tags = [SPENSO_TAG.index.clone()]
655    ),
656    hedgeaind: symbol!(
657        "hedge",
658        print = |a, opt, _state| {
659            match opt.custom_print_mode.get("spenso") {
660                Some(PrintUserData::Integer(i)) => {
661                    let AtomView::Fun(f) = a else {
662                        return None;
663                    };
664                    let SpensoPrintSettings {
665                        index_subscripts, ..
666                    } = SpensoPrintSettings::from(*i as usize);
667
668                    let mut out = "".to_string();
669                    let mut first = true;
670                    for arg in f.iter() {
671                        let Ok(i) = isize::try_from(arg) else {
672                            return None;
673                        };
674
675                        if !first {
676                            out.push('.');
677                        }
678                        first = false;
679                        if index_subscripts {
680                            out.push_str(&to_superscript(i));
681                        } else {
682                            out.push_str(&to_subscript(i));
683                        }
684                    }
685                    Some(out)
686                }
687                _ => None,
688            }
689        },
690        tags = [SPENSO_TAG.index.clone()]
691    ),
692    uv_subgraph: symbol!(
693        "gammalooprs::uv::subgraph",
694        print = |a, opt, _state| {
695            match opt.custom_print_mode.get("spenso") {
696                Some(PrintUserData::Integer(_)) => {
697                    let AtomView::Fun(f) = a else {
698                        return None;
699                    };
700                    if f.get_nargs() != 2 {
701                        return None;
702                    }
703
704                    let mut args = f.iter();
705                    let current = args.next().unwrap();
706                    let mut out = String::new();
707                    if let AtomView::Var(current) = current
708                        && current.get_symbol().get_stripped_name().starts_with("S_")
709                    {
710                        out.push_str(current.get_symbol().get_stripped_name());
711                    } else {
712                        out.push_str("S_");
713                        current
714                            .format(&mut out, opt, PrintState::new())
715                            .unwrap();
716                    }
717                    out.push('⊛');
718                    let given = args.next().unwrap();
719                    if let AtomView::Var(given) = given
720                        && let Some(label) =
721                            given.get_symbol().get_stripped_name().strip_prefix("S_")
722                    {
723                        out.push_str(label);
724                    } else {
725                        given.format(&mut out, opt, PrintState::new()).unwrap();
726                    }
727                    Some(out)
728                }
729                _ => None,
730            }
731        }
732    ),
733    uv_approx: symbol!(
734        "Approx",
735        print = |a, opt, _state| spenso_print_uv_unary!(a, opt, "K[", "]")
736    ),
737    uv_integrate: symbol!(
738        "Integrate",
739        print = |a, opt, _state| spenso_print_uv_unary!(a, opt, "⟨", "⟩")
740    ),
741    uv_series: symbol!(
742        "Series",
743        print = |a, opt, _state| spenso_print_uv_unary!(a, opt, "Σ(", ")")
744    ),
745    uv_truncate: symbol!(
746        "Truncate",
747        print = |a, opt, _state| {
748            let prefix = if opt.typst_mode().is_some() {
749                r#"op("Tr")("#
750            } else {
751                "Tr("
752            };
753            spenso_print_uv_unary!(a, opt, prefix, ")")
754        }
755    ),
756    ct_marker: symbol!(
757        "CT",
758        print = |a, opt, _state| {
759            spenso_print_uv_unary!(a, opt, "", "").map(|out| {
760                if opt.color_builtin_symbols {
761                    nu_ansi_term::Color::Magenta.paint(out).to_string()
762                } else {
763                    out
764                }
765            })
766        }
767    ),
768    is_function: symbol!("is_function"),
769    is_symbol: symbol!("is_symbol"),
770    nc2_1: symbol!("NC2_1"),
771    rescale: symbol!("t";Scalar,Real,Positive),
772    integrated_loop_scale: symbol!("uvIntegratedLoopScale";Scalar,Real,Positive),
773    rescale_mass: symbol!("t_m";Scalar,Real,Positive),
774    _linear: symbol!("_linear";Linear),
775    linearize: symbol!(
776        "linearize",
777        norm = |f, out| {
778            if let AtomView::Fun(ff) = f
779                && ff.get_nargs() == 1
780                && let AtomView::Fun(arg) = ff.iter().next().unwrap()
781            {
782                let mut args = vec![];
783                for a in arg.iter() {
784                    if let AtomView::Fun(a) = a
785                        && a.get_symbol().get_wildcard_level() > 0
786                    {
787                        return;
788                    }
789                    if let AtomView::Var(a) = a
790                        && a.get_symbol().get_wildcard_level() > 0
791                    {
792                        return;
793                    }
794
795                    args.push(a);
796                }
797                let expr = GS
798                    ._linear
799                    .call_args(args.as_slice())
800                    .replace(GS._linear.call_args([W_.a___]))
801                    .with(arg.get_symbol().call_args([W_.a___]));
802                println!(":{}->{}", f, expr);
803                **out = expr;
804            }
805        }
806    ),
807    broadcasting_sqrt: symbol!(
808        "broadcasting_sqrt",
809        tag = SPENSO_TAG.broadcast,
810        der = |a, _, out| {
811            **out = Atom::num(1) / (Atom::num(2) * a);
812        }
813    ),
814    expansion: symbol!("expansion"),
815    rescale_star: symbol!("t⃰"),
816    hfunction_lu_cut: symbol!("h_lu_cut"),
817    hfunction_left_th: symbol!("h_left_th"),
818    hfunction_right_th: symbol!("h_right_th"),
819    eta: symbol!("η"),
820    eta_left: symbol!("η_left"),
821    eta_right: symbol!("η_right"),
822    deta_lu_cut: symbol!("∇η_lu_cut"),
823    deta_left_th: symbol!("∇η_left_th"),
824    deta_right_th: symbol!("∇η_right_th"),
825    edgeid: symbol!("eid"),
826    vertexid: symbol!("vid"),
827    source_id: symbol!("source"),
828    sink_id: symbol!("sink"),
829    sign: symbol!("σ"; Scalar),
830    selected: symbol!("selected"),
831    theta: symbol!("θ"),
832    m_uv_expansion: symbol!(
833        "mUVexp",
834        print = |a, opt, _state| {
835            let AtomView::Var(_a) = a else {
836                return None;
837            };
838            match opt.custom_print_mode.get("spenso") {
839                Some(PrintUserData::Integer(_)) if opt.typst_mode().is_some() => {
840                    Some("m_\"UVexp\"".to_string())
841                }
842                _ => None,
843            }
844        }
845    ),
846    m_uv_vacuum: symbol!(
847        "mUV",
848        print = |a, opt, _state| {
849            let AtomView::Var(_a) = a else {
850                return None;
851            };
852            match opt.custom_print_mode.get("spenso") {
853                Some(PrintUserData::Integer(_)) if opt.typst_mode().is_some() => {
854                    Some("m_\"UV\"".to_string())
855                }
856                _ => None,
857            }
858        }
859    ),
860    mu_r_sq: symbol!(
861        "μᵣ²",
862        print = |a, opt, _state| {
863            let AtomView::Var(_a) = a else {
864                return None;
865            };
866            match opt.custom_print_mode.get("spenso") {
867                Some(PrintUserData::Integer(_)) if opt.typst_mode().is_some() => {
868                    Some("mu_r^2".to_string())
869                }
870                _ => None,
871            }
872        }
873    ),
874    delta_vec: ETS.delta,
875    expr: symbol!(
876        "expr",
877        print = |_, opt, _state| {
878            Some(match opt.typst_mode()? {
879                TypstMode::Math | TypstMode::Markup => "#expr".to_string(),
880                TypstMode::Code => "expr".to_string(),
881            })
882        }
883    ),
884    num: symbol!("num"),
885    den: symbol!(
886        "denom",
887        der = |_, arg, out| {
888            if arg != 3 {
889                **out = Atom::Zero;
890            } else {
891                **out = Atom::num(1);
892            }
893        }
894    ),
895    ubar: symbol!(
896        "ubar",
897        print = |a, opt, _state| {
898            spenso_print_scripted_indexed!(a, opt, "u̅", "overline(u)")
899        },
900        tags = [SPENSO_TAG.rank1.clone(), SPENSO_TAG.tensor.clone()]
901    ),
902    vbar: symbol!(
903        "vbar",
904        print = |a, opt, _state| {
905            spenso_print_scripted_indexed!(a, opt, "v̅", "overline(v)")
906        },
907        tags = [SPENSO_TAG.rank1.clone(), SPENSO_TAG.tensor.clone()]
908    ),
909    dot: symbol!("dot"),
910    dim: symbol!("dim"),
911    v: symbol!(
912        "v",
913        print = |a, opt, _state| { spenso_print_scripted_indexed!(a, opt, "v") },
914        tags = [SPENSO_TAG.rank1.clone(), SPENSO_TAG.tensor.clone()]
915    ),
916    u: symbol!(
917        "u",
918        print = |a, opt, _state| { spenso_print_scripted_indexed!(a, opt, "u") },
919        tags = [SPENSO_TAG.rank1.clone(), SPENSO_TAG.tensor.clone()]
920    ),
921    emr_mom: symbol!(
922        "Q",
923        print = |a, opt, _state| { spenso_print_scripted_indexed!(a, opt, "q") },
924        tags = [SPENSO_TAG.rank1.clone(), SPENSO_TAG.tensor.clone()]
925    ),
926    orientation_delta: symbol!("orientation_delta"),
927    emr_vec: symbol!(
928        "Q3",
929        norm = |f, out| {
930            if let AtomView::Fun(ff) = f
931                && ff.get_nargs() == 2
932            {
933                let mut iter = ff.iter();
934                let eid = iter.next().unwrap();
935                if let AtomView::Fun(cind) = iter.next().unwrap()
936                    && cind.get_symbol() == AIND_SYMBOLS.cind
937                    && let Some(i) = cind.iter().next()
938                    && let Ok(i) = i64::try_from(i)
939                {
940                    if i == 0 {
941                        **out = Atom::Zero;
942                    } else {
943                        **out = get_symbol!("Q").unwrap().call_args([eid, cind.as_view()])
944                    }
945                }
946            }
947        },
948        print = |a, opt, _state| {
949            spenso_print_scripted_indexed!(a, opt, "q⃗", "arrow(q)")
950        },
951        tags = [SPENSO_TAG.rank1.clone(), SPENSO_TAG.tensor.clone()]
952    ),
953    ose: symbol!(
954        "OSE"; Scalar;
955        print = |a, opt, _state| {
956            spenso_print_simple_indexed!(a, opt, "Eᵒˢ", r#"E^("os")"#)
957        },
958            der = |_, arg, out| {
959                if arg == 1 {
960                    **out = Atom::num(1);
961                }
962            }
963    ),
964    energy: symbol!(
965        "E",
966        print = |a, opt, _state| { spenso_print_simple_indexed!(a, opt, "E") }
967    ),
968    external_mom: symbol!(
969        "P",
970        print = |a, opt, _state| { spenso_print_scripted_indexed!(a, opt, "p") },
971        tags = [SPENSO_TAG.rank1.clone(), SPENSO_TAG.tensor.clone()]
972    ),
973    loop_mom: symbol!(
974        "K",
975        print = |a, opt, _state| { spenso_print_scripted_indexed!(a, opt, "k") },
976        tags = [SPENSO_TAG.rank1.clone(), SPENSO_TAG.tensor.clone()]
977    ),
978    epsilon: symbol!(
979        "ϵ",
980        print = |a, opt, _state| { spenso_print_scripted_indexed!(a, opt, "ϵ") },
981        tags = [SPENSO_TAG.rank1.clone(), SPENSO_TAG.tensor.clone()]
982    ),
983    pi: Symbol::PI,
984    color_wrap: symbol!("color"),
985    epsilonbar: symbol!(
986        "ϵbar",
987        print = |a, opt, _state| {
988            spenso_print_scripted_indexed!(a, opt, "ϵ̅", "overline(epsilon.alt)")
989        },
990        tags = [SPENSO_TAG.rank1.clone(), SPENSO_TAG.tensor.clone()]
991    ),
992    coeff: symbol!("coef"),
993    radius_left: symbol!("r_left"),
994    radius_star_left: symbol!("r⃰_left"),
995    uv_damp_plus_left: symbol!("damp_plus_left"),
996    uv_damp_minus_left: symbol!("damp_minus_left"),
997    radius_right: symbol!("r_right"),
998    radius_star_right: symbol!("r⃰_right"),
999    uv_damp_plus_right: symbol!("damp_plus_right"),
1000    uv_damp_minus_right: symbol!("damp_minus_right"),
1001};
1002}
1003
1004impl GammaloopSymbols {
1005    pub fn integrand<O: GraphOrientation>(&self, i: usize, orientation: &O) -> Atom {
1006        let args = orientation
1007            .orientation()
1008            .iter()
1009            .map(|(_, ori)| match ori {
1010                Orientation::Default => Atom::num(1),
1011                Orientation::Reversed => Atom::num(-1),
1012                Orientation::Undirected => Atom::num(0),
1013            })
1014            .collect_vec();
1015
1016        FunctionBuilder::new(self.integrand)
1017            .add_arg(i)
1018            .add_args(&args)
1019            .finish()
1020    }
1021
1022    pub fn wrap_tree_denoms<'a>(&self, arg: impl Into<AtomOrView<'a>>) -> Atom {
1023        self.tree_denom_wrapper.call_args([arg.into().as_view()])
1024    }
1025
1026    pub fn do_dot_product_in_sqrt<'a>(&self, arg: impl Into<AtomOrView<'a>>) -> Atom {
1027        let a = arg.into();
1028
1029        let mink = Minkowski {}.new_rep(4).to_symbolic([Atom::var(W_.i_)]);
1030
1031        let pat = function!(self.emr_vec, W_.a_, mink) * function!(self.emr_vec, W_.b_, mink);
1032        let rhs = function!(
1033            self.emr_mom,
1034            W_.a_,
1035            Atom::from(ExpandedIndex::from_iter([1]))
1036        ) * function!(
1037            self.emr_mom,
1038            W_.b_,
1039            Atom::from(ExpandedIndex::from_iter([1]))
1040        ) + function!(
1041            self.emr_mom,
1042            W_.a_,
1043            Atom::from(ExpandedIndex::from_iter([2]))
1044        ) * function!(
1045            self.emr_mom,
1046            W_.b_,
1047            Atom::from(ExpandedIndex::from_iter([2]))
1048        ) + function!(
1049            self.emr_mom,
1050            W_.a_,
1051            Atom::from(ExpandedIndex::from_iter([3]))
1052        ) * function!(
1053            self.emr_mom,
1054            W_.b_,
1055            Atom::from(ExpandedIndex::from_iter([3]))
1056        );
1057
1058        let a = a.replace(pat).with(rhs);
1059
1060        let pat = function!(self.emr_vec, W_.a_, mink).pow(2);
1061        let rhs = function!(
1062            self.emr_mom,
1063            W_.a_,
1064            Atom::from(ExpandedIndex::from_iter([1]))
1065        )
1066        .pow(2)
1067            + function!(
1068                self.emr_mom,
1069                W_.a_,
1070                Atom::from(ExpandedIndex::from_iter([2]))
1071            )
1072            .pow(2)
1073            + function!(
1074                self.emr_mom,
1075                W_.a_,
1076                Atom::from(ExpandedIndex::from_iter([3]))
1077            )
1078            .pow(2);
1079        // * GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([1])))
1080        // + GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([2])))
1081        //     * GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([2])))
1082        // + GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([3])))
1083        //     * GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([3])));
1084
1085        // let dot = self.emr_vec_index(e, mink.to_atom()) * self.emr_vec_index(e, mink.to_atom())
1086        a.replace(pat).with(rhs)
1087
1088        // a.replace_map(|a, ctx, out| {
1089        //     if let AtomView::Pow(p) = a {
1090        //         let (a, b) = p.get_base_exp();
1091        //         let Ok(exp) = Rational::try_from(b) else {
1092        //             return;
1093        //         };
1094
1095        //         if exp.denominator() == Integer::from(2) {
1096        //             **out = self
1097        //                 .broadcasting_sqrt
1098        //                 .call_args([a])
1099        //                 .pow(Atom::num(exp.numerator()));
1100        //         }
1101        //     }
1102        // });
1103    }
1104
1105    pub fn to_broadcasting_sqrt<'a>(&self, arg: impl Into<AtomOrView<'a>>) -> Atom {
1106        let a = arg.into();
1107        a.replace_map(|a, _ctx, out| {
1108            if let AtomView::Pow(p) = a {
1109                let (a, b) = p.get_base_exp();
1110                let Ok(exp) = Rational::try_from(b) else {
1111                    return;
1112                };
1113
1114                if exp.denominator() == 2 {
1115                    **out = self
1116                        .broadcasting_sqrt
1117                        .call_args([a])
1118                        .pow(Atom::num(exp.numerator()));
1119                }
1120            }
1121        })
1122    }
1123
1124    pub(crate) fn emr_mom<'a>(&self, e: EdgeIndex, arg: impl Into<AtomOrView<'a>>) -> Atom {
1125        let a = arg.into();
1126        function!(self.emr_mom, usize::from(e) as i64, a.as_view())
1127    }
1128
1129    pub(crate) fn localizing_integrand(&self, lmb: &LoopMomentumBasis) -> Atom {
1130        // Normalize each factor with int d^3k / (|k|^2 + rls^2)^2 = pi^2 / rls,
1131        // so the localizing integrand itself integrates to one.
1132        let pi_atom = (Symbol::PI).to_atom();
1133        let normalization_term_integral = (pi_atom.pow(2)) / GS.renormalization_localization_scale;
1134
1135        let mut res = Atom::one();
1136
1137        for l in lmb.loop_edges.iter() {
1138            //TODO: Add orientation localisation prefactor (Sum of valid orientation thetas)/(number of valid orientations)
1139            res /= normalization_term_integral.as_view();
1140
1141            let spatial_norm_sq = function!(self.emr_mom, l.0, GS.cind(1)).pow(2)
1142                + function!(self.emr_mom, l.0, GS.cind(2)).pow(2)
1143                + function!(self.emr_mom, l.0, GS.cind(3)).pow(2);
1144
1145            // Per-orientation CFF localizer of the normalized cubic tadpole.
1146            let denominator = spatial_norm_sq
1147                + GS.renormalization_localization_scale * GS.renormalization_localization_scale;
1148            res /= denominator.as_view() * denominator.as_view();
1149        }
1150
1151        if res.is_one() {
1152            res
1153        } else {
1154            function!(self.localizing_integrand, res)
1155        }
1156    }
1157
1158    pub(crate) fn emr_vec(&self, e: EdgeIndex) -> Atom {
1159        function!(GS.emr_vec, usize::from(e) as i64)
1160    }
1161
1162    pub(crate) fn emr_vec_index<'a>(&self, e: EdgeIndex, index: impl Into<AtomOrView<'a>>) -> Atom {
1163        function!(GS.emr_vec, usize::from(e) as i64, index.into().as_view())
1164    }
1165
1166    pub(crate) fn cind(&self, e: usize) -> Atom {
1167        AIND_SYMBOLS.cind.call_args([e as i64])
1168    }
1169
1170    pub(crate) fn energy_delta<'a>(&self, index: impl Into<AtomOrView<'a>>) -> Atom {
1171        self.delta_vec
1172            .call_args([self.cind(0), index.into().into_owned()])
1173    }
1174
1175    pub(crate) fn ose_full(
1176        &self,
1177        e: EdgeIndex,
1178        lmb_id: EdgeIndex,
1179        e_mass: Atom,
1180        index: Option<Atom>,
1181        inner_product: bool,
1182    ) -> Atom {
1183        let m2 = &e_mass * &e_mass;
1184
1185        let mink: Representation<Minkowski> = Minkowski {}.new_rep(4); //.slot(Aind::new_dummy());
1186        let q3q3 = if inner_product {
1187            mink.inner_product(self.emr_vec(e), self.emr_vec(e))
1188        } else {
1189            let mink = mink.slot::<Aind, Aind>(Aind::new_dummy()).to_atom();
1190
1191            self.emr_vec_index(e, mink.as_view()) * self.emr_vec_index(e, mink.as_view())
1192        };
1193
1194        let ose = function!(self.ose, lmb_id.0, (m2 - q3q3)).pow((1, 2));
1195
1196        if let Some(index) = index {
1197            ose * self.energy_delta(index)
1198        } else {
1199            ose
1200        }
1201    }
1202
1203    pub(crate) fn split_mom_pattern(
1204        &self,
1205        e: EdgeIndex,
1206        lmb_id: EdgeIndex,
1207        e_mass: Atom,
1208        inner_product: bool,
1209    ) -> Replacement {
1210        let id = Minkowski {}.to_symbolic([W_.a__]);
1211        Replacement::new(
1212            self.emr_mom(e, &id).to_pattern(),
1213            self.emr_vec_index(e, &id)
1214                + self.ose_full(e, lmb_id, e_mass, Some(id), inner_product) * sign_atom(e),
1215        )
1216    }
1217
1218    /// Add the sign by splitting Q(i,mu)-> Q3(i,mu)+OSE(i)*σ(i)*δ(cind(0),mu)
1219    pub fn split_mom_pattern_simple(&self, e: EdgeIndex) -> Replacement {
1220        let eidc = usize::from(e) as i64;
1221        let index = Minkowski {}.to_symbolic([W_.a__]);
1222        Replacement::new(
1223            self.emr_mom(e, &index).to_pattern(),
1224            function!(GS.ose, eidc) * sign_atom(e) * self.energy_delta(&index)
1225                + function!(GS.emr_vec, eidc, &index),
1226        )
1227    }
1228
1229    pub(crate) fn add_parametric_sign(&self, e: EdgeIndex) -> Replacement {
1230        Replacement::new(
1231            self.emr_mom(e, AIND_SYMBOLS.cind.call_args([Atom::Zero]))
1232                .to_pattern(),
1233            sign_atom(e) * self.ose(e),
1234        )
1235    }
1236
1237    pub(crate) fn ose(&self, e: EdgeIndex) -> Atom {
1238        function!(GS.ose, usize::from(e) as i64)
1239    }
1240}
1241
1242pub(crate) fn sign_atom(eid: EdgeIndex) -> Atom {
1243    FunctionBuilder::new(symbol!("σ"))
1244        .add_arg(usize::from(eid) as i64)
1245        .finish()
1246}
1247
1248#[cfg(test)]
1249mod tests {
1250    use insta::assert_snapshot;
1251    use spenso::shadowing::symbolica_utils::LogPrint;
1252
1253    use super::*;
1254
1255    #[test]
1256    fn test_print() {
1257        let p = GS.emr_mom(EdgeIndex(1), Atom::Zero);
1258
1259        assert_snapshot!(p.log_print(None),@"q₁(0)")
1260    }
1261
1262    #[test]
1263    fn typst_uses_structural_accents_and_scripts() {
1264        let expressions = [
1265            (
1266                GS.ubar.call_args([Atom::num(1), Atom::num(11)]),
1267                "overline(u)_1^(11)",
1268            ),
1269            (
1270                GS.vbar.call_args([Atom::num(2), Atom::num(12)]),
1271                "overline(v)_2^(12)",
1272            ),
1273            (
1274                GS.ose.call_args([Atom::num(3), Atom::num(13)]),
1275                r#"E^("os")_3(13)"#,
1276            ),
1277            (
1278                GS.emr_vec.call_args([Atom::num(4), Atom::num(14)]),
1279                "arrow(q)_4^(14)",
1280            ),
1281            (
1282                GS.epsilonbar.call_args([Atom::num(5), Atom::num(15)]),
1283                "overline(epsilon.alt)_5^(15)",
1284            ),
1285            (
1286                GS.emr_mom.call_args([Atom::num(6), Atom::num(16)]),
1287                "q_6^(16)",
1288            ),
1289        ];
1290
1291        for (expression, expected) in expressions {
1292            assert_eq!(
1293                expression
1294                    .printer(SpensoPrintSettings::typst_options())
1295                    .to_string(),
1296                expected
1297            );
1298        }
1299    }
1300
1301    #[test]
1302    fn typst_syntax_requires_real_typst_mode() {
1303        let mut symbolica = SpensoPrintSettings::typst().nice_symbolica();
1304        symbolica.color_builtin_symbols = false;
1305
1306        assert_eq!(
1307            GS.ubar
1308                .call_args([Atom::num(1), Atom::num(11)])
1309                .printer(symbolica.clone())
1310                .to_string(),
1311            "u̅₁^(11)"
1312        );
1313        assert_eq!(
1314            Atom::var(GS.m_uv_vacuum).printer(symbolica).to_string(),
1315            "mUV"
1316        );
1317
1318        assert_eq!(
1319            Atom::var(GS.m_uv_expansion)
1320                .printer(SpensoPrintSettings::typst_options())
1321                .to_string(),
1322            r#"m_"UVexp""#
1323        );
1324        assert_eq!(
1325            Atom::var(GS.m_uv_vacuum)
1326                .printer(SpensoPrintSettings::typst_options())
1327                .to_string(),
1328            r#"m_"UV""#
1329        );
1330        assert_eq!(
1331            Atom::var(GS.mu_r_sq)
1332                .printer(SpensoPrintSettings::typst_options())
1333                .to_string(),
1334            "mu_r^2"
1335        );
1336    }
1337}