1use idenso::{
2 color::{ColorSimplifier, ColorSimplifySettings},
3 dirac::GammaSimplifier,
4 shorthands::metric::MetricSimplifier,
5};
6use linnet::{
7 half_edge::{
8 HedgeGraph,
9 involution::{Flow, HedgePair, Orientation},
10 subgraph::SubSetLike,
11 },
12 parser::{DotEdgeData, DotGraph, DotHedgeData, DotVertexData},
13};
14use spenso::shadowing::symbolica_utils::SpensoPrintSettings;
15use symbolica::atom::AtomCore;
16
17use crate::{
18 graph::Graph,
19 processes::DotExportSettings,
20 utils::{GS, W_},
21 uv::UltravioletGraph,
22};
23
24use super::{
25 ParseGraph,
26 string_utils::{ToQuoted, dot_statement_value},
27};
28
29impl Graph {
30 pub fn to_dot_graph_with_settings(&self, settings: &DotExportSettings) -> DotGraph {
31 let mut dotgraph = if settings.split_xs_by_initial_states {
32 self.to_split_dotgraph_with_autogenerated_fields(settings.include_autogenerated_fields)
33 } else {
34 self.to_dot_graph_with_autogenerated_fields(settings.include_autogenerated_fields)
35 };
36
37 if settings.output_full_numerator {
38 let mut num = (self
39 .numerator(&self.full_filter(), &self.empty_subgraph())
40 .get_single_atom()
41 .unwrap()
42 * &self.global_prefactor.num
43 * &self.global_prefactor.projector
44 * &self.overall_factor)
45 .simplify_metrics();
46
47 if settings.do_color_algebra {
48 num = num.simplify_color_with(
49 ColorSimplifySettings::default().with_cof_dimension_invariants(),
50 );
51 }
52
53 if settings.do_gamma_algebra {
54 num = num.simplify_gamma();
55 crate::debug_tags!(#generation, #graph, #inspect, #dump;
56 stage = "graph_serialization_after_simplify_gamma",
57 log.after_gamma = num,
58 "Graph serialization after gamma simplification"
59 );
60 }
61
62 dotgraph.global_data.statements.insert(
63 "full_num".into(),
64 dot_statement_value(
65 &num.printer(SpensoPrintSettings::typst_options())
66 .to_string(),
67 ),
68 );
69 }
70
71 dotgraph
72 }
73
74 pub fn to_split_dotgraph(&self) -> DotGraph {
75 self.to_split_dotgraph_with_autogenerated_fields(false)
76 }
77
78 fn to_split_dotgraph_with_autogenerated_fields(
79 &self,
80 include_autogenerated_fields: bool,
81 ) -> DotGraph {
82 let global_data = self.global_data();
83
84 let mut graph: HedgeGraph<DotEdgeData, DotVertexData, DotHedgeData> =
85 if self.initial_state_cut.is_empty() {
86 self.underlying.map_data_ref(
87 |_, _, v| v.to_dot_data(include_autogenerated_fields),
88 |_, _, p, e| {
89 if let HedgePair::Unpaired { flow, .. } = p {
90 e.map(|e| {
91 let mut dot = e.to_dot_data(include_autogenerated_fields);
92 match flow {
93 Flow::Source => {
94 dot.add_statement("pos", "x:@-left!".to_string());
95 }
96 Flow::Sink => {
97 dot.add_statement("pos", "x:@+right!".to_string());
98 }
99 }
100 dot
101 })
102 } else {
103 e.map(|e| e.to_dot_data(include_autogenerated_fields))
104 }
105 },
106 |_, d| d.into(),
107 )
108 } else {
109 self.initial_state_cut
110 .clone()
111 .to_owned_graph_ref(&self.underlying)
112 .map(
113 |_, _, v| v.to_dot_data(include_autogenerated_fields),
114 |_, _, p, _, e| {
115 e.map(|e| {
116 let mut dot =
117 e.edge_data().to_dot_data(include_autogenerated_fields);
118
119 match e.orientation() {
120 Orientation::Default => {
121 dot.add_statement("is_cut", self.inv(p.any_hedge()));
122 dot.add_statement(
123 "pos",
124 format!("x:@-left!,y:@edge{}!", e.index),
125 );
126 }
127 Orientation::Reversed => {
128 dot.add_statement("is_cut", p.any_hedge());
129 dot.add_statement(
130 "pos",
131 format!("x:@+right!,y:@edge{}!", e.index),
132 );
133 }
134 Orientation::Undirected => {}
135 }
136 dot
137 })
138 },
139 |_, d| d.into(),
140 )
141 };
142
143 for (_, i, _) in self.iter_edges() {
145 let loop_expr = self
146 .loop_momentum_basis
147 .loop_atom(i, GS.loop_mom, &[W_.a___], false);
148 let external_expr =
149 self.loop_momentum_basis
150 .ext_atom(i, GS.external_mom, &[W_.a___], false);
151
152 graph[i].add_statement("lmb_rep", (loop_expr + external_expr).to_quoted());
153 }
154
155 for (l, i) in self.loop_momentum_basis.loop_edges.iter_enumerated() {
156 graph[i].0.add_statement("lmb_id", l);
157 }
158
159 DotGraph { global_data, graph }
160 }
161
162 fn to_dot_graph_with_autogenerated_fields(
163 &self,
164 include_autogenerated_fields: bool,
165 ) -> DotGraph {
166 let global_data = self.global_data();
167
168 let mut graph: HedgeGraph<DotEdgeData, DotVertexData, DotHedgeData> =
169 self.underlying.map_data_ref(
170 |_, _, v| v.to_dot_data(include_autogenerated_fields),
171 |_, _, _, e| e.map(|e| e.to_dot_data(include_autogenerated_fields)),
172 |_, d| d.into(),
173 );
174
175 for (_, i, _) in self.iter_edges() {
176 let loop_expr = self
177 .loop_momentum_basis
178 .loop_atom(i, GS.loop_mom, &[W_.a___], false);
179 let external_expr =
180 self.loop_momentum_basis
181 .ext_atom(i, GS.external_mom, &[W_.a___], false);
182
183 graph[i].add_statement("lmb_rep", (loop_expr + external_expr).to_quoted());
184 }
185
186 for i in self.initial_state_cut.left.included_iter() {
187 let eid = graph[&i];
188 graph[eid].add_statement("is_cut", i);
189 }
190
191 for (l, i) in self.loop_momentum_basis.loop_edges.iter_enumerated() {
192 graph[i].0.add_statement("lmb_id", l);
193 }
194
195 DotGraph { global_data, graph }
196 }
197}
198
199impl From<&Graph> for DotGraph {
200 fn from(value: &Graph) -> Self {
201 value.to_dot_graph_with_autogenerated_fields(false)
202 }
203}
204
205impl From<&ParseGraph> for DotGraph {
206 fn from(value: &ParseGraph) -> Self {
207 let global_data = value.global_data();
208 let graph: HedgeGraph<DotEdgeData, DotVertexData, DotHedgeData> = value.graph.map_data_ref(
209 |_, _, v| v.into(),
210 |_, _, _, e| e.map(|e| e.into()),
211 |_, d| d.into(),
212 );
213
214 DotGraph { global_data, graph }
215 }
216}
217
218impl ParseGraph {
219 #[allow(dead_code)]
220 fn to_simple_dot(&self) -> DotGraph {
221 let global_data = self.global_data();
222 let graph: HedgeGraph<DotEdgeData, DotVertexData, DotHedgeData> = self.graph.map_data_ref(
223 |_, _, v| v.into(),
224 |_, _, _, e| e.map(|e| e.into()),
225 |_, d| d.into(),
226 );
227
228 DotGraph { global_data, graph }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use symbolica::{atom::Atom, function};
235
236 use super::Graph;
237 use crate::{dot, graph::parse::IntoGraph, processes::DotExportSettings, utils::GS};
238
239 #[test]
240 fn full_numerator_is_a_spenso_aware_typst_fragment() {
241 let graph: Graph = dot!(digraph G {
242 ext [style=invis]
243 node [num=1]
244 ext -> A
245 C -> A
246 A -> D
247 D -> B
248 B -> C
249 C -> D
250 B -> ext
251 })
252 .unwrap();
253 let graph = graph.with_global_numerator_only(
254 "typst_full_num".to_string(),
255 function!(GS.uv_truncate, Atom::num(1)),
256 );
257 let dot = graph.to_dot_graph_with_settings(&DotExportSettings {
258 output_full_numerator: true,
259 ..DotExportSettings::default()
260 });
261 let full_num = &dot.global_data.statements["full_num"];
262
263 assert_eq!(full_num, r#"op(\"Tr\")(1)"#);
264 assert!(!full_num.contains("gammalooprs::Truncate"));
265 }
266}