gammalooprs/graph/
hedge_data.rs1use itertools::Itertools;
2use linnet::{
3 half_edge::involution::{Flow, Hedge},
4 parser::DotHedgeData,
5};
6use serde::{Deserialize, Serialize};
7use spenso::structure::{
8 OrderedStructure, PermutedStructure, TensorStructure,
9 representation::{LibraryRep, LibrarySlot},
10 slot::IsAbstractSlot,
11};
12use symbolica::atom::{Atom, FunctionBuilder};
13
14use crate::{graph::edge::PossibleParticle, numerator::aind::Aind};
15
16use super::{Autogen, parse::ParseGraph};
17
18use color_eyre::Result;
19
20#[derive(Clone, bincode_trait_derive::Encode, bincode_trait_derive::Decode)]
21#[trait_decode(trait = symbolica::state::HasStateMap)]
22pub struct HedgeIndices {
23 pub vertex_indices: OrderedStructure<LibraryRep, Aind>,
24 pub edge_indices: OrderedStructure<LibraryRep, Aind>,
25}
26
27impl HedgeIndices {
28 pub(crate) fn new(edge_indices: OrderedStructure<LibraryRep, Aind>) -> Self {
29 Self {
30 vertex_indices: edge_indices.clone().dual(),
31 edge_indices,
32 }
33 }
34}
35
36#[derive(Clone, bincode_trait_derive::Encode, bincode_trait_derive::Decode)]
37#[trait_decode(trait = symbolica::state::HasStateMap)]
38pub struct NumIndices {
39 pub color_indices: HedgeIndices,
40 pub spin_indices: HedgeIndices,
41}
42
43impl NumIndices {
44 fn from_particle(particle: &PossibleParticle, flow: Flow, h: Hedge) -> Self {
45 let creps = particle.color_reps(flow);
46 let sreps = particle.spin_reps();
47
48 let mut init = 0;
49 let mut last = None;
50 let color_structure: PermutedStructure<_> = creps
51 .external_reps_iter()
52 .map(|r| {
53 if let Some(l) = last {
54 if l != r {
55 last = Some(r);
56 init = 0;
57 } else {
58 init += 1;
59 }
60 } else {
61 last = Some(r);
62 init = 0;
63 }
64 r.slot(Aind::Hedge(h.0 as u16, init))
65 })
66 .collect();
67
68 let spin_structure: PermutedStructure<_> = sreps
69 .external_reps_iter()
70 .map(|r| {
71 if let Some(l) = last {
72 if l != r {
73 last = Some(r);
74 init = 0;
75 } else {
76 init += 1;
77 }
78 } else {
79 last = Some(r);
80 init = 0;
81 }
82 r.slot(Aind::Hedge(h.0 as u16, init))
83 })
84 .collect();
85
86 NumIndices {
87 color_indices: HedgeIndices::new(color_structure.structure),
88 spin_indices: HedgeIndices::new(spin_structure.structure),
89 }
90 }
91
92 pub(crate) fn parse<'a>(
93 graph: &'a ParseGraph,
94 ) -> impl FnMut(Hedge, &'a ParseHedgeData) -> Self {
95 |h, _| {
96 let eid = graph[&h];
97 let flow = graph.flow(h);
98
99 Self::from_particle(&graph[eid].particle, flow, h)
100 }
101 }
102}
103
104#[derive(Clone, bincode_trait_derive::Encode, bincode_trait_derive::Decode)]
105#[trait_decode(trait = symbolica::state::HasStateMap)]
106pub struct HedgeData {
107 pub num_indices: NumIndices,
108 pub ufo_order: Autogen<u8>,
109}
110
111impl HedgeData {
112 pub(crate) fn edge_color_slots<'a>(&'a self) -> impl Iterator<Item = LibrarySlot<Aind>> + 'a {
113 self.num_indices
114 .color_indices
115 .edge_indices
116 .external_structure_iter()
117 }
118
119 pub(crate) fn edge_spin_slots<'a>(&'a self) -> impl Iterator<Item = LibrarySlot<Aind>> + 'a {
120 self.num_indices
121 .spin_indices
122 .edge_indices
123 .external_structure_iter()
124 }
125
126 pub(crate) fn polarization(&self, mut builder: FunctionBuilder) -> Atom {
127 for s in self.edge_spin_slots() {
128 builder = builder.add_arg(s.to_atom())
129 }
130 builder.finish()
131 }
132
133 pub(crate) fn color_kronekers(&self, other: &Self) -> Atom {
134 let mut color = Atom::num(1);
135 for (i, j) in self.edge_color_slots().zip_eq(other.edge_color_slots()) {
136 if i.rep().matches(&j.rep()) {
137 color *= j.rep().id(i.aind, j.aind);
138 } else {
139 panic!("Should be the same rep found:{} and {}", i, j)
140 }
141 }
142
143 color
144 }
145}
146
147#[derive(Debug, Clone, Default, Serialize, Deserialize)]
148pub struct ParseHedgeData {
149 pub ufo_order: Option<u8>,
150}
151
152impl ParseHedgeData {
153 pub(crate) fn parse<'a>() -> impl FnMut((Hedge, &'a DotHedgeData)) -> Result<Self> {
154 |(_, h)| {
155 let Some(statement) = h
156 .statement
157 .as_deref()
158 .map(str::trim)
159 .filter(|s| !s.is_empty())
160 else {
161 return Ok(ParseHedgeData::default());
162 };
163
164 Ok(json5::from_str(statement)?)
165 }
166 }
167}
168
169impl From<&HedgeData> for DotHedgeData {
170 fn from(value: &HedgeData) -> Self {
171 let payload = ParseHedgeData {
172 ufo_order: (!value.ufo_order.autogenerated).then_some(value.ufo_order.value),
173 };
174
175 let statement = if payload.ufo_order.is_none() {
176 None
177 } else {
178 Some(json5::to_string(&payload).expect("serializing hedge payload should not fail"))
179 };
180
181 DotHedgeData::from(statement)
182 }
183}
184
185impl From<&ParseHedgeData> for DotHedgeData {
186 fn from(value: &ParseHedgeData) -> Self {
187 let statement = if value.ufo_order.is_none() {
188 None
189 } else {
190 Some(json5::to_string(value).expect("serializing hedge payload should not fail"))
191 };
192
193 DotHedgeData::from(statement)
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use linnet::{half_edge::involution::Hedge, parser::DotHedgeData};
200
201 use super::ParseHedgeData;
202
203 #[test]
204 fn parse_json5_hedge_payload() {
205 let dot = DotHedgeData::from(Some("{ufo_order: 2}".to_string()));
206 let parsed = ParseHedgeData::parse()((Hedge(0), &dot)).unwrap();
207
208 assert_eq!(parsed.ufo_order, Some(2));
209 }
210
211 #[test]
212 fn serialize_parse_hedge_data_to_json5_statement() {
213 let dot = DotHedgeData::from(Some("{ufo_order: 1}".to_string()));
214 let parsed = ParseHedgeData::parse()((Hedge(0), &dot)).unwrap();
215
216 let serialized: DotHedgeData = (&parsed).into();
217 let statement = serialized.statement.expect("statement should be present");
218
219 assert!(statement.contains("ufo_order"));
220 }
221}