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