1use std::fmt::Display;
2
3use crate::graph::{Graph, LMBext, LmbIndex, LoopMomentumBasis};
4use crate::integrands::process::evaluators::SingleOrAllOrientations;
5use crate::momentum::{FourMomentum, Polarization, Rotatable, Rotation, SignOrZero, ThreeMomentum};
6
7use crate::momentum::signature::LoopSignature;
8use crate::utils::hyperdual_utils::new_constant;
9use crate::utils::{F, FloatLike, Length, PrecisionUpgradable};
10use crate::{DependentMomentaConstructor, define_index, settings::runtime::kinematic::Externals};
11use bincode_trait_derive::{Decode, Encode};
12use color_eyre::Result;
13use derive_more::{From, Into};
14use eyre::eyre;
15use linnet::half_edge::HedgeGraph;
16use linnet::half_edge::involution::{EdgeIndex, EdgeVec, Orientation};
17use linnet::half_edge::nodestore::NodeStorageOps;
18use linnet::half_edge::subgraph::subset::SubSet;
19use linnet::half_edge::subgraph::{
20 Inclusion, InternalSubGraph, ModifySubSet, SuBitGraph, SubSetLike, SubSetOps,
21};
22use linnet::half_edge::typed_vec::IndexLike;
23use serde::{Deserialize, Serialize};
24use std::ops::{Add, Index, IndexMut, Sub};
25use symbolica::domains::dual::HyperDual;
26use tabled::settings::Style;
27
28use typed_index_collections::TiVec;
29
30#[derive(
31 From,
32 Into,
33 Copy,
34 Clone,
35 Hash,
36 Eq,
37 Ord,
38 PartialEq,
39 PartialOrd,
40 bincode::Encode,
41 bincode::Decode,
42 Debug,
43 Serialize,
44 Deserialize,
45)]
46pub struct LoopIndex(pub usize);
47
48define_index!(
67 pub struct ExternalIndex;
68);
69
70impl Display for LoopIndex {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 write!(f, "{}", self.0)
79 }
80}
81
82impl Display for ExternalIndex {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(f, "{}", self.0)
85 }
86}
87
88#[derive(From, Into, Serialize, Deserialize, Clone, PartialEq, Debug, Encode, Decode)]
89pub struct LoopMomenta<T>(pub Vec<ThreeMomentum<T>>);
90
91impl<T: Display> Display for LoopMomenta<T> {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 let mut table_builder = tabled::builder::Builder::new();
94 for (i, mom) in self.0.iter().enumerate() {
95 let mut row = Vec::new();
96 row.push(i.to_string());
97 for m in mom {
98 row.push(m.to_string());
99 }
100 table_builder.push_record(row);
101 }
102
103 write!(f, "{}", table_builder.build().with(Style::modern_rounded()))
104 }
105}
106
107impl<T> Length for LoopMomenta<T> {
108 fn is_empty(&self) -> bool {
109 self.0.is_empty()
110 }
111
112 fn len(&self) -> usize {
113 self.0.len()
114 }
115}
116
117pub type Subspace<'a> = Option<&'a [LoopIndex]>; #[derive(Clone, Debug, bincode::Encode, bincode::Decode)]
120pub struct SubspaceData {
121 subgraph: InternalSubGraph,
122 #[bincode(with_serde)]
123 complement_subgraph: SuBitGraph,
124 lmb: LmbIndex,
125 lmb_indices: Vec<LoopIndex>,
126}
127
128impl SubspaceData {
129 pub(crate) fn is_mergable_with(&self, other: &Self) -> bool {
130 self.lmb == other.lmb
131 && self
132 .lmb_indices
133 .iter()
134 .all(|idx| !other.lmb_indices.contains(idx))
135 && other
136 .lmb_indices
137 .iter()
138 .all(|idx| !self.lmb_indices.contains(idx))
139 }
140
141 fn cleaned_filter_pessimist<E, V, H, N: NodeStorageOps<NodeData = V>>(
142 mut filter: SuBitGraph,
143 graph: &HedgeGraph<E, V, H, N>,
144 ) -> InternalSubGraph {
145 let mut to_remove = SuBitGraph::empty(filter.size());
146
147 for i in filter.included_iter() {
148 if !filter.includes(&graph.inv(i)) {
149 to_remove.add(i);
150 }
151 }
152 filter.subtract_with(&to_remove);
153
154 InternalSubGraph {
155 filter,
156 loopcount: None,
157 }
158 }
159
160 pub(crate) fn new_with_user_selected_lmb(
161 subgraph: SuBitGraph,
162 lmb: LmbIndex,
163 graph: &Graph,
164 all_lmbs: &TiVec<LmbIndex, LoopMomentumBasis>,
165 ) -> Result<Self> {
166 let bridges = graph.bridges_of(&subgraph);
167
168 let mut subgraph = Self::cleaned_filter_pessimist(subgraph.subtract(&bridges), graph);
169 subgraph.set_loopcount(graph);
170
171 let edges_in_subgraph = graph
172 .iter_edges_of(&subgraph.filter)
173 .map(|ed| ed.1)
174 .collect::<Vec<_>>();
175 let complement_subgraph = graph.full_filter().subtract(&subgraph.filter);
176
177 let parent_lmb = &all_lmbs[lmb];
178 let compatible_sub_lmb =
179 graph.try_compatible_sub_lmb(&subgraph, graph.full_crown(&subgraph), parent_lmb)?;
180 let mut lmb_indices = compatible_sub_lmb
181 .loop_edges
182 .iter()
183 .map(|edge_index| {
184 parent_lmb
185 .loop_edges
186 .iter_enumerated()
187 .find_map(|(loop_index, parent_edge)| {
188 (parent_edge == edge_index).then_some(loop_index)
189 })
190 .ok_or_else(|| {
191 eyre!(
192 "Compatible subgraph LMB edge {:?} is not a defining edge of parent LMB {:?}",
193 edge_index,
194 parent_lmb.loop_edges,
195 )
196 })
197 })
198 .collect::<Result<Vec<_>>>()?;
199 lmb_indices.sort();
200
201 if lmb_indices.len() != subgraph.loopcount.unwrap()
202 || compatible_sub_lmb.loop_edges.len() != subgraph.loopcount.unwrap()
203 {
204 return Err(color_eyre::eyre::eyre!(
205 "Provided loop momentum basis is not topologically compatible with subgraph, lmb_indices: {:?}, lmb_edges: {:?}, compatible_sub_lmb_edges: {:?}, subgraph loopcount: {}, edges_in_subgraph: {:?}",
206 lmb_indices,
207 parent_lmb.loop_edges,
208 compatible_sub_lmb.loop_edges,
209 subgraph.loopcount.unwrap(),
210 edges_in_subgraph,
211 ));
212 }
213
214 Ok(Self {
215 subgraph,
216 complement_subgraph,
217 lmb,
218 lmb_indices,
219 })
220 }
221
222 #[allow(dead_code)]
224 pub(crate) fn new(
225 subgraph: SuBitGraph,
226 graph: &Graph,
227 all_lmbs: &TiVec<LmbIndex, LoopMomentumBasis>,
228 ) -> Result<Self> {
229 let mut errors = Vec::new();
230 for (lmb_index, _) in all_lmbs.iter_enumerated() {
231 match Self::new_with_user_selected_lmb(subgraph.clone(), lmb_index, graph, all_lmbs) {
232 Ok(subspace) => return Ok(subspace),
233 Err(error) => errors.push(format!("LMB {}: {error:#}", usize::from(lmb_index))),
234 }
235 }
236
237 Err(eyre!(
238 "No topology-compatible loop momentum basis found for subgraph:\n{}",
239 errors.join("\n")
240 ))
241 }
242 pub(crate) fn get_lmb<'a>(
243 &self,
244 all_lmbs: &'a TiVec<LmbIndex, LoopMomentumBasis>,
245 ) -> &'a LoopMomentumBasis {
246 &all_lmbs[self.lmb]
247 }
248
249 pub(crate) fn does_not_contain<'a>(
250 &'a self,
251 edges: &'a [EdgeIndex],
252 graph: &'a Graph,
253 ) -> impl Iterator<Item = EdgeIndex> + 'a {
254 graph
255 .iter_edges_of(&self.complement_subgraph)
256 .map(|ed| ed.1)
257 .filter(|e| edges.contains(e))
258 }
259
260 pub(crate) fn contains<'a>(
261 &'a self,
262 edges: &'a [EdgeIndex],
263 graph: &'a Graph,
264 ) -> impl Iterator<Item = EdgeIndex> + 'a {
265 graph
266 .iter_edges_of(&self.subgraph)
267 .map(|ed| ed.1)
268 .filter(|e| edges.contains(e))
269 }
270
271 pub(crate) fn project_loop_signature<'a>(
272 &'a self,
273 signature: &'a LoopSignature,
274 ) -> impl Iterator<Item = SignOrZero> + 'a {
275 signature.iter_enumerated().map(|(loop_index, sign)| {
276 if self.lmb_indices.contains(&loop_index) {
277 *sign
278 } else {
279 SignOrZero::Zero
280 }
281 })
282 }
283
284 pub(crate) fn project_loop_signature_filtered<'a>(
285 &'a self,
286 signature: &'a LoopSignature,
287 ) -> impl Iterator<Item = SignOrZero> + 'a {
288 signature
289 .iter_enumerated()
290 .filter_map(move |(loop_index, sign)| {
291 if self.lmb_indices.contains(&loop_index) {
292 Some(*sign)
293 } else {
294 None
295 }
296 })
297 }
298
299 pub(crate) fn project_complement_loop_signature<'a>(
300 &'a self,
301 signature: &'a LoopSignature,
302 ) -> impl Iterator<Item = SignOrZero> + 'a {
303 signature.iter_enumerated().map(|(loop_index, sign)| {
304 if !self.lmb_indices.contains(&loop_index) {
305 *sign
306 } else {
307 SignOrZero::Zero
308 }
309 })
310 }
311
312 pub(crate) fn iter_lmb_indices<'a>(&'a self) -> impl Iterator<Item = LoopIndex> + 'a {
313 self.lmb_indices.iter().copied()
314 }
315
316 pub(crate) fn iter_basis_edges<'a>(
317 &'a self,
318 all_lmbs: &'a TiVec<LmbIndex, LoopMomentumBasis>,
319 ) -> impl Iterator<Item = EdgeIndex> + 'a {
320 self.lmb_indices
321 .iter()
322 .map(|&loop_index| self.get_lmb(all_lmbs).loop_edges[loop_index])
323 }
324
325 pub(crate) fn contains_loop_index(&self, loop_index: LoopIndex) -> bool {
326 self.lmb_indices.contains(&loop_index)
327 }
328
329 pub(crate) fn loopcount(&self) -> usize {
330 self.subgraph.loopcount.unwrap()
331 }
332
333 pub(crate) fn as_subspace_simple(&self) -> Subspace<'_> {
334 Some(self.lmb_indices.as_slice())
335 }
336}
337
338impl<T> LoopMomenta<T> {
342 pub(crate) fn iter(&self) -> std::slice::Iter<'_, ThreeMomentum<T>> {
343 self.0.iter()
344 }
345
346 pub(crate) fn first(&self) -> Option<&ThreeMomentum<T>> {
347 self.0.first()
348 }
349
350 pub(crate) fn iter_enumerated(&self) -> impl Iterator<Item = (LoopIndex, &ThreeMomentum<T>)> {
351 self.0.iter().enumerate().map(|(i, m)| (LoopIndex(i), m))
352 }
353}
354
355impl<T> IntoIterator for LoopMomenta<T> {
356 type Item = ThreeMomentum<T>;
357 type IntoIter = std::vec::IntoIter<ThreeMomentum<T>>;
358
359 fn into_iter(self) -> Self::IntoIter {
360 self.0.into_iter()
361 }
362}
363
364impl<T> FromIterator<ThreeMomentum<T>> for LoopMomenta<T> {
365 fn from_iter<I: IntoIterator<Item = ThreeMomentum<T>>>(iter: I) -> Self {
366 LoopMomenta(iter.into_iter().collect())
367 }
368}
369
370impl<T> Index<LoopIndex> for LoopMomenta<T> {
371 type Output = ThreeMomentum<T>;
372
373 fn index(&self, index: LoopIndex) -> &Self::Output {
374 &self.0[index.0]
375 }
376}
377
378impl<T> IndexMut<LoopIndex> for LoopMomenta<T> {
379 fn index_mut(&mut self, index: LoopIndex) -> &mut Self::Output {
380 &mut self.0[index.0]
381 }
382}
383
384impl<T: FloatLike> LoopMomenta<F<T>> {
385 pub(crate) fn hyper_radius_squared(&self, subspace: Subspace) -> F<T> {
386 let zero = self.0[0].px.zero();
387 match subspace {
388 None => self.iter().fold(zero, |acc, x| acc + x.norm_squared()),
389 Some(subspace) => subspace
390 .iter()
391 .fold(zero, |acc, &i| acc + self[i].norm_squared()),
392 }
393 }
394
395 pub(crate) fn rescale(&self, factor: &F<T>, subspace: Subspace) -> Self {
396 match subspace {
397 None => LoopMomenta::from_iter(self.iter().map(|k| k * factor)),
398 Some(subspace) => LoopMomenta::from_iter(self.iter_enumerated().map(|(i, k)| {
400 if subspace.contains(&i) {
401 k * factor
402 } else {
403 k.clone()
404 }
405 })),
406 }
407 }
408
409 pub(crate) fn rescale_with_hyper_dual(
410 &self,
411 factor: &HyperDual<F<T>>,
412 subspace: Subspace,
413 ) -> LoopMomenta<HyperDual<F<T>>> {
414 match subspace {
415 None => LoopMomenta::from_iter(self.iter().map(|k| {
416 ThreeMomentum::new(
417 new_constant(factor, &k.px) * factor,
418 new_constant(factor, &k.py) * factor,
419 new_constant(factor, &k.pz) * factor,
420 )
421 })),
422 Some(subspace) => LoopMomenta::from_iter(self.iter_enumerated().map(|(i, k)| {
423 if subspace.contains(&i) {
424 ThreeMomentum::new(
425 new_constant(factor, &k.px) * factor,
426 new_constant(factor, &k.py) * factor,
427 new_constant(factor, &k.pz) * factor,
428 )
429 } else {
430 ThreeMomentum::new(
431 new_constant(factor, &k.px),
432 new_constant(factor, &k.py),
433 new_constant(factor, &k.pz),
434 )
435 }
436 })),
437 }
438 }
439
440 pub(crate) fn rotate(&self, rotation: &Rotation) -> Self {
441 LoopMomenta::from_iter(self.iter().map(|k| k.rotate(rotation)))
442 }
443
444 pub(crate) fn lmb_transform(
445 &self,
446 from: &LoopMomentumBasis,
447 to: &LoopMomentumBasis,
448 externals: &ExternalThreeMomenta<F<T>>,
449 ) -> Self {
450 LoopMomenta::from_iter(
451 to.loop_edges
452 .iter()
453 .map(|e_id| from.edge_signatures[*e_id].compute_momentum(self, externals)),
454 )
455 }
456}
457
458impl<T: FloatLike> LoopMomenta<HyperDual<F<T>>> {
459 pub fn lmb_transform(
460 &self,
461 from: &LoopMomentumBasis,
462 to: &LoopMomentumBasis,
463 externals: &ExternalThreeMomenta<HyperDual<F<T>>>,
464 ) -> Self {
465 LoopMomenta::from_iter(to.loop_edges.iter().map(|e_id| {
466 from.edge_signatures[*e_id]
467 .try_compute_momentum(&self.0, &externals.raw)
468 .unwrap()
469 }))
470 }
471
472 pub fn rescale(&self, factor: &HyperDual<F<T>>, subspace: Subspace) -> Self {
473 match subspace {
474 None => LoopMomenta::from_iter(self.iter().map(|k| k * factor)),
475 Some(subspace) => LoopMomenta::from_iter(self.iter_enumerated().map(|(i, k)| {
477 if subspace.contains(&i) {
478 k * factor
479 } else {
480 k.clone()
481 }
482 })),
483 }
484 }
485}
486
487impl LoopMomenta<F<f64>> {
488 pub(crate) fn cast<T: FloatLike>(&self) -> LoopMomenta<F<T>> {
489 LoopMomenta::from_iter(self.iter().map(|m| m.map(&|x| F::from_ff64(x))))
490 }
491}
492
493impl<T: FloatLike> Sub<&LoopMomenta<F<T>>> for &LoopMomenta<F<T>> {
494 type Output = LoopMomenta<F<T>>;
495
496 fn sub(self, rhs: &LoopMomenta<F<T>>) -> Self::Output {
497 LoopMomenta::from_iter(self.iter().zip(rhs.iter()).map(|(l, r)| l - r))
498 }
499}
500
501impl<T: FloatLike> Add<&LoopMomenta<F<T>>> for &LoopMomenta<F<T>> {
502 type Output = LoopMomenta<F<T>>;
503
504 fn add(self, rhs: &LoopMomenta<F<T>>) -> Self::Output {
505 LoopMomenta::from_iter(self.iter().zip(rhs.iter()).map(|(l, r)| l + r))
506 }
507}
508
509pub type ExternalThreeMomenta<T> = TiVec<ExternalIndex, ThreeMomentum<T>>;
514pub type ExternalFourMomenta<T> = TiVec<ExternalIndex, FourMomentum<T>>;
516pub type PolarizationVectors<T> = TiVec<ExternalIndex, Polarization<T>>; fn extract_external_spatial<T: Clone>(
534 external_four_momenta: &ExternalFourMomenta<T>,
535) -> ExternalThreeMomenta<T> {
536 ExternalThreeMomenta::from_iter(external_four_momenta.iter().map(|fm| fm.spatial.clone()))
537}
538
539#[derive(Debug, Clone)]
540pub struct BareMomentumSample<T: FloatLike> {
541 pub loop_moms: LoopMomenta<F<T>>,
542 pub dual_loop_moms: Option<LoopMomenta<HyperDual<F<T>>>>,
543 pub loop_mom_cache_id: usize,
544 pub loop_mom_base_cache_id: usize,
546 pub external_moms: ExternalFourMomenta<F<T>>,
547 pub external_mom_cache_id: usize,
548 pub external_mom_base_cache_id: usize,
550 pub jacobian: F<T>,
551 pub orientation: Option<usize>,
552 pub parameterization_branch: Option<usize>,
553}
554
555#[derive(Debug, Clone)]
556pub struct MomentumSample<T: FloatLike> {
557 pub sample: BareMomentumSample<T>,
558 }
560
561impl<T: FloatLike> Display for MomentumSample<T> {
562 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
563 let mut table = tabled::builder::Builder::new();
564
565 table.push_record(["Sample"]);
566
567 table.push_record(["Loop Momenta", "p_x", "p_y", "p_z"]);
568 for (index, loop_mom) in self.loop_moms().0.iter().enumerate() {
571 table.push_record([
572 index.to_string(),
573 loop_mom.px.to_string(),
574 loop_mom.py.to_string(),
575 loop_mom.pz.to_string(),
576 ]);
577 }
578
579 table.push_record(["External Momenta", "E", "p_x", "p_y", "p_z"]);
580 for (index, external_mom) in self.external_moms().iter_enumerated() {
581 table.push_record([
582 index.to_string(),
583 external_mom.temporal.to_string(),
584 external_mom.spatial.px.to_string(),
585 external_mom.spatial.py.to_string(),
586 external_mom.spatial.pz.to_string(),
587 ]);
588 }
589
590 table.push_record(["Jacobian".into(), format!("{:+e}", self.sample.jacobian)]);
591 table.build().with(Style::rounded()).fmt(f)
592 }
593}
594
595impl<T: FloatLike> BareMomentumSample<T> {
596 #[inline(never)]
597 pub(crate) fn new(
598 loop_moms: LoopMomenta<F<T>>,
599 loop_mom_cache_id: usize,
600 external_moms: &Externals,
601 external_mom_cache_id: usize,
602 jacobian: F<T>,
603 dependent_momenta_constructor: DependentMomentaConstructor,
604 orientation: Option<usize>,
605 ) -> Result<Self> {
606 let external_moms = external_moms.get_dependent_externals(dependent_momenta_constructor)?;
607
608 Ok(Self {
609 loop_moms,
610 dual_loop_moms: None,
611 loop_mom_cache_id,
612 loop_mom_base_cache_id: loop_mom_cache_id, external_mom_cache_id,
614 external_mom_base_cache_id: external_mom_cache_id, external_moms,
616 jacobian,
617 orientation,
618 parameterization_branch: None,
619 })
620 }
621
622 pub(crate) fn one(&self) -> F<T> {
623 if let Some(f) = self.loop_moms.first() {
624 f.px.one()
625 } else if let Some(f) = self.external_moms.first() {
626 f.spatial.px.one()
627 } else {
628 panic!("No momenta in sample")
629 }
630 }
631
632 pub(crate) fn zero(&self) -> F<T> {
633 if let Some(f) = self.loop_moms.first() {
634 f.px.zero()
635 } else if let Some(f) = self.external_moms.first() {
636 f.spatial.px.zero()
637 } else {
638 panic!("No momenta in sample")
639 }
640 }
641
642 #[inline]
644 fn cast_sample<T2: FloatLike>(&self) -> BareMomentumSample<T2>
645 where
646 F<T2>: From<F<T>>,
647 {
648 BareMomentumSample {
649 loop_mom_cache_id: self.loop_mom_cache_id,
650 loop_mom_base_cache_id: self.loop_mom_base_cache_id,
651 external_mom_cache_id: self.external_mom_cache_id,
652 external_mom_base_cache_id: self.external_mom_base_cache_id,
653 loop_moms: self.loop_moms.iter().map(ThreeMomentum::cast).collect(),
654 dual_loop_moms: self
655 .dual_loop_moms
656 .as_ref()
657 .map(|_dlm| todo!("make sure the cast works if there are hyperdual momenta")),
658 external_moms: self.external_moms.iter().map(FourMomentum::cast).collect(),
659 jacobian: self.jacobian.clone().into(),
660 orientation: self.orientation,
661 parameterization_branch: self.parameterization_branch,
662 }
663 }
664
665 pub(crate) fn higher_precision(&self) -> BareMomentumSample<T::Higher>
666 where
667 T::Higher: FloatLike,
668 T::Lower: FloatLike,
669 {
670 BareMomentumSample {
671 loop_moms: self.loop_moms.iter().map(ThreeMomentum::higher).collect(),
672 dual_loop_moms: self.dual_loop_moms.as_ref().map(|dlm| {
673 LoopMomenta::from_iter(dlm.iter().map(|m| m.clone().map(&|x| x.higher())))
674 }),
675 external_moms: self
676 .external_moms
677 .iter()
678 .map(FourMomentum::higher)
679 .collect(),
680 loop_mom_cache_id: self.loop_mom_cache_id,
681 loop_mom_base_cache_id: self.loop_mom_base_cache_id,
682 external_mom_cache_id: self.external_mom_cache_id,
683 external_mom_base_cache_id: self.external_mom_base_cache_id,
684 jacobian: self.jacobian.higher(),
685 orientation: self.orientation,
686 parameterization_branch: self.parameterization_branch,
687 }
688 }
689
690 pub(crate) fn lower_precision(&self) -> BareMomentumSample<T::Lower>
691 where
692 T::Higher: FloatLike,
693 T::Lower: FloatLike,
694 {
695 BareMomentumSample {
696 loop_moms: self.loop_moms.iter().map(ThreeMomentum::lower).collect(),
697 dual_loop_moms: self.dual_loop_moms.as_ref().map(|dlm| {
698 LoopMomenta::from_iter(dlm.iter().map(|m| m.clone().map(&|x| x.lower())))
699 }),
700 external_moms: self.external_moms.iter().map(FourMomentum::lower).collect(),
701 jacobian: self.jacobian.lower(),
702 orientation: self.orientation,
703 loop_mom_cache_id: self.loop_mom_cache_id,
704 loop_mom_base_cache_id: self.loop_mom_base_cache_id,
705 external_mom_cache_id: self.external_mom_cache_id,
706 external_mom_base_cache_id: self.external_mom_base_cache_id,
707 parameterization_branch: self.parameterization_branch,
708 }
709 }
710
711 #[inline]
712 pub(crate) fn rotate(
713 &self,
714 rotation: &Rotation,
715 loop_mom_cache_id: usize,
716 external_mom_cache_id: usize,
717 ) -> Self {
718 Self {
719 loop_moms: self.loop_moms.iter().map(|l| l.rotate(rotation)).collect(),
720 dual_loop_moms: self
721 .dual_loop_moms
722 .as_ref()
723 .map(|dlm| LoopMomenta::from_iter(dlm.iter().map(|l| l.rotate(rotation)))),
724 external_moms: self
725 .external_moms
726 .iter()
727 .map(|l| l.rotate(rotation))
728 .collect(),
729 loop_mom_cache_id,
730 loop_mom_base_cache_id: self.loop_mom_base_cache_id, external_mom_cache_id,
732 external_mom_base_cache_id: self.external_mom_base_cache_id, jacobian: self.jacobian.clone(),
734 orientation: self.orientation,
735 parameterization_branch: self.parameterization_branch,
736 }
737 }
738
739 #[inline]
740 #[allow(dead_code)]
741 pub(crate) fn rescaled_loop_momenta(&self, factor: &F<T>, subspace: Subspace) -> Self {
742 Self {
743 loop_moms: self.loop_moms.rescale(factor, subspace),
744 dual_loop_moms: self
745 .dual_loop_moms
746 .as_ref()
747 .map(|dlm| dlm.rescale(&new_constant(&dlm[LoopIndex(0)].px, factor), subspace)),
748 loop_mom_cache_id: self.loop_mom_cache_id + 1,
749 loop_mom_base_cache_id: self.loop_mom_base_cache_id, external_moms: self.external_moms.clone(),
751 external_mom_cache_id: self.external_mom_cache_id,
752 external_mom_base_cache_id: self.external_mom_base_cache_id, jacobian: self.jacobian.clone(),
754 orientation: self.orientation,
755 parameterization_branch: self.parameterization_branch,
756 }
757 }
758
759 #[inline]
760 pub(crate) fn lmb_transform(&self, from: &LoopMomentumBasis, to: &LoopMomentumBasis) -> Self {
761 Self {
762 loop_moms: self.loop_moms.lmb_transform(
763 from,
764 to,
765 &extract_external_spatial(&self.external_moms),
766 ),
767 dual_loop_moms: self.dual_loop_moms.as_ref().map(|dlm| {
768 let dual_externals = self
769 .external_moms
770 .iter()
771 .map(|four_mom| {
772 ThreeMomentum::new(
773 new_constant(&dlm[LoopIndex(0)].px, &four_mom.spatial.px),
774 new_constant(&dlm[LoopIndex(0)].px, &four_mom.spatial.py),
775 new_constant(&dlm[LoopIndex(0)].px, &four_mom.spatial.pz),
776 )
777 })
778 .collect();
779 dlm.lmb_transform(from, to, &dual_externals)
780 }),
781 loop_mom_cache_id: self.loop_mom_cache_id + 1,
782 loop_mom_base_cache_id: self.loop_mom_base_cache_id, external_moms: self.external_moms.clone(),
784 external_mom_cache_id: self.external_mom_cache_id,
785 external_mom_base_cache_id: self.external_mom_base_cache_id, jacobian: self.jacobian.clone(),
787 orientation: self.orientation,
788 parameterization_branch: self.parameterization_branch,
789 }
790 }
791}
792
793impl<T: FloatLike> MomentumSample<T> {
794 pub(crate) fn orientations<'a, OID: From<usize> + Copy + IndexLike>(
795 &self,
796 filter: &'a SubSet<OID>,
797 orientations: &'a TiVec<OID, EdgeVec<Orientation>>,
798 ) -> SingleOrAllOrientations<'a, OID>
799 where
800 usize: From<OID>,
801 {
802 if let Some(o) = self.sample.orientation {
803 let id = if filter.is_full() {
804 OID::from(o)
805 } else {
806 filter
807 .included_iter()
808 .nth(o)
809 .unwrap_or_else(|| {
810 panic!(
811 "sampled orientation index {o} must resolve within the filtered orientation subset"
812 )
813 })
814 };
815 SingleOrAllOrientations::Single {
816 id,
817 orientation: &orientations[id],
818 }
819 } else {
820 SingleOrAllOrientations::All {
821 all: orientations,
822 filter,
823 }
824 }
825 }
826
827 pub(crate) fn loop_moms(&self) -> &LoopMomenta<F<T>> {
836 &self.sample.loop_moms
837 }
838
839 pub(crate) fn external_moms(&self) -> &ExternalFourMomenta<F<T>> {
840 &self.sample.external_moms
841 }
842
843 pub(crate) fn jacobian(&self) -> F<T> {
844 self.sample.jacobian.clone()
845 }
846
847 pub(crate) fn new(
848 loop_moms: LoopMomenta<F<T>>,
849 loop_mom_cache_id: usize,
850 external_moms: &Externals,
851 external_mom_cache_id: usize,
852 jacobian: F<T>,
853 dependent_momenta_constructor: DependentMomentaConstructor,
854 orientation: Option<usize>,
855 ) -> Result<Self> {
856 Ok(Self {
858 sample: BareMomentumSample::new(
859 loop_moms,
860 loop_mom_cache_id,
861 external_moms,
862 external_mom_cache_id,
863 jacobian,
864 dependent_momenta_constructor,
865 orientation,
866 )?,
867 })
869 }
870
871 pub(crate) fn one(&self) -> F<T> {
872 self.sample.one()
873 }
874
875 pub(crate) fn zero(&self) -> F<T> {
876 self.sample.zero()
877 }
878
879 #[inline]
881 pub(crate) fn cast_sample<T2: FloatLike>(&self) -> MomentumSample<T2>
882 where
883 F<T2>: From<F<T>>,
884 {
885 MomentumSample {
886 sample: self.sample.cast_sample(),
887 }
888 }
889
890 pub(crate) fn higher_precision(&self) -> MomentumSample<T::Higher>
891 where
892 T::Higher: FloatLike + Default,
893 T::Lower: FloatLike + Default,
894 {
895 MomentumSample {
896 sample: self.sample.higher_precision(),
897 }
898 }
899
900 pub(crate) fn lower_precision(&self) -> MomentumSample<T::Lower>
901 where
902 T::Lower: FloatLike + Default,
903 T::Higher: FloatLike + Default,
904 {
905 MomentumSample {
906 sample: self.sample.lower_precision(),
907 }
908 }
909
910 #[inline]
911 pub(crate) fn rotate(
912 &self,
913 rotation: &Rotation,
914 loop_mom_cache_id: usize,
915 external_mom_cache_id: usize,
916 ) -> Self {
917 Self {
918 sample: self
919 .sample
920 .rotate(rotation, loop_mom_cache_id, external_mom_cache_id),
921 }
922 }
923
924 #[allow(dead_code)]
925 #[inline]
926 pub(crate) fn rescaled_loop_momenta(&self, factor: &F<T>, subspace: Subspace) -> Self {
927 Self {
928 sample: self.sample.rescaled_loop_momenta(factor, subspace),
929 }
930 }
931
932 #[inline]
933 pub(crate) fn lmb_transform(&self, from: &LoopMomentumBasis, to: &LoopMomentumBasis) -> Self {
934 Self {
935 sample: self.sample.lmb_transform(from, to),
936 }
937 }
938}