Skip to main content

gammalooprs/cff/
hsurface.rs

1use crate::cff::cff_graph::VertexSet;
2use crate::utils::{cut_energy, external_energy_atom_from_index, ose_atom_from_index};
3use bincode_trait_derive::{Decode, Encode};
4
5use derive_more::{From, Into};
6use itertools::Itertools;
7use linnet::half_edge::involution::EdgeIndex;
8use serde::{Deserialize, Serialize};
9use symbolica::atom::Atom;
10use symbolica::parse;
11use tracing::warn;
12use typed_index_collections::TiVec;
13
14use crate::graph::LoopMomentumBasis;
15use crate::momentum::SignOrZero;
16
17use super::esurface::Esurface;
18use super::esurface::ExternalShift;
19
20#[derive(
21    From, Into, Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Encode, Decode, Hash,
22)]
23pub struct HsurfaceID(usize);
24pub type HsurfaceCollection = TiVec<HsurfaceID, Hsurface>;
25pub type HsurfaceCache<T> = TiVec<HsurfaceID, T>;
26
27#[derive(Serialize, Deserialize, Debug, Clone)]
28/// H-surface of the supergraph, is most likely E-surface of the amplitude, kind of badly named.
29pub struct Hsurface {
30    pub positive_energies: Vec<EdgeIndex>,
31    pub negative_energies: Vec<EdgeIndex>,
32    pub external_shift: ExternalShift,
33    pub vertex_set: VertexSet,
34}
35
36impl PartialEq for Hsurface {
37    fn eq(&self, other: &Self) -> bool {
38        self.positive_energies == other.positive_energies
39            && self.negative_energies == other.negative_energies
40    }
41}
42
43impl Hsurface {
44    pub(crate) fn to_atom(&self, cut_edges: &[EdgeIndex]) -> Atom {
45        self.to_atom_impl(cut_edges, external_energy_atom_from_index)
46    }
47
48    pub(crate) fn to_atom_in_lmb(&self, cut_edges: &[EdgeIndex], lmb: &LoopMomentumBasis) -> Atom {
49        self.to_atom_impl(cut_edges, |edge| {
50            lmb.edge_signatures[edge].external.iter_enumerated().fold(
51                Atom::Zero,
52                |sum, (external_index, sign)| {
53                    let atom = external_energy_atom_from_index(lmb.ext_edges[external_index]);
54                    match sign {
55                        SignOrZero::Zero => sum,
56                        SignOrZero::Plus => sum + atom,
57                        SignOrZero::Minus => sum - atom,
58                    }
59                },
60            )
61        })
62    }
63
64    fn to_atom_impl(
65        &self,
66        cut_edges: &[EdgeIndex],
67        external_shift_atom: impl Fn(EdgeIndex) -> Atom,
68    ) -> Atom {
69        let (symbolic_positive_energies, symbolic_negative_energies) =
70            [&self.positive_energies, &self.negative_energies]
71                .iter()
72                .map(|energies| {
73                    energies
74                        .iter()
75                        .map(|i| {
76                            if cut_edges.contains(i) {
77                                cut_energy(*i)
78                            } else {
79                                ose_atom_from_index(*i)
80                            }
81                        })
82                        .collect_vec()
83                })
84                .collect_tuple()
85                .unwrap_or_else(|| unreachable!());
86
87        let symbolic_shift = self
88            .external_shift
89            .iter()
90            .fold(Atom::new(), |sum, (i, sign)| {
91                Atom::num(*sign) * external_shift_atom(*i) + &sum
92            });
93
94        let symbolic_sum_positive_energies = symbolic_positive_energies
95            .iter()
96            .fold(Atom::new(), |sum, e| sum + e);
97
98        let symbolic_sum_negative_energies = symbolic_negative_energies
99            .iter()
100            .fold(Atom::new(), |sum, e| sum + e);
101
102        symbolic_sum_positive_energies - &symbolic_sum_negative_energies + &symbolic_shift
103    }
104
105    #[allow(dead_code)]
106    pub(crate) fn equality_under_energy_conservation(
107        &self,
108        other: &Esurface,
109        constraints: &[&Esurface],
110    ) -> Option<bool> {
111        if !self.external_shift.is_empty() {
112            warn!("this is not handled yet");
113            return None;
114        }
115        constraints
116            .iter()
117            .find(|esurface| {
118                self.negative_energies
119                    .iter()
120                    .all(|index| esurface.energies.contains(index))
121            })
122            .map(|constraint| {
123                let energies_to_be_added = constraint
124                    .energies
125                    .iter()
126                    .filter(|index| !self.negative_energies.contains(index));
127
128                let mut new_positive_energies = self.positive_energies.clone();
129                new_positive_energies.extend(energies_to_be_added);
130                new_positive_energies.sort();
131
132                let new_esurface = Esurface {
133                    energies: new_positive_energies,
134                    external_shift: constraint.external_shift.clone(),
135                    vertex_set: VertexSet::dummy(),
136                };
137
138                other == &new_esurface
139            })
140    }
141
142    pub fn equality_by_try_convert(&self, other: &Esurface) -> bool {
143        let negative_as_external_shift = self
144            .negative_energies
145            .iter()
146            .map(|e| (*e, -1))
147            .collect_vec();
148
149        let self_as_esurface = Esurface {
150            energies: self.positive_energies.clone(),
151            external_shift: negative_as_external_shift,
152            vertex_set: VertexSet::dummy(),
153        };
154
155        self_as_esurface == *other
156    }
157}
158
159impl From<HsurfaceID> for Atom {
160    fn from(value: HsurfaceID) -> Self {
161        parse!(&format!("H({})", Into::<usize>::into(value)))
162    }
163}
164
165#[cfg(test)]
166mod tests {
167
168    use linnet::half_edge::involution::EdgeIndex;
169    use linnet::half_edge::subgraph::{SuBitGraph, SubSetLike};
170    use symbolica::atom::{Atom, AtomCore};
171
172    use symbolica::parse;
173
174    use crate::cff::{cff_graph::VertexSet, esurface::Esurface};
175    use crate::graph::LoopMomentumBasis;
176    use crate::momentum::signature::LoopExtSignature;
177    use crate::utils::{external_energy_atom_from_index, test_utils::dummy_hedge_graph};
178
179    use super::Hsurface;
180
181    #[test]
182    fn test_equality_under_energy_conservation() {
183        let constraint = Esurface {
184            energies: vec![EdgeIndex::from(1), EdgeIndex::from(2)],
185            external_shift: vec![(EdgeIndex::from(0), -1)],
186            vertex_set: VertexSet::dummy(),
187        };
188
189        let hsurface = Hsurface {
190            positive_energies: vec![EdgeIndex::from(3), EdgeIndex::from(5)],
191            negative_energies: vec![EdgeIndex::from(2)],
192            external_shift: vec![],
193            vertex_set: VertexSet::dummy(),
194        };
195
196        let other = Esurface {
197            energies: vec![EdgeIndex::from(1), EdgeIndex::from(3), EdgeIndex::from(5)],
198            external_shift: vec![(EdgeIndex::from(0), -1)],
199            vertex_set: VertexSet::dummy(),
200        };
201
202        assert!(
203            hsurface
204                .equality_under_energy_conservation(&other, &[&constraint])
205                .unwrap()
206        );
207    }
208
209    #[test]
210    fn to_atom_in_lmb_uses_canonical_external_edges_not_carrier_edges() {
211        let dummy_graph = dummy_hedge_graph(9);
212        let mut edge_signatures = dummy_graph
213            .new_edgevec_from_iter(
214                (0..9).map(|_| LoopExtSignature::from((Vec::<isize>::new(), vec![0, 0]))),
215            )
216            .unwrap();
217        edge_signatures[EdgeIndex::from(8)] =
218            LoopExtSignature::from((Vec::<isize>::new(), vec![0, 1]));
219        let lmb = LoopMomentumBasis {
220            tree: SuBitGraph::empty(0),
221            loop_edges: vec![].into(),
222            ext_edges: vec![EdgeIndex::from(2), EdgeIndex::from(6)].into(),
223            edge_signatures,
224        };
225        let hsurface = Hsurface {
226            positive_energies: vec![],
227            negative_energies: vec![],
228            external_shift: vec![(EdgeIndex::from(8), -1)],
229            vertex_set: VertexSet::dummy(),
230        };
231
232        let atom = hsurface.to_atom_in_lmb(&[], &lmb).expand();
233        let expected =
234            (Atom::num(-1) * external_energy_atom_from_index(EdgeIndex::from(6))).expand();
235
236        assert_eq!(atom.to_canonical_string(), expected.to_canonical_string());
237    }
238
239    mod failing {
240        use super::*;
241
242        #[test]
243        fn test_to_atom() {
244            let external_shift = vec![(EdgeIndex::from(4), -1), (EdgeIndex::from(5), 1)];
245            let h_surface = Hsurface {
246                positive_energies: vec![EdgeIndex::from(0), EdgeIndex::from(1)],
247                negative_energies: vec![EdgeIndex::from(2), EdgeIndex::from(3)],
248                external_shift,
249                vertex_set: VertexSet::dummy(),
250            };
251
252            let h_surface_atom = h_surface.to_atom(&[]);
253            let expected_atom = parse!(
254                "Q(0, cind(0)) + Q(1, cind(0)) - Q(2, cind(0)) - Q(3, cind(0)) - P(4, cind(0)) + P(5, cind(0))"
255            );
256            let diff = h_surface_atom - &expected_atom;
257            let diff = diff.expand();
258            assert_eq!(diff, Atom::new());
259        }
260    }
261}