Skip to main content

gammalooprs/cff/
mod.rs

1use std::{collections::BTreeMap, fmt::Display};
2
3use bincode_trait_derive::{Decode, Encode};
4use linnet::half_edge::{
5    involution::{EdgeVec, Orientation},
6    subgraph::{SubGraphLike, SubSetLike, SubSetOps},
7};
8use symbolica::atom::{Atom, AtomCore};
9
10use crate::{
11    cff::orientations::GraphOrientation,
12    graph::{FeynmanGraph, Graph, cuts::CutSet, get_cff_inverse_energy_product_impl},
13    settings::global::OrientationPattern,
14    utils::GS,
15    uv::Integrands,
16};
17use color_eyre::Result;
18
19pub mod cff_graph;
20pub mod orientations;
21//pub mod cut_expression;
22pub mod esurface;
23pub mod expression;
24pub mod generation;
25pub mod hsurface;
26pub mod surface;
27pub mod tree;
28
29pub struct CFFTerm {
30    // One per orientation
31    pub expression: Vec<Atom>,
32    pub orientations: Vec<EdgeVec<Orientation>>,
33}
34
35impl CFFTerm {
36    pub fn expression_with_selectors(&self) -> Atom {
37        let mut result = Atom::Zero;
38        for (expr, orient) in self.expression.iter().zip(self.orientations.iter()) {
39            result += expr.clone() * orient.orientation_thetas();
40        }
41        result
42    }
43}
44
45#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Encode, Decode)]
46// This describes the combinations of residues that are selected.
47pub struct CutCFFIndex {
48    pub left_threshold_order: Option<usize>,
49    pub right_threshold_order: Option<usize>,
50    pub lu_cut_order: Option<usize>,
51}
52
53impl CutCFFIndex {
54    pub fn new_all_none() -> Self {
55        Self {
56            left_threshold_order: None,
57            right_threshold_order: None,
58            lu_cut_order: None,
59        }
60    }
61}
62
63impl Display for CutCFFIndex {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        let mut parts = vec![];
66        if let Some(order) = self.lu_cut_order {
67            parts.push(format!("lu_cut_{}", order));
68        }
69
70        if let Some(order) = self.left_threshold_order {
71            parts.push(format!("left_th_{}", order));
72        }
73
74        if let Some(order) = self.right_threshold_order {
75            parts.push(format!("right_th_{}", order));
76        }
77
78        if parts.is_empty() {
79            write!(f, "")
80        } else {
81            write!(f, "{}", parts.join("_"))
82        }
83    }
84}
85
86pub struct CutCFF {
87    pub terms: BTreeMap<CutCFFIndex, CFFTerm>,
88}
89
90impl CutCFF {
91    pub fn expression_with_selectors(&self) -> Integrands {
92        self.terms
93            .iter()
94            .map(|(index, term)| (*index, term.expression_with_selectors()))
95            .collect()
96    }
97}
98
99impl Graph {
100    pub fn cff<S: SubGraphLike + SubSetLike>(
101        &mut self,
102        contract_subgraph: &S,
103        cutset: &CutSet,
104        orientation_pattern: &OrientationPattern,
105    ) -> Result<CutCFF> {
106        let canonize_esurface = self.get_esurface_canonization(&self.loop_momentum_basis);
107        let mut contract_edges = vec![];
108
109        for (p, eid, _) in self.iter_edges_of(contract_subgraph) {
110            if p.is_paired() {
111                contract_edges.push(eid);
112            }
113        }
114
115        let cff = [(
116            CutCFFIndex::new_all_none(),
117            self.generate_cff(&contract_edges, &canonize_esurface, orientation_pattern)?,
118        )];
119
120        let mut residues = BTreeMap::new();
121
122        cff.into_iter()
123            .flat_map(|(index, cff_expression)| {
124                if let Some(right_threshold) = cutset.residue_selector.right_th_cut.as_ref() {
125                    cff_expression
126                        .select_esurface_residue(right_threshold)
127                        .into_iter()
128                        .enumerate()
129                        .map(|(i, residue)| {
130                            let mut new_index = index;
131                            new_index.right_threshold_order = Some(i + 1);
132                            (new_index, residue)
133                        })
134                        .collect()
135                } else {
136                    vec![(index, cff_expression)]
137                }
138            })
139            .flat_map(|(index, cff_expression)| {
140                if let Some(left_threshold) = cutset.residue_selector.left_th_cut.as_ref() {
141                    cff_expression
142                        .select_esurface_residue(left_threshold)
143                        .into_iter()
144                        .enumerate()
145                        .map(|(i, residue)| {
146                            let mut new_index = index;
147                            new_index.left_threshold_order = Some(i + 1);
148                            (new_index, residue)
149                        })
150                        .collect()
151                } else {
152                    vec![(index, cff_expression)]
153                }
154            })
155            .flat_map(|(index, cff_expression)| {
156                if let Some(lu_cut) = cutset.residue_selector.lu_cut.as_ref() {
157                    cff_expression
158                        .select_esurface_residue(lu_cut)
159                        .into_iter()
160                        .enumerate()
161                        .map(|(i, residue)| {
162                            let mut new_index = index;
163                            new_index.lu_cut_order = Some(i + 1);
164                            (new_index, residue)
165                        })
166                        .collect()
167                } else {
168                    vec![(index, cff_expression)]
169                }
170            })
171            .for_each(|(index, residue)| {
172                residues.insert(index, residue);
173            });
174
175        // println!("residue orders: {}", residue.len());
176
177        let graph_without_is_cut = self
178            .underlying
179            .full_filter()
180            .subtract(&self.initial_state_cut.left)
181            .subtract(&self.initial_state_cut.right);
182
183        // The CFF carries the measure normalization for the loop variables that remain after
184        // contracting a UV subgraph. Fully contracted integrated CTs therefore get no extra CFF
185        // measure factor, while ordinary root terms get the full graph-loop factor.
186        let cff_loop_number = self
187            .get_loop_number()
188            .saturating_sub(self.cyclotomatic_number(contract_subgraph));
189        let cff_phase = (-Atom::i()).pow(cff_loop_number as i64);
190        let cff_normalization = cff_phase / (Atom::var(GS.pi) * 2).pow(3 * cff_loop_number as i64);
191        crate::debug_tags!(#cff, #trace;
192            stage = "graph_cff_normalization",
193            graph = %self.name,
194            cff_loop_number = cff_loop_number,
195            log.cff_normalization = cff_normalization,
196            "Graph CFF normalization"
197        );
198
199        let mut terms = BTreeMap::new();
200
201        let replacement_rules = if cutset.canonicalize_external_shifts {
202            self.surface_cache
203                .get_all_replacements_in_lmb(&[], &self.loop_momentum_basis)
204        } else {
205            self.surface_cache.get_all_replacements(&[])
206        };
207
208        for (cut_cff_index, expr) in residues.into_iter() {
209            let mut cff_term = CFFTerm {
210                expression: vec![],
211                orientations: vec![],
212            };
213            for orientation in expr.orientations.iter() {
214                let eta_expr = orientation.expression.to_atom_inv();
215                let mut ose_expr = eta_expr.replace_multiple(&replacement_rules);
216
217                let inverse_energies = get_cff_inverse_energy_product_impl(
218                    self,
219                    &graph_without_is_cut,
220                    &contract_edges,
221                );
222
223                ose_expr *= inverse_energies;
224                ose_expr *= cff_normalization.clone();
225
226                crate::debug_tags!(#cff, #trace;
227                    stage = "graph_cff_term_expr",
228                    graph = %self.name,
229                    cut_index = ?cut_cff_index,
230                    log.expr = ose_expr,
231                    "Graph CFF term expression"
232                );
233                // println!("ose expr :{}", ose_expr);
234                cff_term.expression.push(ose_expr);
235                cff_term
236                    .orientations
237                    .push(orientation.data.orientation.clone());
238            }
239            terms.insert(cut_cff_index, cff_term);
240        }
241
242        let cut_cff = CutCFF { terms };
243        Ok(cut_cff)
244    }
245}