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;
21pub mod esurface;
23pub mod expression;
24pub mod generation;
25pub mod hsurface;
26pub mod surface;
27pub mod tree;
28
29pub struct CFFTerm {
30 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)]
46pub 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 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 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 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}