1use std::{borrow::Borrow, ops::Deref};
2
3use itertools::Itertools;
4use linnet::half_edge::{
5 HedgeGraph, NodeIndex,
6 involution::{EdgeData, EdgeIndex, EdgeVec, Flow, HedgePair},
7 subgraph::{
8 HedgeNode, InternalSubGraph, ModifySubSet, OrientedCut, SuBitGraph, SubGraphLike,
9 SubSetLike, SubSetOps,
10 },
11};
12
13use spenso::{
14 algebra::{algebraic_traits::IsZero, complex::Complex},
15 structure::concrete_index::ExpandedIndex,
16};
17use symbolica::{
19 atom::{Atom, AtomCore},
20 id::Replacement,
21};
22use typed_index_collections::TiVec;
23
24use crate::{
25 cff::generation::ShiftRewrite,
26 integrands::process::param_builder::{ParamBuilderGraph, SplitPolarizations},
27 model::{ArcParticle, Model},
28 momentum::sample::{ExternalFourMomenta, ExternalIndex, LoopMomenta},
29 momentum::signature::{ExternalSignature, SignatureLike},
30 momentum::{PolDef, SignOrZero},
31 numerator::graph::ReversibleEdge,
32 utils::{F, FloatLike, GS, external_energy_atom_from_index, ose_atom_from_index},
33 uv::uv_graph::UVE,
34};
35
36use super::{
37 Edge, Graph, HedgeData, LoopMomentumBasis, Vertex, get_cff_inverse_energy_product_impl,
38};
39
40pub trait FeynmanGraph {
41 fn get_emr_vec_cache<T: FloatLike>(
42 &self,
43 loop_moms: &LoopMomenta<F<T>>,
44 external_moms: &ExternalFourMomenta<F<T>>,
45 lmb: &LoopMomentumBasis,
46 ) -> Vec<F<T>>;
47
48 fn num_virtual_edges(&self, subgraph: SuBitGraph) -> usize;
49 fn is_incoming_to(&self, edge: EdgeIndex, vertex: NodeIndex) -> bool;
50 fn add_signs_to_edges(&self, node_id: NodeIndex) -> Vec<isize>;
58 fn get_cff_inverse_energy_product(&self) -> Atom;
59 fn get_loop_number(&self) -> usize;
60 fn get_real_mass_vector<T: FloatLike>(&self, model: &Model) -> EdgeVec<F<T>>;
61 fn get_external_masses<T: FloatLike>(&self, model: &Model) -> TiVec<ExternalIndex, F<T>>;
62 fn get_energy_cache<T: FloatLike>(
63 &self,
64 model: &Model,
65
66 loop_moms: &LoopMomenta<F<T>>,
67 external_moms: &ExternalFourMomenta<F<T>>,
68 lmb: &LoopMomentumBasis,
69 ) -> EdgeVec<F<T>>;
70 fn get_esurface_canonization(&self, lmb: &LoopMomentumBasis) -> Option<ShiftRewrite>;
71 fn external_in_or_out_signature(&self) -> ExternalSignature;
72
73 fn get_external_partcles(&self) -> Vec<ArcParticle>;
74 fn get_external_signature(&self) -> SignatureLike<ExternalIndex>;
75 fn get_energy_atoms(&self) -> Vec<Atom>;
76 fn expected_scale(&self, e_cm: F<f64>, model: &Model) -> F<f64>;
80 fn dummy_list(&self) -> Vec<EdgeIndex>;
81 fn no_dummy(&self) -> SuBitGraph;
82 fn all_st_cuts_for_cs(
83 &self,
84 source_nodes: HedgeNode,
85 target_nodes: HedgeNode,
86 initial_state_tree: &SuBitGraph,
87 ) -> Vec<(SuBitGraph, OrientedCut, SuBitGraph)>;
88}
89
90impl Deref for Graph {
91 type Target = HedgeGraph<Edge, Vertex, HedgeData>;
92
93 fn deref(&self) -> &Self::Target {
94 &self.underlying
95 }
96}
97
98impl SplitPolarizations for Graph {
99 fn polarizations(&self) -> Vec<Atom> {
100 self.polarizations.iter().map(|a| a.1.clone()).collect()
101 }
102}
103
104impl<'a, V, E: UVE> SplitPolarizations
105 for (&'a Vec<(PolDef, Atom)>, &'a HedgeGraph<E, V, HedgeData>)
106{
107 fn polarizations(&self) -> Vec<Atom> {
108 self.0.iter().map(|a| a.1.clone()).collect()
109 }
110}
111
112impl<'a, V, E: UVE> ParamBuilderGraph for (&'a Vec<(PolDef, Atom)>, &'a HedgeGraph<E, V, HedgeData>)
113where
114 for<'b> EdgeData<&'b E>: ReversibleEdge,
115{
116 fn iter_edge_ids(&self) -> impl Iterator<Item = EdgeIndex> + '_ {
117 self.1.iter_edge_ids()
118 }
119
120 fn get_external_energy_atoms(&self) -> Vec<Atom> {
121 self.1.get_external_energy_atoms()
122 }
123
124 fn get_ose_replacements(&self) -> Vec<Replacement> {
125 self.1.get_ose_replacements()
126 }
127
128 fn explicit_ose_atom(&self, edge: EdgeIndex) -> Atom {
129 self.1.explicit_ose_atom(edge)
130 }
131
132 fn loop_mom_params(&self, lmb: &LoopMomentumBasis) -> Vec<Atom> {
133 lmb.loop_edges
134 .iter()
135 .flat_map(|edge_id| {
136 vec![
137 GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([1]))),
138 GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([2]))),
139 GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([3]))),
140 ]
141 })
142 .collect()
143 }
144
145 fn external_spatial_params(&self) -> Vec<Atom> {
146 self.1.external_spatial_params()
147 }
148}
149
150impl<V, E: UVE> ParamBuilderGraph for HedgeGraph<E, V, HedgeData>
151where
152 for<'a> EdgeData<&'a E>: ReversibleEdge,
153{
154 fn iter_edge_ids(&self) -> impl Iterator<Item = EdgeIndex> + '_ {
155 self.iter_edges().map(|(_, e, _)| e)
156 }
157
158 fn get_external_energy_atoms(&self) -> Vec<Atom> {
159 let external_filter: SuBitGraph = self.external_filter();
160
161 external_filter
162 .included_iter()
163 .map(|hedge| external_energy_atom_from_index(self[&hedge]))
164 .collect()
165 }
166
167 fn explicit_ose_atom(&self, edge: EdgeIndex) -> Atom {
168 let mass = self[edge].mass_atom();
169 let mass2 = &mass * &mass;
170
171 let dot = GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([1])))
174 * GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([1])))
175 + GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([2])))
176 * GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([2])))
177 + GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([3])))
178 * GS.emr_mom(edge, Atom::from(ExpandedIndex::from_iter([3])));
179
180 (dot + mass2).sqrt()
181 }
182
183 fn loop_mom_params(&self, lmb: &LoopMomentumBasis) -> Vec<Atom> {
184 lmb.loop_edges
185 .iter()
186 .flat_map(|edge_id| {
187 vec![
188 GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([1]))),
189 GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([2]))),
190 GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([3]))),
191 ]
192 })
193 .collect()
194 }
195
196 fn external_spatial_params(&self) -> Vec<Atom> {
197 let external_filter: SuBitGraph = self.external_filter();
198
199 external_filter
200 .included_iter()
201 .flat_map(|hedge| {
202 let edge_id = self[&hedge];
203 vec![
204 GS.emr_mom(edge_id, Atom::from(ExpandedIndex::from_iter([1]))),
205 GS.emr_mom(edge_id, Atom::from(ExpandedIndex::from_iter([2]))),
206 GS.emr_mom(edge_id, Atom::from(ExpandedIndex::from_iter([3]))),
207 ]
208 })
209 .collect()
210 }
211
212 fn get_ose_replacements(&self) -> Vec<Replacement> {
213 self.iter_edges()
214 .filter(|(pair, _, _)| matches!(pair, HedgePair::Paired { .. }))
215 .map(|(_, edge_id, _)| {
216 let ose_atom = ose_atom_from_index(edge_id);
217 let explicit = self.explicit_ose_atom(edge_id);
218 Replacement::new(ose_atom.to_pattern(), explicit.to_pattern())
219 })
220 .collect()
221 }
222}
223
224impl ParamBuilderGraph for Graph {
225 fn iter_edge_ids(&self) -> impl Iterator<Item = EdgeIndex> + '_ {
230 self.underlying.iter_edge_ids()
231 }
232
233 fn get_external_energy_atoms(&self) -> Vec<Atom> {
234 self.external_momentum_edge_order()
235 .into_iter()
236 .map(external_energy_atom_from_index)
237 .collect()
238 }
239
240 fn get_ose_replacements(&self) -> Vec<Replacement> {
241 if self.initial_state_cut.nedges(&self.underlying) == 0 {
242 self.underlying.get_ose_replacements()
243 } else {
244 let underlying_without_is_cut = self
245 .underlying
246 .full_filter()
247 .subtract(&self.initial_state_cut.left)
248 .subtract(&self.initial_state_cut.right);
249
250 self.underlying
251 .iter_edges_of(&underlying_without_is_cut)
252 .map(|(_, edge_id, _)| {
253 let ose_atom = ose_atom_from_index(edge_id);
254 let explicit = self.underlying.explicit_ose_atom(edge_id);
255 Replacement::new(ose_atom.to_pattern(), explicit.to_pattern())
256 })
257 .collect()
258 }
259 }
260
261 fn explicit_ose_atom(&self, edge: EdgeIndex) -> Atom {
262 self.underlying.explicit_ose_atom(edge)
263 }
264
265 #[allow(unused_variables)]
266 fn loop_mom_params(&self, lmb: &LoopMomentumBasis) -> Vec<Atom> {
267 self.loop_momentum_basis
268 .loop_edges
269 .iter()
270 .flat_map(|edge_id| {
271 vec![
272 GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([1]))),
273 GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([2]))),
274 GS.emr_mom(*edge_id, Atom::from(ExpandedIndex::from_iter([3]))),
275 ]
276 })
277 .collect()
278 }
279
280 fn external_spatial_params(&self) -> Vec<Atom> {
281 self.external_momentum_edge_order()
282 .into_iter()
283 .flat_map(|edge_id| {
284 vec![
285 GS.emr_mom(edge_id, Atom::from(ExpandedIndex::from_iter([1]))),
286 GS.emr_mom(edge_id, Atom::from(ExpandedIndex::from_iter([2]))),
287 GS.emr_mom(edge_id, Atom::from(ExpandedIndex::from_iter([3]))),
288 ]
289 })
290 .collect()
291 }
292}
293
294impl FeynmanGraph for Graph {
400 fn num_virtual_edges(&self, subgraph: SuBitGraph) -> usize {
401 let internal_subgraph = InternalSubGraph::cleaned_filter_pessimist(subgraph, self);
402 self.count_internal_edges(&internal_subgraph)
403 }
404
405 fn is_incoming_to(&self, edge: EdgeIndex, vertex: NodeIndex) -> bool {
406 let (_, pair) = self[&edge];
407 match pair {
408 HedgePair::Unpaired { hedge, flow } => {
409 self.node_id(hedge) == vertex && matches!(flow, Flow::Sink)
410 }
411 HedgePair::Paired { source: _, sink } => self.node_id(sink) == vertex,
412 HedgePair::Split {
413 source: _,
414 sink,
415 split: _,
416 } => self.node_id(sink) == vertex,
417 }
418 }
419
420 fn add_signs_to_edges(&self, node_id: NodeIndex) -> Vec<isize> {
443 let node_hairs: SuBitGraph = self.iter_crown(node_id).into();
444
445 self.iter_edges_of(&node_hairs)
446 .map(|(_, edge_index, _)| {
447 if !self.is_incoming_to(edge_index, node_id) {
448 -(Into::<usize>::into(edge_index) as isize)
449 } else {
450 Into::<usize>::into(edge_index) as isize
451 }
452 })
453 .collect()
454 }
455
456 fn get_cff_inverse_energy_product(&self) -> Atom {
458 let full_subgraph = self.full_filter();
459 get_cff_inverse_energy_product_impl(self, &full_subgraph, &[])
460 }
461
462 fn get_loop_number(&self) -> usize {
463 let internal_subgraph =
464 InternalSubGraph::cleaned_filter_pessimist(self.full_filter(), self);
465 let n_initial = self.initial_state_cut.nedges(self);
466 self.cyclotomatic_number(&internal_subgraph) - n_initial
467 }
468
469 fn get_real_mass_vector<T: FloatLike>(&self, model: &Model) -> EdgeVec<F<T>> {
470 self.new_edgevec(|edge, _edge_id, _| {
471 let c = edge
472 .mass_value(model, &self.param_builder)
473 .unwrap_or(Complex {
474 re: F::from_f64(0.0),
475 im: F::from_f64(0.0),
476 });
477
478 if c.im.is_zero() {
479 c.re
480 } else {
481 panic!(
482 "Complex masses not yet supported in gammaLoop for {}:{}",
483 edge.mass_atom(),
484 c
485 )
486 }
487 })
488 }
489
490 fn get_external_masses<T: FloatLike>(&self, model: &Model) -> TiVec<ExternalIndex, F<T>> {
491 let external_filter: SuBitGraph = self.external_filter();
492
493 self.iter_edges_of(&external_filter)
494 .sorted_by_key(|(pair, _, _)| match pair {
495 HedgePair::Unpaired { hedge, .. } => *hedge,
496 _ => unreachable!(),
497 })
498 .map(|(_, _, edge)| {
499 let c = edge
500 .data
501 .mass_value(model, &self.param_builder)
502 .unwrap_or(Complex {
503 re: F::from_f64(0.0),
504 im: F::from_f64(0.0),
505 });
506
507 if c.im.is_zero() {
508 c.re
509 } else {
510 panic!(
511 "Complex masses not yet supported in gammaLoop for {}:{}",
512 edge.data.mass_atom(),
513 c
514 )
515 }
516 })
517 .collect()
518 }
519
520 fn get_energy_cache<T: FloatLike>(
521 &self,
522 model: &Model,
523 loop_moms: &LoopMomenta<F<T>>,
524 external_moms: &ExternalFourMomenta<F<T>>,
525 lmb: &LoopMomentumBasis,
526 ) -> EdgeVec<F<T>> {
527 self.new_edgevec_from_iter(
528 lmb.edge_signatures
529 .borrow()
530 .into_iter()
531 .map(|(_, sig)| sig.compute_four_momentum_from_three(loop_moms, external_moms))
532 .zip(self.iter_edges())
533 .map(|(emr_mom, (p, _, edge))| {
534 if p.is_paired() {
535 emr_mom
536 .spatial
537 .on_shell_energy(edge.data.mass_value(model, &self.param_builder).map(
538 |m| {
539 if m.im.is_non_zero() {
540 panic!("Complex masses not yet supported in gammaLoop")
541 }
542 F::<T>::from_ff64(m.re)
543 },
544 ))
545 .value
546 } else {
547 emr_mom.temporal.value }
549 }),
550 )
551 .unwrap()
552 }
553
554 fn get_emr_vec_cache<T: FloatLike>(
555 &self,
556 loop_moms: &LoopMomenta<F<T>>,
557 external_moms: &ExternalFourMomenta<F<T>>,
558 lmb: &LoopMomentumBasis,
559 ) -> Vec<F<T>> {
560 if self.initial_state_cut.nedges(&self.underlying) == 0 {
561 self.iter_edges()
562 .flat_map(|(pair, edge_id, _)| {
563 if let HedgePair::Paired { .. } = pair {
564 let emr_vec = lmb.edge_signatures[edge_id]
565 .compute_three_momentum_from_four(loop_moms, external_moms);
566 vec![emr_vec.px, emr_vec.py, emr_vec.pz]
567 } else {
568 vec![]
569 }
570 })
571 .collect()
572 } else {
573 let underlying_without_is_cut = self
574 .underlying
575 .full_filter()
576 .subtract(&self.initial_state_cut.left)
577 .subtract(&self.initial_state_cut.right);
578
579 self.underlying
580 .iter_edges_of(&underlying_without_is_cut)
581 .sorted_by(|a, b| a.1.cmp(&b.1))
582 .flat_map(|(_pair, edge_id, _)| {
583 let emr_vec = lmb.edge_signatures[edge_id]
584 .compute_three_momentum_from_four(loop_moms, external_moms);
585 vec![emr_vec.px, emr_vec.py, emr_vec.pz]
586 })
587 .collect()
588 }
589 }
590
591 fn get_esurface_canonization(&self, lmb: &LoopMomentumBasis) -> Option<ShiftRewrite> {
592 lmb.ext_edges
600 .iter_enumerated()
601 .find(|(external_index, edge_id)| {
602 lmb.edge_signatures[**edge_id].external[*external_index] == SignOrZero::Zero
603 })
604 .map(|(_, dep_mom_edge_id)| {
605 let dep_mom_signatrue = &lmb.edge_signatures[*dep_mom_edge_id].external;
606
607 let external_shift = lmb
608 .ext_edges
609 .iter()
610 .zip(dep_mom_signatrue)
611 .filter(|(_, dep_mom_sign)| dep_mom_sign.is_sign())
612 .map(|(external_edge, dep_mom_sign)| (*external_edge, dep_mom_sign as i64))
613 .collect_vec();
614
615 ShiftRewrite {
616 dependent_momentum: *dep_mom_edge_id,
617 dependent_momentum_expr: external_shift,
618 }
619 })
620 }
621
622 fn external_in_or_out_signature(&self) -> ExternalSignature {
623 let external_filter: SuBitGraph = self.external_filter();
624
625 self.iter_edges_of(&external_filter)
626 .sorted_by_key(|(pair, _, _)| match pair {
627 HedgePair::Unpaired { hedge, .. } => *hedge,
628 _ => unreachable!(),
629 })
630 .map(|(pair, _, _)| match pair {
631 HedgePair::Unpaired {
632 flow: Flow::Sink, ..
633 } => 1i8,
634 HedgePair::Unpaired {
635 flow: Flow::Source, ..
636 } => -1i8,
637 _ => unreachable!(),
638 })
639 .collect()
640 }
641
642 fn get_external_partcles(&self) -> Vec<ArcParticle> {
643 let external_filter: SuBitGraph = self.external_filter();
644
645 self.iter_edges_of(&external_filter)
646 .sorted_by_key(|(pair, _, _)| match pair {
647 HedgePair::Unpaired { hedge, .. } => *hedge,
648 _ => unreachable!(),
649 })
650 .filter_map(|(_, _, data)| data.data.particle())
651 .collect()
652 }
653
654 fn get_external_signature(&self) -> SignatureLike<ExternalIndex> {
655 let externals: SuBitGraph = self.external_filter();
656
657 SignatureLike::from_iter(
658 self.iter_edges_of(&externals)
659 .sorted_by_key(|(pair, _, _)| match pair {
660 HedgePair::Unpaired { hedge, .. } => *hedge,
661 _ => unreachable!(),
662 })
663 .map(|(pair, _, _)| match pair {
664 HedgePair::Unpaired {
665 flow: Flow::Source, ..
666 } => SignOrZero::Minus,
667 HedgePair::Unpaired {
668 flow: Flow::Sink, ..
669 } => SignOrZero::Plus,
670 _ => unreachable!(),
671 }),
672 )
673 }
674
675 fn get_energy_atoms(&self) -> Vec<Atom> {
676 self.iter_edges()
677 .map(|(pair, edge_id, _)| match pair {
678 HedgePair::Paired { .. } => ose_atom_from_index(edge_id),
679 HedgePair::Unpaired { .. } => external_energy_atom_from_index(edge_id),
680 _ => unreachable!(),
681 })
682 .collect_vec()
683 }
684
685 fn expected_scale(&self, e_cm: F<f64>, model: &Model) -> F<f64> {
686 let mut scale = Complex::new_re(F(1.0));
687 for (_, _, vertex) in self.iter_nodes() {
688 let coupling_value = vertex
690 .vertex_rule
691 .as_ref()
692 .map(|r| {
693 r.couplings
694 .iter()
695 .flat_map(|couplings| {
696 couplings.iter().map(|coupling| {
697 coupling
698 .as_ref()
699 .map(|coupling| {
700 model.couplings[coupling]
701 .value
702 .map(|x| Complex::new(F(x.re), F(x.im)))
703 .unwrap_or(Complex::new_re(F(1.0)))
704 })
705 .unwrap_or(Complex::new_re(F(1.0)))
706 })
707 })
708 .fold(Complex::new_re(F(1.0)), |product, term| product * term)
709 })
710 .unwrap_or(Complex::new_re(F(1.0)));
711
712 scale *= Complex::new_re(e_cm.powi(vertex.dod.value)) * coupling_value;
713 }
714
715 for (_, _, edge_data) in self.iter_edges_of(
716 &self
717 .full_filter()
718 .subtract(&self.external_filter::<SuBitGraph>()),
719 ) {
720 scale *= Complex::new_re(e_cm.powi(edge_data.data.dod.value))
721 }
722
723 scale.norm_squared().sqrt()
724 }
725
726 fn no_dummy(&self) -> SuBitGraph {
727 let mut subgraph = self.full_filter();
728 for (hedge_pair, _, edge) in self.iter_edges() {
729 if edge.data.is_dummy {
730 subgraph.sub(hedge_pair)
731 }
732 }
733 subgraph
734 }
735
736 fn dummy_list(&self) -> Vec<EdgeIndex> {
737 self.iter_edges()
738 .filter_map(|(_, edge_index, edge_data)| {
739 if edge_data.data.is_dummy {
740 Some(edge_index)
741 } else {
742 None
743 }
744 })
745 .collect()
746 }
747
748 fn all_st_cuts_for_cs(
749 &self,
750 source_nodes: HedgeNode,
751 target_nodes: HedgeNode,
752 initial_state_tree: &SuBitGraph,
753 ) -> Vec<(SuBitGraph, OrientedCut, SuBitGraph)> {
754 self.underlying
755 .all_cuts(source_nodes, target_nodes)
756 .into_iter()
757 .map(|(mut l, mut c, mut r)| {
758 c.left.subtract_with(&self.initial_state_cut.left);
760 c.right.subtract_with(&self.initial_state_cut.left);
761 c.left.subtract_with(&self.initial_state_cut.right);
762 c.right.subtract_with(&self.initial_state_cut.right);
763 l.subtract_with(initial_state_tree);
764 r.subtract_with(initial_state_tree);
765
766 (l, c, r)
767 })
768 .collect_vec()
769 }
770}