1use std::{
2 collections::BTreeMap,
3 ops::Deref,
4 path::{Path, PathBuf},
5};
6
7use crate::{
8 cff::generation::SurfaceCache,
9 feyngen::{
10 GenerationType,
11 diagram_generator::{EdgeColor, NodeColorWithVertexRule},
12 },
13 graph::{
14 GraphGroup, GroupId, LoopMomentumBasis, attribute_warnings::warn_about_unknown_attributes,
15 edge::EdgeExtraData,
16 },
17 integrands::process::ParamBuilder,
18 model::Model,
19 momentum::sample::LoopIndex,
20 numerator::{
21 GlobalPrefactor,
22 aind::{Aind, NewAind},
23 graph::GeneratePolarizations,
24 ufo::UFO,
25 },
26 processes::DotExportSettings,
27 utils::symbolica_ext::DOD,
28 uv::{UltravioletGraph, uv_graph::UVE},
29};
30use ahash::{AHashMap, AHashSet};
31use idenso::{
32 color::{ColorSimplifier, ColorSimplifySettings},
33 tensor::SymbolicNetParse,
34};
35use spenso::shadowing::symbolica_utils::LogPrint;
36
37use color_eyre::{Report, Result, Section};
38
39use eyre::{Context, Ok, eyre};
40use itertools::Itertools;
41use linnet::{
44 half_edge::{
45 HedgeGraph, NodeIndex,
46 builder::HedgeGraphBuilder,
47 involution::{EdgeData, EdgeIndex, EdgeVec, Flow, Hedge, HedgePair},
48 nodestore::NodeStorageVec,
49 subgraph::{Inclusion, ModifySubSet, OrientedCut, SuBitGraph, SubSetLike, SubSetOps},
50 swap::Swap,
51 },
52 parser::{DotEdgeData, DotGraph, DotHedgeData, DotVertexData, GraphSet, HedgeParseError},
53 permutation::Permutation,
54};
55use spenso::{
56 contraction::Contract,
57 network::parsing::ParseSettings,
58 structure::{HasStructure, OrderedStructure, representation::Euclidean, slot::IsAbstractSlot},
59 tensors::{data::StorageTensor, parametric::ParamTensor},
60};
61use symbolica::{
62 atom::{Atom, AtomOrView},
63 graph::Graph as SymbolicaGraph,
64};
65use tracing::instrument;
66use tracing::{debug, warn};
67use typed_index_collections::TiVec;
68
69use super::{
70 Autogen, Edge, Graph, HedgeData, LMBext, Vertex,
71 edge::{EdgeMass, ParseEdge},
72 global::ParseData,
73 hedge_data::{NumIndices, ParseHedgeData},
74 vertex::ParseVertex,
75};
76
77pub fn extract_oriented_particles_from_vertex_hedges<I, V>(
79 graph: &HedgeGraph<ParseEdge, V, ParseHedgeData>,
80 hedges: I,
81 model: &Model,
82) -> Vec<crate::model::ArcParticle>
83where
84 I: Iterator<Item = Hedge>,
85{
86 hedges
87 .filter_map(|h| {
88 let eid = graph[&h];
89 if graph[eid].is_dummy {
90 return None;
91 }
92 let particle = graph[eid].particle.particle()?;
93 Some(if graph.flow(h) != Flow::Sink {
94 particle.get_anti_particle(model)
95 } else {
96 particle
97 })
98 })
99 .collect()
100}
101
102type NumGraph = HedgeGraph<ParseEdge, ParseVertex, HedgeData>;
104type UnderlyingGraph = HedgeGraph<Edge, Vertex, HedgeData>;
105
106pub mod string_utils;
107pub use string_utils::{FromStripedStr, StripParse, ToQuoted};
108
109#[derive(Clone, Debug)]
110pub struct ParseGraph {
111 pub global_data: ParseData,
112 pub graph: HedgeGraph<ParseEdge, ParseVertex, ParseHedgeData>,
113}
114
115impl Deref for ParseGraph {
116 type Target = HedgeGraph<ParseEdge, ParseVertex, ParseHedgeData>;
117
118 fn deref(&self) -> &Self::Target {
119 &self.graph
120 }
121}
122
123impl ParseGraph {
124 pub fn n_fermion_loops(&self) -> usize {
125 let fermions: SuBitGraph = self.graph.from_filter(|a| a.particle.is_fermion());
126
127 self.graph.cyclotomatic_number(&fermions)
128 }
129 pub fn n_external_fermion_loops(&mut self) -> Result<usize> {
130 let internal = self.n_fermion_loops();
131 self.graph
132 .sew(
133 |_, ae, _, be| {
134 if let (Some(a), Some(b)) = (ae.data.is_cut, be.data.is_cut) {
135 a == b
136 } else {
137 false
138 }
139 },
140 |af, ae, bf, be| match (af, bf) {
141 (Flow::Sink, Flow::Source) => (Flow::Sink, ae),
142 (Flow::Source, Flow::Sink) => (Flow::Source, be),
143 _ => panic!("Cannot sew hedges with flow {:?} and {:?}", af, bf),
144 },
145 )
146 .map_err(|e| eyre::eyre!("Graph sewing failed: {:?}", e))?;
147
148 Ok(self.n_fermion_loops() - internal)
149 }
150
151 pub fn debug_dot(&self) -> String {
152 DotGraph::from(self).debug_dot()
153 }
154 pub(crate) fn hedge_order(&self, model: &Model) -> Result<Vec<u8>> {
155 let mut hedges = vec![None; self.n_hedges()];
156
157 for (_, neighs, v) in self.iter_nodes() {
158 self.process_vertex_hedges(&mut hedges, neighs, v, model)?;
159 }
160
161 hedges
162 .into_iter()
163 .collect::<Option<Vec<u8>>>()
164 .ok_or_else(|| eyre!("Nodes do not cover hedges"))
165 }
166
167 fn process_vertex_hedges(
168 &self,
169 hedges: &mut [Option<u8>],
170 neighs: impl Iterator<Item = Hedge>,
171 vertex: &ParseVertex,
172 model: &Model,
173 ) -> Result<()> {
174 let (mut particles, vertex_name) = Self::extract_vertex_particles(vertex);
175 let mut other_order = particles.len();
176
177 let hedge_vec: Vec<_> = neighs.collect();
178 let oriented_particles =
179 extract_oriented_particles_from_vertex_hedges(self, hedge_vec.iter().copied(), model);
180
181 let mut particle_iter = oriented_particles.into_iter();
183
184 for h in hedge_vec {
185 let eid = self[&h];
186
187 let order = if self[eid].is_dummy || self[eid].particle.particle().is_none() {
188 other_order += 1;
189 (other_order - 1) as u8
190 } else {
191 let oriented_particle = particle_iter
193 .next()
194 .expect("Mismatch between hedges and oriented particles");
195 debug!("Oriented particle: {h} : {}", oriented_particle.name);
196 if let Some(name) = &vertex_name {
198 if let Some((pos, matched_particle)) = particles
199 .iter_mut()
200 .find_position(|p| **p == Some(oriented_particle.clone()))
201 {
202 *matched_particle = None;
203 pos as u8
204 } else {
205 return Err(eyre!(
206 "Particle {} not in vertex rule {}",
207 oriented_particle.name.to_string(),
208 name
209 ));
210 }
211 } else {
212 other_order += 1;
213 (other_order - 1) as u8
214 }
215 };
216
217 hedges[h.0] = Some(order);
218 }
219
220 if particles.iter().any(|p| p.is_some()) {
222 return Err(eyre!(
223 "Particles to vertex rules no match for set: {:?}",
224 particles
225 ));
226 }
227
228 Ok(())
229 }
230
231 fn extract_vertex_particles(
232 vertex: &ParseVertex,
233 ) -> (Vec<Option<crate::model::ArcParticle>>, Option<String>) {
234 if let Some(vertex_rule) = &vertex.vertex_rule {
235 let particles = vertex_rule
236 .particles
237 .iter()
238 .map(|p| Some(p.clone()))
239 .collect();
240 (particles, Some(vertex_rule.name.to_string()))
241 } else {
242 (Vec::new(), None)
243 }
244 }
245
246 #[instrument(skip_all, fields(graph= %graph.to_dot(),name = %graph_name.as_ref(),external_connections = ?external_connections))]
247 pub(crate) fn from_symbolica_graph(
248 model: &Model,
249 graph_name: impl AsRef<str>,
250 graph: &SymbolicaGraph<NodeColorWithVertexRule, EdgeColor>,
251 symmetry_factor: Atom,
252 external_connections: &[(Option<usize>, Option<usize>)],
253 ) -> Result<Self> {
254 fn mark_edge_as_seen(seen_edges: &mut AHashSet<usize>, edge_idx: usize) -> Result<()> {
255 if !seen_edges.insert(edge_idx) {
256 return Err(eyre!(
257 "External connections must be unique: edge {} already used",
258 edge_idx
259 ));
260 }
261 Ok(())
262 }
263
264 fn validate_edge_compatibility(
265 out_edge: &symbolica::graph::Edge<EdgeColor>,
266 in_edge: &symbolica::graph::Edge<EdgeColor>,
267 in_id: usize,
268 out_id: usize,
269 ) -> Result<()> {
270 if out_edge.directed != in_edge.directed {
271 return Err(eyre!(
272 "External edges must have the same directedness, for edge ids {} and {} found {:?} and {:?}",
273 in_id,
274 out_id,
275 in_edge,
276 out_edge
277 ));
278 }
279
280 if in_edge.data.pdg.abs() != out_edge.data.pdg.abs() {
281 return Err(eyre!(
282 "External edges must have the same pdg in abs, for edge ids {} and {} found {:?} and {:?}",
283 in_id,
284 out_id,
285 in_edge,
286 out_edge
287 ));
288 }
289
290 Ok(())
291 }
292
293 #[allow(clippy::too_many_arguments)]
297 fn process_single_connection_internal(
298 edge_idx: usize,
299 flow: Flow,
300 hedge: Option<Hedge>,
301 vertex_map: &AHashMap<usize, NodeIndex>,
302 seen_edges: &mut AHashSet<usize>,
303 graph: &SymbolicaGraph<NodeColorWithVertexRule, EdgeColor>,
304 model: &Model,
305 builder: &mut HedgeGraphBuilder<ParseEdge, ParseVertex, ParseHedgeData>,
306 ) -> Result<()> {
307 if !seen_edges.contains(&edge_idx) {
309 mark_edge_as_seen(seen_edges, edge_idx)?;
310 }
311
312 let edge = &graph.edges()[edge_idx];
313 let mut data = ParseEdge::from_symbolica_edge(model, &edge.data, hedge);
314
315 let (out_vertex, in_vertex) = edge.vertices;
316 let mut orientation = data.particle.orientation();
317
318 let (node_idx, final_orientation, final_flow) = match flow {
320 Flow::Source => {
321 if let Some(&sink_node) = vertex_map.get(&in_vertex) {
322 data.particle = data.particle.reverse(model);
323 orientation = orientation.reverse();
324 (sink_node, orientation, flow)
325 } else if let Some(&source_node) = vertex_map.get(&out_vertex) {
326 (source_node, orientation, flow)
327 } else {
328 return Err(eyre!(
329 "Outgoing external edges must be attached to an external node (degree 1)"
330 ));
331 }
332 }
333 Flow::Sink => {
334 if let Some(&sink_node) = vertex_map.get(&in_vertex) {
335 (sink_node, orientation, flow)
336 } else if let Some(&source_node) = vertex_map.get(&out_vertex) {
337 data.particle = data.particle.reverse(model);
338 orientation = orientation.reverse();
339 (source_node, orientation, flow)
340 } else {
341 return Err(eyre!(
342 "Incoming external edges must be attached to an external node (degree 1)"
343 ));
344 }
345 }
346 };
347
348 builder.add_external_edge(node_idx, data, final_orientation, final_flow);
350 Ok(())
351 }
352
353 let mut builder = HedgeGraphBuilder::new();
355
356 let mut tags_to_edge_id = BTreeMap::new();
357 let mut vertex_map = AHashMap::new();
358 for (i, n) in graph.nodes().iter().enumerate() {
359 if n.edges.len() == 1 {
360 tags_to_edge_id.insert(n.data.external_tag, n.edges[0]);
361 } else {
362 vertex_map.insert(i, builder.add_node(ParseVertex::from(&n.data)));
363 }
364 }
365
366 let mut seen_edges = AHashSet::new();
367 let mut generation_type: Option<GenerationType> = None;
368
369 for (i, &(in_tag, out_tag)) in external_connections.iter().enumerate() {
371 if let Some(in_tag) = in_tag {
372 let in_edge_idx = tags_to_edge_id[&(in_tag as i32)];
373 mark_edge_as_seen(&mut seen_edges, in_edge_idx)?;
374 let in_edge = &graph.edges()[in_edge_idx];
375
376 let is_cut_hedge = if let Some(out_id) = out_tag {
377 let out_edge_idx = tags_to_edge_id[&(out_id as i32)];
378 mark_edge_as_seen(&mut seen_edges, out_edge_idx)?;
379
380 let out_edge = &graph.edges()[out_edge_idx];
381
382 validate_edge_compatibility(out_edge, in_edge, in_tag, out_id)?;
383 if let Some(existing_type) = &generation_type {
384 if *existing_type != GenerationType::CrossSection {
385 return Err(eyre!(
386 "Cannot have both incoming and outgoing external connections for amplitudes"
387 ));
388 }
389 } else {
390 generation_type = Some(GenerationType::CrossSection);
391 }
392 Some(Hedge(i))
393 } else {
394 None
395 };
396
397 process_single_connection_internal(
398 in_edge_idx,
399 Flow::Sink,
400 is_cut_hedge,
401 &vertex_map,
402 &mut seen_edges,
403 graph,
404 model,
405 &mut builder,
406 )?;
407 } else if out_tag.is_some() {
408 if let Some(existing_type) = &generation_type {
409 if *existing_type != GenerationType::Amplitude {
410 return Err(eyre!(
411 "Cannot mix single directional connections with bidirectional ones for cross sections"
412 ));
413 }
414 } else {
415 generation_type = Some(GenerationType::Amplitude);
416 }
417 }
418 }
419
420 for (in_id, out_id) in external_connections.iter() {
422 if let Some(out_id) = out_id
423 && in_id.is_none()
424 {
425 let out_edge_idx = tags_to_edge_id[&(*out_id as i32)];
426 mark_edge_as_seen(&mut seen_edges, out_edge_idx)?;
427
428 process_single_connection_internal(
429 out_edge_idx,
430 Flow::Source,
431 None,
432 &vertex_map,
433 &mut seen_edges,
434 graph,
435 model,
436 &mut builder,
437 )?;
438 }
439 }
440
441 for (i, edge) in graph.edges().iter().enumerate() {
443 if seen_edges.contains(&i) {
444 continue;
445 }
446 let (source_v, sink_v) = edge.vertices;
447
448 let source = vertex_map[&source_v];
449 let sink = vertex_map[&sink_v];
450 let data = ParseEdge::from_symbolica_edge(model, &edge.data, None);
451 let orientation = data.particle.orientation();
452 builder.add_edge(source, sink, data, orientation);
453 }
454
455 for (i, &(in_id, out_id)) in external_connections.iter().enumerate() {
457 if let Some(out_id) = out_id
458 && in_id.is_some()
459 {
460 let out_edge_idx = tags_to_edge_id[&(out_id as i32)];
461
462 process_single_connection_internal(
463 out_edge_idx,
464 Flow::Source,
465 Some(Hedge(i)),
466 &vertex_map,
467 &mut seen_edges,
468 graph,
469 model,
470 &mut builder,
471 )?;
472 }
473 }
474
475 let mut parsed = ParseGraph {
476 global_data: ParseData {
477 name: graph_name.as_ref().into(),
478 overall_factor: symmetry_factor,
479 ..Default::default()
480 },
481 graph: builder.into(),
482 };
483
484 debug!("Parsing {}", parsed.debug_dot());
485 parsed.fix_cp_vertex_rules(model)?;
486 debug!("Parsing fixed{}", parsed.debug_dot());
487 Ok(parsed)
488 }
489
490 fn fix_cp_vertex_rules(&mut self, model: &Model) -> Result<()> {
491 let mut new_nodes = AHashMap::new();
492 for (node_id, neighs, v) in self.graph.iter_nodes() {
493 let (particles, vertex_name) = Self::extract_vertex_particles(v);
494 let Some(vertex) = vertex_name.map(|a| model.get_vertex_rule(a)) else {
495 continue;
496 };
497 let hedge_vec: Vec<_> = neighs.collect();
498 let particles = particles.into_iter().flatten().sorted().collect_vec();
499 let mut oriented_particles = extract_oriented_particles_from_vertex_hedges(
500 self,
501 hedge_vec.iter().copied(),
502 model,
503 );
504 oriented_particles.sort();
505
506 let couplings = vertex.coupling_orders(model);
507 if particles != oriented_particles {
519 debug!("Need to change");
520 let cp_particles: Vec<_> = oriented_particles
521 .iter()
522 .map(|a| a.get_anti_particle(model))
523 .sorted()
524 .collect();
525 if cp_particles == particles {
526 let res = model
527 .particle_set_to_vertex_rules_map
528 .get(&oriented_particles);
529
530 if let Some(res) = res {
531 let possible: Vec<_> = res
532 .iter()
533 .filter(|a| a.coupling_orders(model) == couplings)
534 .collect();
535
536 if possible.len() == 1 {
537 new_nodes.insert(node_id, possible[0].clone());
538 } else {
539 let particles = particles.iter().map(|p| p.name.as_str()).collect_vec();
540
541 return Err(eyre!(
542 "Multiple compatible vertex rules for {:?}",
543 particles
544 ));
545 }
546 } else {
547 return Err(eyre!(
548 "Failed to find CP vertex rule for particles: {:?} for node {node_id} in graph {}",
549 particles,
550 self.global_data.name,
551 ));
552 }
553 } else {
554 let particles = particles.iter().map(|p| p.name.as_str()).collect_vec();
555 return Err(eyre!(
556 "Failed to find CP vertex rule for particles: {:?} for node {node_id} in graph {}",
557 particles,
558 self.global_data.name,
559 ));
560 }
561 }
562 }
563
564 for (node_id, vr) in new_nodes {
565 debug!("New vr for {node_id}:{}", vr.name);
566 self.graph[node_id].vertex_rule = Some(vr);
567 }
568 Ok(())
569 }
570}
571
572impl ParseGraph {
573 pub(crate) fn from_parsed(graph: DotGraph, model: &Model) -> Result<Self> {
574 warn_about_unknown_attributes(&graph);
575 let global_data = graph.global_data.into();
576 let graph = graph
577 .graph
578 .map_data_ref_result(
579 |_, _, v| Ok(v),
580 ParseEdge::parse(model),
581 ParseHedgeData::parse(),
582 )?
583 .map_data_ref_result(
584 ParseVertex::parse(model, &global_data),
585 |_, _, _, e| Ok(e.map(Clone::clone)),
586 |(_, h)| Ok(h.clone()),
587 )?;
588
589 Ok(Self { graph, global_data })
590 }
591}
592
593struct InitialGraphData {
595 overall_factor: Atom,
596 global_prefactor: GlobalPrefactor,
597 additional_params: Vec<Atom>,
598 add_polarizations: bool,
599 group_id: Option<GroupId>,
600 is_group_master: bool,
601 name: String,
602}
603
604struct CutProcessingResult {
606 lmb_ids: BTreeMap<LoopIndex, EdgeIndex>,
607 xs_ext_id: BTreeMap<Hedge, (EdgeIndex, Hedge)>,
608 initial_hedges: SuBitGraph,
609 full_cut: SuBitGraph,
610}
611
612impl CutProcessingResult {
613 fn permute(&mut self, graph: &mut NumGraph) -> Result<()> {
614 let (h_perm, edge_perm): (Vec<_>, Vec<_>) = self
615 .xs_ext_id
616 .iter()
617 .enumerate()
618 .map(|(target_pos, (_, (edge_idx, h_id)))| {
619 ((h_id.0, target_pos), (edge_idx.0, target_pos))
620 })
621 .unzip();
622
623 let per = Permutation::from_mappings(edge_perm, graph.n_edges()).unwrap();
624 let perh = Permutation::from_mappings(h_perm, graph.n_hedges()).unwrap();
625
626 debug!("Before: {}", graph.dot(&self.initial_hedges));
627 <HedgeGraph<_, _, _> as Swap<Hedge>>::permute(graph, &perh);
628 let trans = perh.transpositions();
629
630 for (i, j) in trans.into_iter().rev() {
631 self.full_cut.swap(Hedge(i), Hedge(j));
632 }
634
635 debug!("Before after: {}", graph.dot(&self.initial_hedges));
636 <HedgeGraph<_, _, _> as Swap<EdgeIndex>>::permute(graph, &per);
637
638 debug!(" after: {}", graph.dot(&self.initial_hedges));
639 Ok(())
640 }
641}
642
643struct NumeratorData {
645 color_edge: EdgeVec<Atom>,
646 spin_edge: EdgeVec<Atom>,
647 color_vertex: Vec<Option<ParamTensor<OrderedStructure<Euclidean, Aind>>>>,
648 spin_vertex: Vec<Option<ParamTensor<OrderedStructure<Euclidean, Aind>>>>,
649}
650
651fn display_graph_source_path(path: &Path) -> PathBuf {
652 if path.is_absolute() {
653 return path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
654 }
655
656 let joined = std::env::current_dir()
657 .map(|cwd| cwd.join(path))
658 .unwrap_or_else(|_| path.to_path_buf());
659 joined.canonicalize().unwrap_or(joined)
660}
661
662impl Graph {
663 pub fn dot_serialize(&self, settings: &DotExportSettings) -> String {
664 let mut out = String::new();
665 self.dot_serialize_fmt(&mut out, settings).unwrap();
666 out
667 }
668
669 pub(crate) fn dot_serialize_io(
670 &self,
671 writer: &mut impl std::io::Write,
672 settings: &DotExportSettings,
673 ) -> Result<(), std::io::Error> {
674 let g = self.to_dot_graph_with_settings(settings);
675 g.write_io(writer)
676 }
677
678 #[allow(dead_code)]
679 pub(crate) fn dot_split_serialize_io(
680 &self,
681 writer: &mut impl std::io::Write,
682 ) -> Result<(), std::io::Error> {
683 let g = self.to_split_dotgraph();
684 g.write_io(writer)
685 }
686
687 pub fn dot_serialize_fmt(
688 &self,
689 writer: &mut impl std::fmt::Write,
690 settings: &DotExportSettings,
691 ) -> Result<(), std::fmt::Error> {
692 let g = self.to_dot_graph_with_settings(settings);
693 g.write_fmt(writer)
694 }
695
696 pub(crate) fn from_parsed_with_validation(graph: ParseGraph, model: &Model) -> Result<Self> {
697 let res = Self::from_parsed(graph, model)?;
698 res.validate_full_numerator_tensor_network()
699 .with_context(|| {
700 format!(
701 "Failed to validate full numerator tensor network for graph {}",
702 res.name
703 )
704 })?;
705 Ok(res)
706 }
707
708 #[instrument(skip_all, fields(graph= %graph.debug_dot(),name = %graph.global_data.name.as_str()))]
709 pub(crate) fn from_parsed(graph: ParseGraph, model: &Model) -> Result<Self> {
710 let (initial_data, mut graph) = Self::extract_initial_data(&graph, model)?;
711
712 graph
714 .sew(
715 |_, ae, _, be| {
716 if let (Some(a), Some(b)) = (ae.data.is_cut, be.data.is_cut) {
717 a == b
718 } else {
719 false
720 }
721 },
722 |af, ae, bf, be| match (af, bf) {
723 (Flow::Sink, Flow::Source) => (Flow::Sink, ae),
724 (Flow::Source, Flow::Sink) => (Flow::Source, be),
725 _ => panic!("Cannot sew hedges with flow {:?} and {:?}", af, bf),
726 },
727 )
728 .map_err(|e| eyre::eyre!("Graph sewing failed: {:?}", e))?;
729
730 let mut cut_result = Self::process_cut_edges(&graph)?;
731
732 cut_result.permute(&mut graph)?;
733
734 let numerators = Self::generate_numerators(&graph, model)?;
735
736 let initial_state_cut =
737 OrientedCut::from_underlying_strict(cut_result.initial_hedges, &graph)?;
738
739 let (global_prefactor, param_builder) = Self::setup_global_prefactor_and_params(
740 initial_data.global_prefactor,
741 initial_data.add_polarizations,
742 initial_data.additional_params.clone(),
743 &initial_state_cut,
744 &graph,
745 model,
746 )
747 .with_context(|| {
748 format!(
749 "Failed to setup_global_prefactor_and_params for graph {}",
750 initial_data.name
751 )
752 })?;
753
754 let underlying = Self::build_underlying_graph(
755 graph,
756 &initial_state_cut,
757 &numerators,
758 model,
759 ¶m_builder,
760 )
761 .with_context(|| format!("Failed to build underlying graph {}", initial_data.name))?;
762
763 let loop_momentum_basis = Self::setup_loop_momentum_basis(
764 &underlying,
765 &cut_result.full_cut,
766 &cut_result.lmb_ids,
767 &cut_result.xs_ext_id,
768 )
769 .with_context(|| format!("Failed to build lmb for graph {}", initial_data.name))?;
770
771 let mut full_without_initials = underlying.full_filter();
772 full_without_initials.subtract_with(&initial_state_cut.left);
773 let mut tree_edges = underlying.bridges_of(&full_without_initials);
774 tree_edges.union_with(&initial_state_cut.left);
775
776 let mut g = Graph {
777 overall_factor: initial_data.overall_factor,
778 polarizations: global_prefactor.polarizations(),
779 global_prefactor,
780 tree_edges,
781 name: initial_data.name,
782 loop_momentum_basis,
783 initial_state_cut,
784 underlying,
785 surface_cache: SurfaceCache::new(),
786 group_id: initial_data.group_id,
787 is_group_master: initial_data.is_group_master,
788 param_builder,
789 };
790
791 let external_momentum_edge_order = g.external_momentum_edge_order();
792 g.loop_momentum_basis
793 .canonicalize_external_order(&external_momentum_edge_order);
794
795 let updated_param_builder_with_lmb = ParamBuilder::new(
796 &g,
797 model,
798 &g.loop_momentum_basis,
799 initial_data.additional_params,
800 );
801
802 debug!(
803 "Updated param builder with LMB: {}\n{}",
804 g.loop_momentum_basis,
805 updated_param_builder_with_lmb.table(),
806 );
807
808 g.param_builder = updated_param_builder_with_lmb;
809
810 debug!("{}", g.debug_dot());
811
812 Ok(g)
813 }
814
815 fn validate_full_numerator_tensor_network(&self) -> Result<()> {
816 let full_num = self
817 .numerator(&self.full_filter(), &self.empty_subgraph())
818 .get_single_atom()
819 .unwrap()
820 * &self.global_prefactor.num
821 * &self.global_prefactor.projector
822 * &self.overall_factor;
823 let color_simplified = full_num
824 .as_view()
825 .simplify_color_with(ColorSimplifySettings::default().with_cof_dimension_invariants());
826 if !full_num.is_zero() && color_simplified.is_zero() {
827 warn!(
828 "Full numerator for graph '{}' becomes zero after color algebra. The graph/projector color structure likely annihilates the amplitude.",
829 self.name
830 );
831 }
832 let net = full_num
833 .parse_to_symbolic_net::<Aind>(&ParseSettings::default())
834 .map_err(Report::from)?;
835 let dangling = net.graph.dangling_indices();
836 if !dangling.is_empty() {
837 return Err(eyre!(
838 "Full numerator still has dangling tensor indices: \n{}",
839 dangling
840 .iter()
841 .map(|slot| format!(
842 "{}:{}",
843 slot.to_atom().log_print(None),
844 slot.to_atom().to_plain_string()
845 ))
846 .join(",\n")
847 ));
848 }
849
850 Ok(())
851 }
852
853 fn extract_initial_data(
854 parse_graph: &ParseGraph,
855 model: &Model,
856 ) -> Result<(InitialGraphData, NumGraph)> {
857 let hedge_order = parse_graph.hedge_order(model)?;
858 let global_data = &parse_graph.global_data;
859
860 let initial_data = InitialGraphData {
861 additional_params: global_data.parameters.clone(),
862 overall_factor: global_data.overall_factor.clone(),
863 global_prefactor: GlobalPrefactor {
864 num: global_data.num.clone(),
865 projector: global_data.projectors.clone().unwrap_or(Atom::one()),
866 },
867 add_polarizations: global_data.projectors.is_none(),
868 group_id: global_data.group_id,
869 is_group_master: global_data.is_group_master,
870 name: global_data.name.clone(),
871 };
872
873 let num_graph = parse_graph.graph.map_data_ref(
874 |_, _, v| v.clone(),
875 |_, _, _, e| e.map(|e| e.clone()),
876 |h, hd| HedgeData {
877 num_indices: NumIndices::parse(parse_graph)(h, hd),
878 ufo_order: Autogen::from_option_or_generate(hd.ufo_order, || hedge_order[h.0]),
879 },
880 );
881
882 Ok((initial_data, num_graph))
883 }
884
885 fn process_cut_edges(graph: &NumGraph) -> Result<CutProcessingResult> {
886 let mut lmb_ids: BTreeMap<LoopIndex, EdgeIndex> = BTreeMap::new();
887 let mut xs_ext_id: BTreeMap<Hedge, (EdgeIndex, Hedge)> = BTreeMap::new();
888 let mut full_cut: SuBitGraph = graph.full_filter();
889
890 for (p, eid, e) in graph.iter_edges() {
891 let HedgePair::Paired { sink, .. } = p else {
892 if e.data.is_cut.is_some() {
893 return Err(eyre!("Cut edge must be paired"));
895 } else {
896 continue;
897 }
898 };
899
900 if let Some(lmb_id) = e.data.lmb_id {
901 if let Some(old_value) = lmb_ids.insert(lmb_id, eid) {
902 return Err(eyre!(
903 "lmb_id {lmb_id:?} already exists with value {old_value:?}",
904 ));
905 }
906 debug!("Cutting {eid} for lmb_id{lmb_id}");
907 full_cut.sub(p);
908 } else if let Some(h) = e.data.is_cut {
909 if let Some(old_value) = xs_ext_id.insert(h, (eid, sink)) {
910 return Err(eyre!("h {h:?} already exists with value {old_value:?}",));
911 }
912 full_cut.sub(p);
913 }
914 }
915
916 let mut initial_hedges: SuBitGraph = graph.empty_subgraph();
919 for (target_pos, _) in xs_ext_id.iter().enumerate() {
920 initial_hedges.add(Hedge(target_pos));
921 }
922
923 Ok(CutProcessingResult {
924 full_cut,
925 lmb_ids,
926 xs_ext_id,
927 initial_hedges,
928 })
929 }
930
931 fn generate_numerators(graph: &NumGraph, model: &Model) -> Result<NumeratorData> {
932 let mut color_edge: EdgeVec<_> = vec![Atom::num(1); graph.n_edges()].into();
933 let mut spin_edge: EdgeVec<_> = vec![Atom::i(); graph.n_edges()].into();
934
935 for (p, eid, e) in graph.iter_edges() {
936 if let HedgePair::Paired { source, sink } = p {
937 let prop = e
938 .data
939 .particle
940 .particle()
941 .map(|p| model.get_propagator_for_particle(&p.name).numerator.clone())
942 .unwrap_or(Atom::num(1));
943
944 color_edge[eid] = graph[source].color_kronekers(&graph[sink]);
945
946 let spin_slots = [
947 &graph[source].num_indices.spin_indices.edge_indices,
948 &graph[sink].num_indices.spin_indices.edge_indices,
949 ];
950
951 let momenta = [(Flow::Source, graph[&source]), (Flow::Sink, graph[&sink])];
952 let spin_nume = UFO.reindex_spin(&spin_slots, &momenta, prop, |i| {
953 Aind::Edge(usize::from(eid) as u16, i as u16)
954 })?;
955
956 spin_edge[eid] = spin_nume;
957 }
958 }
959
960 let (color_vertex, spin_vertex) = Self::generate_vertex_numerators(graph)?;
961
962 Ok(NumeratorData {
963 color_edge,
964 spin_edge,
965 color_vertex,
966 spin_vertex,
967 })
968 }
969
970 fn setup_global_prefactor_and_params<'a, A: Into<AtomOrView<'a>>, P: IntoIterator<Item = A>>(
971 mut global_prefactor: GlobalPrefactor,
972 add_polarizations: bool,
973 params: P,
974 initial_state_cut: &OrientedCut,
975 graph: &NumGraph,
976 model: &Model,
977 ) -> Result<(GlobalPrefactor, ParamBuilder)> {
978 debug!("Initial state cut: {}", graph.dot(&initial_state_cut.left));
979
980 if add_polarizations {
981 let external_edges = initial_state_cut
982 .left
983 .union(initial_state_cut)
984 .union(&graph.external_filter::<SuBitGraph>());
985 let polarizations = graph.generate_polarizations_of(&external_edges);
986 global_prefactor.projector *= polarizations;
987 }
988
989 let polarizations = global_prefactor.polarizations();
990 let param_builder =
991 ParamBuilder::new(&(&polarizations, graph), model, &graph.lmb(), params);
992
993 Ok((global_prefactor, param_builder))
994 }
995
996 #[allow(clippy::type_complexity)]
997 fn generate_vertex_numerators(
998 graph: &NumGraph,
999 ) -> Result<(
1000 Vec<Option<ParamTensor<OrderedStructure<Euclidean, Aind>>>>,
1001 Vec<Option<ParamTensor<OrderedStructure<Euclidean, Aind>>>>,
1002 )> {
1003 let mut color_vertex: Vec<Option<ParamTensor<OrderedStructure<Euclidean, Aind>>>> =
1004 vec![None; graph.n_nodes()];
1005 let mut spin_vertex = color_vertex.clone();
1006
1007 for (ni, c, v) in graph.iter_nodes() {
1008 let mut color_slots = vec![];
1009 let mut spin_slots = vec![];
1010 let mut order = vec![];
1011 let mut momenta = vec![];
1012 for h in c {
1013 color_slots.push(&graph[h].num_indices.color_indices.vertex_indices);
1014 spin_slots.push(&graph[h].num_indices.spin_indices.vertex_indices);
1015 order.push(graph[h].ufo_order.value);
1016 momenta.push((graph.flow(h), graph[&h]));
1017 }
1018
1019 let perm = Permutation::sort(&order);
1020 perm.apply_slice_in_place(&mut color_slots);
1021 perm.apply_slice_in_place(&mut spin_slots);
1022 perm.apply_slice_in_place(&mut momenta);
1023
1024 let Some(vertex_rule) = &v.vertex_rule else {
1025 continue;
1026 };
1027
1028 let [mut color_structure, couplings, mut spin_structure] =
1029 vertex_rule.tensors(ni.aind(1), ni.aind(0));
1030
1031 spin_structure.map_data_ref_mut_result(|a| {
1032 *a =
1033 UFO.reindex_spin(&spin_slots, &momenta, (*a).clone(), |u| ni.aind(u as u16))?;
1034
1035 Ok(())
1036 })?;
1037
1038 color_structure.map_data_ref_mut_result(|a| {
1041 *a = UFO.reindex_color(&color_slots, (*a).clone(), |u| ni.aind(u as u16))?;
1042 Ok(())
1043 })?;
1044
1045 spin_vertex[ni.0] = Some(spin_structure.contract(&couplings).unwrap());
1046 color_vertex[ni.0] = Some(color_structure);
1047 }
1048
1049 Ok((color_vertex, spin_vertex))
1050 }
1051
1052 fn build_underlying_graph(
1053 graph: NumGraph,
1054 initial_state_cut: &OrientedCut,
1055 numerators: &NumeratorData,
1056 model: &Model,
1057 param_builder: &ParamBuilder,
1058 ) -> Result<UnderlyingGraph> {
1059 let mut loop_edge_filter = graph.full_filter();
1060 loop_edge_filter.subtract_with(&graph.bridges_of(&loop_edge_filter));
1061 let mut vertex_color_nums = numerators.color_vertex.clone();
1062 let mut vertex_spin_nums = numerators.spin_vertex.clone();
1063 let intermediate: UnderlyingGraph = graph.map_result(
1064 |_, i, v| {
1065 let num = match v.num {
1066 Some(num) => Autogen::explicit(num),
1067 None => Autogen::generated(
1068 vertex_spin_nums[i.0]
1069 .take()
1070 .unwrap()
1071 .contract(&vertex_color_nums[i.0].take().unwrap())
1072 .unwrap()
1073 .scalar()
1074 .unwrap(),
1075 ),
1076 };
1077
1078 let dod = match v.dod {
1079 Some(dod) => Autogen::explicit(dod),
1080 None => Autogen::generated(if num.autogenerated {
1081 v.vertex_rule.as_ref().map(|vr| vr.dod).unwrap_or(0)
1082 } else {
1083 num.all_dod()
1084 }),
1085 };
1086
1087 Ok(Vertex {
1088 name: Autogen::from_option_or_generate(v.name, || i.to_string()),
1089 num,
1090 dod,
1091 vertex_rule: v.vertex_rule,
1092 })
1093 },
1094 |_, _, p, eid, ed| {
1095 let e = ed.data;
1096 if e.particle.is_fermion() && !e.particle.is_self_antiparticle()&& e.particle.orientation() != ed.orientation {
1097 return Err(eyre!(
1098 "Edge orientation {:?} does not match particle orientation {:?} for edge {},{}",
1099 ed.orientation,
1100 e.particle.orientation(),
1101 eid,
1102 e
1103 ));
1104 }
1105
1106 let mass = EdgeMass::from_atom(e.mass_atom(), model, param_builder)?;
1107
1108 let num = match e.num {
1109 Some(num) => Autogen::explicit(num),
1110 None => Autogen::generated(if initial_state_cut.left.intersects(&p) {
1111 numerators.color_edge[eid].clone()
1112 } else {
1113 &numerators.color_edge[eid] * &numerators.spin_edge[eid]
1114 }),
1115 };
1116
1117 let dod = match e.dod {
1118 Some(dod) => Autogen::explicit(dod),
1119 None => Autogen::generated(if num.autogenerated {
1120 if let Some(particle) = e.particle.particle() {
1121 model.get_propagator_for_particle(&particle.name).dod
1122 } else {
1123 -2
1124 }
1125 } else {
1126 num.edge_dod(eid) -2
1127 }),
1128 };
1129
1130 Ok(EdgeData::new(
1131 Edge {
1132 mass,
1133 is_dummy: e.is_dummy,
1134 name: Autogen::from_option_or_generate(e.name, || eid.to_string()),
1135 particle: e.particle,
1136 num,
1137 dod,
1138 extra_data: EdgeExtraData {
1139 momtrop_edge_power: e.momtrop_edge_power,
1140 vakint_edge_power: e.vakint_edge_power,
1141 }
1142 },
1143 ed.orientation,
1144 ))
1145 },
1146 |_, h| Ok(h),
1147 )?;
1148
1149 Ok(intermediate)
1150 }
1151
1152 fn setup_loop_momentum_basis(
1153 underlying: &UnderlyingGraph,
1154 full_cut: &SuBitGraph,
1155 lmb_ids: &BTreeMap<LoopIndex, EdgeIndex>,
1156 xs_ext_id: &BTreeMap<Hedge, (EdgeIndex, Hedge)>,
1157 ) -> Result<LoopMomentumBasis> {
1158 debug!("{}", underlying.dot(full_cut));
1159
1160 let mut loop_momentum_basis = if full_cut.included_iter().next().is_some() {
1161 let mut full = underlying.full_filter();
1162
1163 for (p, _, i) in underlying.iter_edges() {
1164 if i.data.is_dummy {
1165 full.sub(p);
1166 }
1167 }
1168 let external = underlying.internal_crown(&full);
1169 underlying.lmb_impl(&full, full_cut, external)?
1170 } else {
1171 return Err(eyre!(
1172 "No included edges found in full_cut for loop momentum basis setup"
1173 ));
1174 };
1175
1176 let inv_lmb_ids: BTreeMap<_, _> = lmb_ids
1177 .iter()
1178 .map(|(k, v)| {
1179 (*v, *k)
1181 })
1182 .collect();
1183
1184 for e in 0..xs_ext_id.len() {
1185 let (l, _) = loop_momentum_basis
1186 .loop_edges
1187 .iter()
1188 .find_position(|a| *a == &EdgeIndex(e))
1189 .unwrap();
1190
1191 loop_momentum_basis.put_loop_to_ext(LoopIndex(l));
1192 }
1193
1194 let mut swapped = true;
1196 while swapped {
1197 swapped = false;
1198 for i in 0..loop_momentum_basis.loop_edges.len() {
1199 if let Some(&target_pos) =
1200 inv_lmb_ids.get(&loop_momentum_basis.loop_edges[LoopIndex(i)])
1201 && target_pos.0 < loop_momentum_basis.loop_edges.len()
1202 && target_pos.0 != i
1203 {
1204 loop_momentum_basis.swap_loops(LoopIndex(i), target_pos);
1205 swapped = true;
1206 break;
1207 }
1208 }
1209 }
1210
1211 Ok(loop_momentum_basis)
1212 }
1213
1214 pub fn from_dot(graph: DotGraph, model: &Model) -> Result<Self> {
1215 Self::from_parsed(ParseGraph::from_parsed(graph, model)?, model)
1216 }
1217 pub fn from_file<P>(p: P, model: &Model) -> Result<Vec<Self>>
1218 where
1219 P: AsRef<Path>,
1220 {
1221 Self::from_path(p, model)
1222 }
1223
1224 pub fn from_path<P>(p: P, model: &Model) -> Result<Vec<Self>>
1225 where
1226 P: AsRef<Path>,
1227 {
1228 let path = p.as_ref();
1229
1230 if path.is_dir() {
1231 let mut all_graphs = Vec::new();
1233 let entries = std::fs::read_dir(path)
1234 .with_context(|| format!("Failed to read directory: {}", path.display()))?;
1235
1236 let mut dot_files = Vec::new();
1237 for entry in entries {
1238 let entry = entry?;
1239 let file_path = entry.path();
1240 if file_path.is_file() && file_path.extension().is_some_and(|ext| ext == "dot") {
1241 dot_files.push(file_path);
1242 }
1243 }
1244
1245 dot_files.sort();
1247
1248 for dot_file in dot_files {
1249 let graphs = Self::from_single_file(&dot_file, model)?;
1250 all_graphs.extend(graphs);
1251 }
1252
1253 if all_graphs.is_empty() {
1254 return Err(eyre!(
1255 "No .dot files found in directory: {}",
1256 path.display()
1257 ));
1258 }
1259
1260 Ok(all_graphs)
1261 } else {
1262 Self::from_single_file(path, model)
1264 }
1265 }
1266
1267 fn from_single_file<P>(p: P, model: &Model) -> Result<Vec<Self>>
1268 where
1269 P: AsRef<Path>,
1270 {
1271 let hedge_graph_set: GraphSet<
1272 DotEdgeData,
1273 DotVertexData,
1274 DotHedgeData,
1275 linnet::parser::GlobalData,
1276 NodeStorageVec<DotVertexData>,
1277 > = GraphSet::from_file(p.as_ref()).map_err(|a| match a {
1278 HedgeParseError::GraphFromFile(e) => match e.as_ref() {
1279 dot_parser::ast::GraphFromFileError::FileError(e) => eyre!(e.to_string())
1280 .with_note(|| {
1281 format!(
1282 "Tried to access the file at: {}",
1283 display_graph_source_path(p.as_ref()).display()
1284 )
1285 }),
1286 dot_parser::ast::GraphFromFileError::ParseError(e) => {
1287 eyre!("Dot parsing error: {}", e)
1288 }
1289 dot_parser::ast::GraphFromFileError::PestParseError(e) => {
1290 eyre!(e.to_string())
1291 }
1292 },
1293 HedgeParseError::ParseError(i) => color_eyre::Report::from(i),
1294 _ => {
1295 eyre!("Hedge parse error")
1296 }
1297 })?;
1298 Self::from_hedge_graph_set(hedge_graph_set, model)
1299 }
1300
1301 pub fn from_string<Str: AsRef<str>>(s: Str, model: &Model) -> Result<Vec<Self>> {
1302 let hedge_graph_set: GraphSet<
1303 DotEdgeData,
1304 DotVertexData,
1305 DotHedgeData,
1306 linnet::parser::GlobalData,
1307 NodeStorageVec<DotVertexData>,
1308 > = GraphSet::from_string(s).map_err(|a| match a {
1309 HedgeParseError::GraphFromFile(e) => match e.as_ref() {
1310 dot_parser::ast::GraphFromFileError::FileError(e) => {
1311 eyre!(e.to_string())
1312 }
1313 dot_parser::ast::GraphFromFileError::ParseError(e) => {
1314 eyre!("Dot parsing error: {}", e)
1315 }
1316 dot_parser::ast::GraphFromFileError::PestParseError(e) => {
1317 eyre!(e.to_string())
1318 }
1319 },
1320 HedgeParseError::ParseError(i) => color_eyre::Report::from(i),
1321 _ => {
1322 eyre!("Hedge parse error")
1323 }
1324 })?;
1325
1326 Self::from_hedge_graph_set(hedge_graph_set, model)
1327 }
1328
1329 fn from_hedge_graph_set(
1330 set: GraphSet<
1331 DotEdgeData,
1332 DotVertexData,
1333 DotHedgeData,
1334 linnet::parser::GlobalData,
1335 NodeStorageVec<DotVertexData>,
1336 >,
1337 model: &Model,
1338 ) -> Result<Vec<Self>> {
1339 let mut graphs = Vec::new();
1340
1341 for (graph, global_data) in set.set.into_iter().zip(set.global_data) {
1342 let graph = DotGraph { global_data, graph };
1343 debug!("Parsing: \n{}", graph.debug_dot());
1344 graphs.push(Graph::from_parsed(
1345 ParseGraph::from_parsed(graph, model)?,
1346 model,
1347 )?);
1348 }
1349 Ok(graphs)
1350 }
1351}
1352
1353pub mod serialization;
1354
1355pub(crate) fn complete_group_parsing(graphs: &mut [Graph]) -> Result<TiVec<GroupId, GraphGroup>> {
1357 let defined_group_ids = graphs
1359 .iter()
1360 .filter_map(|graph| graph.group_id)
1361 .sorted()
1362 .dedup()
1363 .collect_vec();
1364
1365 let expected_group_ids = (0..defined_group_ids.len()).map(GroupId).collect_vec();
1366
1367 if defined_group_ids != expected_group_ids {
1368 return Err(eyre!(
1369 "invalid group ids, group ids must start at 0 and contain no gaps"
1370 ));
1371 }
1372 let mut current_group_id = defined_group_ids.len();
1374 for graph in graphs.iter_mut() {
1375 if graph.group_id.is_none() {
1376 graph.group_id = Some(GroupId(current_group_id));
1377 graph.is_group_master = true;
1378 current_group_id += 1;
1379 }
1380 }
1381
1382 let num_groups = current_group_id;
1383
1384 (0..num_groups)
1386 .map(|group_id| {
1387 let group_id = GroupId(group_id);
1388 let graphs_in_group = graphs
1389 .iter()
1390 .enumerate()
1391 .filter_map(|(i, g)| {
1392 if g.group_id == Some(group_id) {
1393 Some(i)
1394 } else {
1395 None
1396 }
1397 })
1398 .collect_vec();
1399
1400 if graphs_in_group.len() == 1 {
1402 graphs[graphs_in_group[0]].is_group_master = true;
1403 Ok(GraphGroup {
1404 master: graphs_in_group[0],
1405 remaining: vec![],
1406 })
1407 } else {
1408 let master = graphs_in_group
1410 .iter()
1411 .find(|&&i| graphs[i].is_group_master)
1412 .copied();
1413
1414 if let Some(master) = master {
1415 let remaining = graphs_in_group
1417 .into_iter()
1418 .filter(|&i| i != master)
1419 .collect_vec();
1420
1421 let duplicate_master = remaining.iter().any(|&i| graphs[i].is_group_master);
1422
1423 if duplicate_master {
1424 return Err(eyre!(
1425 "Multiple group masters defined for group {group_id:?}"
1426 ));
1427 }
1428 Ok(GraphGroup { master, remaining })
1429 } else {
1430 let master = graphs_in_group[0];
1432 graphs[master].is_group_master = true;
1433 Ok(GraphGroup {
1434 master,
1435 remaining: graphs_in_group[1..].to_vec(),
1436 })
1437 }
1438 }
1439 })
1440 .collect::<Result<TiVec<GroupId, GraphGroup>>>()
1441}
1442
1443pub mod from_dot;
1444pub use from_dot::*;
1445#[cfg(test)]
1446pub mod tests;