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#[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
106struct 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
132fn 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 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 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 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 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 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, #[bincode(with_serde)]
724 pub hsurface_cache: HsurfaceCollection, }
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 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 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, ¶ms)
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 #[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 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 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 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 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 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}