Skip to main content

gammalooprs/cff/
surface.rs

1/// A surface whose inverse is zero, useful for setting surface terms to zero.
2pub use three_dimensional_reps::surface::InfiniteSurface;
3/// A surface equal to one, useful for representing a single vertex.
4pub 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}