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 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 pub dim_epsilon: Symbol,
154
155 pub epsilon: Symbol,
156 pub epsilonbar: Symbol,
157 pub rescale: Symbol,
158 pub integrated_loop_scale: Symbol,
160 pub rescale_mass: Symbol,
161 pub rescale_star: Symbol,
162 pub pi: Symbol,
163
164 pub m_uv_expansion: Symbol,
170 pub m_uv_vacuum: Symbol,
175 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 pub selected: Symbol,
183
184 pub expansion: Symbol,
186 pub delta_vec: Symbol,
188 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 pub ct_marker: Symbol,
205
206 pub nc2_1: Symbol,
207 pub expr: Symbol,
208 pub num: Symbol,
209 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 a.replace(pat).with(rhs)
1087
1088 }
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 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 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 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); 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 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),@"[35mq₁[0m(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}