Skip to main content

gammalooprs/graph/
global.rs

1use eyre::Context;
2use linnet::parser::GlobalData;
3use symbolica::atom::Atom;
4
5use crate::{
6    feyngen::diagram_generator::evaluate_overall_factor,
7    graph::{GroupId, threshold_counterterms::ThresholdCountertermSpec},
8};
9
10use super::{
11    Graph,
12    parse::{ParseGraph, StripParse, ToQuoted},
13};
14
15#[derive(Clone, Debug)]
16pub struct ParseData {
17    pub name: String,
18    pub overall_factor: Atom,
19    pub projectors: Option<Atom>,
20    pub num: Atom,
21    pub parameters: Vec<Atom>,
22    pub group_id: Option<GroupId>,
23    pub is_group_master: bool,
24    pub threshold_counterterms: Option<ThresholdCountertermSpec>,
25}
26
27impl Default for ParseData {
28    fn default() -> Self {
29        ParseData {
30            name: String::new(),
31            overall_factor: Atom::one(),
32            projectors: None,
33            parameters: Vec::new(),
34            num: Atom::one(),
35            group_id: None,
36            is_group_master: false,
37            threshold_counterterms: None,
38        }
39    }
40}
41
42impl ParseData {
43    pub(crate) fn with_overall_factor(self, overall_factor: Atom) -> Self {
44        ParseData {
45            name: self.name,
46            overall_factor,
47            projectors: self.projectors,
48            num: self.num,
49            parameters: self.parameters,
50            group_id: self.group_id,
51            is_group_master: self.is_group_master,
52            threshold_counterterms: self.threshold_counterterms,
53        }
54    }
55
56    pub(crate) fn with_projectors(self, polarizations: Atom) -> Self {
57        ParseData {
58            name: self.name,
59            overall_factor: self.overall_factor,
60            projectors: Some(polarizations),
61            num: self.num,
62            parameters: self.parameters,
63            group_id: self.group_id,
64            is_group_master: self.is_group_master,
65            threshold_counterterms: self.threshold_counterterms,
66        }
67    }
68
69    pub(crate) fn with_num(self, num: Atom) -> Self {
70        ParseData {
71            name: self.name,
72            overall_factor: self.overall_factor,
73            projectors: self.projectors,
74            num,
75            parameters: self.parameters,
76            group_id: self.group_id,
77            is_group_master: self.is_group_master,
78            threshold_counterterms: self.threshold_counterterms,
79        }
80    }
81}
82
83impl TryFrom<linnet::parser::GlobalData> for ParseData {
84    type Error = color_eyre::Report;
85
86    fn try_from(value: linnet::parser::GlobalData) -> color_eyre::Result<Self> {
87        let mut parse_data = ParseData {
88            name: value.name,
89            ..Default::default()
90        };
91
92        if let Some(factor) = value.statements.get("overall_factor") {
93            parse_data = parse_data
94                .with_overall_factor(factor.strip_parse().context("overall_factor").unwrap());
95        }
96
97        if let Some(polarizations) = value.statements.get("projector") {
98            parse_data = parse_data
99                .with_projectors(polarizations.strip_parse().context("projector").unwrap());
100        }
101
102        if let Some(factor) = value.statements.get("num") {
103            parse_data = parse_data.with_num(factor.strip_parse().context("num").unwrap());
104        }
105
106        if let Some(is_group_master) = value.statements.get("is_group_master") {
107            parse_data.is_group_master = is_group_master
108                .strip_parse()
109                .context("is_group_master")
110                .unwrap();
111        }
112
113        if let Some(group_id) = value.statements.get("group_id") {
114            parse_data.group_id =
115                Some(GroupId(group_id.strip_parse().context("group_id").unwrap()));
116        }
117
118        if let Some(params) = value.statements.get("params") {
119            let params: String = params.strip_parse().context("params").unwrap();
120            parse_data.parameters = params
121                .split(';')
122                .map(str::trim)
123                .filter(|param| !param.is_empty())
124                .map(|param| {
125                    param
126                        .strip_parse()
127                        .with_context(|| format!("params entry {param}"))
128                        .unwrap()
129                })
130                .collect();
131        }
132
133        if let Some(threshold_counterterms) = value.statements.get("threshold_counterterms") {
134            // Linnet decodes the quoted-string layer. Unknown Graphviz escapes remain
135            // literal, preserving meaningful TOML backslashes for its own parser.
136            parse_data.threshold_counterterms = Some(
137                ThresholdCountertermSpec::parse_toml(threshold_counterterms)
138                    .context("threshold_counterterms graph attribute")?,
139            );
140        }
141
142        Ok(parse_data)
143    }
144}
145
146impl Graph {
147    pub(crate) fn global_data(&self, include_autogenerated_fields: bool) -> GlobalData {
148        let mut g = GlobalData::from(());
149
150        // println!("Name: {}", self.name);
151        g.add_name(self.name.clone());
152
153        g.statements
154            .insert("num".to_string(), self.global_prefactor.num.to_quoted());
155
156        g.statements.insert(
157            "projector".to_string(),
158            self.global_prefactor.projector.to_quoted(),
159        );
160        // g.statements.insert(
161        //     "overall_factor".to_string(),
162        //     self.global_prefactor.color.to_canonical_string(),
163        // );
164        g.statements.insert(
165            "overall_factor".to_string(),
166            self.overall_factor.to_quoted(),
167        );
168
169        g.statements.insert(
170            "overall_factor_evaluated".to_string(),
171            evaluate_overall_factor(self.overall_factor.as_view()).to_quoted(),
172        );
173
174        if !self.param_builder.pairs.additional_params.params.is_empty() {
175            let params = self
176                .param_builder
177                .pairs
178                .additional_params
179                .params
180                .iter()
181                .map(ToQuoted::to_quoted)
182                .collect::<Vec<_>>()
183                .join(";");
184            g.statements.insert("params".to_string(), params);
185        }
186
187        // Preserve native implicit defaults on group members; serializing them as explicit
188        // master-topology directives would change their meaning on reimport.
189        if !self.threshold_counterterms.autogenerated
190            || (include_autogenerated_fields && (self.group_id.is_none() || self.is_group_master))
191        {
192            let threshold_counterterms = self
193                .threshold_counterterms
194                .to_toml()
195                .expect("validated threshold_counterterms should serialize to TOML");
196            g.statements.insert(
197                "threshold_counterterms".to_string(),
198                // Keep line breaks literal for readable embedded text; Linnet supplies
199                // the outer quotes and escapes the semantic statement value.
200                format!("\n{threshold_counterterms}"),
201            );
202        }
203
204        g
205    }
206}
207
208impl ParseGraph {
209    pub(crate) fn global_data(&self) -> GlobalData {
210        let mut g = GlobalData::from(());
211
212        // println!("Name: {}", self.name);
213        g.add_name(self.global_data.name.clone());
214
215        g.statements
216            .insert("num".to_string(), self.global_data.num.to_quoted());
217        if let Some(proj) = &self.global_data.projectors {
218            g.statements
219                .insert("projector".to_string(), proj.to_quoted());
220        }
221
222        // g.statements.insert(
223        //     "overall_factor".to_string(),
224        //     self.global_prefactor.color.to_canonical_string(),
225        // );
226        g.statements.insert(
227            "overall_factor".to_string(),
228            self.global_data.overall_factor.to_quoted(),
229        );
230
231        if !self.global_data.parameters.is_empty() {
232            let params = self
233                .global_data
234                .parameters
235                .iter()
236                .map(ToQuoted::to_quoted)
237                .collect::<Vec<_>>()
238                .join(";");
239            g.statements.insert("params".to_string(), params);
240        }
241
242        if let Some(threshold_counterterms) = &self.global_data.threshold_counterterms {
243            let threshold_counterterms = threshold_counterterms
244                .to_toml()
245                .expect("validated threshold_counterterms should serialize to TOML");
246            g.statements.insert(
247                "threshold_counterterms".to_string(),
248                format!("\n{threshold_counterterms}"),
249            );
250        }
251
252        g
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use linnet::{
259        half_edge::nodestore::NodeStorageVec,
260        parser::{DotGraph, DotVertexData},
261    };
262
263    use crate::{
264        dot,
265        graph::{Graph, parse::IntoGraph},
266        initialisation::test_initialise,
267        processes::DotExportSettings,
268    };
269
270    #[test]
271    fn params_roundtrip_in_global_data() {
272        test_initialise().unwrap();
273        match dot!(digraph params_roundtrip {
274            graph [
275                overall_factor = 1;
276                multiplicity_factor = 1;
277                params = "a;b;c";
278            ]
279            edge [pdg=1000]
280            ext [style=invis]
281            ext -> v4
282            ext -> v5
283            v6 -> ext
284            v5 -> v4 [lmb_index=0];
285            v6 -> v5;
286            v4 -> v6;
287        },"scalars")
288        {
289            Ok(g) => {
290                let g: Graph = g;
291                // g.to_dot_graph_with_settings().dot()
292                let serialized = g.dot_serialize(&DotExportSettings::default());
293                let parsed: DotGraph<NodeStorageVec<DotVertexData>> =
294                    DotGraph::from_string(serialized).unwrap();
295
296                assert_eq!(
297                    parsed.global_data.statements.get("params"),
298                    Some(&"a;b;c".to_string())
299                );
300            }
301            Err(e) => {
302                eprintln!("Graph parsing failed: {:?}", e);
303            }
304        }
305    }
306}