1use std::fmt;
2
3use color_eyre::Result;
4use eyre::{eyre, Context};
5use gammalooprs::{
6 cff::esurface::EsurfaceExistenceStatus,
7 graph::{FeynmanGraph, Graph},
8 integrands::process::{ActiveF64Backend, LmbMultiChannelingSetup, ParamBuilder},
9 model::Model,
10 processes::{
11 Amplitude, CrossSection, CrossSectionCut, CutId, ProcessCollection,
12 ThresholdCountertermAssociation, ThresholdCountertermStatus,
13 },
14 settings::{global::FrozenCompilationMode, runtime::ParameterizationSettings, RuntimeSettings},
15 utils::F,
16 DependentMomentaConstructor,
17};
18use linnet::half_edge::involution::{EdgeVec, Orientation};
19use schemars::JsonSchema;
20use serde::{Deserialize, Serialize};
21
22use crate::state::State;
23
24#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
25#[serde(rename_all = "snake_case")]
26pub enum IntegrandKind {
27 Amplitude,
28 CrossSection,
29}
30
31impl fmt::Display for IntegrandKind {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 match self {
34 Self::Amplitude => f.write_str("amplitude"),
35 Self::CrossSection => f.write_str("cross section"),
36 }
37 }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
41pub struct IntegrandGraphInfo {
42 pub graph_id: usize,
43 pub name: String,
44 pub is_master: bool,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
48pub struct IntegrandOrientationInfo {
49 pub orientation_id: usize,
50 pub signature: Vec<i8>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
54pub struct IntegrandLoopMomentumBasisInfo {
55 pub basis_id: usize,
56 pub channel_id: Option<usize>,
57 pub edge_ids: Vec<usize>,
58 pub matches_generation_basis: bool,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
62pub struct IntegrandCutInfo {
63 pub cut_id: usize,
64 pub edge_ids: Vec<usize>,
65 pub raising_power: usize,
66 pub left_thresholds: Vec<IntegrandCutThresholdInfo>,
67 pub right_thresholds: Vec<IntegrandCutThresholdInfo>,
68}
69
70#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
71#[serde(rename_all = "snake_case")]
72pub enum IntegrandThresholdStatus {
73 NoRadialDependence,
74 AlwaysPinched,
75 CanBecomePinched,
76 ProvenNonExisting,
77 PotentiallyExisting,
78}
79
80impl IntegrandThresholdStatus {
81 pub fn as_str(self) -> &'static str {
82 match self {
83 Self::NoRadialDependence => "no_radial_dependence",
84 Self::AlwaysPinched => "always_pinched",
85 Self::CanBecomePinched => "can_become_pinched",
86 Self::ProvenNonExisting => "proven_non_existing",
87 Self::PotentiallyExisting => "potentially_existing",
88 }
89 }
90
91 pub fn is_currently_viable(self) -> bool {
92 matches!(self, Self::CanBecomePinched | Self::PotentiallyExisting)
93 }
94
95 pub fn can_become_pinched(self) -> bool {
96 self == Self::CanBecomePinched
97 }
98}
99
100impl From<ThresholdCountertermStatus> for IntegrandThresholdStatus {
101 fn from(value: ThresholdCountertermStatus) -> Self {
102 match value {
103 ThresholdCountertermStatus::NoRadialDependence => Self::NoRadialDependence,
104 ThresholdCountertermStatus::AlwaysPinched => Self::AlwaysPinched,
105 ThresholdCountertermStatus::CanBecomePinched => Self::CanBecomePinched,
106 ThresholdCountertermStatus::ProvenNonExisting => Self::ProvenNonExisting,
107 ThresholdCountertermStatus::PotentiallyExisting => Self::PotentiallyExisting,
108 }
109 }
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
113pub struct IntegrandCutThresholdInfo {
114 pub esurface_id: usize,
115 pub status: IntegrandThresholdStatus,
116 pub cut_boundary_edge_ids: Vec<usize>,
117 pub threshold_boundary_edge_ids: Vec<usize>,
118 pub invariant_bound_is_applicable: bool,
119}
120
121impl IntegrandCutThresholdInfo {
122 fn from_association(
123 association: &ThresholdCountertermAssociation,
124 graph: &Graph,
125 cut: &CrossSectionCut,
126 model: &Model,
127 param_builder: &ParamBuilder,
128 settings: &RuntimeSettings,
129 ) -> Self {
130 Self {
131 esurface_id: association.esurface_id.0,
132 status: association
133 .classify_for_model(
134 graph,
135 cut,
136 model,
137 param_builder,
138 settings,
139 settings.subtraction.esurface_existence_threshold,
140 )
141 .into(),
142 cut_boundary_edge_ids: association
143 .cut_boundary_edges
144 .iter()
145 .map(|edge_id| edge_id.0)
146 .collect(),
147 threshold_boundary_edge_ids: association
148 .threshold_boundary_edges
149 .iter()
150 .map(|edge_id| edge_id.0)
151 .collect(),
152 invariant_bound_is_applicable: association.invariant_bound_is_applicable,
153 }
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
158pub struct IntegrandActiveThresholdCutInfo {
159 pub cut_id: usize,
160 pub can_become_pinched: bool,
161}
162
163#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
164#[serde(rename_all = "snake_case")]
165pub enum IntegrandEsurfaceClassification {
166 NonExisting,
167 Pinched,
168 Existing,
169}
170
171impl IntegrandEsurfaceClassification {
172 pub fn as_str(self) -> &'static str {
173 match self {
174 Self::NonExisting => "non_existing",
175 Self::Pinched => "pinched",
176 Self::Existing => "existing",
177 }
178 }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
182pub struct IntegrandThresholdEsurfaceInfo {
183 pub esurface_id: usize,
184 pub representative_graph_id: usize,
185 pub edge_ids: Vec<usize>,
186 pub classification: Option<IntegrandEsurfaceClassification>,
187 pub active_cuts: Vec<IntegrandActiveThresholdCutInfo>,
188}
189
190impl IntegrandCutInfo {
191 fn from_cross_section_cut(
192 graph_term: &gammalooprs::integrands::process::cross_section::CrossSectionGraphTerm,
193 cut_id: CutId,
194 cut: &CrossSectionCut,
195 raising_power: usize,
196 model: &Model,
197 param_builder: &ParamBuilder,
198 settings: &RuntimeSettings,
199 ) -> Self {
200 let associations = &graph_term.cut_threshold_associations[cut_id];
201 let threshold_info = |association| {
202 IntegrandCutThresholdInfo::from_association(
203 association,
204 &graph_term.graph,
205 cut,
206 model,
207 param_builder,
208 settings,
209 )
210 };
211
212 Self {
213 cut_id: usize::from(cut_id),
215 edge_ids: cut_edge_ids(&graph_term.graph, cut),
216 raising_power,
217 left_thresholds: associations.left.iter().map(&threshold_info).collect(),
218 right_thresholds: associations.right.iter().map(threshold_info).collect(),
219 }
220 }
221
222 fn active_threshold_cut(&self, esurface_id: usize) -> Option<IntegrandActiveThresholdCutInfo> {
223 let mut is_currently_viable = false;
224 let mut can_become_pinched = false;
225
226 for threshold in self
227 .left_thresholds
228 .iter()
229 .chain(self.right_thresholds.iter())
230 .filter(|threshold| threshold.esurface_id == esurface_id)
231 {
232 is_currently_viable |= threshold.status.is_currently_viable();
233 can_become_pinched |= threshold.status.can_become_pinched();
234 }
235
236 is_currently_viable.then_some(IntegrandActiveThresholdCutInfo {
237 cut_id: self.cut_id,
238 can_become_pinched,
239 })
240 }
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
244pub struct IntegrandGraphGroupInfo {
245 pub group_id: usize,
246 pub graphs: Vec<IntegrandGraphInfo>,
247 pub orientation_edge_ids: Vec<usize>,
248 pub orientations: Vec<IntegrandOrientationInfo>,
249 pub loop_momentum_bases: Vec<IntegrandLoopMomentumBasisInfo>,
250 pub threshold_esurface_ids: Vec<usize>,
251 pub threshold_esurfaces: Vec<IntegrandThresholdEsurfaceInfo>,
252 pub cuts: Vec<IntegrandCutInfo>,
253}
254
255fn threshold_esurface_edge_ids(
256 esurfaces: &gammalooprs::cff::esurface::EsurfaceCollection,
257 esurface_id: usize,
258) -> Vec<usize> {
259 esurfaces
260 .iter()
261 .nth(esurface_id)
262 .expect("threshold esurface id should resolve in collection")
263 .energies
264 .iter()
265 .map(|edge_id| edge_id.0)
266 .collect()
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
270pub struct IntegrandInfo {
271 pub process_id: usize,
273 pub process_name: String,
275 pub integrand_name: String,
277 pub kind: IntegrandKind,
279 pub generation_compilation: FrozenCompilationMode,
281 pub active_f64_backend: ActiveF64Backend,
283 pub graph_count: usize,
285 pub graph_group_count: usize,
287 pub record_size_bytes: usize,
289 pub graph_groups: Vec<IntegrandGraphGroupInfo>,
291}
292
293pub(crate) fn collect_integrand_info(
294 state: &State,
295 process_id: usize,
296 integrand_name: &str,
297) -> Result<IntegrandInfo> {
298 let process = &state.process_list.processes[process_id];
299 let resolved = process.get_integrand(integrand_name)?;
300 let generated = resolved.require_generated()?;
301 let generation_compilation = generated.frozen_compilation().clone();
302 let active_f64_backend = generated.active_f64_backend();
303
304 match (generated, &process.collection) {
305 (
306 gammalooprs::integrands::process::ProcessIntegrand::Amplitude(integrand),
307 ProcessCollection::Amplitudes(amplitudes),
308 ) => {
309 let amplitude = amplitudes.get(&resolved.canonical_name).ok_or_else(|| {
310 eyre!(
311 "Could not resolve amplitude record '{}' in process #{} ({})",
312 resolved.canonical_name,
313 process.definition.process_id,
314 process.definition.folder_name
315 )
316 })?;
317 let model = state.resolve_model_for_integrand(
318 process.definition.process_id,
319 &resolved.canonical_name,
320 )?;
321 Ok(IntegrandInfo {
322 process_id: process.definition.process_id,
323 process_name: process.definition.folder_name.clone(),
324 integrand_name: resolved.canonical_name,
325 kind: IntegrandKind::Amplitude,
326 generation_compilation: generation_compilation.clone(),
327 active_f64_backend,
328 graph_count: amplitude.graphs.len(),
329 graph_group_count: amplitude.graph_group_structure.len(),
330 record_size_bytes: integrand_record_size_from_amplitude(amplitude)?,
331 graph_groups: amplitude_graph_groups(integrand, &model)?,
332 })
333 }
334 (
335 gammalooprs::integrands::process::ProcessIntegrand::CrossSection(integrand),
336 ProcessCollection::CrossSections(cross_sections),
337 ) => {
338 let cross_section = cross_sections
339 .get(&resolved.canonical_name)
340 .ok_or_else(|| {
341 eyre!(
342 "Could not resolve cross-section record '{}' in process #{} ({})",
343 resolved.canonical_name,
344 process.definition.process_id,
345 process.definition.folder_name
346 )
347 })?;
348 let model = state.resolve_model_for_integrand(
349 process.definition.process_id,
350 &resolved.canonical_name,
351 )?;
352 Ok(IntegrandInfo {
353 process_id: process.definition.process_id,
354 process_name: process.definition.folder_name.clone(),
355 integrand_name: resolved.canonical_name,
356 kind: IntegrandKind::CrossSection,
357 generation_compilation,
358 active_f64_backend,
359 graph_count: cross_section.supergraphs.len(),
360 graph_group_count: cross_section.graph_group_structure.len(),
361 record_size_bytes: integrand_record_size_from_cross_section(cross_section)?,
362 graph_groups: cross_section_graph_groups(integrand, &model)?,
363 })
364 }
365 _ => Err(eyre!(
366 "Process/integrand type mismatch for '{}' in process #{} ({})",
367 resolved.canonical_name,
368 process.definition.process_id,
369 process.definition.folder_name
370 )),
371 }
372}
373
374fn orientation_signature(orientation: &EdgeVec<Orientation>) -> Vec<i8> {
375 orientation
376 .iter()
377 .map(|(_, orientation)| match *orientation {
378 Orientation::Default => 1,
379 Orientation::Reversed => -1,
380 Orientation::Undirected => 0,
381 })
382 .collect()
383}
384
385fn orientation_edge_ids(orientation: &EdgeVec<Orientation>) -> Vec<usize> {
386 orientation.iter().map(|(edge_id, _)| edge_id.0).collect()
387}
388
389fn cut_edge_ids(graph: &Graph, cut: &CrossSectionCut) -> Vec<usize> {
390 cut.cut
391 .iter_edges(&graph.underlying)
392 .map(|(_, edge)| {
393 graph
394 .underlying
395 .iter_edges()
396 .find_map(|(_, edge_id, edge_data)| {
397 (edge_data.data.name == edge.data.name).then_some(edge_id.0)
398 })
399 .expect("cut edge should resolve in graph")
400 })
401 .collect()
402}
403
404fn cut_raising_powers(
405 graph: &gammalooprs::integrands::process::cross_section::CrossSectionGraphTerm,
406) -> typed_index_collections::TiVec<CutId, usize> {
407 let mut raising_powers: typed_index_collections::TiVec<CutId, usize> =
408 vec![1; graph.cuts.len()].into();
409 for cut_group in graph.cut_group_data.cut_groups.iter() {
410 let raising_power = cut_group.related_esurface_group.max_occurence;
411 for cut_id in &cut_group.cuts {
412 raising_powers[*cut_id] = raising_power;
413 }
414 }
415 raising_powers
416}
417
418fn lmb_channel_ids(
419 lmbs: &typed_index_collections::TiVec<
420 gammalooprs::graph::LmbIndex,
421 gammalooprs::graph::LoopMomentumBasis,
422 >,
423 multi_channeling_setup: &LmbMultiChannelingSetup,
424 graph_name: &str,
425 parameterization_settings: &ParameterizationSettings,
426) -> Result<Vec<Option<usize>>> {
427 let mut channel_ids = vec![None; lmbs.len()];
428 for (channel_id, lmb_index) in multi_channeling_setup
429 .effective_channels(graph_name, parameterization_settings)?
430 .into_iter()
431 .enumerate()
432 {
433 channel_ids[usize::from(lmb_index)] = Some(channel_id);
434 }
435 Ok(channel_ids)
436}
437
438fn amplitude_graph_groups(
439 integrand: &gammalooprs::integrands::process::amplitude::AmplitudeIntegrand,
440 model: &Model,
441) -> Result<Vec<IntegrandGraphGroupInfo>> {
442 let parameterization_settings = integrand
443 .settings
444 .sampling
445 .get_parameterization_settings()
446 .unwrap_or_default();
447 let external_momenta = integrand
448 .settings
449 .kinematics
450 .externals
451 .get_dependent_externals::<f64>(DependentMomentaConstructor::Amplitude(
452 &integrand.data.external_signature,
453 ))
454 .context("While constructing amplitude external momenta for integrand information")?;
455 let e_cm = F(integrand.settings.kinematics.e_cm);
456 let existence_tolerance = F(integrand.settings.subtraction.esurface_existence_threshold);
457 let real_mass_vectors = integrand
458 .data
459 .graph_terms
460 .iter()
461 .map(|graph_term| graph_term.graph.get_real_mass_vector::<f64>(model))
462 .collect::<Vec<_>>();
463 integrand
464 .data
465 .graph_group_structure
466 .iter_enumerated()
467 .map(|(group_id, group)| {
468 let master_graph_id = group
469 .into_iter()
470 .next()
471 .expect("graph group should not be empty");
472 let master_graph = &integrand.data.graph_terms[master_graph_id];
473 let channel_ids = lmb_channel_ids(
474 &master_graph.lmbs,
475 &master_graph.multi_channeling_setup,
476 &master_graph.graph.name,
477 ¶meterization_settings,
478 )?;
479 let threshold_esurfaces = integrand.data.group_derived_data[group_id]
483 .esurface_map
484 .iter_enumerated()
485 .filter_map(|(group_esurface_id, raised_esurface_map)| {
486 let (representative_graph_id, raised_esurface_id) = raised_esurface_map
487 .iter_enumerated()
488 .filter_map(|(graph_group_position, raised_esurface_id)| {
489 raised_esurface_id.map(|raised_esurface_id| {
490 let graph_id = group[graph_group_position];
491 (graph_id, raised_esurface_id)
492 })
493 })
494 .find(|(graph_id, raised_esurface_id)| {
495 integrand.data.graph_terms[*graph_id]
496 .threshold_counterterm
497 .generated_mask[*raised_esurface_id]
498 })?;
499 let graph_term = &integrand.data.graph_terms[representative_graph_id];
500 let local_esurface_id =
501 graph_term.threshold_counterterm.raised_data.raised_groups
502 [raised_esurface_id]
503 .esurface_ids[0];
504
505 let mut classification = IntegrandEsurfaceClassification::NonExisting;
506 for (graph_group_position, raised_esurface_id) in raised_esurface_map
507 .iter_enumerated()
508 .filter_map(|(graph_group_position, raised_esurface_id)| {
509 raised_esurface_id.map(|raised_esurface_id| {
510 (graph_group_position, raised_esurface_id)
511 })
512 })
513 {
514 let graph_id = group[graph_group_position];
515 let candidate = &integrand.data.graph_terms[graph_id];
516 let candidate_esurface_id =
517 candidate.threshold_counterterm.raised_data.raised_groups
518 [raised_esurface_id]
519 .esurface_ids[0];
520 let candidate_status = candidate.esurfaces[candidate_esurface_id]
521 .existence_status(
522 &external_momenta,
523 &candidate.graph.loop_momentum_basis,
524 &real_mass_vectors[graph_id],
525 &e_cm,
526 &existence_tolerance,
527 );
528 match candidate_status {
529 EsurfaceExistenceStatus::Existing => {
530 classification = IntegrandEsurfaceClassification::Existing;
531 break;
532 }
533 EsurfaceExistenceStatus::Pinched => {
534 classification = IntegrandEsurfaceClassification::Pinched;
535 }
536 EsurfaceExistenceStatus::NonExisting => {}
537 }
538 }
539
540 Some(IntegrandThresholdEsurfaceInfo {
541 esurface_id: group_esurface_id.0,
542 representative_graph_id,
543 edge_ids: threshold_esurface_edge_ids(
544 &graph_term.esurfaces,
545 local_esurface_id.0,
546 ),
547 classification: Some(classification),
548 active_cuts: Vec::new(),
549 })
550 })
551 .collect::<Vec<_>>();
552 let threshold_esurface_ids = threshold_esurfaces
553 .iter()
554 .map(|threshold| threshold.esurface_id)
555 .collect();
556 Ok(IntegrandGraphGroupInfo {
557 group_id: usize::from(group_id),
558 graphs: group
559 .into_iter()
560 .map(|graph_id| IntegrandGraphInfo {
561 graph_id,
562 name: integrand.data.graph_terms[graph_id].graph.name.clone(),
563 is_master: graph_id == master_graph_id,
564 })
565 .collect(),
566 orientation_edge_ids: master_graph
567 .orientations
568 .first()
569 .map(orientation_edge_ids)
570 .unwrap_or_default(),
571 orientations: master_graph
572 .orientations
573 .iter()
574 .enumerate()
575 .map(|(orientation_id, orientation)| IntegrandOrientationInfo {
576 orientation_id,
577 signature: orientation_signature(orientation),
578 })
579 .collect(),
580 loop_momentum_bases: master_graph
581 .lmbs
582 .iter_enumerated()
583 .map(|(basis_id, lmb)| IntegrandLoopMomentumBasisInfo {
584 basis_id: usize::from(basis_id),
585 channel_id: channel_ids[usize::from(basis_id)],
586 edge_ids: lmb.loop_edges.iter().map(|edge_id| edge_id.0).collect(),
587 matches_generation_basis: lmb.loop_edges.len()
588 == master_graph.graph.loop_momentum_basis.loop_edges.len()
589 && lmb.loop_edges.iter().all(|edge_id| {
590 master_graph
591 .graph
592 .loop_momentum_basis
593 .loop_edges
594 .contains(edge_id)
595 }),
596 })
597 .collect(),
598 threshold_esurface_ids,
599 threshold_esurfaces,
600 cuts: Vec::new(),
601 })
602 })
603 .collect()
604}
605
606fn cross_section_graph_groups(
607 integrand: &gammalooprs::integrands::process::cross_section::CrossSectionIntegrand,
608 model: &Model,
609) -> Result<Vec<IntegrandGraphGroupInfo>> {
610 let parameterization_settings = integrand
611 .settings
612 .sampling
613 .get_parameterization_settings()
614 .unwrap_or_default();
615 integrand
616 .data
617 .graph_group_structure
618 .iter()
619 .enumerate()
620 .map(|(group_id, group)| {
621 let master_graph_id = group
622 .into_iter()
623 .next()
624 .expect("graph group should not be empty");
625 let master_graph = &integrand.data.graph_terms[master_graph_id];
626 let mut active_model_param_builder: ParamBuilder =
627 master_graph.graph.param_builder.clone();
628 active_model_param_builder.update_model_values(model);
629 let channel_ids = lmb_channel_ids(
630 &master_graph.lmbs,
631 &master_graph.multi_channeling_setup,
632 &master_graph.graph.name,
633 ¶meterization_settings,
634 )?;
635 let cut_raising_powers = cut_raising_powers(master_graph);
636
637 let cuts = master_graph
638 .cuts
639 .iter_enumerated()
640 .map(|(cut_id, cut)| {
641 IntegrandCutInfo::from_cross_section_cut(
642 master_graph,
643 cut_id,
644 cut,
645 cut_raising_powers[cut_id],
646 model,
647 &active_model_param_builder,
648 &integrand.settings,
649 )
650 })
651 .collect::<Vec<_>>();
652
653 let threshold_esurface_ids = master_graph
654 .threshold_candidate_esurface_ids
655 .iter()
656 .map(|esurface_id| esurface_id.0)
657 .collect::<Vec<_>>();
658
659 let threshold_esurfaces = threshold_esurface_ids
660 .iter()
661 .copied()
662 .map(|esurface_id| {
663 let active_cuts = cuts
664 .iter()
665 .filter_map(|cut| cut.active_threshold_cut(esurface_id))
666 .collect();
667
668 IntegrandThresholdEsurfaceInfo {
669 esurface_id,
670 representative_graph_id: master_graph_id,
671 edge_ids: threshold_esurface_edge_ids(
672 &master_graph.graph.surface_cache.esurface_cache,
673 esurface_id,
674 ),
675 classification: None,
676 active_cuts,
677 }
678 })
679 .collect::<Vec<_>>();
680
681 Ok(IntegrandGraphGroupInfo {
682 group_id,
683 graphs: group
684 .into_iter()
685 .map(|graph_id| IntegrandGraphInfo {
686 graph_id,
687 name: integrand.data.graph_terms[graph_id].graph.name.clone(),
688 is_master: graph_id == master_graph_id,
689 })
690 .collect(),
691 orientation_edge_ids: master_graph
692 .orientations
693 .first()
694 .map(orientation_edge_ids)
695 .unwrap_or_default(),
696 orientations: master_graph
697 .orientations
698 .iter()
699 .enumerate()
700 .map(|(orientation_id, orientation)| IntegrandOrientationInfo {
701 orientation_id,
702 signature: orientation_signature(orientation),
703 })
704 .collect(),
705 loop_momentum_bases: master_graph
706 .lmbs
707 .iter_enumerated()
708 .map(|(basis_id, lmb)| IntegrandLoopMomentumBasisInfo {
709 basis_id: usize::from(basis_id),
710 channel_id: channel_ids[usize::from(basis_id)],
711 edge_ids: lmb.loop_edges.iter().map(|edge_id| edge_id.0).collect(),
712 matches_generation_basis: lmb.loop_edges.len()
713 == master_graph.graph.loop_momentum_basis.loop_edges.len()
714 && lmb.loop_edges.iter().all(|edge_id| {
715 master_graph
716 .graph
717 .loop_momentum_basis
718 .loop_edges
719 .contains(edge_id)
720 }),
721 })
722 .collect(),
723 threshold_esurface_ids,
724 threshold_esurfaces,
725 cuts,
726 })
727 })
728 .collect()
729}
730
731fn integrand_record_size_from_amplitude(amplitude: &Amplitude) -> Result<usize> {
732 let mut record = amplitude.clone();
733 record.integrand = None;
734 let encoded =
735 bincode::encode_to_vec(&record, bincode::config::standard()).with_context(|| {
736 format!(
737 "While serializing amplitude '{}' for integrand info",
738 amplitude.name
739 )
740 })?;
741 Ok(encoded.len())
742}
743
744fn integrand_record_size_from_cross_section(cross_section: &CrossSection) -> Result<usize> {
745 let mut record = cross_section.clone();
746 record.integrand = None;
747 let encoded =
748 bincode::encode_to_vec(&record, bincode::config::standard()).with_context(|| {
749 format!(
750 "While serializing cross-section '{}' for integrand info",
751 cross_section.name
752 )
753 })?;
754 Ok(encoded.len())
755}