1pub use three_dimensional_reps::surface::InfiniteSurface;
3pub use three_dimensional_reps::surface::UnitSurface;
5pub use three_dimensional_reps::surface::{
6 EsurfaceCollection, EsurfaceID, HsurfaceCollection, HsurfaceID, HybridSurfaceID,
7 LinearEnergyExpr, LinearSurface, LinearSurfaceCollection, LinearSurfaceID, LinearSurfaceKind,
8 SurfaceAtom, SurfaceOrigin,
9};
10
11use linnet::{half_edge::involution::EdgeIndex, num_traits::SignOrZero};
12use symbolica::{
13 atom::{Atom, AtomCore},
14 id::{Pattern, Replacement},
15};
16
17use super::{esurface::Esurface, hsurface::Hsurface};
18use crate::{
19 graph::LoopMomentumBasis,
20 utils::{GS, cut_energy, external_energy_atom_from_index, ose_atom_from_index},
21};
22
23pub type HybridSurface = three_dimensional_reps::surface::HybridSurface<Esurface, Hsurface>;
24pub type HybridSurfaceRef<'a> =
25 three_dimensional_reps::surface::HybridSurfaceRef<'a, Esurface, Hsurface>;
26pub type HybridSurfaceCollection =
27 three_dimensional_reps::surface::HybridSurfaceCollection<Esurface, Hsurface>;
28pub type HybridSurfaceCache<T> = three_dimensional_reps::surface::HybridSurfaceCache<T>;
29pub type SurfaceCache = three_dimensional_reps::surface::SurfaceCache<Esurface, Hsurface>;
30
31impl SurfaceAtom for Esurface {
32 fn to_atom(&self, cut_edges: &[EdgeIndex]) -> Atom {
33 self.to_atom(cut_edges)
34 }
35}
36
37impl SurfaceAtom for Hsurface {
38 fn to_atom(&self, cut_edges: &[EdgeIndex]) -> Atom {
39 self.to_atom(cut_edges)
40 }
41}
42
43pub trait GammaLoopLinearEnergyExpr {
44 fn to_atom_gs(&self, cut_edges: &[EdgeIndex]) -> Atom;
45}
46
47impl GammaLoopLinearEnergyExpr for LinearEnergyExpr {
48 fn to_atom_gs(&self, cut_edges: &[EdgeIndex]) -> Atom {
49 let internal = self
50 .internal_terms
51 .iter()
52 .fold(Atom::new(), |acc, (edge_id, coeff)| {
53 let energy = if cut_edges.contains(edge_id) {
54 cut_energy(*edge_id)
55 } else {
56 ose_atom_from_index(*edge_id)
57 };
58 acc + Atom::num(coeff.clone()) * energy
59 });
60
61 let external = self
62 .external_terms
63 .iter()
64 .fold(Atom::new(), |acc, (edge_id, coeff)| {
65 acc + Atom::num(coeff.clone()) * external_energy_atom_from_index(*edge_id)
66 });
67
68 let scale = if self.uniform_scale_coeff.is_zero() {
69 Atom::new()
70 } else {
71 Atom::num(self.uniform_scale_coeff.clone()) * Atom::var(GS.numerator_sampling_scale)
72 };
73
74 internal + external + scale + Atom::num(self.constant.clone())
75 }
76}
77
78pub trait GammaLoopHybridSurfaceRef {
79 fn to_atom_gs(&self, cut_edges: &[EdgeIndex]) -> Atom;
80}
81
82impl<E: SurfaceAtom, H: SurfaceAtom> GammaLoopHybridSurfaceRef
83 for three_dimensional_reps::surface::HybridSurfaceRef<'_, E, H>
84{
85 fn to_atom_gs(&self, cut_edges: &[EdgeIndex]) -> Atom {
86 match self {
87 three_dimensional_reps::surface::HybridSurfaceRef::Esurface(surface) => {
88 surface.to_atom(cut_edges)
89 }
90 three_dimensional_reps::surface::HybridSurfaceRef::Hsurface(surface) => {
91 surface.to_atom(cut_edges)
92 }
93 three_dimensional_reps::surface::HybridSurfaceRef::Linear(surface) => {
94 surface.expression.to_atom_gs(cut_edges)
95 }
96 three_dimensional_reps::surface::HybridSurfaceRef::Unit(_) => Atom::num(1),
97 three_dimensional_reps::surface::HybridSurfaceRef::Infinite(_) => {
98 symbolica::parse!("η_inf")
99 }
100 }
101 }
102}
103
104pub trait GammaLoopSurfaceCache {
105 fn get_all_replacements_gs(&self, cut_edges: &[EdgeIndex]) -> Vec<Replacement>;
106 fn get_all_replacements_gs_in_lmb(
107 &self,
108 cut_edges: &[EdgeIndex],
109 lmb: &LoopMomentumBasis,
110 ) -> Vec<Replacement>;
111}
112
113impl<E: SurfaceAtom, H: SurfaceAtom> GammaLoopSurfaceCache
114 for three_dimensional_reps::surface::SurfaceCache<E, H>
115{
116 fn get_all_replacements_gs(&self, cut_edges: &[EdgeIndex]) -> Vec<Replacement> {
117 self.iter_all_surfaces()
118 .map(|(id, surface)| {
119 let id_atom = Pattern::from(Atom::from(id));
120 let surface_atom = Pattern::from(surface.to_atom_gs(cut_edges));
121 Replacement::new(id_atom, surface_atom)
122 })
123 .collect()
124 }
125
126 fn get_all_replacements_gs_in_lmb(
127 &self,
128 cut_edges: &[EdgeIndex],
129 lmb: &LoopMomentumBasis,
130 ) -> Vec<Replacement> {
131 let external_shift_replacements = lmb
132 .edge_signatures
133 .iter()
134 .map(|(edge_id, signature)| {
135 let canonical = signature.external.iter_enumerated().fold(
136 Atom::Zero,
137 |sum, (external_index, sign)| {
138 let energy = external_energy_atom_from_index(lmb.ext_edges[external_index]);
139 match sign {
140 SignOrZero::Zero => sum,
141 SignOrZero::Plus => sum + energy,
142 SignOrZero::Minus => sum - energy,
143 }
144 },
145 );
146 Replacement::new(
147 external_energy_atom_from_index(edge_id).to_pattern(),
148 canonical,
149 )
150 })
151 .collect::<Vec<_>>();
152
153 self.iter_all_surfaces()
154 .map(|(id, surface)| {
155 let id_atom = Pattern::from(Atom::from(id));
156 let surface_atom = Pattern::from(
157 surface
158 .to_atom_gs(cut_edges)
159 .replace_multiple(&external_shift_replacements),
160 );
161 Replacement::new(id_atom, surface_atom)
162 })
163 .collect()
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use linnet::half_edge::{
170 involution::EdgeIndex,
171 subgraph::{SuBitGraph, SubSetLike},
172 };
173 use symbolica::atom::{Atom, AtomCore};
174
175 use super::{EsurfaceID, GammaLoopSurfaceCache, SurfaceCache};
176 use crate::{
177 cff::{VertexSet, esurface::Esurface},
178 graph::LoopMomentumBasis,
179 momentum::signature::LoopExtSignature,
180 utils::{external_energy_atom_from_index, test_utils::dummy_hedge_graph},
181 };
182
183 #[test]
184 fn replacements_in_lmb_use_canonical_external_edges_not_carrier_edges() {
185 let dummy_graph = dummy_hedge_graph(9);
186 let mut edge_signatures = dummy_graph
187 .new_edgevec_from_iter(
188 (0..9).map(|_| LoopExtSignature::from((Vec::<isize>::new(), vec![0, 0]))),
189 )
190 .unwrap();
191 edge_signatures[EdgeIndex::from(8)] =
192 LoopExtSignature::from((Vec::<isize>::new(), vec![0, 1]));
193 let lmb = LoopMomentumBasis {
194 tree: SuBitGraph::empty(0),
195 loop_edges: vec![].into(),
196 ext_edges: vec![EdgeIndex::from(2), EdgeIndex::from(6)].into(),
197 edge_signatures,
198 };
199 let mut surfaces = SurfaceCache::new();
200 surfaces.esurface_cache.push(Esurface {
201 energies: vec![],
202 external_shift: vec![(EdgeIndex::from(8), -1)],
203 vertex_set: VertexSet::dummy(),
204 });
205
206 let atom = Atom::from(EsurfaceID::from(0))
207 .replace_multiple(surfaces.get_all_replacements_gs_in_lmb(&[], &lmb))
208 .expand();
209 let expected =
210 (Atom::num(-1) * external_energy_atom_from_index(EdgeIndex::from(6))).expand();
211
212 assert!((atom - expected).is_zero());
213 }
214}