Skip to main content

gammalooprs/numerator/
ufo.rs

1use crate::utils::{GS, W_};
2use idenso::color::CS;
3use idenso::dirac::AGS;
4use idenso::representations::{
5    Bispinor, ColorAdjoint, ColorAntiFundamental, ColorAntiSextet, ColorFundamental, ColorSextet,
6};
7use linnet::half_edge::involution::{EdgeIndex, Flow};
8use spenso::network::library::symbolic::ETS;
9use spenso::structure::representation::{LibraryRep, Minkowski, RepName};
10use spenso::structure::slot::{IsAbstractSlot, Slot};
11use spenso::structure::{OrderedStructure, TensorStructure};
12use symbolica::atom::FunctionBuilder;
13use symbolica::atom::{AtomCore, AtomView};
14use symbolica::domains::rational::Rational;
15use symbolica::symbol;
16use symbolica::{
17    atom::{Atom, Symbol},
18    function,
19    id::Replacement,
20};
21
22use color_eyre::Result;
23
24use super::aind::Aind;
25
26#[allow(dead_code)]
27pub struct UFOSymbols {
28    pub identity: Symbol,
29    pub identityl: Symbol,
30    pub gamma: Symbol,
31    pub gamma5: Symbol,
32    pub projm: Symbol,
33    pub projp: Symbol,
34    pub sigma: Symbol,
35    pub charge_conj: Symbol,
36    pub metric: Symbol,
37    pub momentum: Symbol,
38    pub levicivita: Symbol,
39    pub t: Symbol,
40    pub f: Symbol,
41    pub d: Symbol,
42    pub antilevicivita: Symbol,
43    pub t6: Symbol,
44    pub k6: Symbol,
45    pub idx: Symbol,
46    pub dummy: Symbol,
47    pub k6bar: Symbol,
48    pub complexconjugate: Symbol,
49    pub pslash: Symbol,
50    pub complex: Symbol,
51}
52
53spenso::symbolica_init_lazy_static! {
54pub static UFO, UFO_INNER: UFOSymbols = || UFOSymbols {
55    identity: symbol!("UFO::Identity"),
56    identityl: symbol!("UFO::IdentityL"),
57    gamma: symbol!("UFO::Gamma"),
58    complexconjugate: symbol!(
59        "UFO::complexconjugate",
60        norm = |f, out| {
61            if let AtomView::Fun(ff) = f
62                && ff.get_nargs() == 1
63            {
64                **out = ff.iter().next().unwrap().conj();
65            }
66        }
67    ),
68    idx: symbol!("UFO::idx"),
69    dummy: symbol!("UFO::dummy"),
70    gamma5: symbol!("UFO::Gamma5"),
71    projm: symbol!("UFO::ProjM"),
72    projp: symbol!("UFO::ProjP"),
73    sigma: symbol!("UFO::Sigma"),
74    charge_conj: symbol!("UFO::C"),
75    metric: symbol!("UFO::Metric"),
76    momentum: symbol!("UFO::P"),
77    levicivita: symbol!("UFO::Epsilon"),
78    t: symbol!("UFO::T"),
79    f: symbol!("UFO::f"),
80    d: symbol!("UFO::d"),
81    antilevicivita: symbol!("UFO::EpsilonBar"),
82    t6: symbol!("UFO::T6"),
83    k6: symbol!("UFO::K6"),
84    k6bar: symbol!("UFO::K6Bar"),
85    pslash: symbol!("UFO::PSlash"),
86    complex: symbol!(
87        "UFO::complex",
88        norm = |f, out| {
89            if let AtomView::Fun(ff) = f {
90                let mut re = Rational::zero();
91                let mut im = Rational::zero();
92                let mut count = 0;
93                for i in ff.iter() {
94                    if let Ok(i) = Rational::try_from(i) {
95                        if count == 0 {
96                            re = i;
97                        } else {
98                            im = i;
99                        }
100                        count += 1;
101                    } else if let Ok(i) = i64::try_from(i) {
102                        if count == 0 {
103                            re = i.into();
104                        } else {
105                            im = i.into();
106                        }
107                        count += 1;
108                    }
109
110                    if count > 1 {
111                        break;
112                    }
113                }
114
115                if count == 2 {
116                    **out = Atom::num(symbolica::domains::float::Complex { re, im });
117                }
118            }
119        }
120    ),
121};
122}
123
124impl UFOSymbols {
125    pub fn idx(&self, id: usize, shift: usize) -> Atom {
126        function!(self.idx, shift, id)
127    }
128    pub(crate) fn reindex_spin(
129        &self,
130        slots: &[&OrderedStructure<LibraryRep, Aind>],
131        momenta: &[(Flow, EdgeIndex)],
132        mut atom: Atom,
133        dummy: impl Fn(usize) -> Aind,
134    ) -> Result<Atom> {
135        let mink: LibraryRep = Minkowski {}.into();
136        let bis: LibraryRep = Bispinor {}.into();
137
138        // for s in slots {
139        //     println!("{}", s);
140        // }
141        // println!("In{}", atom.to_ordered_simple());
142
143        //`P(i,j)` = $$p_j^{\mu_i}$$
144        // and P(i)= $$p^{\mu_i}$$
145        atom = atom
146            .replace(self.momentum.call_args([W_.a_, W_.b___]))
147            .with(self.momentum.call((W_.b___, mink.to_symbolic([W_.a_]))))
148            .replace(self.momentum.call((self.idx.call((1, W_.a_)), W_.b_)))
149            .with(self.momentum.call_args([W_.a_, W_.b_]));
150
151        // Fill in representations
152        let reps: Vec<_> = [
153            (
154                self.gamma.call_args([W_.a_, W_.i_, W_.j_]),
155                self.gamma.call_args([
156                    mink.to_symbolic([W_.a_]),
157                    bis.to_symbolic([W_.i_]),
158                    bis.to_symbolic([W_.j_]),
159                ]),
160            ),
161            (
162                self.identityl.call_args([W_.a_, W_.b_]),
163                self.identityl
164                    .call_args([mink.to_symbolic([W_.a_]), mink.to_symbolic([W_.b_])]),
165            ),
166            (
167                self.metric.call_args([W_.a_, W_.b_]),
168                self.metric
169                    .call_args([mink.to_symbolic([W_.a_]), mink.to_symbolic([W_.b_])]),
170            ),
171            (
172                self.sigma.call_args([W_.a_, W_.b_, W_.i_, W_.j_]),
173                self.sigma.call_args([
174                    mink.to_symbolic([W_.a_]),
175                    mink.to_symbolic([W_.b_]),
176                    bis.to_symbolic([W_.i_]),
177                    bis.to_symbolic([W_.j_]),
178                ]),
179            ),
180            (
181                self.pslash.call_args([W_.i_, W_.j_, W_.a___]),
182                self.pslash
183                    .call((bis.to_symbolic([W_.i_]), bis.to_symbolic([W_.j_]), W_.a___)),
184            ),
185            (
186                self.identity.call_args([W_.i_, W_.j_]),
187                self.identity
188                    .call_args([bis.to_symbolic([W_.i_]), bis.to_symbolic([W_.j_])]),
189            ),
190            (
191                self.gamma5.call_args([W_.i_, W_.j_]),
192                self.gamma5
193                    .call_args([bis.to_symbolic([W_.i_]), bis.to_symbolic([W_.j_])]),
194            ),
195            (
196                self.projm.call_args([W_.i_, W_.j_]),
197                self.projm
198                    .call_args([bis.to_symbolic([W_.i_]), bis.to_symbolic([W_.j_])]),
199            ),
200            (
201                self.projp.call_args([W_.i_, W_.j_]),
202                self.projp
203                    .call_args([bis.to_symbolic([W_.i_]), bis.to_symbolic([W_.j_])]),
204            ),
205            (
206                self.charge_conj.call_args([W_.i_, W_.j_]),
207                self.charge_conj
208                    .call_args([bis.to_symbolic([W_.i_]), bis.to_symbolic([W_.j_])]),
209            ),
210        ]
211        .into_iter()
212        .map(|(pat, rep)| Replacement::new(pat.to_pattern(), rep))
213        .collect();
214
215        atom = atom
216            .replace_multiple(&reps)
217            .replace(self.levicivita.call((W_.a___, W_.i_, W_.b___)))
218            .repeat()
219            .with(
220                self.levicivita
221                    .call((W_.a___, mink.to_symbolic([W_.i_]), W_.b___)),
222            );
223
224        for (i, s) in slots.iter().enumerate() {
225            let i = i + 1;
226            for (shift, r) in s.external_structure_iter().enumerate() {
227                let rep = r.rep_name();
228                let wrappedi = self.idx(i, shift + 1);
229                let wrappedi = wrappedi.as_view();
230
231                // println!("{}", rep.to_symbolic([i]));
232                atom = atom
233                    .replace(rep.to_symbolic([wrappedi]))
234                    .min_level(1)
235                    .max_level(Some(1))
236                    .with(r.to_atom())
237                    .replace(rep.to_symbolic([i]))
238                    .min_level(1)
239                    .max_level(Some(1))
240                    .with(r.to_atom())
241            }
242        }
243        // debug!(in = atom.printer(LOGPRINTOPTS).to_string());
244
245        // Handle dummies:
246
247        let mut max_dummy = 0;
248
249        // debug!("Before dummies : {}", atom.to_ordered_simple());
250
251        for rep in [mink, bis] {
252            let mut max_dummy = 0;
253
254            atom = atom.replace_map(|term, _, out| {
255                let AtomView::Fun(f) = term else {
256                    return;
257                };
258
259                let name = f.get_symbol();
260                if name != rep.symbol() {
261                    return;
262                }
263
264                let mut fbuilder = FunctionBuilder::new(name);
265
266                if f.get_nargs() == 1 {
267                    fbuilder = fbuilder.add_arg(4);
268                    let arg = f.iter().next().unwrap();
269                    let a = if let Ok(a) = i64::try_from(arg) {
270                        if a < 0 {
271                            (-a) as usize
272                        } else {
273                            return;
274                        }
275                    } else if let AtomView::Fun(f) = arg
276                        && f.get_symbol() == self.dummy
277                        && f.get_nargs() == 1
278                        && let Ok(a) = usize::try_from(f.iter().next().unwrap())
279                    {
280                        a
281                    } else {
282                        return;
283                    };
284                    if a > max_dummy {
285                        max_dummy = a;
286                    }
287                    fbuilder = fbuilder.add_arg(Atom::from(dummy(a)));
288                    **out = fbuilder.finish();
289                }
290            });
291        }
292
293        let mink = Minkowski {}.new_rep(4);
294        let bis = Bispinor {}.new_rep(4);
295        // debug!(after_dummies = atom.printer(LOGPRINTOPTS).to_string());
296        // debug!("After dummies{}", atom);
297
298        let reps: Vec<_> = [
299            (
300                self.identity
301                    .call_args([bis.pattern(W_.a_), bis.pattern(W_.b_)]),
302                bis.g(W_.a_, W_.b_),
303            ),
304            (
305                self.identityl
306                    .call_args([mink.pattern(W_.a_), mink.pattern(W_.b_)]),
307                mink.g(W_.a_, W_.b_),
308            ),
309            (
310                self.metric
311                    .call_args([mink.pattern(W_.a_), mink.pattern(W_.b_)]),
312                mink.g(W_.a_, W_.b_),
313            ),
314            (
315                self.gamma
316                    .call_args([mink.pattern(W_.i_), bis.pattern(W_.a_), bis.pattern(W_.b_)]),
317                AGS.gamma
318                    .call_args([bis.pattern(W_.a_), bis.pattern(W_.b_), mink.pattern(W_.i_)]),
319            ),
320            (
321                self.gamma5
322                    .call_args([bis.pattern(W_.a_), bis.pattern(W_.b_)]),
323                AGS.gamma5
324                    .call_args([bis.pattern(W_.a_), bis.pattern(W_.b_)]),
325            ),
326            (
327                self.projm
328                    .call_args([bis.pattern(W_.a_), bis.pattern(W_.b_)]),
329                AGS.projm
330                    .call_args([bis.pattern(W_.a_), bis.pattern(W_.b_)]),
331            ),
332            (
333                self.projp
334                    .call_args([bis.pattern(W_.a_), bis.pattern(W_.b_)]),
335                AGS.projp
336                    .call_args([bis.pattern(W_.a_), bis.pattern(W_.b_)]),
337            ),
338            (
339                self.sigma.call_args([
340                    bis.pattern(W_.a_),
341                    bis.pattern(W_.b_),
342                    mink.pattern(W_.i_),
343                    mink.pattern(W_.j_),
344                ]),
345                AGS.sigma.call_args([
346                    bis.pattern(W_.a_),
347                    bis.pattern(W_.b_),
348                    mink.pattern(W_.i_),
349                    mink.pattern(W_.j_),
350                ]),
351            ),
352        ]
353        .into_iter()
354        .map(|(pat, rep)| Replacement::new(pat.to_pattern(), rep))
355        .collect();
356
357        // println!("out:{atom}");
358        atom = atom.replace_multiple(&reps);
359
360        atom = atom.replace_map(|term, _, out| {
361            if let AtomView::Fun(f) = term
362                && f.get_symbol() == self.pslash
363            {
364                let mut gamma = FunctionBuilder::new(AGS.gamma);
365
366                let mut count = 0;
367
368                for a in f.iter() {
369                    count += 1;
370                    if count <= 2 {
371                        gamma = gamma.add_arg(a);
372                    } else if let Ok(i) = i64::try_from(a) {
373                        max_dummy += 1;
374
375                        let minki: Slot<Minkowski, Aind> = mink.slot(dummy(max_dummy));
376
377                        gamma = gamma.add_arg(minki.to_atom());
378
379                        **out = gamma.finish() * GS.emr_mom(momenta[i as usize].1, minki.to_atom());
380                        return;
381                    }
382                }
383
384                if count == 2 {
385                    max_dummy += 1;
386
387                    let minki: Slot<Minkowski, Aind> = mink.slot(dummy(max_dummy));
388
389                    gamma = gamma.add_arg(minki.to_atom());
390
391                    **out = gamma.finish() * GS.emr_mom(momenta[0].1, minki.to_atom());
392                }
393            }
394        });
395
396        for (i, (f, e)) in momenta.iter().enumerate() {
397            atom = atom
398                .replace(function!(UFO.momentum, (i + 1) as i64, W_.a_))
399                .with(match f {
400                    Flow::Sink => GS.emr_mom(*e, W_.a_),
401                    Flow::Source => -GS.emr_mom(*e, W_.a_),
402                })
403        }
404
405        atom = atom
406            .replace(function!(UFO.momentum, W_.a_))
407            .with(GS.emr_mom(momenta[0].1, W_.a_));
408
409        // debug!(out = atom.printer(LOGPRINTOPTS).to_string());
410        // println!("out:{atom:#}");
411
412        if atom
413            .replace(self.gamma.call_args([W_.a__]))
414            .match_iter()
415            .next()
416            .is_some()
417            || atom
418                .replace(self.sigma.call_args([W_.a___]))
419                .match_iter()
420                .next()
421                .is_some()
422            || atom
423                .replace(self.pslash.call_args([W_.a___]))
424                .match_iter()
425                .next()
426                .is_some()
427            || atom
428                .replace(self.gamma5.call_args([W_.a__]))
429                .match_iter()
430                .next()
431                .is_some()
432            || atom
433                .replace(self.projm.call_args([W_.a__]))
434                .match_iter()
435                .next()
436                .is_some()
437            || atom
438                .replace(self.projp.call_args([W_.a__]))
439                .match_iter()
440                .next()
441                .is_some()
442            || atom
443                .replace(self.charge_conj.call_args([W_.a__]))
444                .match_iter()
445                .next()
446                .is_some()
447        {
448            return Err(color_eyre::eyre::eyre!(
449                "Some spinor structures were not fully re-indexed: {}",
450                atom
451            ));
452        }
453
454        Ok(atom)
455    }
456
457    pub(crate) fn reindex_color(
458        &self,
459        slots: &[&OrderedStructure<LibraryRep, Aind>],
460        mut atom: Atom,
461        dummy: impl Fn(usize) -> Aind,
462    ) -> Result<Atom> {
463        let adj = ColorAdjoint {};
464        let fund = ColorFundamental {};
465        let antifund = ColorAntiFundamental {};
466        let sext = ColorSextet {};
467        let antisext = ColorAntiSextet {};
468        // for s in slots {
469        //     debug!("{}", s);
470        // }
471
472        // debug!("In:{}", atom);
473        // Fill in representations
474        let reps: Vec<_> = [
475            (
476                self.t.call_args([W_.a_, W_.i_, W_.j_]),
477                self.t.call_args([
478                    adj.to_symbolic([W_.a_]),
479                    fund.to_symbolic([W_.i_]),
480                    antifund.to_symbolic([W_.j_]),
481                ]),
482            ),
483            (
484                self.f.call_args([W_.a_, W_.b_, W_.c_]),
485                self.f.call_args([
486                    adj.to_symbolic([W_.a_]),
487                    adj.to_symbolic([W_.b_]),
488                    adj.to_symbolic([W_.c_]),
489                ]),
490            ),
491            (
492                self.d.call_args([W_.a_, W_.b_, W_.c_]),
493                self.d.call_args([
494                    adj.to_symbolic([W_.a_]),
495                    adj.to_symbolic([W_.b_]),
496                    adj.to_symbolic([W_.c_]),
497                ]),
498            ),
499            (
500                self.levicivita.call_args([W_.i_, W_.j_, W_.k_]),
501                self.levicivita.call_args([
502                    fund.to_symbolic([W_.i_]),
503                    fund.to_symbolic([W_.j_]),
504                    fund.to_symbolic([W_.k_]),
505                ]),
506            ),
507            (
508                self.antilevicivita.call_args([W_.i_, W_.j_, W_.k_]),
509                self.levicivita.call_args([
510                    antifund.to_symbolic([W_.i_]),
511                    antifund.to_symbolic([W_.j_]),
512                    antifund.to_symbolic([W_.k_]),
513                ]),
514            ),
515            (
516                self.t6.call_args([W_.a_, W_.i_, W_.j_]),
517                self.t6.call_args([
518                    adj.to_symbolic([W_.a_]),
519                    sext.to_symbolic([W_.i_]),
520                    antisext.to_symbolic([W_.j_]),
521                ]),
522            ),
523            (
524                self.k6.call_args([W_.a_, W_.i_, W_.j_]),
525                self.k6.call_args([
526                    sext.to_symbolic([W_.a_]),
527                    antifund.to_symbolic([W_.i_]),
528                    antifund.to_symbolic([W_.j_]),
529                ]),
530            ),
531            (
532                self.k6bar.call_args([W_.a_, W_.i_, W_.j_]),
533                self.k6bar.call_args([
534                    antisext.to_symbolic([W_.a_]),
535                    fund.to_symbolic([W_.i_]),
536                    fund.to_symbolic([W_.j_]),
537                ]),
538            ),
539        ]
540        .into_iter()
541        .map(|(pat, rep)| Replacement::new(pat.to_pattern(), rep))
542        .collect();
543
544        // println!("preid:{atom}");
545        atom = atom.replace_multiple(&reps);
546
547        // Now identify the kroneker reps:
548
549        atom = atom
550            .replace(
551                function!(self.identity, W_.a_, W_.b_)
552                    * function!(W_.f_, W_.a___, function!(W_.g_, W_.a_), W_.b___),
553            )
554            .repeat()
555            .with(
556                self.identity.call((W_.g_.call_args([W_.a_]), W_.b_))
557                    * W_.f_.call((W_.a___, W_.g_.call_args([W_.a_]), W_.b___)),
558            );
559
560        // debug!("Postid:{}", atom);
561
562        // println!("postid:{atom}");
563
564        for (i, s) in slots.iter().enumerate() {
565            let i = i + 1;
566            for (shift, r) in s.external_structure_iter().enumerate() {
567                let rep = r.rep_name();
568                let wrappedi = self.idx(i, shift + 1);
569                let wrappedi = wrappedi.as_view();
570
571                // println!("{}", rep.to_symbolic([i]));
572                atom = atom
573                    .replace(rep.to_symbolic([wrappedi]))
574                    .min_level(1)
575                    .max_level(Some(1))
576                    .with(r.to_atom())
577                    .replace(self.identity.call((wrappedi, W_.i_)))
578                    .with(self.identity.call((r.to_atom(), W_.i_)))
579                    .replace(self.identity.call((W_.i_, wrappedi)))
580                    .with(self.identity.call((W_.i_, r.to_atom())))
581                    .replace(rep.to_symbolic([i]))
582                    .min_level(1)
583                    .max_level(Some(1))
584                    .with(r.to_atom())
585                    .replace(self.identity.call((i, W_.i_)))
586                    .with(self.identity.call((r.to_atom(), W_.i_)))
587                    .replace(self.identity.call((W_.i_, i)))
588                    .with(self.identity.call((W_.i_, r.to_atom())));
589            }
590        }
591
592        // println!("slot:{atom}");
593        // Handle dummies:
594        //
595        for rep in [
596            LibraryRep::from(adj).new_rep(8),
597            fund.new_rep(3).to_lib(),
598            antifund.new_rep(3).to_lib(),
599            sext.new_rep(6).to_lib(),
600            antisext.new_rep(6).to_lib(),
601        ] {
602            let mut max_dummy = 0;
603
604            atom = atom.replace_map(|term, _, out| {
605                let AtomView::Fun(f) = term else {
606                    return;
607                };
608
609                let name = f.get_symbol();
610                if name != rep.rep.symbol() {
611                    return;
612                }
613
614                let mut fbuilder = FunctionBuilder::new(name);
615
616                if f.get_nargs() == 1 {
617                    fbuilder = fbuilder.add_arg(rep.dim.to_symbolic());
618                    let arg = f.iter().next().unwrap();
619                    let a = if let Ok(a) = i64::try_from(arg) {
620                        if a < 0 {
621                            (-a) as usize
622                        } else {
623                            return;
624                        }
625                    } else if let AtomView::Fun(f) = arg
626                        && f.get_symbol() == self.dummy
627                        && f.get_nargs() == 1
628                        && let Ok(a) = usize::try_from(f.iter().next().unwrap())
629                    {
630                        a
631                    } else {
632                        return;
633                    };
634                    if a > max_dummy {
635                        max_dummy = a;
636                    }
637                    fbuilder = fbuilder.add_arg(Atom::from(dummy(a)));
638                    **out = fbuilder.finish();
639                }
640            });
641        }
642
643        let reps: Vec<_> = [
644            (
645                self.identity.call_args([W_.a_, W_.b_]),
646                ETS.metric.call_args([W_.a_, W_.b_]),
647            ),
648            (
649                self.t.call_args([
650                    adj.to_symbolic([W_.a__]),
651                    fund.to_symbolic([W_.i__]),
652                    antifund.to_symbolic([W_.j__]),
653                ]),
654                CS.t.call_args([
655                    adj.to_symbolic([W_.a__]),
656                    fund.to_symbolic([W_.i__]),
657                    antifund.to_symbolic([W_.j__]),
658                ]),
659            ),
660            (
661                self.f.call_args([
662                    adj.to_symbolic([W_.a__]),
663                    adj.to_symbolic([W_.b__]),
664                    adj.to_symbolic([W_.c__]),
665                ]),
666                CS.f.call_args([
667                    adj.to_symbolic([W_.a__]),
668                    adj.to_symbolic([W_.b__]),
669                    adj.to_symbolic([W_.c__]),
670                ]),
671            ),
672        ]
673        .into_iter()
674        .map(|(pat, rep)| Replacement::new(pat.to_pattern(), rep))
675        .collect();
676
677        atom = atom.replace_multiple(&reps);
678
679        if atom
680            .replace(self.t.call_args([W_.a___]))
681            .match_iter()
682            .next()
683            .is_some()
684            || atom
685                .replace(self.f.call_args([W_.a___]))
686                .match_iter()
687                .next()
688                .is_some()
689            || atom
690                .replace(self.d.call_args([W_.a___]))
691                .match_iter()
692                .next()
693                .is_some()
694            || atom
695                .replace(self.levicivita.call_args([W_.a___]))
696                .match_iter()
697                .next()
698                .is_some()
699            || atom
700                .replace(self.antilevicivita.call_args([W_.a___]))
701                .match_iter()
702                .next()
703                .is_some()
704            || atom
705                .replace(self.t6.call_args([W_.a___]))
706                .match_iter()
707                .next()
708                .is_some()
709            || atom
710                .replace(self.k6.call_args([W_.a___]))
711                .match_iter()
712                .next()
713                .is_some()
714            || atom
715                .replace(self.k6bar.call_args([W_.a___]))
716                .match_iter()
717                .next()
718                .is_some()
719        {
720            return Err(color_eyre::eyre::eyre!(
721                "Some color structures were not fully re-indexed: {}",
722                atom
723            ));
724        }
725
726        // println!("Out:{:#}", atom);
727        Ok(atom)
728    }
729}
730
731#[cfg(test)]
732pub mod test {
733
734    #[test]
735    fn ufo_spin_processing() {}
736}