Skip to main content

gammalooprs/cff/
generation.rs

1use std::collections::HashMap;
2
3use crate::{
4    cff::{
5        expression::OrientationData,
6        hsurface::{Hsurface, HsurfaceID},
7        surface::{HybridSurface, HybridSurfaceID, InfiniteSurface},
8        tree::Tree,
9    },
10    graph::{Graph, LoopMomentumBasis, get_cff_inverse_energy_product_impl},
11    processes::{CrossSectionCut, CutId},
12    settings::global::OrientationPattern,
13};
14use ahash::HashSet;
15use bincode::{Decode, Encode};
16use color_eyre::Report;
17use color_eyre::Result;
18use itertools::Itertools;
19use linnet::half_edge::{
20    HedgeGraph,
21    involution::{EdgeVec, HedgePair},
22    subgraph::{OrientedCut, SubGraphLike, SubSetOps},
23};
24use linnet::half_edge::{
25    involution::{EdgeIndex, Orientation},
26    subgraph::InternalSubGraph,
27};
28use symbolica::{
29    atom::{Atom, AtomCore},
30    id::{Pattern, Replacement},
31};
32use typed_index_collections::TiVec;
33
34use serde::{Deserialize, Serialize};
35
36use tracing::debug;
37
38use super::{
39    cff_graph::CFFGenerationGraph,
40    esurface::{Esurface, EsurfaceCollection, EsurfaceID, ExternalShift},
41    expression::{CFFExpression, OrientationID},
42    hsurface::HsurfaceCollection,
43    surface::{HybridSurfaceRef, UnitSurface},
44};
45
46#[derive(Debug, Clone)]
47struct GenerationData {
48    graph: CFFGenerationGraph,
49    surface_id: Option<HybridSurfaceID>,
50}
51
52#[derive(Debug, Clone)]
53pub struct ShiftRewrite {
54    pub dependent_momentum: EdgeIndex,
55    pub dependent_momentum_expr: ExternalShift,
56}
57
58fn forget_graphs(data: GenerationData) -> HybridSurfaceID {
59    data.surface_id.expect("corrupted expression tree")
60}
61
62impl GenerationData {
63    fn insert_esurface(&mut self, surface_id: HybridSurfaceID) {
64        self.surface_id = Some(surface_id);
65    }
66}
67
68#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
69#[allow(dead_code)]
70struct CFFTreeNodePointer {
71    term_id: usize,
72    node_id: usize,
73}
74
75// Orientation is a bitset that represents the orientation of the edges in a graph.
76// 0 means +, 1 means -, in this representation the original graph is represented by the number 0
77#[derive(Debug, Clone, Copy)]
78struct OrientationGenerator {
79    identifier: usize,
80    num_edges: usize,
81}
82
83impl OrientationGenerator {
84    #[allow(unused)]
85    fn default(num_edges: usize) -> Self {
86        Self {
87            identifier: 0,
88            num_edges,
89        }
90    }
91}
92
93impl IntoIterator for OrientationGenerator {
94    type Item = Orientation;
95    type IntoIter = OrientationIterator;
96
97    fn into_iter(self) -> Self::IntoIter {
98        OrientationIterator {
99            identifier: self.identifier,
100            current_location: 0,
101            num_edges: self.num_edges,
102        }
103    }
104}
105
106// OrientationIterator allows us to iterate over the edges in a graph, and
107// view their orientation as a boolean
108struct OrientationIterator {
109    identifier: usize,
110    current_location: usize,
111    num_edges: usize,
112}
113
114impl Iterator for OrientationIterator {
115    type Item = Orientation;
116    fn next(&mut self) -> Option<Self::Item> {
117        if self.current_location < self.num_edges {
118            let result_bool = self.identifier & (1 << self.current_location) == 0;
119            let result = match result_bool {
120                true => Orientation::Default,
121                false => Orientation::Reversed,
122            };
123
124            self.current_location += 1;
125            Some(result)
126        } else {
127            None
128        }
129    }
130}
131
132// This function returns an iterator over all possible orientations of a graph
133fn iterate_possible_orientations(num_edges: usize) -> impl Iterator<Item = OrientationGenerator> {
134    if num_edges > 64 {
135        panic!("Maximum number of edges supported is currently 64")
136    }
137
138    let max_size = 2_usize.pow(num_edges as u32);
139    (0..max_size).map(move |x| OrientationGenerator {
140        identifier: x,
141        num_edges,
142    })
143}
144
145#[cfg(test)]
146fn get_orientations<E, V, H>(
147    graph: &HedgeGraph<E, V, H>,
148    dummy_edges: &[EdgeIndex],
149) -> Vec<CFFGenerationGraph> {
150    let internal_subgraph = InternalSubGraph::cleaned_filter_pessimist(graph.full_filter(), graph);
151    let num_virtual_edges = graph.count_internal_edges(&internal_subgraph);
152    let virtual_possible_orientations = iterate_possible_orientations(num_virtual_edges);
153
154    virtual_possible_orientations
155        .map(|orientation_of_virtuals| {
156            let mut orientation_of_virtuals = orientation_of_virtuals.into_iter();
157
158            let global_orientation = graph.new_edgevec(|_, __, hedge_pair| match hedge_pair {
159                HedgePair::Unpaired { .. } => Orientation::Default,
160                HedgePair::Paired { .. } => orientation_of_virtuals
161                    .next()
162                    .expect(" unable to reconstruct orientation"),
163                HedgePair::Split { .. } => todo!(),
164            });
165
166            assert!(
167                orientation_of_virtuals.next().is_none(),
168                "did not saturate virtual orientations when constructing global orientation"
169            );
170
171            CFFGenerationGraph::new(graph, global_orientation, dummy_edges)
172        })
173        .collect_vec()
174}
175
176pub(crate) fn get_orientations_from_subgraph<E, V, H, S: SubGraphLike>(
177    graph: &HedgeGraph<E, V, H>,
178    subgraph: &S,
179    reversed_dangling: &[EdgeIndex],
180) -> Vec<CFFGenerationGraph> {
181    let num_virtual_edges = graph.count_internal_edges(subgraph);
182    let virtual_possible_orientations = iterate_possible_orientations(num_virtual_edges);
183
184    virtual_possible_orientations
185        .map(|orientation_of_virtuals| {
186            let mut orientation_of_virtuals = orientation_of_virtuals.into_iter();
187
188            let global_orientation = graph.new_edgevec(|_, edge_id, _| {
189                if let Some((pair, _, _)) = graph
190                    .iter_edges_of(subgraph)
191                    .find(|(_pair, id, _)| *id == edge_id)
192                {
193                    match pair {
194                        HedgePair::Paired { .. } => orientation_of_virtuals
195                            .next()
196                            .expect("orientation generation corrupted, not enough edges"),
197                        HedgePair::Unpaired { .. } => Orientation::Default,
198                        HedgePair::Split { .. } => {
199                            if reversed_dangling.contains(&edge_id) {
200                                Orientation::Reversed
201                            } else {
202                                Orientation::Default
203                            }
204                        }
205                    }
206                } else {
207                    Orientation::Undirected
208                }
209            });
210
211            CFFGenerationGraph::new_from_subgraph(graph, global_orientation, subgraph).unwrap()
212        })
213        .filter(|cff_graph| !cff_graph.has_directed_cycle_initial())
214        .collect()
215}
216
217#[allow(unused)]
218fn get_orientations_with_cut<E, V, H>(
219    graph: &HedgeGraph<E, V, H>,
220    oriented_cut: &OrientedCut,
221) -> Vec<EdgeVec<Orientation>> {
222    let internal_subgraph = InternalSubGraph::cleaned_filter_pessimist(graph.full_filter(), graph);
223    let num_virtual_edges = graph.count_internal_edges(&internal_subgraph);
224
225    let virtual_possible_orientations = iterate_possible_orientations(num_virtual_edges);
226
227    let orientations_consistent_with_cut = virtual_possible_orientations
228        .map(|orientation_of_virtuals| {
229            // pad a virtual orientation with orientations of externals.
230            let mut orientation_of_virtuals = orientation_of_virtuals.into_iter();
231
232            let global_orientation = graph.new_edgevec(|_, __, hedge_pair| match hedge_pair {
233                HedgePair::Unpaired { .. } => Orientation::Default,
234                HedgePair::Paired { .. } => orientation_of_virtuals
235                    .next()
236                    .expect(" unable to reconstruct orientation"),
237                HedgePair::Split { .. } => todo!(),
238            });
239
240            assert!(
241                orientation_of_virtuals.next().is_none(),
242                "did not saturate virtual orientations when constructing global orientation"
243            );
244
245            global_orientation
246        })
247        .filter(|global_orientation| {
248            // filter out orientations that are not consistent with the cut
249            let edges_in_cut = graph.iter_edges_of(oriented_cut).map(|(_, id, _)| id);
250            let orientation_of_edges_in_cut = oriented_cut.iter_edges(graph).map(|(or, _)| or);
251
252            edges_in_cut
253                .zip(orientation_of_edges_in_cut)
254                .all(|(edge_id, orientation)| global_orientation[edge_id] == orientation)
255        })
256        .filter(|global_orientation| {
257            // filter out orientations that have a directed cycle
258            let graph = CFFGenerationGraph::new(graph, global_orientation.clone(), &[]);
259            !graph.has_directed_cycle_initial()
260        });
261
262    orientations_consistent_with_cut.collect_vec()
263}
264
265#[cfg(test)]
266fn generate_cff_expression<E, V, H>(
267    graph: &HedgeGraph<E, V, H>,
268    canonize_esurface: &Option<ShiftRewrite>,
269    edges_in_initial_state_cut: &[EdgeIndex],
270    dummy_edges: &[EdgeIndex],
271) -> Result<CFFExpression<OrientationID>> {
272    let graphs = get_orientations(graph, dummy_edges);
273    debug!("number of orientations: {}", graphs.len());
274    let mut surface_cache = SurfaceCache {
275        esurface_cache: EsurfaceCollection::from_iter(std::iter::empty()),
276        hsurface_cache: HsurfaceCollection::from_iter(std::iter::empty()),
277    };
278    let graph_cff = generate_cff_from_orientations(
279        graphs,
280        &mut surface_cache,
281        edges_in_initial_state_cut,
282        canonize_esurface,
283    )?;
284
285    // patch the surface cache
286    Ok(graph_cff)
287}
288
289impl Graph {
290    pub(crate) fn generate_cff(
291        &mut self,
292        contract_edges: &[EdgeIndex],
293        canonize_esurface: &Option<ShiftRewrite>,
294        orientation_pattern: &OrientationPattern,
295    ) -> Result<CFFExpression<OrientationID>> {
296        let mut seed_graph = CFFGenerationGraph::new_from_graph(self);
297
298        for edge in contract_edges {
299            seed_graph = seed_graph.contract_edge(*edge);
300        }
301
302        let edges_in_initial_state_cut = self
303            .iter_edges_of(&self.initial_state_cut)
304            .map(|x| x.1)
305            .collect_vec();
306
307        let virtual_edges_of_contracted_graph = seed_graph.num_virtual_edges();
308
309        let orientations = iterate_possible_orientations(virtual_edges_of_contracted_graph);
310
311        let mut oriented_acyclic_graphs = vec![];
312
313        for orientation in orientations {
314            let mut orientation_iterator = orientation.into_iter();
315
316            let global_orientation = self.new_edgevec(|_, edge_id, hedge_pair| {
317                if hedge_pair.is_unpaired() || contract_edges.contains(&edge_id) {
318                    Orientation::Undirected
319                } else if edges_in_initial_state_cut.contains(&edge_id) {
320                    Orientation::Default
321                } else {
322                    orientation_iterator
323                        .next()
324                        .expect("orientation generation corrupted, not enough edges")
325                }
326            });
327
328            if orientation_pattern.filter(&global_orientation) {
329                let mut cff_graph = seed_graph.clone();
330                cff_graph.apply_orientation(global_orientation)?;
331
332                if !cff_graph.has_directed_cycle_initial() {
333                    oriented_acyclic_graphs.push(cff_graph);
334                }
335            }
336        }
337
338        generate_cff_from_orientations(
339            oriented_acyclic_graphs,
340            &mut self.surface_cache,
341            &edges_in_initial_state_cut,
342            canonize_esurface,
343        )
344    }
345}
346
347pub fn generate_cff_expression_from_subgraph<E, V, H, S: SubGraphLike>(
348    graph: &HedgeGraph<E, V, H>,
349    subgraph: &S,
350    canonize_esurface: &Option<ShiftRewrite>,
351    reversed_dangling: &[EdgeIndex],
352    edges_in_initial_state_cut: &[EdgeIndex],
353    surface_cache: &mut SurfaceCache,
354) -> Result<CFFExpression<OrientationID>> {
355    let graphs = get_orientations_from_subgraph(graph, subgraph, reversed_dangling);
356    let cff = generate_cff_from_orientations(
357        graphs,
358        surface_cache,
359        edges_in_initial_state_cut,
360        canonize_esurface,
361    )?;
362    Ok(cff)
363}
364
365#[derive(Copy, Clone, Debug)]
366pub struct ConstraintData<'a> {
367    pub constraints: &'a [&'a Esurface],
368    pub illegal_esurfaces: &'a [&'a Esurface],
369}
370
371#[derive(Copy, Clone, Debug)]
372pub struct UvCffTopology<'a> {
373    pub contract_edges: &'a [EdgeIndex],
374    pub edges_in_initial_state_cut: &'a [EdgeIndex],
375    pub orientation: &'a EdgeVec<Orientation>,
376    pub cut_edges: &'a [EdgeIndex],
377}
378
379pub fn generate_uv_cff<E, V, H, S: SubGraphLike>(
380    graph: &HedgeGraph<E, V, H>,
381    subgraph: &S,
382    canonize_esurface: &Option<ShiftRewrite>,
383    topology: UvCffTopology<'_>,
384    setup: PostProcessingSetup<'_>,
385) -> Result<Atom> {
386    let mut generation_graph =
387        CFFGenerationGraph::new_from_subgraph(graph, topology.orientation.clone(), subgraph)?;
388
389    for contracted_edge in topology.contract_edges {
390        generation_graph = generation_graph.contract_edge(*contracted_edge);
391    }
392
393    generation_graph.remove_self_edges();
394
395    if generation_graph.has_directed_cycle_initial() {
396        return Ok(Atom::new());
397    }
398
399    let mut surface_cache = SurfaceCache {
400        esurface_cache: EsurfaceCollection::from_iter(std::iter::empty()),
401        hsurface_cache: HsurfaceCollection::from_iter(std::iter::empty()),
402    };
403
404    let generate_tree_for_orientation = generate_tree_for_orientation(
405        generation_graph,
406        &mut surface_cache,
407        topology.edges_in_initial_state_cut,
408        canonize_esurface,
409    );
410
411    let mut tree = generate_tree_for_orientation.map(forget_graphs);
412
413    post_process(
414        &mut tree,
415        topology.orientation,
416        subgraph,
417        &surface_cache,
418        setup,
419    );
420
421    let surface_cache_to_use = setup
422        .rewrite_esurfaces
423        .map_or(&surface_cache, |rewrite| rewrite.allowed_targets);
424
425    let atom_tree = tree.to_atom_inv();
426    let atom_tree_substituted =
427        surface_cache_to_use.substitute_energies(&atom_tree, topology.cut_edges);
428    let inverse_energies =
429        get_cff_inverse_energy_product_impl(graph, subgraph, topology.contract_edges);
430
431    Ok(atom_tree_substituted * &inverse_energies)
432}
433
434#[derive(Clone, Copy)]
435pub struct PostProcessingSetup<'a> {
436    pub constraint_data: Option<ConstraintData<'a>>,
437    pub rewrite_esurfaces: Option<EsurfaceRewritingInstructions<'a>>,
438}
439
440#[derive(Clone, Copy)]
441pub struct EsurfaceRewritingInstructions<'a> {
442    pub allowed_targets: &'a SurfaceCache,
443    pub graph: &'a Graph,
444    pub cuts: &'a TiVec<CutId, CrossSectionCut>,
445    pub subgraph_location: (Option<CutId>, Option<CutId>),
446}
447
448fn post_process<S: SubGraphLike>(
449    tree: &mut Tree<HybridSurfaceID>,
450    orientation: &EdgeVec<Orientation>,
451    subgraph: &S,
452    surface_cache: &SurfaceCache,
453    setup: PostProcessingSetup<'_>,
454) {
455    if let Some(constraint_data) = setup.constraint_data {
456        tree.map_mut(|surface_id| {
457            let esurface_is_allowed = match surface_id {
458                HybridSurfaceID::Esurface(esurface_id) => {
459                    let esurface_to_compare = &surface_cache.esurface_cache[*esurface_id];
460                    constraint_data
461                        .illegal_esurfaces
462                        .iter()
463                        .all(|illegal_esurface| esurface_to_compare != *illegal_esurface)
464                }
465                HybridSurfaceID::Hsurface(hsurface_id) => {
466                    let hsurface_to_compare = &surface_cache.hsurface_cache[*hsurface_id];
467                    constraint_data
468                        .illegal_esurfaces
469                        .iter()
470                        .all(|illegal_esurface| {
471                            !hsurface_to_compare
472                                .equality_under_energy_conservation(
473                                    illegal_esurface,
474                                    constraint_data.constraints,
475                                )
476                                .unwrap_or(
477                                    hsurface_to_compare.equality_by_try_convert(illegal_esurface),
478                                )
479                        })
480                }
481                HybridSurfaceID::Unit => true,
482                HybridSurfaceID::Infinite => true,
483            };
484
485            if !esurface_is_allowed {
486                *surface_id = HybridSurfaceID::Infinite
487            }
488        });
489    }
490
491    if let Some(rewrite_esurfaces) = setup.rewrite_esurfaces {
492        let hashset_of_appearing_ids = tree
493            .iter_nodes()
494            .map(|node| node.data)
495            .collect::<HashSet<HybridSurfaceID>>();
496
497        let mut id_map = HashMap::<HybridSurfaceID, HybridSurfaceID>::new();
498        id_map.insert(HybridSurfaceID::Unit, HybridSurfaceID::Unit);
499        id_map.insert(HybridSurfaceID::Infinite, HybridSurfaceID::Infinite);
500
501        for appearing_id in hashset_of_appearing_ids.iter() {
502            let surface_to_rewrite = surface_cache.get_surface(*appearing_id);
503
504            match surface_to_rewrite {
505                HybridSurfaceRef::Unit(_) => continue,
506                HybridSurfaceRef::Infinite(_) => continue,
507                HybridSurfaceRef::Esurface(esurface) => {
508                    if let Some(esurface_id) = rewrite_esurfaces
509                        .allowed_targets
510                        .esurface_cache
511                        .position(|allowed_esurface| allowed_esurface == esurface)
512                    {
513                        let new_id = HybridSurfaceID::Esurface(esurface_id);
514                        id_map.insert(*appearing_id, new_id);
515                    } else {
516                        let complete_to_right =
517                            if let Some(cut_id) = rewrite_esurfaces.subgraph_location.1 {
518                                let edges_in_cut = rewrite_esurfaces
519                                    .graph
520                                    .iter_edges_of(&rewrite_esurfaces.cuts[cut_id].cut)
521                                    .map(|(_, edge_id, _)| edge_id)
522                                    .collect_vec();
523
524                                edges_in_cut
525                                    .iter()
526                                    .all(|edge_id| esurface.energies.contains(edge_id))
527                            } else {
528                                false
529                            };
530
531                        let complete_to_left =
532                            if let Some(cut_id) = rewrite_esurfaces.subgraph_location.0 {
533                                let edges_in_cut = rewrite_esurfaces
534                                    .graph
535                                    .iter_edges_of(&rewrite_esurfaces.cuts[cut_id].cut)
536                                    .map(|(_, edge_id, _)| edge_id)
537                                    .collect_vec();
538
539                                edges_in_cut
540                                    .iter()
541                                    .all(|edge_id| esurface.energies.contains(edge_id))
542                            } else {
543                                false
544                            };
545
546                        if complete_to_left && complete_to_right {
547                            panic!("esurface has no connected component");
548                        }
549
550                        if !complete_to_left && !complete_to_right {
551                            println!("esurface: {:#?}", esurface);
552                            panic!("esurface cannot be rewritten to any allowed target");
553                        }
554
555                        let vertices_to_add = if complete_to_left {
556                            let cut_id = rewrite_esurfaces.subgraph_location.0.unwrap();
557                            &rewrite_esurfaces.cuts[cut_id].left
558                        } else if complete_to_right {
559                            let cut_id = rewrite_esurfaces.subgraph_location.1.unwrap();
560                            &rewrite_esurfaces.cuts[cut_id].right
561                        } else {
562                            unreachable!()
563                        };
564
565                        let new_esurface_subgraph = esurface
566                            .vertex_set
567                            .subgraph(rewrite_esurfaces.graph)
568                            .union(vertices_to_add);
569
570                        let new_esurface = Esurface::new_from_subgraph(
571                            &new_esurface_subgraph,
572                            rewrite_esurfaces.graph,
573                            orientation,
574                        );
575
576                        let new_esurface_id = rewrite_esurfaces
577                            .allowed_targets
578                            .esurface_cache
579                            .position(|allowed_esurface| allowed_esurface == &new_esurface)
580                            .expect("constructed esurface not in allowed targets");
581
582                        let new_id = HybridSurfaceID::Esurface(new_esurface_id);
583                        id_map.insert(*appearing_id, new_id);
584                    }
585                }
586                HybridSurfaceRef::Hsurface(hsurface) => {
587                    let complete_to_left =
588                        if let Some(cut_id) = rewrite_esurfaces.subgraph_location.0 {
589                            let edges_in_cut = rewrite_esurfaces
590                                .graph
591                                .iter_edges_of(&rewrite_esurfaces.cuts[cut_id].cut)
592                                .map(|(_, edge_id, _)| edge_id)
593                                .collect_vec();
594
595                            hsurface
596                                .negative_energies
597                                .iter()
598                                .all(|edge_id| edges_in_cut.contains(edge_id))
599                        } else {
600                            false
601                        };
602
603                    let complete_to_right =
604                        if let Some(cut_id) = rewrite_esurfaces.subgraph_location.1 {
605                            let edges_in_cut = rewrite_esurfaces
606                                .graph
607                                .iter_edges_of(&rewrite_esurfaces.cuts[cut_id].cut)
608                                .map(|(_, edge_id, _)| edge_id)
609                                .collect_vec();
610
611                            hsurface
612                                .negative_energies
613                                .iter()
614                                .all(|edge_id| edges_in_cut.contains(edge_id))
615                        } else {
616                            false
617                        };
618
619                    if complete_to_left && complete_to_right {
620                        panic!(
621                            "hsurface has no connected component supergraph, it cannot exist, but it does"
622                        );
623                    }
624
625                    if !complete_to_left && !complete_to_right {
626                        println!("hsurface: {:#?}", hsurface);
627                        panic!("hsurface cannot be rewritten to any allowed target");
628                    }
629
630                    let vertices_to_add = if complete_to_left {
631                        let cut_id = rewrite_esurfaces.subgraph_location.0.unwrap();
632                        &rewrite_esurfaces.cuts[cut_id].left
633                    } else if complete_to_right {
634                        let cut_id = rewrite_esurfaces.subgraph_location.1.unwrap();
635                        &rewrite_esurfaces.cuts[cut_id].right
636                    } else {
637                        unreachable!()
638                    };
639
640                    let new_esurface_subgraph = hsurface
641                        .vertex_set
642                        .subgraph(rewrite_esurfaces.graph)
643                        .union(vertices_to_add);
644
645                    let new_esurface = Esurface::new_from_subgraph(
646                        &new_esurface_subgraph,
647                        rewrite_esurfaces.graph,
648                        orientation,
649                    );
650
651                    let new_esurface_id = rewrite_esurfaces
652                        .allowed_targets
653                        .esurface_cache
654                        .position(|allowed_esurface| allowed_esurface == &new_esurface)
655                        .unwrap_or_else(|| {
656                            println!("for graph: {}", rewrite_esurfaces.graph.name.clone());
657                            println!("dot: \n {}", rewrite_esurfaces.graph.debug_dot());
658                            println!("subgraph: \n {}", rewrite_esurfaces.graph.dot(subgraph));
659
660                            println!("from hsurface: {:?}", hsurface);
661                            println!("constructed esurface: {:?}", new_esurface);
662                            panic!("constructed esurface not in allowed targets");
663                        });
664
665                    let new_id = HybridSurfaceID::Esurface(new_esurface_id);
666                    id_map.insert(*appearing_id, new_id);
667                }
668            }
669        }
670
671        tree.map_mut(|surface_id| *surface_id = id_map[surface_id]);
672    }
673}
674
675fn generate_cff_from_orientations<O: From<usize> + Into<usize>>(
676    orientations_and_graphs: Vec<CFFGenerationGraph>,
677    generator_cache: &mut SurfaceCache,
678    edges_in_initial_state_cut: &[EdgeIndex],
679    canonize_esurface: &Option<ShiftRewrite>,
680) -> Result<CFFExpression<O>, Report> {
681    // filter cyclic orientations beforehand
682    let acyclic_orientations_and_graphs = orientations_and_graphs
683        .into_iter()
684        .filter(|graph| !graph.has_directed_cycle_initial())
685        .collect_vec();
686
687    debug!(
688        "number of acyclic orientations: {}",
689        acyclic_orientations_and_graphs.len()
690    );
691
692    let terms = acyclic_orientations_and_graphs
693        .into_iter()
694        .map(|graph| {
695            let global_orientation = graph.global_orientation.clone();
696            let tree = generate_tree_for_orientation(
697                graph.clone(),
698                generator_cache,
699                edges_in_initial_state_cut,
700                canonize_esurface,
701            );
702            let expression = tree.map(forget_graphs);
703
704            crate::cff::expression::OrientationExpression {
705                expression,
706                data: OrientationData {
707                    orientation: global_orientation,
708                },
709            }
710        })
711        .collect_vec();
712
713    Ok(CFFExpression {
714        orientations: terms.into(),
715        surfaces: generator_cache.clone(),
716    })
717}
718
719#[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)]
720pub struct SurfaceCache {
721    #[bincode(with_serde)]
722    pub esurface_cache: EsurfaceCollection, // Esurfaces of the supergraph
723    #[bincode(with_serde)]
724    pub hsurface_cache: HsurfaceCollection, // Anything else.
725}
726
727impl SurfaceCache {
728    pub fn substitute_energies(&self, atom: &Atom, cut_edges: &[EdgeIndex]) -> Atom {
729        let replacement_rules = self.get_all_replacements(cut_edges);
730        atom.replace_multiple(&replacement_rules)
731    }
732
733    pub(crate) fn iter_all_surfaces(
734        &'_ self,
735    ) -> impl Iterator<Item = (HybridSurfaceID, HybridSurfaceRef<'_>)> + '_ {
736        let esurface_id_iter = self.esurface_cache.iter_enumerated().map(|(id, esurface)| {
737            (
738                HybridSurfaceID::Esurface(id),
739                HybridSurfaceRef::Esurface(esurface),
740            )
741        });
742
743        let hsurface_id_iter = self.hsurface_cache.iter_enumerated().map(|(id, hsurface)| {
744            (
745                HybridSurfaceID::Hsurface(id),
746                HybridSurfaceRef::Hsurface(hsurface),
747            )
748        });
749
750        esurface_id_iter.chain(hsurface_id_iter)
751    }
752
753    pub(crate) fn get_all_replacements(&self, cut_edges: &[EdgeIndex]) -> Vec<Replacement> {
754        self.iter_all_surfaces()
755            .map(|(id, surface)| {
756                let id_atom = Pattern::from(Atom::from(id));
757                let surface_atom = Pattern::from(surface.to_atom(cut_edges));
758                Replacement::new(id_atom, surface_atom)
759            })
760            .collect()
761    }
762
763    pub(crate) fn get_all_replacements_in_lmb(
764        &self,
765        cut_edges: &[EdgeIndex],
766        lmb: &LoopMomentumBasis,
767    ) -> Vec<Replacement> {
768        self.iter_all_surfaces()
769            .map(|(id, surface)| {
770                let id_atom = Pattern::from(Atom::from(id));
771                let surface_atom = Pattern::from(surface.to_atom_in_lmb(cut_edges, lmb));
772                Replacement::new(id_atom, surface_atom)
773            })
774            .collect()
775    }
776
777    #[allow(dead_code)]
778    pub(crate) fn get_surface(&self, surface_id: HybridSurfaceID) -> HybridSurfaceRef<'_> {
779        match surface_id {
780            HybridSurfaceID::Esurface(id) => HybridSurfaceRef::Esurface(&self.esurface_cache[id]),
781            HybridSurfaceID::Hsurface(id) => HybridSurfaceRef::Hsurface(&self.hsurface_cache[id]),
782            HybridSurfaceID::Unit => HybridSurfaceRef::Unit(UnitSurface {}),
783            HybridSurfaceID::Infinite => HybridSurfaceRef::Infinite(InfiniteSurface {}),
784        }
785    }
786
787    #[allow(dead_code)]
788    pub(crate) fn new() -> Self {
789        Self {
790            esurface_cache: EsurfaceCollection::from_iter(std::iter::empty()),
791            hsurface_cache: HsurfaceCollection::from_iter(std::iter::empty()),
792        }
793    }
794}
795
796fn generate_tree_for_orientation(
797    graph: CFFGenerationGraph,
798    generator_cache: &mut SurfaceCache,
799    edges_in_initial_state_cut: &[EdgeIndex],
800    canonize_esurface: &Option<ShiftRewrite>,
801) -> Tree<GenerationData> {
802    let mut tree = Tree::from_root(GenerationData {
803        graph,
804        surface_id: None,
805    });
806
807    while let Some(()) = advance_tree(
808        &mut tree,
809        generator_cache,
810        edges_in_initial_state_cut,
811        canonize_esurface,
812    ) {}
813
814    tree
815}
816
817fn advance_tree(
818    tree: &mut Tree<GenerationData>,
819    generator_cache: &mut SurfaceCache,
820    edges_in_initial_state_cut: &[EdgeIndex],
821    canonize_esurface: &Option<ShiftRewrite>,
822) -> Option<()> {
823    let bottom_layer = tree.get_bottom_layer();
824
825    let (children_optional, new_surfaces_for_tree): (
826        Vec<Option<Vec<CFFGenerationGraph>>>,
827        Vec<HybridSurfaceID>,
828    ) = bottom_layer
829        .iter()
830        .map(|&node_id| {
831            let node = &tree.get_node(node_id);
832            let graph = &node.data.graph;
833
834            let (option_children, surface) = graph.generate_children();
835
836            // treat the edges in the initial state cut as true externals
837            let surface = match surface {
838                HybridSurface::Esurface(esurface) => {
839                    let energies_to_be_moved = esurface
840                        .energies
841                        .iter()
842                        .filter(|edge_id| edges_in_initial_state_cut.contains(edge_id))
843                        .copied()
844                        .collect_vec();
845
846                    if energies_to_be_moved.is_empty() {
847                        HybridSurface::Esurface(esurface)
848                    } else {
849                        let new_energies = esurface
850                            .energies
851                            .iter()
852                            .filter(|edge_id| !energies_to_be_moved.contains(edge_id))
853                            .copied()
854                            .collect_vec();
855
856                        let mut new_shift = esurface.external_shift.clone();
857                        for energy_to_move in energies_to_be_moved.iter() {
858                            new_shift.push((*energy_to_move, 1));
859                        }
860
861                        new_shift.sort_by_key(|(edge_id, _)| *edge_id);
862
863                        HybridSurface::Esurface(Esurface {
864                            energies: new_energies,
865                            external_shift: new_shift,
866                            vertex_set: esurface.vertex_set,
867                        })
868                    }
869                }
870                HybridSurface::Unit(unit) => HybridSurface::Unit(unit),
871                HybridSurface::Infinite(infinite) => HybridSurface::Infinite(infinite),
872                HybridSurface::Hsurface(hsurface) => {
873                    let positive_energies_to_be_moved = hsurface
874                        .positive_energies
875                        .iter()
876                        .filter(|edge_id| edges_in_initial_state_cut.contains(edge_id))
877                        .copied()
878                        .collect_vec();
879
880                    let negative_energies_to_be_moved = hsurface
881                        .negative_energies
882                        .iter()
883                        .filter(|edge_id| edges_in_initial_state_cut.contains(edge_id))
884                        .copied()
885                        .collect_vec();
886
887                    if positive_energies_to_be_moved.is_empty()
888                        && negative_energies_to_be_moved.is_empty()
889                    {
890                        HybridSurface::Hsurface(hsurface)
891                    } else if !positive_energies_to_be_moved.is_empty()
892                        && negative_energies_to_be_moved.is_empty()
893                    {
894                        let new_positive_energies = hsurface
895                            .positive_energies
896                            .iter()
897                            .filter(|edge_id| !positive_energies_to_be_moved.contains(edge_id))
898                            .copied()
899                            .collect_vec();
900
901                        let mut new_shift = hsurface.external_shift.clone();
902
903                        for positive_energy_to_move in positive_energies_to_be_moved.iter() {
904                            new_shift.push((*positive_energy_to_move, 1));
905                        }
906
907                        new_shift.sort_by_key(|(edge_id, _)| *edge_id);
908
909                        HybridSurface::Hsurface(Hsurface {
910                            positive_energies: new_positive_energies,
911                            negative_energies: hsurface.negative_energies.clone(),
912                            external_shift: new_shift,
913                            vertex_set: hsurface.vertex_set,
914                        })
915                    } else if !negative_energies_to_be_moved.is_empty()
916                        && positive_energies_to_be_moved.is_empty()
917                    {
918                        let new_negative_energies = hsurface
919                            .negative_energies
920                            .iter()
921                            .filter(|edge_id| !negative_energies_to_be_moved.contains(edge_id))
922                            .copied()
923                            .collect_vec();
924
925                        let mut new_shift = hsurface.external_shift.clone();
926
927                        for negative_energy_to_move in negative_energies_to_be_moved.iter() {
928                            new_shift.push((*negative_energy_to_move, -1));
929                        }
930
931                        new_shift.sort_by_key(|(edge_id, _)| *edge_id);
932
933                        if new_negative_energies.is_empty() {
934                            HybridSurface::Esurface(Esurface {
935                                energies: hsurface.positive_energies.clone(),
936                                external_shift: new_shift,
937                                vertex_set: hsurface.vertex_set,
938                            })
939                        } else {
940                            HybridSurface::Hsurface(Hsurface {
941                                positive_energies: hsurface.positive_energies.clone(),
942                                negative_energies: new_negative_energies,
943                                external_shift: new_shift,
944                                vertex_set: hsurface.vertex_set,
945                            })
946                        }
947                    } else {
948                        unreachable!()
949                    }
950                }
951            };
952
953            let surface_id = match surface {
954                HybridSurface::Esurface(mut esurface) => {
955                    if let Some(shift_rewrite) = canonize_esurface {
956                        esurface.canonicalize_shift(shift_rewrite);
957                    }
958                    let option_esurface_id = generator_cache
959                        .esurface_cache
960                        .position(|val| *val == esurface);
961
962                    let esurface_id = match option_esurface_id {
963                        Some(esurface_id) => esurface_id,
964                        None => {
965                            generator_cache.esurface_cache.push(esurface);
966                            Into::<EsurfaceID>::into(generator_cache.esurface_cache.len() - 1)
967                        }
968                    };
969
970                    HybridSurfaceID::Esurface(esurface_id)
971                }
972                HybridSurface::Hsurface(hsurface) => {
973                    let option_hsurface_id = generator_cache
974                        .hsurface_cache
975                        .position(|val| val == &hsurface);
976
977                    let hsurface_id = match option_hsurface_id {
978                        Some(hsurface_id) => hsurface_id,
979                        None => {
980                            generator_cache.hsurface_cache.push(hsurface);
981                            Into::<HsurfaceID>::into(generator_cache.hsurface_cache.len() - 1)
982                        }
983                    };
984
985                    HybridSurfaceID::Hsurface(hsurface_id)
986                }
987                HybridSurface::Unit(_) => HybridSurfaceID::Unit,
988                HybridSurface::Infinite(_) => HybridSurfaceID::Infinite,
989            };
990
991            (option_children, surface_id)
992        })
993        .unzip();
994
995    bottom_layer
996        .iter()
997        .zip(new_surfaces_for_tree)
998        .for_each(|(&node_id, esurface_id)| {
999            tree.apply_mut_closure(node_id, |data| data.insert_esurface(esurface_id))
1000        });
1001
1002    let all_some = children_optional.iter().all(Option::is_some);
1003    let all_none = children_optional.iter().all(Option::is_none);
1004
1005    assert!(
1006        all_some || all_none,
1007        "Some cff branches have finished earlier than others"
1008    );
1009
1010    let children = if all_some && !all_none {
1011        children_optional
1012            .into_iter()
1013            .map(Option::unwrap)
1014            .collect_vec()
1015    } else {
1016        return None;
1017    };
1018
1019    bottom_layer
1020        .iter()
1021        .zip(children)
1022        .for_each(|(&node_id, children)| {
1023            children.into_iter().for_each(|child| {
1024                let child_node = GenerationData {
1025                    graph: child,
1026                    surface_id: None,
1027                };
1028
1029                tree.insert_node(node_id, child_node);
1030            });
1031        });
1032    Some(())
1033}
1034
1035#[cfg(test)]
1036mod tests_cff {
1037    use std::{ops::Range, vec};
1038
1039    use ahash::HashMap;
1040
1041    use linnet::half_edge::{
1042        builder::HedgeGraphBuilder, involution::Flow, nodestore::NodeStorageVec,
1043    };
1044    use symbolica::{
1045        evaluate::{ExpressionEvaluator, FunctionMap, OptimizationSettings},
1046        parse, symbol,
1047    };
1048    use utils::FloatLike;
1049
1050    use crate::{
1051        cff::cff_graph::CFFEdgeType,
1052        momentum::{FourMomentum, ThreeMomentum},
1053        settings::global::OrientationPattern,
1054        utils::{
1055            self, F, RefDefault, external_energy_atom_from_index, ose_atom_from_index,
1056            test_utils::dummy_hedge_graph,
1057        },
1058    };
1059
1060    use super::*;
1061
1062    // helper function to make a symbolica evaluator
1063    impl CFFExpression<OrientationID> {
1064        fn quick_symbolica_evaluator(
1065            &self,
1066            external_range: Range<usize>,
1067            virtual_range: Range<usize>,
1068        ) -> ExpressionEvaluator<F<f64>> {
1069            let expression_atom_no_energy_sub = self.to_atom(OrientationPattern::default());
1070            let num_energies = external_range.end.max(virtual_range.end);
1071            let mut expression_atom = self
1072                .surfaces
1073                .substitute_energies(&expression_atom_no_energy_sub, &[]);
1074            for edge_id in 0..num_energies {
1075                let edge_id = EdgeIndex::from(edge_id);
1076                expression_atom = expression_atom
1077                    .replace(ose_atom_from_index(edge_id))
1078                    .with(external_energy_atom_from_index(edge_id));
1079            }
1080
1081            let params = (0..num_energies)
1082                .map(|i| external_energy_atom_from_index(EdgeIndex::from(i)))
1083                .collect_vec();
1084
1085            let function_map = FunctionMap::new();
1086
1087            let mut tree = expression_atom
1088                .as_view()
1089                .to_evaluation_tree(&function_map, &params)
1090                .unwrap();
1091
1092            tree.horner_scheme();
1093            tree.common_subexpression_elimination();
1094            tree.linearize(&OptimizationSettings::default())
1095                .map_coeff(&|c| (&c.re).into())
1096        }
1097    }
1098
1099    // helper function to do some quick tests
1100    #[allow(unused)]
1101    fn generate_orientations_for_testing(
1102        edges: Vec<(usize, usize)>,
1103        incoming_vertices: Vec<usize>,
1104    ) -> Vec<CFFGenerationGraph> {
1105        let num_edges = edges.len();
1106        let incoming_vertices = incoming_vertices
1107            .into_iter()
1108            .map(|v| (v, CFFEdgeType::External))
1109            .collect_vec();
1110
1111        iterate_possible_orientations(num_edges)
1112            .map(|or| {
1113                let orientation_vector = or.into_iter().collect_vec();
1114                let mut new_edges = edges.clone();
1115                for (edge_id, edge_orientation) in orientation_vector.iter().enumerate() {
1116                    match edge_orientation {
1117                        Orientation::Default => {
1118                            new_edges[edge_id] = edges[edge_id];
1119                        }
1120                        Orientation::Reversed => {
1121                            let rotated_edge = (edges[edge_id].1, edges[edge_id].0);
1122                            new_edges[edge_id] = rotated_edge;
1123                        }
1124                        Orientation::Undirected => {
1125                            unreachable!("unexpected orientation")
1126                        }
1127                    }
1128                }
1129
1130                CFFGenerationGraph::from_vec(new_edges, incoming_vertices.clone(), None)
1131            })
1132            .filter(|graph| !graph.has_directed_cycle_initial())
1133            .collect_vec()
1134    }
1135
1136    #[allow(unused)]
1137    fn compute_one_loop_energy<T: FloatLike>(
1138        k: ThreeMomentum<F<T>>,
1139        p: ThreeMomentum<F<T>>,
1140        m: F<T>,
1141    ) -> F<T> {
1142        ((k + p).norm_squared() + &m * &m).sqrt()
1143    }
1144
1145    #[test]
1146    fn test_orientation_struct() {
1147        let orientations = iterate_possible_orientations(3).collect_vec();
1148        assert_eq!(orientations.len(), 8);
1149
1150        let orientation1 = orientations[0].into_iter().collect_vec();
1151        assert_eq!(
1152            orientation1,
1153            vec![
1154                Orientation::Default,
1155                Orientation::Default,
1156                Orientation::Default
1157            ]
1158        );
1159
1160        let orientation2 = orientations[1].into_iter().collect_vec();
1161        assert_eq!(
1162            orientation2,
1163            vec![
1164                Orientation::Reversed,
1165                Orientation::Default,
1166                Orientation::Default
1167            ]
1168        );
1169
1170        let orientation3 = orientations[2].into_iter().collect_vec();
1171        assert_eq!(
1172            orientation3,
1173            vec![
1174                Orientation::Default,
1175                Orientation::Reversed,
1176                Orientation::Default
1177            ]
1178        );
1179
1180        let orientation4 = orientations[3].into_iter().collect_vec();
1181        assert_eq!(
1182            orientation4,
1183            vec![
1184                Orientation::Reversed,
1185                Orientation::Reversed,
1186                Orientation::Default
1187            ]
1188        );
1189
1190        let orientation5 = orientations[4].into_iter().collect_vec();
1191        assert_eq!(
1192            orientation5,
1193            vec![
1194                Orientation::Default,
1195                Orientation::Default,
1196                Orientation::Reversed
1197            ]
1198        );
1199
1200        let orientation6 = orientations[5].into_iter().collect_vec();
1201        assert_eq!(
1202            orientation6,
1203            vec![
1204                Orientation::Reversed,
1205                Orientation::Default,
1206                Orientation::Reversed
1207            ]
1208        );
1209
1210        let orientation7 = orientations[6].into_iter().collect_vec();
1211        assert_eq!(
1212            orientation7,
1213            vec![
1214                Orientation::Default,
1215                Orientation::Reversed,
1216                Orientation::Reversed
1217            ]
1218        );
1219
1220        let orientation8 = orientations[7].into_iter().collect_vec();
1221        assert_eq!(
1222            orientation8,
1223            vec![
1224                Orientation::Reversed,
1225                Orientation::Reversed,
1226                Orientation::Reversed
1227            ]
1228        );
1229    }
1230
1231    #[test]
1232    fn fishnet2b2() {
1233        let edges = vec![
1234            (0, 1),
1235            (1, 2),
1236            (3, 4),
1237            (4, 5),
1238            (6, 7),
1239            (7, 8),
1240            (0, 3),
1241            (1, 4),
1242            (2, 5),
1243            (3, 6),
1244            (4, 7),
1245            (5, 8),
1246        ];
1247
1248        let incoming_vertices = vec![0, 2, 6, 8];
1249
1250        let dep_mom = EdgeIndex::from(3);
1251        let dep_mom_expr = vec![
1252            (EdgeIndex::from(0), -1),
1253            (EdgeIndex::from(1), -1),
1254            (EdgeIndex::from(2), -1),
1255        ];
1256
1257        let shift_rewrite = ShiftRewrite {
1258            dependent_momentum: dep_mom,
1259            dependent_momentum_expr: dep_mom_expr,
1260        };
1261
1262        let orientations = generate_orientations_for_testing(edges, incoming_vertices);
1263
1264        // get time before cff generation
1265        let start = std::time::Instant::now();
1266
1267        let mut surface_cache = SurfaceCache::new();
1268
1269        let _cff = generate_cff_from_orientations::<OrientationID>(
1270            orientations,
1271            &mut surface_cache,
1272            &[],
1273            &Some(shift_rewrite),
1274        )
1275        .unwrap();
1276
1277        let finish = std::time::Instant::now();
1278        println!("time to generate cff: {:?}", finish - start);
1279    }
1280
1281    #[test]
1282    fn cube() {
1283        let edges = vec![
1284            (0, 1),
1285            (1, 3),
1286            (3, 2),
1287            (2, 0),
1288            (4, 5),
1289            (5, 7),
1290            (7, 6),
1291            (6, 4),
1292            (0, 4),
1293            (1, 5),
1294            (2, 6),
1295            (3, 7),
1296        ];
1297
1298        let mut external_data = HashMap::default();
1299        for v in 0..8 {
1300            external_data.insert(v, vec![12 + v]);
1301        }
1302
1303        let mut position_map = HashMap::default();
1304        for i in 0..edges.len() {
1305            position_map.insert(i, i);
1306        }
1307
1308        let dep_mom = EdgeIndex::from(7);
1309        let dep_mom_expr = (0..7).map(|i| (EdgeIndex::from(i), -1)).collect();
1310
1311        let shift_rewrite = ShiftRewrite {
1312            dependent_momentum: dep_mom,
1313            dependent_momentum_expr: dep_mom_expr,
1314        };
1315
1316        let incoming_vertices = vec![0, 1, 2, 3, 4, 5, 6, 7];
1317
1318        let orientations = generate_orientations_for_testing(edges, incoming_vertices);
1319
1320        // get time before cff generation
1321        let _start = std::time::Instant::now();
1322
1323        let mut surface_cache = SurfaceCache::new();
1324
1325        let _cff = generate_cff_from_orientations::<OrientationID>(
1326            orientations,
1327            &mut surface_cache,
1328            &[],
1329            &Some(shift_rewrite),
1330        )
1331        .unwrap();
1332
1333        let _finish = std::time::Instant::now();
1334    }
1335
1336    fn proper_atom(graph: &HedgeGraph<(), ()>) -> Atom {
1337        let cff = generate_cff_expression(graph, &None, &[], &[]).unwrap();
1338
1339        let mut cff_atom = cff.to_atom(OrientationPattern::default());
1340        cff_atom = cff.surfaces.substitute_energies(&cff_atom, &[]);
1341        let inverse_energy_product =
1342            get_cff_inverse_energy_product_impl(graph, &graph.full_graph(), &[]);
1343
1344        cff_atom *= inverse_energy_product;
1345        cff_atom
1346    }
1347
1348    #[test]
1349    fn test_dot_trick_bubble() {
1350        let mut dotted_topology_builder = HedgeGraphBuilder::new();
1351        let dotted_nodes = (0..3)
1352            .map(|_| dotted_topology_builder.add_node(()))
1353            .collect_vec();
1354
1355        dotted_topology_builder.add_edge(dotted_nodes[0], dotted_nodes[1], (), false);
1356        dotted_topology_builder.add_edge(dotted_nodes[1], dotted_nodes[2], (), false);
1357        dotted_topology_builder.add_edge(dotted_nodes[2], dotted_nodes[0], (), false);
1358        let dotted_topology = dotted_topology_builder.build();
1359
1360        let mut dotted_cff_atom = proper_atom(&dotted_topology);
1361        dotted_cff_atom = dotted_cff_atom
1362            .replace(parse!("OSE(2)"))
1363            .with(parse!("OSE(1)"));
1364
1365        let mut topology_builder = HedgeGraphBuilder::new();
1366        let nodes = (0..2).map(|_| topology_builder.add_node(())).collect_vec();
1367
1368        topology_builder.add_edge(nodes[0], nodes[1], (), false);
1369        topology_builder.add_edge(nodes[0], nodes[1], (), false);
1370        let toplogy = topology_builder.build();
1371        let mut cff_atom = proper_atom(&toplogy);
1372
1373        cff_atom = cff_atom.replace(parse!("OSE(1)")).with(parse!("OSE1"));
1374        cff_atom = cff_atom.derivative(symbol!("OSE1"));
1375        cff_atom = cff_atom.replace(parse!("OSE1")).with(parse!("OSE(1)")) / parse!("2*OSE(1)");
1376
1377        let diff = (&cff_atom - &dotted_cff_atom).expand();
1378
1379        println!("cff_atom: {}", cff_atom.expand());
1380        println!("dotted_cff_atom: {}", dotted_cff_atom.expand());
1381
1382        println!("diff: {}", diff);
1383    }
1384
1385    #[test]
1386    fn test_dot_trick_amg() {
1387        let mut dotted_topology_builder = HedgeGraphBuilder::new();
1388        let dotted_nodes = (0..5)
1389            .map(|_| dotted_topology_builder.add_node(()))
1390            .collect_vec();
1391
1392        dotted_topology_builder.add_edge(dotted_nodes[0], dotted_nodes[3], (), false);
1393        dotted_topology_builder.add_edge(dotted_nodes[0], dotted_nodes[2], (), false);
1394        dotted_topology_builder.add_edge(dotted_nodes[0], dotted_nodes[1], (), false);
1395        dotted_topology_builder.add_edge(dotted_nodes[3], dotted_nodes[1], (), false);
1396        dotted_topology_builder.add_edge(dotted_nodes[1], dotted_nodes[2], (), false);
1397        dotted_topology_builder.add_edge(dotted_nodes[3], dotted_nodes[4], (), false);
1398        dotted_topology_builder.add_edge(dotted_nodes[4], dotted_nodes[2], (), false);
1399        let dotted_topology = dotted_topology_builder.build();
1400
1401        let mut dotted_cff_atom = proper_atom(&dotted_topology);
1402
1403        dotted_cff_atom = dotted_cff_atom
1404            .replace(parse!("OSE(6)"))
1405            .with(parse!("OSE(5)"));
1406
1407        let mut topology_builder = HedgeGraphBuilder::new();
1408        let _nodes = (0..4).map(|_| topology_builder.add_node(())).collect_vec();
1409
1410        topology_builder.add_edge(dotted_nodes[0], dotted_nodes[3], (), false);
1411        topology_builder.add_edge(dotted_nodes[0], dotted_nodes[2], (), false);
1412        topology_builder.add_edge(dotted_nodes[0], dotted_nodes[1], (), false);
1413        topology_builder.add_edge(dotted_nodes[3], dotted_nodes[1], (), false);
1414        topology_builder.add_edge(dotted_nodes[1], dotted_nodes[2], (), false);
1415        topology_builder.add_edge(dotted_nodes[3], dotted_nodes[2], (), false);
1416        let topology = topology_builder.build();
1417
1418        let mut cff_atom = proper_atom(&topology);
1419        cff_atom = cff_atom.replace(parse!("OSE(5)")).with(parse!("OSE5"));
1420        cff_atom = cff_atom.derivative(symbol!("OSE5"));
1421        cff_atom = cff_atom.replace(parse!("OSE5")).with(parse!("OSE(5)")) / parse!("2*OSE(5)");
1422
1423        let diff = (cff_atom + dotted_cff_atom).expand();
1424        //.replace(function!(GS.ose, W_.x_))
1425        //.with(parse!("E"))
1426        //.expand();
1427
1428        println!("diff: {}", diff);
1429    }
1430
1431    #[test]
1432    fn test_cff_generation_triangle() {
1433        let triangle = vec![(2, 0), (0, 1), (1, 2)];
1434
1435        let incoming_vertices = vec![0, 1, 2];
1436        let orientations = generate_orientations_for_testing(triangle, incoming_vertices);
1437        assert_eq!(orientations.len(), 6);
1438
1439        let dep_mom = EdgeIndex::from(2);
1440        let dep_mom_expr = vec![(EdgeIndex::from(0), -1), (EdgeIndex::from(1), -1)];
1441
1442        let shift_rewrite = Some(ShiftRewrite {
1443            dependent_momentum: dep_mom,
1444            dependent_momentum_expr: dep_mom_expr,
1445        });
1446
1447        let mut surface_cache = SurfaceCache::new();
1448
1449        let cff = generate_cff_from_orientations(
1450            orientations,
1451            &mut surface_cache,
1452            &[],
1453            &shift_rewrite.clone(),
1454        )
1455        .unwrap();
1456        assert_eq!(
1457            cff.surfaces.esurface_cache.len(),
1458            6,
1459            "too many esurfaces: {:#?}",
1460            cff.surfaces.esurface_cache,
1461        );
1462
1463        let p1 = FourMomentum::from_args(F(1.), F(3.), F(4.), F(5.));
1464        let p2 = FourMomentum::from_args(F(1.), F(6.), F(7.), F(8.));
1465        let p3 = -p1 - p2;
1466        let zero = FourMomentum::from_args(F(0.), F(0.), F(0.), F(0.));
1467        let m = F(0.);
1468
1469        let k = ThreeMomentum::new(F(1.), F(2.), F(3.));
1470
1471        let virtual_energy_cache = [
1472            compute_one_loop_energy(k, zero.spatial, m),
1473            compute_one_loop_energy(k, p1.spatial, m),
1474            compute_one_loop_energy(k, p1.spatial + p2.spatial, m),
1475        ];
1476
1477        let external_energy_cache = [p1.temporal.value, p2.temporal.value, p3.temporal.value];
1478
1479        // combine the virtual and external energies
1480        let mut energy_cache = external_energy_cache.to_vec();
1481        energy_cache.extend(virtual_energy_cache);
1482
1483        let energy_cache = dummy_hedge_graph(6)
1484            .new_edgevec_from_iter(energy_cache)
1485            .unwrap();
1486
1487        let energy_prefactor = virtual_energy_cache
1488            .iter()
1489            .map(|e| (F(2.) * e).inv())
1490            .reduce(|acc, x| acc * x)
1491            .unwrap();
1492
1493        let mut evaluator = cff.quick_symbolica_evaluator(0..3, 3..6);
1494
1495        let cff_res: F<f64> = energy_prefactor
1496            * evaluator.evaluate_single(energy_cache.clone().as_ref())
1497            * F((2. * std::f64::consts::PI).powi(-3));
1498
1499        let target_res = F(6.333_549_225_536_17e-9_f64);
1500        let absolute_error = cff_res - target_res;
1501        let relative_error = absolute_error.abs() / cff_res.abs();
1502
1503        assert!(
1504            relative_error.abs() < F(1.0e-15),
1505            "relative error: {:+e} (ground truth: {:+e} vs reproduced: {:+e})",
1506            relative_error,
1507            target_res,
1508            cff_res
1509        );
1510
1511        // test cff from hedge graph
1512        let mut triangle_hedge_graph_builder = HedgeGraphBuilder::new();
1513
1514        let nodes = (0..3)
1515            .map(|_| triangle_hedge_graph_builder.add_node(()))
1516            .collect_vec();
1517
1518        for node in nodes.clone() {
1519            triangle_hedge_graph_builder.add_external_edge(
1520                node,
1521                (),
1522                Orientation::Undirected,
1523                Flow::Sink,
1524            );
1525        }
1526
1527        triangle_hedge_graph_builder.add_edge(nodes[2], nodes[0], (), Orientation::Undirected);
1528        triangle_hedge_graph_builder.add_edge(nodes[0], nodes[1], (), Orientation::Undirected);
1529        triangle_hedge_graph_builder.add_edge(nodes[1], nodes[2], (), Orientation::Undirected);
1530
1531        let triangle_hedge_graph: HedgeGraph<(), (), ()> =
1532            triangle_hedge_graph_builder.build::<NodeStorageVec<()>>();
1533
1534        let cff_hedge =
1535            generate_cff_expression(&triangle_hedge_graph, &shift_rewrite, &[], &[]).unwrap();
1536        let mut cff_hedge_evaluator = cff_hedge.quick_symbolica_evaluator(0..3, 3..6);
1537
1538        let cff_res: F<f64> = energy_prefactor
1539            * cff_hedge_evaluator.evaluate_single(energy_cache.as_ref())
1540            * F((2. * std::f64::consts::PI).powi(-3));
1541
1542        let target_res = F(6.333_549_225_536_17e-9_f64);
1543        let absolute_error = cff_res - target_res;
1544        let relative_error = absolute_error.abs() / cff_res.abs();
1545
1546        assert!(
1547            relative_error.abs() < F(1.0e-15),
1548            "relative error: {:+e} (ground truth: {:+e} vs reproduced: {:+e})",
1549            relative_error,
1550            target_res,
1551            cff_res
1552        );
1553    }
1554
1555    mod failing {
1556        use super::*;
1557
1558        #[test]
1559        fn test_cff_test_double_triangle() {
1560            let double_triangle_edges = vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)];
1561            let incoming_vertices = vec![0, 3];
1562
1563            let orientations =
1564                generate_orientations_for_testing(double_triangle_edges, incoming_vertices);
1565
1566            let dep_mom = EdgeIndex::from(1);
1567            let dep_mom_expr = vec![(EdgeIndex::from(0), -1)];
1568
1569            let shift_rewrite = Some(ShiftRewrite {
1570                dependent_momentum: dep_mom,
1571                dependent_momentum_expr: dep_mom_expr,
1572            });
1573
1574            let mut surface_cache = SurfaceCache::new();
1575
1576            let cff = generate_cff_from_orientations(
1577                orientations,
1578                &mut surface_cache,
1579                &[],
1580                &shift_rewrite,
1581            )
1582            .unwrap();
1583
1584            let q = FourMomentum::from_args(F(1.), F(2.), F(3.), F(4.));
1585            let zero = FourMomentum::from_args(F(0.), F(0.), F(0.), F(0.));
1586
1587            let k = ThreeMomentum::new(F(6.), F(23.), F(9.));
1588            let l = ThreeMomentum::new(F(3.), F(12.), F(34.));
1589
1590            let m = F::from_f64(0.);
1591
1592            let virtual_energy_cache = [
1593                compute_one_loop_energy(k, zero.spatial, m),
1594                compute_one_loop_energy(q.spatial - k, zero.spatial, m),
1595                compute_one_loop_energy(k - l, zero.spatial, m),
1596                compute_one_loop_energy(l, zero.spatial, m),
1597                compute_one_loop_energy(q.spatial - l, zero.spatial, m),
1598            ];
1599
1600            let external_energy_cache = [q.temporal.value, -q.temporal.value];
1601
1602            let mut energy_cache = external_energy_cache.to_vec();
1603            energy_cache.extend(virtual_energy_cache);
1604
1605            let energy_cache = dummy_hedge_graph(energy_cache.len())
1606                .new_edgevec_from_iter(energy_cache)
1607                .unwrap();
1608
1609            let energy_prefactor = virtual_energy_cache
1610                .iter()
1611                .map(|e| (F(2.) * e).inv())
1612                .reduce(|acc, x| acc * x)
1613                .unwrap();
1614
1615            let mut evaluator = cff.quick_symbolica_evaluator(0..2, 2..7);
1616
1617            let cff_res =
1618                energy_prefactor * evaluator.evaluate_single(energy_cache.clone().as_ref());
1619
1620            let target = F(1.0794792137096797e-13);
1621            let absolute_error = cff_res - target;
1622            let relative_error = absolute_error / cff_res;
1623
1624            assert!(
1625                relative_error.abs() < F(1.0e-15),
1626                "relative error: {:+e}, target: {:+e}, result: {:+e}",
1627                relative_error,
1628                target,
1629                cff_res
1630            );
1631
1632            let mut hedge_double_triangle_builder = HedgeGraphBuilder::new();
1633            let nodes = (0..4)
1634                .map(|_| hedge_double_triangle_builder.add_node(()))
1635                .collect_vec();
1636
1637            hedge_double_triangle_builder.add_external_edge(
1638                nodes[0],
1639                (),
1640                Orientation::Undirected,
1641                Flow::Sink,
1642            );
1643            hedge_double_triangle_builder.add_external_edge(
1644                nodes[3],
1645                (),
1646                Orientation::Undirected,
1647                Flow::Sink,
1648            );
1649
1650            hedge_double_triangle_builder.add_edge(nodes[0], nodes[1], (), Orientation::Undirected);
1651            hedge_double_triangle_builder.add_edge(nodes[0], nodes[2], (), Orientation::Undirected);
1652            hedge_double_triangle_builder.add_edge(nodes[1], nodes[2], (), Orientation::Undirected);
1653            hedge_double_triangle_builder.add_edge(nodes[1], nodes[3], (), Orientation::Undirected);
1654            hedge_double_triangle_builder.add_edge(nodes[2], nodes[3], (), Orientation::Undirected);
1655
1656            let hedge_double_traingle: HedgeGraph<(), (), ()> =
1657                hedge_double_triangle_builder.build::<NodeStorageVec<()>>();
1658            let cff_hedge =
1659                generate_cff_expression(&hedge_double_traingle, &shift_rewrite, &[], &[]).unwrap();
1660            let mut cff_hedge_evaluator = cff_hedge.quick_symbolica_evaluator(0..2, 2..7);
1661            let cff_res =
1662                energy_prefactor * cff_hedge_evaluator.evaluate_single(energy_cache.as_ref());
1663
1664            let target = F(1.0794792137096797e-13);
1665            let absolute_error = cff_res - target;
1666            let relative_error = absolute_error / cff_res;
1667
1668            assert!(
1669                relative_error.abs() < F(1.0e-15),
1670                "relative error: {:+e}, target: {:+e}, result: {:+e}",
1671                relative_error,
1672                target,
1673                cff_res
1674            );
1675
1676            let node_3 = hedge_double_traingle.iter_crown(nodes[3]).into();
1677            let node_0 = hedge_double_traingle.iter_crown(nodes[0]).into();
1678
1679            let cuts = hedge_double_traingle.all_cuts(node_3, node_0);
1680            let mut num_with_6_ors = 0;
1681            let mut num_with_4_ors = 0;
1682            assert_eq!(cuts.len(), 4);
1683            for (_, cut, _) in &cuts {
1684                let orientations = get_orientations_with_cut(&hedge_double_traingle, cut);
1685                if orientations.len() == 4 {
1686                    num_with_4_ors += 1
1687                } else if orientations.len() == 6 {
1688                    num_with_6_ors += 1
1689                }
1690            }
1691
1692            assert_eq!(num_with_4_ors, 2);
1693            assert_eq!(num_with_6_ors, 2);
1694        }
1695
1696        #[test]
1697        fn test_cff_tbt() {
1698            let tbt_edges = vec![
1699                (0, 1),
1700                (2, 0),
1701                (1, 2),
1702                (1, 3),
1703                (2, 4),
1704                (3, 4),
1705                (3, 5),
1706                (5, 4),
1707            ];
1708
1709            let incoming_vertices = vec![0, 5];
1710
1711            let dep_mom = EdgeIndex::from(1);
1712            let dep_mom_expr = vec![(EdgeIndex::from(0), -1)];
1713
1714            let shift_rewrite = Some(ShiftRewrite {
1715                dependent_momentum: dep_mom,
1716                dependent_momentum_expr: dep_mom_expr,
1717            });
1718            let mut surface_cache = SurfaceCache::new();
1719            let orientataions = generate_orientations_for_testing(tbt_edges, incoming_vertices);
1720            let cff = generate_cff_from_orientations(
1721                orientataions,
1722                &mut surface_cache,
1723                &[],
1724                &shift_rewrite,
1725            )
1726            .unwrap();
1727
1728            let q = FourMomentum::from_args(F(1.0), F(2.0), F(3.0), F(4.0));
1729            let zero_vector = q.default();
1730
1731            let p0 = q;
1732            let p5 = -q;
1733
1734            let k = ThreeMomentum::new(F(6.), F(23.), F(9.));
1735            let l = ThreeMomentum::new(F(3.), F(12.), F(34.));
1736            let m = ThreeMomentum::new(F(7.), F(24.), F(1.));
1737
1738            let mass = F(0.);
1739
1740            let energies_cache = [
1741                p0.temporal.value,
1742                p5.temporal.value,
1743                compute_one_loop_energy(k, zero_vector.spatial, mass),
1744                compute_one_loop_energy(k - q.spatial, zero_vector.spatial, mass),
1745                compute_one_loop_energy(k - l, zero_vector.spatial, mass),
1746                compute_one_loop_energy(l, zero_vector.spatial, mass),
1747                compute_one_loop_energy(q.spatial - l, zero_vector.spatial, mass),
1748                compute_one_loop_energy(l - m, zero_vector.spatial, mass),
1749                compute_one_loop_energy(m, zero_vector.spatial, mass),
1750                compute_one_loop_energy(m - q.spatial, zero_vector.spatial, mass),
1751            ];
1752
1753            let virtual_energy_cache = energies_cache[2..].to_vec();
1754
1755            let energy_prefactor = virtual_energy_cache
1756                .iter()
1757                .map(|e| (F(2.) * e).inv())
1758                .reduce(|acc, x| acc * x)
1759                .unwrap();
1760
1761            let energies_cache = dummy_hedge_graph(energies_cache.len())
1762                .new_edgevec_from_iter(energies_cache)
1763                .unwrap();
1764
1765            let mut evaluator = cff.quick_symbolica_evaluator(0..2, 2..10);
1766
1767            let res = evaluator.evaluate_single(energies_cache.clone().as_ref()) * energy_prefactor;
1768
1769            let absolute_error = res - F(1.2625322619777278e-21);
1770            let relative_error = absolute_error / res;
1771            assert!(
1772                relative_error.abs() < F(1.0e-15),
1773                "relative error: {:+e}",
1774                relative_error
1775            );
1776
1777            let mut tbt_hedge_builder = HedgeGraphBuilder::new();
1778            let nodes = (0..6).map(|_| tbt_hedge_builder.add_node(())).collect_vec();
1779            tbt_hedge_builder.add_external_edge(nodes[0], (), Orientation::Undirected, Flow::Sink);
1780            tbt_hedge_builder.add_external_edge(nodes[5], (), Orientation::Undirected, Flow::Sink);
1781
1782            tbt_hedge_builder.add_edge(nodes[0], nodes[1], (), Orientation::Undirected);
1783            tbt_hedge_builder.add_edge(nodes[2], nodes[0], (), Orientation::Undirected);
1784            tbt_hedge_builder.add_edge(nodes[1], nodes[2], (), Orientation::Undirected);
1785            tbt_hedge_builder.add_edge(nodes[1], nodes[3], (), Orientation::Undirected);
1786            tbt_hedge_builder.add_edge(nodes[2], nodes[4], (), Orientation::Undirected);
1787            tbt_hedge_builder.add_edge(nodes[3], nodes[4], (), Orientation::Undirected);
1788            tbt_hedge_builder.add_edge(nodes[3], nodes[5], (), Orientation::Undirected);
1789            tbt_hedge_builder.add_edge(nodes[5], nodes[4], (), Orientation::Undirected);
1790
1791            let tbt_hedge: HedgeGraph<(), (), ()> = tbt_hedge_builder.build::<NodeStorageVec<()>>();
1792            let cff_hedge = generate_cff_expression(&tbt_hedge, &shift_rewrite, &[], &[]).unwrap();
1793
1794            let mut cff_hedge_evaluator = cff_hedge.quick_symbolica_evaluator(0..2, 2..10);
1795            let res =
1796                cff_hedge_evaluator.evaluate_single(energies_cache.as_ref()) * energy_prefactor;
1797
1798            let absolute_error = res - F(1.2625322619777278e-21);
1799            let relative_error = absolute_error / res;
1800            assert!(
1801                relative_error.abs() < F(1.0e-15),
1802                "relative error: {:+e}",
1803                relative_error
1804            );
1805
1806            let node_0 = tbt_hedge.iter_crown(nodes[0]).into();
1807            let node_5 = tbt_hedge.iter_crown(nodes[5]).into();
1808
1809            let cuts = tbt_hedge.all_cuts(node_0, node_5).clone();
1810            assert_eq!(cuts.len(), 9);
1811            let mut num_with_24 = 0;
1812            let mut num_with_16 = 0;
1813            let mut num_with_42 = 0;
1814            let mut num_with_36 = 0;
1815            for (_, cut, _) in cuts.iter() {
1816                let orientations = get_orientations_with_cut(&tbt_hedge, cut);
1817                if orientations.len() == 24 {
1818                    num_with_24 += 1;
1819                }
1820                if orientations.len() == 16 {
1821                    num_with_16 += 1;
1822                }
1823                if orientations.len() == 42 {
1824                    num_with_42 += 1
1825                }
1826                if orientations.len() == 36 {
1827                    num_with_36 += 1;
1828                }
1829            }
1830
1831            assert_eq!(num_with_24, 4);
1832            assert_eq!(num_with_16, 2);
1833            assert_eq!(num_with_42, 2);
1834            assert_eq!(num_with_36, 1);
1835        }
1836    }
1837}