Skip to main content

gammalooprs/integrate/
status_update.rs

1use std::time::Duration;
2
3use itertools::Itertools;
4use spenso::algebra::algebraic_traits::IsZero;
5use spenso::algebra::complex::Complex;
6use symbolica::numerical_integration::StatisticsAccumulator;
7
8use crate::{
9    integrands::evaluation::{NumericalStabilityLevel, StatisticsCounter},
10    settings::runtime::IntegrationStatisticsSnapshot,
11    utils,
12    utils::F,
13};
14
15use super::{
16    ComplexAccumulator, DiscreteGridAccumulatorSummary, IntegrationState,
17    display::{
18        DisplayField, StyledText, TextStyle, UncertaintyNotation, format_abbreviated_count,
19        format_iteration_points, format_max_eval_sample, format_signed_uncertainty,
20        format_significant_percentage, format_total_points, styled_bin_description,
21    },
22    max_eval_entry, max_weight_impact, summary_at_path,
23};
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum IntegrationStatusKind {
27    Live,
28    Iteration,
29    Final,
30}
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub enum IntegrationStatusPhaseDisplay {
34    Both,
35    Real,
36    Imag,
37}
38
39impl IntegrationStatusPhaseDisplay {
40    pub(crate) fn shows_real(self) -> bool {
41        matches!(self, Self::Both | Self::Real)
42    }
43
44    pub(crate) fn shows_imag(self) -> bool {
45        matches!(self, Self::Both | Self::Imag)
46    }
47}
48
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub enum ContributionSortMode {
51    Index,
52    Integral,
53    Error,
54}
55
56#[derive(Clone, Debug, PartialEq)]
57pub struct IntegrationStatusViewOptions {
58    pub phase_display: IntegrationStatusPhaseDisplay,
59    pub training_phase_display: IntegrationStatusPhaseDisplay,
60    pub training_slot: usize,
61    pub slot_training_phase_displays: Vec<IntegrationStatusPhaseDisplay>,
62    pub per_slot_training_phase: bool,
63    pub target_relative_accuracy: Option<f64>,
64    pub target_absolute_accuracy: Option<f64>,
65    pub show_statistics: bool,
66    pub show_max_weight_details: bool,
67    pub show_top_discrete_grid: bool,
68    pub show_discrete_contributions_sum: bool,
69    pub contribution_sort: ContributionSortMode,
70    pub show_max_weight_info_for_discrete_bins: bool,
71}
72
73impl IntegrationStatusViewOptions {
74    pub(crate) fn for_final(self) -> Self {
75        Self {
76            show_statistics: true,
77            ..self
78        }
79    }
80}
81
82#[derive(Clone, Copy, Debug, Eq, PartialEq)]
83pub(crate) struct LiveIterationProgress {
84    pub(crate) completed_points: usize,
85    pub(crate) target_points: usize,
86}
87
88pub(crate) struct StatusUpdateBuildRequest<'a> {
89    pub(crate) kind: IntegrationStatusKind,
90    pub(crate) integration_state: &'a IntegrationState,
91    pub(crate) cores: usize,
92    pub(crate) elapsed_time: Duration,
93    pub(crate) iteration_elapsed_time: Duration,
94    pub(crate) cur_points: usize,
95    pub(crate) total_points_display: usize,
96    pub(crate) n_samples_evaluated: usize,
97    pub(crate) targets: &'a [Option<Complex<F<f64>>>],
98    pub(crate) render_options: &'a IntegrationStatusViewOptions,
99    pub(crate) live_progress: Option<LiveIterationProgress>,
100}
101
102impl<'a> StatusUpdateBuildRequest<'a> {
103    pub(crate) fn new(
104        kind: IntegrationStatusKind,
105        integration_state: &'a IntegrationState,
106        targets: &'a [Option<Complex<F<f64>>>],
107        render_options: &'a IntegrationStatusViewOptions,
108    ) -> Self {
109        Self {
110            kind,
111            integration_state,
112            cores: 0,
113            elapsed_time: Duration::ZERO,
114            iteration_elapsed_time: Duration::ZERO,
115            cur_points: 0,
116            total_points_display: 0,
117            n_samples_evaluated: 0,
118            targets,
119            render_options,
120            live_progress: None,
121        }
122    }
123
124    pub(crate) fn with_timing(
125        mut self,
126        cores: usize,
127        elapsed_time: Duration,
128        iteration_elapsed_time: Duration,
129        cur_points: usize,
130        total_points_display: usize,
131        n_samples_evaluated: usize,
132    ) -> Self {
133        self.cores = cores;
134        self.elapsed_time = elapsed_time;
135        self.iteration_elapsed_time = iteration_elapsed_time;
136        self.cur_points = cur_points;
137        self.total_points_display = total_points_display;
138        self.n_samples_evaluated = n_samples_evaluated;
139        self
140    }
141
142    pub(crate) fn with_live_progress(
143        mut self,
144        live_progress: Option<LiveIterationProgress>,
145    ) -> Self {
146        self.live_progress = live_progress;
147        self
148    }
149}
150
151#[derive(Clone, Debug, Eq, PartialEq)]
152pub(crate) struct StatusMeta {
153    pub(crate) elapsed_time: Duration,
154    pub(crate) iteration_elapsed_time: Duration,
155    pub(crate) iteration: usize,
156    pub(crate) current_iteration_points: usize,
157    pub(crate) total_points: usize,
158    pub(crate) n_samples_evaluated: usize,
159    pub(crate) cores: usize,
160    pub(crate) training_slot: usize,
161    pub(crate) training_phase_display: IntegrationStatusPhaseDisplay,
162    pub(crate) live_progress: Option<LiveIterationProgress>,
163    pub(crate) show_eta_to_target: bool,
164    pub(crate) eta_to_target_specification: Option<String>,
165    pub(crate) eta_to_target: Option<Duration>,
166}
167
168#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
169pub(crate) struct TargetAccuracyStatus {
170    pub(crate) relative_reached: bool,
171    pub(crate) absolute_reached: bool,
172    pub(crate) eta_to_target: Option<Duration>,
173}
174
175impl TargetAccuracyStatus {
176    pub(crate) fn is_reached(self) -> bool {
177        self.relative_reached || self.absolute_reached
178    }
179}
180
181#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
182pub(crate) enum ComponentKind {
183    Real,
184    Imag,
185}
186
187#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
188pub(crate) enum IntegralView {
189    #[default]
190    Signed,
191    Absolute,
192}
193
194impl ComponentKind {
195    pub(crate) fn from_training_phase_display(
196        display: IntegrationStatusPhaseDisplay,
197    ) -> Option<Self> {
198        match display {
199            IntegrationStatusPhaseDisplay::Real => Some(Self::Real),
200            IntegrationStatusPhaseDisplay::Imag => Some(Self::Imag),
201            IntegrationStatusPhaseDisplay::Both => None,
202        }
203    }
204
205    pub(crate) fn all_for_display(display: IntegrationStatusPhaseDisplay) -> Vec<Self> {
206        let mut components = Vec::new();
207        if display.shows_real() {
208            components.push(Self::Real);
209        }
210        if display.shows_imag() {
211            components.push(Self::Imag);
212        }
213        components
214    }
215
216    pub(crate) fn tag(self) -> &'static str {
217        self.tag_for_view(IntegralView::Signed)
218    }
219
220    pub(crate) fn tag_for_view(self, view: IntegralView) -> &'static str {
221        match (self, view) {
222            (Self::Real, IntegralView::Signed) => "re",
223            (Self::Imag, IntegralView::Signed) => "im",
224            (Self::Real, IntegralView::Absolute) => "|re|",
225            (Self::Imag, IntegralView::Absolute) => "|im|",
226        }
227    }
228
229    pub(crate) fn phase_name(self) -> &'static str {
230        match self {
231            Self::Real => "real",
232            Self::Imag => "imag",
233        }
234    }
235
236    pub(crate) fn text_style(self) -> TextStyle {
237        match self {
238            Self::Real => TextStyle::pink().bold(),
239            Self::Imag => TextStyle::yellow().bold(),
240        }
241    }
242
243    pub(crate) fn label_display(self) -> StyledText {
244        self.label_display_for_view(IntegralView::Signed)
245    }
246
247    pub(crate) fn label_display_for_view(self, view: IntegralView) -> StyledText {
248        StyledText::styled(self.tag_for_view(view), self.text_style())
249    }
250
251    fn display_field(self) -> DisplayField<Self> {
252        DisplayField::new(self, self.label_display())
253    }
254}
255
256#[derive(Clone, Copy, Debug, Eq, PartialEq)]
257pub(crate) enum ContributionKind {
258    All,
259    Sum,
260    Bin(usize),
261}
262
263#[derive(Clone, Debug)]
264pub struct StatusUpdate {
265    pub(crate) kind: IntegrationStatusKind,
266    pub(crate) meta: StatusMeta,
267    pub(crate) targets: Vec<Option<Complex<F<f64>>>>,
268    pub(crate) slot_training_phase_displays: Vec<IntegrationStatusPhaseDisplay>,
269    pub(crate) per_slot_training_phase: bool,
270    pub(crate) main_results: MainResultsSection,
271    pub(crate) max_weight_details: Option<MaxWeightDetailsSection>,
272    pub(crate) absolute_max_weight_details: Option<MaxWeightDetailsSection>,
273    pub(crate) discrete_max_weight_details: Option<DiscreteMaxWeightDetailsSection>,
274    pub(crate) absolute_discrete_max_weight_details: Option<DiscreteMaxWeightDetailsSection>,
275    pub(crate) statistics: Option<StatisticsSection>,
276}
277
278impl StatusUpdate {
279    pub fn kind(&self) -> IntegrationStatusKind {
280        self.kind
281    }
282
283    pub fn is_initial_live_status(&self) -> bool {
284        self.kind == IntegrationStatusKind::Live
285            && self
286                .meta
287                .live_progress
288                .is_some_and(|progress| progress.completed_points == 0)
289    }
290
291    pub(crate) fn training_component(&self) -> Option<ComponentKind> {
292        ComponentKind::from_training_phase_display(self.meta.training_phase_display)
293    }
294
295    pub(crate) fn statistics_snapshot(&self) -> Option<IntegrationStatisticsSnapshot> {
296        self.statistics
297            .as_ref()
298            .map(StatisticsSection::global_snapshot)
299    }
300
301    pub(crate) fn training_target(&self) -> Option<F<f64>> {
302        self.target_for_slot(self.meta.training_slot)
303    }
304
305    pub(crate) fn target_for_slot(&self, slot_index: usize) -> Option<F<f64>> {
306        let component = self.training_component()?;
307        self.target_for_slot_component(slot_index, component)
308    }
309
310    pub(crate) fn target_for_slot_component(
311        &self,
312        slot_index: usize,
313        component: ComponentKind,
314    ) -> Option<F<f64>> {
315        let target = self.targets.get(slot_index)?.as_ref()?;
316        Some(match component {
317            ComponentKind::Real => target.re,
318            ComponentKind::Imag => target.im,
319        })
320    }
321
322    pub(crate) fn target_display_for_slot_component(
323        &self,
324        slot_index: usize,
325        component: ComponentKind,
326    ) -> Option<StyledText> {
327        self.target_for_slot_component(slot_index, component)
328            .map(|value| {
329                StyledText::styled(
330                    format_target_value_for_display(value.0),
331                    TextStyle::blue().bold(),
332                )
333            })
334    }
335
336    pub(crate) fn training_component_for_slot(&self, slot_index: usize) -> Option<ComponentKind> {
337        let phase = if self.per_slot_training_phase {
338            self.slot_training_phase_displays
339                .get(slot_index)
340                .copied()
341                .unwrap_or(self.meta.training_phase_display)
342        } else if slot_index == self.meta.training_slot {
343            self.meta.training_phase_display
344        } else {
345            return None;
346        };
347        match phase {
348            IntegrationStatusPhaseDisplay::Real => Some(ComponentKind::Real),
349            IntegrationStatusPhaseDisplay::Imag => Some(ComponentKind::Imag),
350            IntegrationStatusPhaseDisplay::Both => None,
351        }
352    }
353
354    pub(crate) fn slot_component_selected_for_training(
355        &self,
356        slot_index: usize,
357        component: ComponentKind,
358    ) -> bool {
359        self.training_component_for_slot(slot_index) == Some(component)
360    }
361
362    pub(crate) fn target_deltas_for_row_slot(
363        &self,
364        row: &MainResultsRow,
365        slot_index: usize,
366        view: IntegralView,
367    ) -> (Option<DisplayField<f64>>, Option<DisplayField<f64>>) {
368        if view == IntegralView::Absolute {
369            return (None, None);
370        }
371        let Some(cell) = row.slot_cell(slot_index) else {
372            return (None, None);
373        };
374        let Some(value) = cell.statistics(view).value.as_ref() else {
375            return (None, None);
376        };
377        let target = self.target_for_slot_component(slot_index, row.component.raw);
378        format_delta_fields_from_estimate(value.raw.0, value.raw.1, target)
379    }
380
381    pub(crate) fn max_weight_details(
382        &self,
383        view: IntegralView,
384    ) -> Option<&MaxWeightDetailsSection> {
385        match view {
386            IntegralView::Signed => self.max_weight_details.as_ref(),
387            IntegralView::Absolute => self.absolute_max_weight_details.as_ref(),
388        }
389    }
390
391    pub(crate) fn discrete_max_weight_details(
392        &self,
393        view: IntegralView,
394    ) -> Option<&DiscreteMaxWeightDetailsSection> {
395        match view {
396            IntegralView::Signed => self.discrete_max_weight_details.as_ref(),
397            IntegralView::Absolute => self.absolute_discrete_max_weight_details.as_ref(),
398        }
399    }
400}
401
402#[derive(Clone, Copy, Debug, Eq, PartialEq)]
403pub(crate) enum StatisticsScope {
404    Global,
405    Slot(usize),
406}
407
408impl StatusMeta {
409    pub(crate) fn iteration_progress_ratio(&self) -> Option<f64> {
410        self.live_progress.map(|progress| {
411            if progress.target_points == 0 {
412                0.0
413            } else {
414                progress.completed_points as f64 / progress.target_points as f64
415            }
416        })
417    }
418
419    pub(crate) fn iteration_eta(&self) -> Option<Duration> {
420        let progress = self.live_progress?;
421        if progress.completed_points == 0 || self.iteration_elapsed_time.is_zero() {
422            return None;
423        }
424
425        let processed_per_second =
426            progress.completed_points as f64 / self.iteration_elapsed_time.as_secs_f64();
427        if processed_per_second <= 0.0 {
428            return None;
429        }
430
431        let remaining_points = progress
432            .target_points
433            .saturating_sub(progress.completed_points);
434        Some(utils::duration_from_secs_f64_saturating(
435            remaining_points as f64 / processed_per_second,
436        ))
437    }
438
439    pub(crate) fn total_sample_rate_per_second(&self) -> Option<f64> {
440        if self.total_points == 0 || self.elapsed_time.is_zero() {
441            return None;
442        }
443        Some(self.total_points as f64 / self.elapsed_time.as_secs_f64())
444    }
445
446    pub(crate) fn sample_core_time(&self) -> Option<String> {
447        if self.n_samples_evaluated == 0 {
448            return None;
449        }
450
451        Some(utils::format_evaluation_time_from_f64(
452            self.elapsed_time.as_secs_f64() / (self.n_samples_evaluated as f64)
453                * (self.cores as f64),
454        ))
455    }
456
457    pub(crate) fn eta_to_target(&self) -> Option<Duration> {
458        self.eta_to_target
459    }
460
461    pub(crate) fn eta_to_target_specification(&self) -> Option<&str> {
462        self.eta_to_target_specification.as_deref()
463    }
464}
465
466pub(crate) fn evaluate_target_accuracy(
467    integration_state: &IntegrationState,
468    total_points: usize,
469    elapsed_time: Duration,
470    targets: &[Option<Complex<F<f64>>>],
471    training_phase_display: IntegrationStatusPhaseDisplay,
472    target_relative_accuracy: Option<f64>,
473    target_absolute_accuracy: Option<f64>,
474) -> TargetAccuracyStatus {
475    if target_relative_accuracy.is_none() && target_absolute_accuracy.is_none() {
476        return TargetAccuracyStatus::default();
477    }
478    if total_points == 0 {
479        return TargetAccuracyStatus::default();
480    }
481
482    let components = ComponentKind::all_for_display(training_phase_display);
483    if components.is_empty() {
484        return TargetAccuracyStatus::default();
485    }
486    if integration_state.all_integrals.is_empty() {
487        return TargetAccuracyStatus::default();
488    }
489
490    let absolute_target_error = target_absolute_accuracy.filter(|target| *target >= 0.0);
491    let relative_target_accuracy = target_relative_accuracy.filter(|target| *target >= 0.0);
492
493    let mut absolute_reached = absolute_target_error.is_some();
494    let mut relative_reached = relative_target_accuracy.is_some();
495    let mut saw_absolute_constraint = false;
496    let mut saw_relative_constraint = false;
497    let mut absolute_eta_to_target = None;
498    let mut relative_eta_to_target = None;
499
500    for (slot_index, accumulator) in integration_state.all_integrals.iter().enumerate() {
501        let target = targets.get(slot_index).and_then(|target| target.as_ref());
502        for component in &components {
503            let (avg, err) = match component {
504                ComponentKind::Real => (accumulator.re.avg.0, accumulator.re.err.0),
505                ComponentKind::Imag => (accumulator.im.avg.0, accumulator.im.err.0),
506            };
507
508            if let Some(target_error) = absolute_target_error {
509                saw_absolute_constraint = true;
510                absolute_reached &= err <= target_error;
511                absolute_eta_to_target = max_duration_option(
512                    absolute_eta_to_target,
513                    estimate_eta_to_target(total_points, elapsed_time, err, Some(target_error)),
514                );
515            }
516
517            if let Some(target_accuracy) = relative_target_accuracy {
518                let reference = target
519                    .map(|target| match component {
520                        ComponentKind::Real => target.re.0,
521                        ComponentKind::Imag => target.im.0,
522                    })
523                    .unwrap_or(avg)
524                    .abs();
525                if reference != 0.0 {
526                    saw_relative_constraint = true;
527                    let target_error = target_accuracy * reference;
528                    relative_reached &= err <= target_error;
529                    relative_eta_to_target = max_duration_option(
530                        relative_eta_to_target,
531                        estimate_eta_to_target(total_points, elapsed_time, err, Some(target_error)),
532                    );
533                }
534            }
535        }
536    }
537
538    if !saw_absolute_constraint {
539        absolute_reached = false;
540        absolute_eta_to_target = None;
541    }
542    if !saw_relative_constraint {
543        relative_reached = false;
544        relative_eta_to_target = None;
545    }
546
547    let eta_to_target = min_duration_option(absolute_eta_to_target, relative_eta_to_target);
548
549    TargetAccuracyStatus {
550        relative_reached,
551        absolute_reached,
552        eta_to_target,
553    }
554}
555
556impl MainResultsSection {
557    pub(crate) fn all_rows(&self) -> impl Iterator<Item = &MainResultsRow> {
558        self.row_groups.iter().flat_map(|group| group.rows.iter())
559    }
560
561    pub(crate) fn row_groups_of_kind(
562        &self,
563        kind: MainResultsRowGroupKind,
564    ) -> impl Iterator<Item = &MainResultsRowGroup> {
565        self.row_groups
566            .iter()
567            .filter(move |group| group.kind == kind)
568    }
569
570    pub(crate) fn find_row(
571        &self,
572        contribution: ContributionKind,
573        component: ComponentKind,
574    ) -> Option<&MainResultsRow> {
575        self.all_rows()
576            .find(|row| row.contribution.raw == contribution && row.component.raw == component)
577    }
578
579    pub(crate) fn metadata_headers(&self) -> Vec<StyledText> {
580        let mut headers = vec![
581            styled_colored("χ²/dof", TextStyle::blue().bold()),
582            styled_colored("mwi", TextStyle::blue().bold()),
583        ];
584        if self.has_target_columns {
585            headers.push(styled_colored("Δ [σ]", TextStyle::blue().bold()));
586            headers.push(styled_colored("Δ [%]", TextStyle::blue().bold()));
587        }
588        headers
589    }
590
591    pub(crate) fn component_header(&self) -> StyledText {
592        styled_plain("")
593    }
594
595    pub(crate) fn summary_headers(&self) -> Vec<StyledText> {
596        let mut headers = vec![self.contribution_header.clone(), self.component_header()];
597        headers.extend(self.slot_headers.iter().cloned());
598        headers
599    }
600
601    pub(crate) fn discrete_headers(&self) -> Vec<StyledText> {
602        vec![
603            self.contribution_header.clone(),
604            self.component_header(),
605            styled_plain("integral"),
606            styled_plain("% err"),
607            styled_plain("χ^2"),
608            styled_plain("m.w.i"),
609            styled_plain("sample %"),
610            styled_plain("# samples"),
611            styled_plain("pdf"),
612        ]
613    }
614
615    pub(crate) fn selected_bin_detail_headers(&self) -> Vec<StyledText> {
616        vec![
617            styled_plain("Integrand"),
618            styled_plain("integral"),
619            styled_plain("% err"),
620            styled_plain("χ^2"),
621            styled_plain("m.w.i"),
622            styled_plain("sample %"),
623            styled_plain("# samples"),
624            styled_plain("pdf"),
625        ]
626    }
627}
628
629impl MainResultsRow {
630    pub(crate) fn slot_cell(&self, slot_index: usize) -> Option<&MainTableSlotCells> {
631        self.slot_cells.get(slot_index)
632    }
633}
634
635impl MaxWeightDetailsSection {
636    pub(crate) fn title(&self) -> StyledText {
637        styled_colored("Maximum weight details", TextStyle::green().bold())
638    }
639
640    pub(crate) fn headers(&self) -> Vec<StyledText> {
641        vec![
642            styled_colored("Integrand", TextStyle::blue().bold()),
643            styled_plain(""),
644            styled_colored("Max eval", TextStyle::blue().bold()),
645            styled_colored("Max eval coordinates", TextStyle::blue().bold()),
646        ]
647    }
648}
649
650impl DiscreteMaxWeightDetailsSection {
651    pub(crate) fn title(&self) -> StyledText {
652        styled_colored(
653            "Maximum weight details by discrete bin",
654            TextStyle::green().bold(),
655        )
656    }
657
658    pub(crate) fn summary_headers(&self) -> Vec<StyledText> {
659        let mut headers = vec![self.contribution_header.clone(), styled_plain("")];
660        headers.extend(self.slot_headers.iter().cloned());
661        headers
662    }
663
664    pub(crate) fn coordinates_header(&self) -> StyledText {
665        styled_colored("Max eval coordinates", TextStyle::blue().bold())
666    }
667
668    pub(crate) fn coordinate_headers(&self) -> Vec<StyledText> {
669        vec![
670            self.contribution_header.clone(),
671            styled_plain(""),
672            styled_plain("Integrand"),
673            self.coordinates_header(),
674        ]
675    }
676}
677
678impl StatisticsSection {
679    pub(crate) fn global_snapshot(&self) -> IntegrationStatisticsSnapshot {
680        self.global.snapshot()
681    }
682
683    fn scoped_counter(&self, scope: StatisticsScope) -> Option<&StatisticsCounter> {
684        match scope {
685            StatisticsScope::Global => Some(&self.global),
686            StatisticsScope::Slot(slot_index) => self.slot_counters.get(slot_index),
687        }
688    }
689
690    fn scope_label(&self, scope: StatisticsScope) -> StyledText {
691        let label = match scope {
692            StatisticsScope::Global => "[global]".to_string(),
693            StatisticsScope::Slot(slot_index) => self
694                .slot_labels
695                .get(slot_index)
696                .map(|label| format!("[{label}]"))
697                .unwrap_or_else(|| "[global]".to_string()),
698        };
699        styled_colored(format!(" {label}"), TextStyle::blue().bold())
700    }
701
702    fn scoped_title(&self, prefix: &str, scope: StatisticsScope) -> StyledText {
703        let mut title = styled_colored(prefix, TextStyle::green().bold());
704        title.append(self.scope_label(scope));
705        title
706    }
707
708    fn uses_global_integrator_scope(scope: StatisticsScope) -> bool {
709        matches!(scope, StatisticsScope::Global)
710    }
711
712    pub(crate) fn statistics_title(&self, scope: StatisticsScope) -> StyledText {
713        self.scoped_title("Integration statistics", scope)
714    }
715
716    pub(crate) fn timing_title(&self, scope: StatisticsScope) -> StyledText {
717        self.scoped_title("Timing composition", scope)
718    }
719
720    pub(crate) fn precision_title(&self, scope: StatisticsScope) -> StyledText {
721        self.scoped_title("Precision mix", scope)
722    }
723
724    pub(crate) fn table_rows(&self, scope: StatisticsScope) -> Vec<StatisticsTableRow> {
725        let snapshot = self
726            .scoped_counter(scope)
727            .map(StatisticsCounter::snapshot)
728            .unwrap_or_else(|| self.global.snapshot());
729        let integrator_value = if Self::uses_global_integrator_scope(scope) {
730            styled_colored(
731                utils::format_evaluation_time_from_f64(snapshot.average_integrator_time_seconds),
732                TextStyle::green(),
733            )
734        } else {
735            styled_plain("N/A")
736        };
737        vec![
738            StatisticsTableRow {
739                row_label: styled_colored("  timing", TextStyle::blue().bold()),
740                entries: vec![
741                    StatisticsTableEntry {
742                        label: styled_plain("total"),
743                        value: styled_colored(
744                            utils::format_evaluation_time_from_f64(
745                                snapshot.average_total_time_seconds,
746                            ),
747                            TextStyle::green(),
748                        ),
749                    },
750                    StatisticsTableEntry {
751                        label: styled_plain("param"),
752                        value: styled_colored(
753                            utils::format_evaluation_time_from_f64(
754                                snapshot.average_parameterization_time_seconds,
755                            ),
756                            TextStyle::green(),
757                        ),
758                    },
759                    StatisticsTableEntry {
760                        label: styled_plain("itg"),
761                        value: styled_colored(
762                            utils::format_evaluation_time_from_f64(
763                                snapshot.average_integrand_time_seconds,
764                            ),
765                            TextStyle::green(),
766                        ),
767                    },
768                    StatisticsTableEntry {
769                        label: styled_plain("evaluators"),
770                        value: styled_colored(
771                            utils::format_evaluation_time_from_f64(
772                                snapshot.average_evaluator_time_seconds,
773                            ),
774                            TextStyle::green(),
775                        ),
776                    },
777                ],
778            },
779            StatisticsTableRow {
780                row_label: styled_colored("  evals", TextStyle::blue().bold()),
781                entries: vec![
782                    StatisticsTableEntry {
783                        label: styled_plain("f64"),
784                        value: styled_colored(
785                            format!("{:.2}%", snapshot.f64_percentage),
786                            TextStyle::green(),
787                        ),
788                    },
789                    StatisticsTableEntry {
790                        label: styled_plain("f128"),
791                        value: styled_colored(
792                            format!("{:.2}%", snapshot.f128_percentage),
793                            TextStyle::blue(),
794                        ),
795                    },
796                    StatisticsTableEntry {
797                        label: styled_plain("arb"),
798                        value: styled_plain(format!("{:.2}%", snapshot.arb_percentage)),
799                    },
800                    StatisticsTableEntry {
801                        label: styled_plain("nans+unstable"),
802                        value: styled_colored(
803                            format!("{:.2}%", snapshot.nan_or_unstable_percentage),
804                            if snapshot.nan_or_unstable_percentage > 0.0 {
805                                TextStyle::red()
806                            } else {
807                                TextStyle::green()
808                            },
809                        ),
810                    },
811                ],
812            },
813            StatisticsTableRow {
814                row_label: styled_colored("  events", TextStyle::blue().bold()),
815                entries: vec![
816                    StatisticsTableEntry {
817                        label: styled_plain("evts #"),
818                        value: styled_colored(
819                            format_abbreviated_count(snapshot.generated_event_count),
820                            TextStyle::green(),
821                        ),
822                    },
823                    StatisticsTableEntry {
824                        label: styled_plain("sel. %"),
825                        value: snapshot
826                            .selection_efficiency_percentage
827                            .map(|value| styled_colored(format!("{value:.2}%"), TextStyle::green()))
828                            .unwrap_or_else(|| styled_plain("N/A")),
829                    },
830                    StatisticsTableEntry {
831                        label: styled_plain("obs"),
832                        value: styled_colored(
833                            utils::format_evaluation_time_from_f64(
834                                snapshot.average_observable_time_seconds,
835                            ),
836                            TextStyle::green(),
837                        ),
838                    },
839                    StatisticsTableEntry {
840                        label: styled_plain("integrator"),
841                        value: integrator_value,
842                    },
843                ],
844            },
845        ]
846    }
847
848    pub(crate) fn timing_mix_segments(&self, scope: StatisticsScope) -> Vec<StatisticsMixSegment> {
849        let snapshot = self
850            .scoped_counter(scope)
851            .map(StatisticsCounter::snapshot)
852            .unwrap_or_else(|| self.global.snapshot());
853        let parameterization = snapshot.average_parameterization_time_seconds.max(0.0);
854        let evaluator = snapshot.average_evaluator_time_seconds.max(0.0);
855        let observable = snapshot.average_observable_time_seconds.max(0.0);
856        let integrand_core =
857            (snapshot.average_integrand_time_seconds.max(0.0) - observable - evaluator).max(0.0);
858        let mut raw_segments = vec![
859            (
860                styled_colored("evaluators", TextStyle::green().bold()),
861                evaluator,
862            ),
863            (
864                styled_colored("itg_core", TextStyle::blue().bold()),
865                integrand_core,
866            ),
867            (
868                styled_colored("obs", TextStyle::yellow().bold()),
869                observable,
870            ),
871            (
872                styled_colored("param", TextStyle::red().bold()),
873                parameterization,
874            ),
875        ];
876        if Self::uses_global_integrator_scope(scope) {
877            raw_segments.push((
878                styled_plain("integrator"),
879                snapshot.average_integrator_time_seconds.max(0.0),
880            ));
881        }
882        let mut segments = normalize_mix_segments(raw_segments);
883        segments.sort_by(|lhs, rhs| {
884            rhs.percentage
885                .partial_cmp(&lhs.percentage)
886                .unwrap_or(std::cmp::Ordering::Equal)
887        });
888        segments
889    }
890
891    pub(crate) fn precision_mix_segments(
892        &self,
893        scope: StatisticsScope,
894    ) -> Vec<StatisticsMixSegment> {
895        let snapshot = self
896            .scoped_counter(scope)
897            .map(StatisticsCounter::snapshot)
898            .unwrap_or_else(|| self.global.snapshot());
899        normalize_mix_segments(vec![
900            (
901                styled_colored("f64", TextStyle::green()),
902                snapshot.f64_percentage,
903            ),
904            (
905                styled_colored("f128", TextStyle::blue()),
906                snapshot.f128_percentage,
907            ),
908            (styled_plain("arb"), snapshot.arb_percentage),
909            (
910                styled_colored("unstbl.+nan.", TextStyle::red()),
911                snapshot.nan_or_unstable_percentage,
912            ),
913        ])
914    }
915
916    pub(crate) fn numerical_stability_median_entries(
917        &self,
918        scope: StatisticsScope,
919    ) -> Vec<StatisticsMedianEntry> {
920        let Some(counter) = self.scoped_counter(scope) else {
921            return Vec::new();
922        };
923        let total_evals = counter.num_evals;
924        let stability_histograms = counter.numerical_stability_snapshot();
925
926        NumericalStabilityLevel::all()
927            .into_iter()
928            .map(|level| {
929                let style = match level {
930                    NumericalStabilityLevel::Double => TextStyle::green(),
931                    NumericalStabilityLevel::Quad => TextStyle::blue(),
932                    NumericalStabilityLevel::ArbPrec => TextStyle::PLAIN,
933                };
934                let value = stability_histograms
935                    .median(level)
936                    .map(|median| styled_colored(median.formatted_relative_accuracy(), style))
937                    .unwrap_or_else(|| styled_plain("N/A"));
938                let processed_count = stability_histograms.processed_sample_count(level);
939                let processed_percentage = if total_evals == 0 {
940                    0.0
941                } else {
942                    processed_count as f64 / total_evals as f64 * 100.0
943                };
944                StatisticsMedianEntry {
945                    label: styled_colored(level.short_label(), style),
946                    value,
947                    processed_percentage: styled_colored(
948                        format_processed_stability_percentage(processed_percentage),
949                        style,
950                    ),
951                }
952            })
953            .collect()
954    }
955
956    pub(crate) fn stability_mix_segments(
957        &self,
958        scope: StatisticsScope,
959    ) -> Vec<StatisticsMixSegment> {
960        let snapshot = self
961            .scoped_counter(scope)
962            .map(StatisticsCounter::snapshot)
963            .unwrap_or_else(|| self.global.snapshot());
964        let unstable = (snapshot.nan_or_unstable_percentage - snapshot.nan_percentage).max(0.0);
965        let stable = (100.0 - snapshot.nan_or_unstable_percentage).max(0.0);
966        normalize_mix_segments(vec![
967            (styled_colored("stable", TextStyle::green()), stable),
968            (styled_colored("unstable", TextStyle::red()), unstable),
969            (
970                styled_colored("nan", TextStyle::red()),
971                snapshot.nan_percentage,
972            ),
973        ])
974    }
975}
976
977#[derive(Clone, Debug)]
978pub(crate) struct MainResultsSection {
979    pub(crate) header_left: StyledText,
980    pub(crate) header_middle: StyledText,
981    pub(crate) header_tail: StyledText,
982    pub(crate) contribution_header: StyledText,
983    pub(crate) slot_headers: Vec<StyledText>,
984    pub(crate) has_discrete_columns: bool,
985    pub(crate) has_target_columns: bool,
986    pub(crate) row_groups: Vec<MainResultsRowGroup>,
987}
988
989#[derive(Clone, Copy, Debug, Eq, PartialEq)]
990pub(crate) enum MainResultsRowGroupKind {
991    All,
992    Sum,
993    Bins,
994}
995
996#[derive(Clone, Debug)]
997pub(crate) struct MainResultsRowGroup {
998    pub(crate) kind: MainResultsRowGroupKind,
999    pub(crate) rows: Vec<MainResultsRow>,
1000}
1001
1002#[derive(Clone, Debug)]
1003pub(crate) struct MainResultsRow {
1004    pub(crate) contribution: DisplayField<ContributionKind>,
1005    pub(crate) component: DisplayField<ComponentKind>,
1006    pub(crate) slot_cells: Vec<MainTableSlotCells>,
1007    pub(crate) delta_sigma: Option<DisplayField<f64>>,
1008    pub(crate) delta_percent: Option<DisplayField<f64>>,
1009}
1010
1011#[derive(Clone, Debug, Default)]
1012pub(crate) struct IntegralStatisticsCells {
1013    pub(crate) value: Option<DisplayField<(F<f64>, F<f64>)>>,
1014    pub(crate) relative_error: Option<DisplayField<f64>>,
1015    pub(crate) chi_sq: Option<DisplayField<f64>>,
1016    pub(crate) max_weight_impact: Option<DisplayField<f64>>,
1017}
1018
1019#[derive(Clone, Debug, Default)]
1020pub(crate) struct MainTableSlotCells {
1021    pub(crate) signed: IntegralStatisticsCells,
1022    pub(crate) absolute: IntegralStatisticsCells,
1023    pub(crate) sample_fraction: Option<DisplayField<f64>>,
1024    pub(crate) sample_count: Option<DisplayField<usize>>,
1025    pub(crate) target_pdf: Option<DisplayField<f64>>,
1026}
1027
1028impl MainTableSlotCells {
1029    pub(crate) fn statistics(&self, view: IntegralView) -> &IntegralStatisticsCells {
1030        match view {
1031            IntegralView::Signed => &self.signed,
1032            IntegralView::Absolute => &self.absolute,
1033        }
1034    }
1035}
1036
1037#[derive(Clone, Debug)]
1038pub(crate) struct MaxWeightDetailsSection {
1039    pub(crate) rows_by_slot: Vec<Vec<MaxWeightDetailsRow>>,
1040}
1041
1042#[derive(Clone, Debug)]
1043pub(crate) struct MaxWeightDetailsRow {
1044    pub(crate) slot: DisplayField<String>,
1045    pub(crate) component_sign: DisplayField<(ComponentKind, bool)>,
1046    pub(crate) max_eval: DisplayField<F<f64>>,
1047    pub(crate) coordinates: DisplayField<String>,
1048}
1049
1050#[derive(Clone, Debug)]
1051pub(crate) struct DiscreteMaxWeightDetailsSection {
1052    pub(crate) contribution_header: StyledText,
1053    pub(crate) slot_headers: Vec<StyledText>,
1054    pub(crate) row_groups: Vec<Vec<DiscreteMaxWeightRow>>,
1055}
1056
1057#[derive(Clone, Debug)]
1058pub(crate) struct DiscreteMaxWeightRow {
1059    pub(crate) contribution: DisplayField<ContributionKind>,
1060    pub(crate) component_sign: DisplayField<(ComponentKind, bool)>,
1061    pub(crate) slot_values: Vec<Option<DisplayField<F<f64>>>>,
1062    pub(crate) slot_coordinates: Vec<SlotCoordinateEntry>,
1063}
1064
1065#[derive(Clone, Debug)]
1066pub(crate) struct SlotCoordinateEntry {
1067    pub(crate) slot: DisplayField<String>,
1068    pub(crate) coordinates: DisplayField<String>,
1069}
1070
1071#[derive(Clone, Debug)]
1072pub(crate) struct StatisticsSection {
1073    pub(crate) global: StatisticsCounter,
1074    pub(crate) slot_counters: Vec<StatisticsCounter>,
1075    pub(crate) slot_labels: Vec<String>,
1076}
1077
1078#[derive(Clone, Debug)]
1079pub(crate) struct StatisticsTableRow {
1080    pub(crate) row_label: StyledText,
1081    pub(crate) entries: Vec<StatisticsTableEntry>,
1082}
1083
1084#[derive(Clone, Debug)]
1085pub(crate) struct StatisticsTableEntry {
1086    pub(crate) label: StyledText,
1087    pub(crate) value: StyledText,
1088}
1089
1090#[derive(Clone, Debug)]
1091pub(crate) struct StatisticsMixSegment {
1092    pub(crate) label: StyledText,
1093    pub(crate) percentage: f64,
1094}
1095
1096#[derive(Clone, Debug)]
1097pub(crate) struct StatisticsMedianEntry {
1098    pub(crate) label: StyledText,
1099    pub(crate) value: StyledText,
1100    pub(crate) processed_percentage: StyledText,
1101}
1102
1103fn format_processed_stability_percentage(percentage: f64) -> String {
1104    if !percentage.is_finite() {
1105        return "N/A".to_string();
1106    }
1107    if percentage > 0.0 && percentage < 0.01 {
1108        format!("{percentage:.2e}%")
1109    } else {
1110        format!("{percentage:.2}%")
1111    }
1112}
1113
1114fn component_accumulator(
1115    accumulator: &ComplexAccumulator,
1116    component: ComponentKind,
1117    view: IntegralView,
1118) -> &StatisticsAccumulator<F<f64>> {
1119    match (component, view) {
1120        (ComponentKind::Real, IntegralView::Signed) => &accumulator.re,
1121        (ComponentKind::Imag, IntegralView::Signed) => &accumulator.im,
1122        (ComponentKind::Real, IntegralView::Absolute) => &accumulator.absolute_re,
1123        (ComponentKind::Imag, IntegralView::Absolute) => &accumulator.absolute_im,
1124    }
1125}
1126
1127fn slot_component_summary<'a>(
1128    integration_state: &'a IntegrationState,
1129    slot_index: usize,
1130    component: ComponentKind,
1131    view: IntegralView,
1132    monitored_path: &[usize],
1133) -> Option<&'a DiscreteGridAccumulatorSummary> {
1134    match (component, view) {
1135        (ComponentKind::Real, IntegralView::Signed) => integration_state.slot_re_summaries
1136            [slot_index]
1137            .as_ref()
1138            .and_then(|summary| summary_at_path(summary, monitored_path)),
1139        (ComponentKind::Imag, IntegralView::Signed) => integration_state.slot_im_summaries
1140            [slot_index]
1141            .as_ref()
1142            .and_then(|summary| summary_at_path(summary, monitored_path)),
1143        (ComponentKind::Real, IntegralView::Absolute) => {
1144            integration_state.slot_absolute_re_summaries[slot_index].as_ref()
1145        }
1146        (ComponentKind::Imag, IntegralView::Absolute) => {
1147            integration_state.slot_absolute_im_summaries[slot_index].as_ref()
1148        }
1149    }
1150}
1151
1152fn sum_estimate_error(summary: &DiscreteGridAccumulatorSummary) -> (F<f64>, F<f64>) {
1153    let (avg, err_sq) = summary
1154        .bins
1155        .iter()
1156        .fold((F(0.0), F(0.0)), |(avg, err_sq), bin| {
1157            (
1158                avg + bin.accumulator.avg,
1159                err_sq + bin.accumulator.err * bin.accumulator.err,
1160            )
1161        });
1162    (avg, F(err_sq.0.sqrt()))
1163}
1164
1165fn total_processed_samples(summary: &DiscreteGridAccumulatorSummary) -> usize {
1166    summary
1167        .bins
1168        .iter()
1169        .map(|bin| bin.accumulator.processed_samples)
1170        .sum()
1171}
1172
1173fn normalize_mix_segments(segments: Vec<(StyledText, f64)>) -> Vec<StatisticsMixSegment> {
1174    let total: f64 = segments.iter().map(|(_, value)| value.max(0.0)).sum();
1175    if total <= f64::EPSILON {
1176        return segments
1177            .into_iter()
1178            .map(|(label, _)| StatisticsMixSegment {
1179                label,
1180                percentage: 0.0,
1181            })
1182            .collect();
1183    }
1184
1185    segments
1186        .into_iter()
1187        .map(|(label, value)| StatisticsMixSegment {
1188            label,
1189            percentage: value.max(0.0) / total * 100.0,
1190        })
1191        .collect()
1192}
1193
1194fn estimate_eta_to_target(
1195    total_points: usize,
1196    elapsed_time: Duration,
1197    current_error: f64,
1198    target_error: Option<f64>,
1199) -> Option<Duration> {
1200    let target_error = target_error?;
1201    if target_error < 0.0 {
1202        return None;
1203    }
1204    if current_error <= target_error {
1205        return Some(Duration::ZERO);
1206    }
1207    if total_points == 0 || elapsed_time.is_zero() || current_error <= 0.0 {
1208        return None;
1209    }
1210    if target_error == 0.0 {
1211        return Some(Duration::MAX);
1212    }
1213
1214    let rate_per_second = total_points as f64 / elapsed_time.as_secs_f64();
1215    if rate_per_second <= 0.0 {
1216        return None;
1217    }
1218
1219    let target_points = total_points as f64 * (current_error / target_error).powi(2);
1220    if !target_points.is_finite() {
1221        return Some(Duration::MAX);
1222    }
1223    let remaining_points = (target_points - total_points as f64).max(0.0);
1224    Some(utils::duration_from_secs_f64_saturating(
1225        remaining_points / rate_per_second,
1226    ))
1227}
1228
1229fn min_duration_option(lhs: Option<Duration>, rhs: Option<Duration>) -> Option<Duration> {
1230    match (lhs, rhs) {
1231        (Some(lhs), Some(rhs)) => Some(lhs.min(rhs)),
1232        (Some(lhs), None) => Some(lhs),
1233        (None, Some(rhs)) => Some(rhs),
1234        (None, None) => None,
1235    }
1236}
1237
1238fn max_duration_option(lhs: Option<Duration>, rhs: Option<Duration>) -> Option<Duration> {
1239    match (lhs, rhs) {
1240        (Some(lhs), Some(rhs)) => Some(lhs.max(rhs)),
1241        (Some(lhs), None) => Some(lhs),
1242        (None, Some(rhs)) => Some(rhs),
1243        (None, None) => None,
1244    }
1245}
1246
1247fn styled_plain(text: impl Into<String>) -> StyledText {
1248    StyledText::plain(text)
1249}
1250
1251fn styled_colored(text: impl Into<String>, style: TextStyle) -> StyledText {
1252    StyledText::styled(text, style)
1253}
1254
1255fn format_eta_to_target_specification(
1256    target_relative_accuracy: Option<f64>,
1257    target_absolute_accuracy: Option<f64>,
1258) -> Option<String> {
1259    let relative_spec = target_relative_accuracy
1260        .filter(|target| *target >= 0.0)
1261        .map(|target| {
1262            format!(
1263                "% err <= {}",
1264                format_percentage_target_value_for_display(target * 100.0)
1265            )
1266        });
1267    let absolute_spec = target_absolute_accuracy
1268        .filter(|target| *target >= 0.0)
1269        .map(|target| {
1270            format!(
1271                "err <= {}",
1272                format_positive_target_value_for_display(target)
1273            )
1274        });
1275
1276    match (relative_spec, absolute_spec) {
1277        (Some(relative), Some(absolute)) => Some(format!("{relative} or {absolute}")),
1278        (Some(relative), None) => Some(relative),
1279        (None, Some(absolute)) => Some(absolute),
1280        (None, None) => None,
1281    }
1282}
1283
1284fn format_target_value_for_display(value: f64) -> String {
1285    if value == 0.0 {
1286        return String::from("+0e0");
1287    }
1288
1289    for precision in 0..=16 {
1290        let candidate = normalize_scientific_exponent(&format!("{:+.*e}", precision, value));
1291        let Ok(parsed) = candidate.parse::<f64>() else {
1292            continue;
1293        };
1294        if ulp_distance(parsed, value) <= 8 {
1295            return candidate;
1296        }
1297    }
1298
1299    normalize_scientific_exponent(&format!("{:+.16e}", value))
1300}
1301
1302fn normalize_scientific_exponent(formatted: &str) -> String {
1303    let Some((mantissa, exponent)) = formatted.rsplit_once('e') else {
1304        return formatted.to_string();
1305    };
1306    let exponent = exponent.parse::<i32>().unwrap_or_default();
1307    format!("{mantissa}e{exponent:+}")
1308}
1309
1310fn ulp_distance(lhs: f64, rhs: f64) -> u64 {
1311    if lhs.is_nan() || rhs.is_nan() {
1312        return u64::MAX;
1313    }
1314    ordered_f64_bits(lhs).abs_diff(ordered_f64_bits(rhs))
1315}
1316
1317fn ordered_f64_bits(value: f64) -> u64 {
1318    let bits = value.to_bits();
1319    if (bits >> 63) != 0 {
1320        !bits
1321    } else {
1322        bits | (1_u64 << 63)
1323    }
1324}
1325
1326fn format_positive_target_value_for_display(value: f64) -> String {
1327    format_target_value_for_display(value)
1328        .trim_start_matches('+')
1329        .to_string()
1330}
1331
1332fn format_percentage_target_value_for_display(value: f64) -> String {
1333    let formatted = if value.abs() <= 1.0e-4 {
1334        format_positive_target_value_for_display(value)
1335    } else {
1336        format_fixed_target_value_for_display(value)
1337    };
1338    format!("{formatted}%")
1339}
1340
1341fn format_fixed_target_value_for_display(value: f64) -> String {
1342    if value == 0.0 {
1343        return String::from("0");
1344    }
1345
1346    for precision in 0..=16 {
1347        let candidate = normalize_fixed_decimal(&format!("{value:.precision$}"));
1348        let Ok(parsed) = candidate.parse::<f64>() else {
1349            continue;
1350        };
1351        if ulp_distance(parsed, value) <= 8 {
1352            return candidate;
1353        }
1354    }
1355
1356    format_positive_target_value_for_display(value)
1357}
1358
1359fn normalize_fixed_decimal(formatted: &str) -> String {
1360    let trimmed = formatted.trim_end_matches('0').trim_end_matches('.');
1361    if trimmed.is_empty() || trimmed == "-0" {
1362        String::from("0")
1363    } else {
1364        trimmed.to_string()
1365    }
1366}
1367
1368fn format_value_field(avg: F<f64>, err: F<f64>) -> DisplayField<(F<f64>, F<f64>)> {
1369    let formatted = format_signed_uncertainty(avg, err, UncertaintyNotation::Scientific);
1370    let uncertainty_style = format_relative_error_field_from_estimate(avg, err)
1371        .map(|field| {
1372            field
1373                .display
1374                .spans
1375                .first()
1376                .map(|span| span.style)
1377                .unwrap_or(TextStyle::PLAIN)
1378                .bold()
1379        })
1380        .unwrap_or(TextStyle::PLAIN.bold());
1381
1382    let display = if let Some(start) = formatted.find('(') {
1383        if let Some(end_offset) = formatted[start..].find(')') {
1384            let end = start + end_offset + 1;
1385            let mut styled = StyledText::new();
1386            styled.push_text(&formatted[..start], TextStyle::blue().bold());
1387            styled.push_text(&formatted[start..end], uncertainty_style);
1388            styled.push_text(&formatted[end..], TextStyle::blue().bold());
1389            styled
1390        } else {
1391            styled_colored(formatted, TextStyle::blue().bold())
1392        }
1393    } else {
1394        styled_colored(formatted, TextStyle::blue().bold())
1395    };
1396
1397    DisplayField::new((avg, err), display)
1398}
1399
1400fn format_relative_error_field_from_estimate(
1401    avg: F<f64>,
1402    err: F<f64>,
1403) -> Option<DisplayField<f64>> {
1404    if avg.is_zero() {
1405        return None;
1406    }
1407
1408    let raw = (err / avg).abs().0 * 100.0;
1409    let formatted = format_significant_percentage(raw, 3, Some((1.0e4, 1.0e-4)));
1410    let style = if raw > 1.0 {
1411        TextStyle::red()
1412    } else {
1413        TextStyle::green()
1414    };
1415    Some(DisplayField::new(raw, styled_colored(formatted, style)))
1416}
1417
1418fn format_relative_error_field(itg: &StatisticsAccumulator<F<f64>>) -> Option<DisplayField<f64>> {
1419    format_relative_error_field_from_estimate(itg.avg, itg.err)
1420}
1421
1422fn format_chi_sq_field(
1423    itg: &StatisticsAccumulator<F<f64>>,
1424    i_iter: usize,
1425) -> Option<DisplayField<f64>> {
1426    if i_iter == 0 {
1427        return None;
1428    }
1429    let raw = itg.chi_sq.0 / (i_iter as f64);
1430    let style = if raw > 5.0 {
1431        TextStyle::red()
1432    } else {
1433        TextStyle::PLAIN
1434    };
1435    Some(DisplayField::new(
1436        raw,
1437        styled_colored(format!("{raw:.3}"), style),
1438    ))
1439}
1440
1441fn format_delta_fields_from_estimate(
1442    avg: F<f64>,
1443    err: F<f64>,
1444    target: Option<F<f64>>,
1445) -> (Option<DisplayField<f64>>, Option<DisplayField<f64>>) {
1446    let Some(target) = target else {
1447        return (None, None);
1448    };
1449
1450    let delta_sigma = if err.is_zero() {
1451        0.0
1452    } else {
1453        (target - avg).abs().0 / err.0
1454    };
1455    let delta_percent = if target.abs().is_non_zero() {
1456        (target - avg).abs().0 / target.abs().0 * 100.0
1457    } else {
1458        0.0
1459    };
1460    let is_outside_target = delta_sigma > 5.0
1461        || (target.abs().is_non_zero() && ((target - avg).abs() / target.abs()).0 > 0.01);
1462    let style = if is_outside_target {
1463        TextStyle::red()
1464    } else {
1465        TextStyle::green()
1466    };
1467
1468    (
1469        Some(DisplayField::new(
1470            delta_sigma,
1471            styled_colored(format!("Δ = {:.3}σ", delta_sigma), style),
1472        )),
1473        Some(DisplayField::new(
1474            delta_percent,
1475            styled_colored(format!("Δ = {:.3}%", delta_percent), style),
1476        )),
1477    )
1478}
1479
1480fn format_mwi_field(itg: &StatisticsAccumulator<F<f64>>) -> Option<DisplayField<f64>> {
1481    let raw = max_weight_impact(itg).0;
1482    let style = if raw > 1.0 {
1483        TextStyle::red()
1484    } else {
1485        TextStyle::PLAIN
1486    };
1487    Some(DisplayField::new(
1488        raw,
1489        styled_colored(format!("{raw:.4e}"), style),
1490    ))
1491}
1492
1493fn contribution_display_field(
1494    contribution: ContributionKind,
1495    integration_state: &IntegrationState,
1496) -> DisplayField<ContributionKind> {
1497    let display = match contribution {
1498        ContributionKind::All => styled_colored("All", TextStyle::green().bold()),
1499        ContributionKind::Sum => styled_colored("Sum", TextStyle::green().bold()),
1500        ContributionKind::Bin(bin_index) => {
1501            if let (Some(axis_label), Some(descriptions)) = (
1502                integration_state
1503                    .first_non_trivial_discrete_label
1504                    .as_deref(),
1505                integration_state
1506                    .first_non_trivial_discrete_bin_descriptions
1507                    .as_ref(),
1508            ) && let Some(description) = descriptions.get(bin_index)
1509            {
1510                let mut text =
1511                    StyledText::plain(format_discrete_bin_prefix(bin_index, descriptions.len()));
1512                text.append(styled_bin_description(axis_label, description));
1513                text
1514            } else {
1515                styled_plain(format!("#{bin_index}"))
1516            }
1517        }
1518    };
1519    DisplayField::new(contribution, display)
1520}
1521
1522pub(crate) fn format_discrete_bin_prefix(bin_index: usize, bin_count: usize) -> String {
1523    let width = if bin_count < 100 {
1524        4
1525    } else if bin_count < 1_000 {
1526        5
1527    } else {
1528        6
1529    };
1530    format!("{:<width$}", format!("#{bin_index}:"))
1531}
1532
1533fn contribution_header(
1534    integration_state: &IntegrationState,
1535    discrete_monitoring_enabled: bool,
1536) -> StyledText {
1537    if discrete_monitoring_enabled
1538        && let Some(label) = integration_state
1539            .first_non_trivial_discrete_label
1540            .as_deref()
1541    {
1542        let mut header = StyledText::styled("Contribution", TextStyle::blue().bold());
1543        header.push_text("\n(idx=", TextStyle::PLAIN);
1544        header.push_text(label, TextStyle::blue().bold());
1545        header.push_text(")", TextStyle::PLAIN);
1546        return header;
1547    }
1548
1549    styled_colored("Contribution", TextStyle::blue().bold())
1550}
1551
1552fn header_left(
1553    elapsed_time: Duration,
1554    iter: usize,
1555    live_progress: Option<LiveIterationProgress>,
1556) -> StyledText {
1557    let mut text = StyledText::plain("[ ");
1558    text.push_text(
1559        format!(
1560            "{:^7}",
1561            utils::format_wdhms(elapsed_time.as_secs() as usize)
1562        ),
1563        TextStyle::PLAIN.bold(),
1564    );
1565    text.push_text(" ] ", TextStyle::PLAIN);
1566    let iteration_label = if live_progress.is_some() {
1567        format!("Iteration #{:-4} ( running   )", iter)
1568    } else {
1569        format!("Iteration #{:-4} ( completed )", iter)
1570    };
1571    text.push_text(iteration_label, TextStyle::green().bold());
1572    text
1573}
1574
1575fn header_middle(
1576    cur_points: usize,
1577    total_points: usize,
1578    live_progress: Option<LiveIterationProgress>,
1579) -> StyledText {
1580    let mut text = StyledText::new();
1581    if let Some(progress) = live_progress {
1582        text.push_text(
1583            format!(
1584                "Iteration progress {}/{} ",
1585                format_iteration_points(progress.completed_points),
1586                format_iteration_points(progress.target_points),
1587            ),
1588            TextStyle::PLAIN,
1589        );
1590        let percentage = if progress.target_points == 0 {
1591            String::from("0.0%")
1592        } else {
1593            format!(
1594                "{:.1}%",
1595                (progress.completed_points as f64) / (progress.target_points as f64) * 100.0
1596            )
1597        };
1598        text.push_text(percentage, TextStyle::green());
1599        text.push_text(" ", TextStyle::PLAIN);
1600    } else if cur_points > 0 {
1601        text.push_text(
1602            format!(
1603                "# samples per iteration = {} ",
1604                format_iteration_points(cur_points)
1605            ),
1606            TextStyle::blue().bold(),
1607        );
1608    }
1609    text.push_text(
1610        format!("# samples total = {}", format_total_points(total_points)),
1611        TextStyle::green().bold(),
1612    );
1613    text
1614}
1615
1616fn header_tail(cores: usize, elapsed_time: Duration, n_samples_evaluated: usize) -> StyledText {
1617    let mut text = StyledText::new();
1618    if n_samples_evaluated == 0 {
1619        text.push_text("N/A /sample/core", TextStyle::red());
1620    } else {
1621        text.push_text(
1622            format!(
1623                "{} /sample/core",
1624                utils::format_evaluation_time_from_f64(
1625                    elapsed_time.as_secs_f64() / (n_samples_evaluated as f64) * (cores as f64),
1626                )
1627            ),
1628            TextStyle::green().bold(),
1629        );
1630    }
1631    text.push_text(" ", TextStyle::PLAIN);
1632    text.push_text(format!("({cores} cores)"), TextStyle::blue().bold());
1633    text
1634}
1635
1636fn main_results_row(
1637    integration_state: &IntegrationState,
1638    targets: &[Option<Complex<F<f64>>>],
1639    monitored_path: Option<&[usize]>,
1640    contribution: ContributionKind,
1641    component: ComponentKind,
1642    has_discrete_columns: bool,
1643) -> Option<MainResultsRow> {
1644    let slot_cells = integration_state
1645        .slot_metas
1646        .iter()
1647        .enumerate()
1648        .map(|(slot_index, _)| match contribution {
1649            ContributionKind::All => {
1650                let integral = &integration_state.all_integrals[slot_index];
1651                let signed = component_accumulator(integral, component, IntegralView::Signed);
1652                let absolute = component_accumulator(integral, component, IntegralView::Absolute);
1653                Some(MainTableSlotCells {
1654                    signed: IntegralStatisticsCells {
1655                        value: Some(format_value_field(signed.avg, signed.err)),
1656                        relative_error: format_relative_error_field(signed),
1657                        chi_sq: format_chi_sq_field(signed, integration_state.iter),
1658                        max_weight_impact: format_mwi_field(signed),
1659                    },
1660                    absolute: IntegralStatisticsCells {
1661                        value: Some(format_value_field(absolute.avg, absolute.err)),
1662                        relative_error: format_relative_error_field(absolute),
1663                        chi_sq: format_chi_sq_field(absolute, integration_state.iter),
1664                        max_weight_impact: format_mwi_field(absolute),
1665                    },
1666                    sample_fraction: None,
1667                    sample_count: None,
1668                    target_pdf: None,
1669                })
1670            }
1671            ContributionKind::Sum => {
1672                let monitored_path = monitored_path?;
1673                let signed_summary = slot_component_summary(
1674                    integration_state,
1675                    slot_index,
1676                    component,
1677                    IntegralView::Signed,
1678                    monitored_path,
1679                )?;
1680                let absolute_summary = slot_component_summary(
1681                    integration_state,
1682                    slot_index,
1683                    component,
1684                    IntegralView::Absolute,
1685                    monitored_path,
1686                )?;
1687                let (signed_avg, signed_err) = sum_estimate_error(signed_summary);
1688                let (absolute_avg, absolute_err) = sum_estimate_error(absolute_summary);
1689                Some(MainTableSlotCells {
1690                    signed: IntegralStatisticsCells {
1691                        value: Some(format_value_field(signed_avg, signed_err)),
1692                        relative_error: format_relative_error_field_from_estimate(
1693                            signed_avg, signed_err,
1694                        ),
1695                        chi_sq: None,
1696                        max_weight_impact: None,
1697                    },
1698                    absolute: IntegralStatisticsCells {
1699                        value: Some(format_value_field(absolute_avg, absolute_err)),
1700                        relative_error: format_relative_error_field_from_estimate(
1701                            absolute_avg,
1702                            absolute_err,
1703                        ),
1704                        chi_sq: None,
1705                        max_weight_impact: None,
1706                    },
1707                    sample_fraction: None,
1708                    sample_count: None,
1709                    target_pdf: None,
1710                })
1711            }
1712            ContributionKind::Bin(bin_index) => {
1713                let slot_context =
1714                    integration_state.monitored_discrete_context_for_slot(slot_index);
1715                let monitored_path = monitored_path?;
1716                let signed_summary = slot_component_summary(
1717                    integration_state,
1718                    slot_index,
1719                    component,
1720                    IntegralView::Signed,
1721                    monitored_path,
1722                )?;
1723                let absolute_summary = slot_component_summary(
1724                    integration_state,
1725                    slot_index,
1726                    component,
1727                    IntegralView::Absolute,
1728                    monitored_path,
1729                )?;
1730                let signed_bin = signed_summary.bins.get(bin_index)?;
1731                let absolute_bin = absolute_summary.bins.get(bin_index)?;
1732                let total_samples = total_processed_samples(signed_summary);
1733                let sample_fraction = if has_discrete_columns && total_samples > 0 {
1734                    let raw = signed_bin.accumulator.processed_samples as f64
1735                        / total_samples as f64
1736                        * 100.0;
1737                    Some(DisplayField::new(
1738                        raw,
1739                        styled_colored(
1740                            format_significant_percentage(raw, 3, None),
1741                            TextStyle::blue(),
1742                        ),
1743                    ))
1744                } else {
1745                    None
1746                };
1747                let sample_count = if has_discrete_columns {
1748                    Some(DisplayField::new(
1749                        signed_bin.accumulator.processed_samples,
1750                        styled_colored(
1751                            format_abbreviated_count(signed_bin.accumulator.processed_samples),
1752                            TextStyle::blue(),
1753                        ),
1754                    ))
1755                } else {
1756                    None
1757                };
1758                let target_pdf = if has_discrete_columns {
1759                    slot_context
1760                        .as_ref()
1761                        .and_then(|ctx| ctx.pdfs.get(bin_index).copied())
1762                        .map(|pdf| {
1763                            DisplayField::new(
1764                                pdf.0 * 100.0,
1765                                styled_plain(format_significant_percentage(pdf.0 * 100.0, 3, None)),
1766                            )
1767                        })
1768                } else {
1769                    None
1770                };
1771                Some(MainTableSlotCells {
1772                    signed: IntegralStatisticsCells {
1773                        value: Some(format_value_field(
1774                            signed_bin.accumulator.avg,
1775                            signed_bin.accumulator.err,
1776                        )),
1777                        relative_error: format_relative_error_field(&signed_bin.accumulator),
1778                        chi_sq: format_chi_sq_field(
1779                            &signed_bin.accumulator,
1780                            integration_state.iter,
1781                        ),
1782                        max_weight_impact: format_mwi_field(&signed_bin.accumulator),
1783                    },
1784                    absolute: IntegralStatisticsCells {
1785                        value: Some(format_value_field(
1786                            absolute_bin.accumulator.avg,
1787                            absolute_bin.accumulator.err,
1788                        )),
1789                        relative_error: format_relative_error_field(&absolute_bin.accumulator),
1790                        chi_sq: format_chi_sq_field(
1791                            &absolute_bin.accumulator,
1792                            integration_state.iter,
1793                        ),
1794                        max_weight_impact: format_mwi_field(&absolute_bin.accumulator),
1795                    },
1796                    sample_fraction,
1797                    sample_count,
1798                    target_pdf,
1799                })
1800            }
1801        })
1802        .collect::<Option<Vec<_>>>()?;
1803
1804    let slot0_target = targets
1805        .first()
1806        .and_then(|target| target.as_ref())
1807        .map(|target| match component {
1808            ComponentKind::Real => target.re,
1809            ComponentKind::Imag => target.im,
1810        });
1811    let (delta_sigma, delta_percent) = match contribution {
1812        ContributionKind::All | ContributionKind::Sum => slot_cells
1813            .first()
1814            .and_then(|cell| cell.statistics(IntegralView::Signed).value.as_ref())
1815            .map(|value| format_delta_fields_from_estimate(value.raw.0, value.raw.1, slot0_target))
1816            .unwrap_or((None, None)),
1817        ContributionKind::Bin(_) => (None, None),
1818    };
1819
1820    Some(MainResultsRow {
1821        contribution: contribution_display_field(contribution, integration_state),
1822        component: component.display_field(),
1823        slot_cells,
1824        delta_sigma,
1825        delta_percent,
1826    })
1827}
1828
1829fn discrete_sort_key(
1830    integration_state: &IntegrationState,
1831    monitored_path: &[usize],
1832    component: ComponentKind,
1833    bin_index: usize,
1834    sort_mode: ContributionSortMode,
1835) -> f64 {
1836    let Some(summary) = slot_component_summary(
1837        integration_state,
1838        0,
1839        component,
1840        IntegralView::Signed,
1841        monitored_path,
1842    ) else {
1843        return 0.0;
1844    };
1845    let Some(bin) = summary.bins.get(bin_index) else {
1846        return 0.0;
1847    };
1848    match sort_mode {
1849        ContributionSortMode::Integral => bin.accumulator.avg.abs().0,
1850        ContributionSortMode::Error => bin.accumulator.err.0,
1851        ContributionSortMode::Index => bin_index as f64,
1852    }
1853}
1854
1855fn build_main_results_section(request: &StatusUpdateBuildRequest<'_>) -> MainResultsSection {
1856    let components = ComponentKind::all_for_display(request.render_options.phase_display);
1857    let discrete_context = request.integration_state.monitored_discrete_context();
1858    let monitored_path = request.integration_state.monitored_discrete_path.as_deref();
1859    let has_discrete_columns = monitored_path.is_some();
1860    let has_target_columns = request.targets.first().is_some_and(Option::is_some);
1861
1862    let mut row_groups = vec![MainResultsRowGroup {
1863        kind: MainResultsRowGroupKind::All,
1864        rows: components
1865            .iter()
1866            .filter_map(|component| {
1867                main_results_row(
1868                    request.integration_state,
1869                    request.targets,
1870                    monitored_path,
1871                    ContributionKind::All,
1872                    *component,
1873                    has_discrete_columns,
1874                )
1875            })
1876            .collect_vec(),
1877    }];
1878
1879    if let Some(discrete_context) = discrete_context.as_ref() {
1880        let sum_rows = components
1881            .iter()
1882            .filter_map(|component| {
1883                main_results_row(
1884                    request.integration_state,
1885                    request.targets,
1886                    monitored_path,
1887                    ContributionKind::Sum,
1888                    *component,
1889                    has_discrete_columns,
1890                )
1891            })
1892            .collect_vec();
1893        if !sum_rows.is_empty() {
1894            row_groups.push(MainResultsRowGroup {
1895                kind: MainResultsRowGroupKind::Sum,
1896                rows: sum_rows,
1897            });
1898        }
1899
1900        let bin_count = discrete_context.pdfs.len();
1901        match request.render_options.contribution_sort {
1902            ContributionSortMode::Index => {
1903                let mut rows = Vec::new();
1904                for bin_index in 0..bin_count {
1905                    for component in &components {
1906                        if let Some(row) = main_results_row(
1907                            request.integration_state,
1908                            request.targets,
1909                            monitored_path,
1910                            ContributionKind::Bin(bin_index),
1911                            *component,
1912                            has_discrete_columns,
1913                        ) {
1914                            rows.push(row);
1915                        }
1916                    }
1917                }
1918                if !rows.is_empty() {
1919                    row_groups.push(MainResultsRowGroup {
1920                        kind: MainResultsRowGroupKind::Bins,
1921                        rows,
1922                    });
1923                }
1924            }
1925            ContributionSortMode::Integral | ContributionSortMode::Error => {
1926                for component in &components {
1927                    let mut bin_indices = (0..bin_count).collect_vec();
1928                    bin_indices.sort_by(|lhs, rhs| {
1929                        discrete_sort_key(
1930                            request.integration_state,
1931                            &discrete_context.path,
1932                            *component,
1933                            *rhs,
1934                            request.render_options.contribution_sort,
1935                        )
1936                        .partial_cmp(&discrete_sort_key(
1937                            request.integration_state,
1938                            &discrete_context.path,
1939                            *component,
1940                            *lhs,
1941                            request.render_options.contribution_sort,
1942                        ))
1943                        .unwrap_or(std::cmp::Ordering::Equal)
1944                    });
1945                    let rows = bin_indices
1946                        .into_iter()
1947                        .filter_map(|bin_index| {
1948                            main_results_row(
1949                                request.integration_state,
1950                                request.targets,
1951                                monitored_path,
1952                                ContributionKind::Bin(bin_index),
1953                                *component,
1954                                has_discrete_columns,
1955                            )
1956                        })
1957                        .collect_vec();
1958                    if !rows.is_empty() {
1959                        row_groups.push(MainResultsRowGroup {
1960                            kind: MainResultsRowGroupKind::Bins,
1961                            rows,
1962                        });
1963                    }
1964                }
1965            }
1966        }
1967    }
1968
1969    MainResultsSection {
1970        header_left: header_left(
1971            request.elapsed_time,
1972            request.integration_state.iter,
1973            request.live_progress,
1974        ),
1975        header_middle: header_middle(
1976            request.cur_points,
1977            request.total_points_display,
1978            request.live_progress,
1979        ),
1980        header_tail: header_tail(
1981            request.cores,
1982            request.elapsed_time,
1983            request.n_samples_evaluated,
1984        ),
1985        contribution_header: contribution_header(request.integration_state, has_discrete_columns),
1986        slot_headers: request
1987            .integration_state
1988            .slot_metas
1989            .iter()
1990            .map(|slot_meta| styled_colored(slot_meta.key(), TextStyle::blue().bold()))
1991            .collect(),
1992        has_discrete_columns,
1993        has_target_columns,
1994        row_groups,
1995    }
1996}
1997
1998fn max_weight_row_descriptors(
1999    phase_display: IntegrationStatusPhaseDisplay,
2000    view: IntegralView,
2001) -> Vec<(ComponentKind, Option<&'static str>, bool)> {
2002    let mut rows = Vec::new();
2003    if phase_display.shows_real() {
2004        match view {
2005            IntegralView::Signed => {
2006                rows.push((ComponentKind::Real, Some("+"), true));
2007                rows.push((ComponentKind::Real, Some("-"), false));
2008            }
2009            IntegralView::Absolute => rows.push((ComponentKind::Real, None, true)),
2010        }
2011    }
2012    if phase_display.shows_imag() {
2013        match view {
2014            IntegralView::Signed => {
2015                rows.push((ComponentKind::Imag, Some("+"), true));
2016                rows.push((ComponentKind::Imag, Some("-"), false));
2017            }
2018            IntegralView::Absolute => rows.push((ComponentKind::Imag, None, true)),
2019        }
2020    }
2021    rows
2022}
2023
2024fn styled_component_sign(
2025    component: ComponentKind,
2026    sign: Option<&'static str>,
2027    positive: bool,
2028    view: IntegralView,
2029) -> DisplayField<(ComponentKind, bool)> {
2030    let mut display = StyledText::new();
2031    display.append(component.label_display_for_view(view));
2032    if let Some(sign) = sign {
2033        display.push_text(" [", TextStyle::PLAIN);
2034        display.push_text(sign, TextStyle::blue());
2035        display.push_text("]", TextStyle::PLAIN);
2036    }
2037    DisplayField::new((component, positive), display)
2038}
2039
2040fn build_max_weight_details_section(
2041    integration_state: &IntegrationState,
2042    render_options: &IntegrationStatusViewOptions,
2043    view: IntegralView,
2044) -> Option<MaxWeightDetailsSection> {
2045    let rows_by_slot = integration_state
2046        .slot_metas
2047        .iter()
2048        .enumerate()
2049        .zip(integration_state.all_integrals.iter())
2050        .filter_map(|((slot_index, slot_meta), integral)| {
2051            let rows = max_weight_row_descriptors(render_options.phase_display, view)
2052                .into_iter()
2053                .filter_map(|(component, sign, positive)| {
2054                    let accumulator = component_accumulator(integral, component, view);
2055                    let (value, coordinates) = max_eval_entry(accumulator, positive)?;
2056                    let coordinates = coordinates
2057                        .map(|sample| {
2058                            format_max_eval_sample(
2059                                sample,
2060                                &integration_state
2061                                    .sampling_state_for_slot(slot_index)
2062                                    .discrete_axis_labels,
2063                                &[],
2064                            )
2065                        })
2066                        .unwrap_or_else(|| "N/A".to_string());
2067                    Some(MaxWeightDetailsRow {
2068                        slot: DisplayField::new(
2069                            slot_meta.key(),
2070                            styled_colored(slot_meta.key(), TextStyle::green()),
2071                        ),
2072                        component_sign: styled_component_sign(component, sign, positive, view),
2073                        max_eval: DisplayField::new(
2074                            value,
2075                            styled_plain(match view {
2076                                IntegralView::Signed => format!("{:+.16e}", value),
2077                                IntegralView::Absolute => format!("{:.16e}", value),
2078                            }),
2079                        ),
2080                        coordinates: DisplayField::new(
2081                            coordinates.clone(),
2082                            styled_plain(coordinates),
2083                        ),
2084                    })
2085                })
2086                .collect_vec();
2087            (!rows.is_empty()).then_some(rows)
2088        })
2089        .collect_vec();
2090
2091    if rows_by_slot.is_empty() {
2092        None
2093    } else {
2094        Some(MaxWeightDetailsSection { rows_by_slot })
2095    }
2096}
2097
2098fn build_discrete_max_weight_details_section(
2099    integration_state: &IntegrationState,
2100    render_options: &IntegrationStatusViewOptions,
2101    view: IntegralView,
2102) -> Option<DiscreteMaxWeightDetailsSection> {
2103    let discrete_context = integration_state.monitored_discrete_context()?;
2104    let contributions = std::iter::once(ContributionKind::All)
2105        .chain((0..discrete_context.pdfs.len()).map(ContributionKind::Bin))
2106        .collect_vec();
2107
2108    let mut row_groups = Vec::new();
2109    for contribution in contributions {
2110        let mut rows = Vec::new();
2111        for (component, sign, positive) in
2112            max_weight_row_descriptors(render_options.phase_display, view)
2113        {
2114            let slot_values = integration_state
2115                .slot_metas
2116                .iter()
2117                .enumerate()
2118                .map(|(slot_index, _)| {
2119                    let accumulator = match contribution {
2120                        ContributionKind::All => component_accumulator(
2121                            &integration_state.all_integrals[slot_index],
2122                            component,
2123                            view,
2124                        ),
2125                        ContributionKind::Bin(bin_index) => {
2126                            let summary = slot_component_summary(
2127                                integration_state,
2128                                slot_index,
2129                                component,
2130                                view,
2131                                &discrete_context.path,
2132                            )?;
2133                            &summary.bins.get(bin_index)?.accumulator
2134                        }
2135                        ContributionKind::Sum => return None,
2136                    };
2137                    Some(max_eval_entry(accumulator, positive).map(|(value, _)| {
2138                        DisplayField::new(
2139                            value,
2140                            styled_plain(match view {
2141                                IntegralView::Signed => format!("{:+.16e}", value),
2142                                IntegralView::Absolute => format!("{:.16e}", value),
2143                            }),
2144                        )
2145                    }))
2146                })
2147                .collect::<Option<Vec<_>>>()?;
2148
2149            let slot_coordinates = integration_state
2150                .slot_metas
2151                .iter()
2152                .enumerate()
2153                .filter_map(|(slot_index, slot_meta)| {
2154                    let coordinates = match contribution {
2155                        ContributionKind::All => {
2156                            let accumulator = component_accumulator(
2157                                &integration_state.all_integrals[slot_index],
2158                                component,
2159                                view,
2160                            );
2161                            max_eval_entry(accumulator, positive)
2162                                .and_then(|(_, sample)| sample)
2163                                .map(|sample| {
2164                                    format_max_eval_sample(
2165                                        sample,
2166                                        &integration_state
2167                                            .sampling_state_for_slot(slot_index)
2168                                            .discrete_axis_labels,
2169                                        &[],
2170                                    )
2171                                })
2172                        }
2173                        ContributionKind::Bin(bin_index) => {
2174                            let slot_context = integration_state
2175                                .monitored_discrete_context_for_slot(slot_index)?;
2176                            let summary = slot_component_summary(
2177                                integration_state,
2178                                slot_index,
2179                                component,
2180                                view,
2181                                &slot_context.path,
2182                            )?;
2183                            max_eval_entry(&summary.bins.get(bin_index)?.accumulator, positive)
2184                                .and_then(|(_, sample)| sample)
2185                                .map(|sample| {
2186                                    format_max_eval_sample(
2187                                        sample,
2188                                        &integration_state
2189                                            .sampling_state_for_slot(slot_index)
2190                                            .discrete_axis_labels,
2191                                        &slot_context.path,
2192                                    )
2193                                })
2194                        }
2195                        ContributionKind::Sum => None,
2196                    }?;
2197
2198                    Some(SlotCoordinateEntry {
2199                        slot: DisplayField::new(
2200                            slot_meta.key(),
2201                            styled_colored(slot_meta.key(), TextStyle::green()),
2202                        ),
2203                        coordinates: DisplayField::new(
2204                            coordinates.clone(),
2205                            styled_plain(coordinates),
2206                        ),
2207                    })
2208                })
2209                .collect_vec();
2210
2211            if slot_values.iter().all(|value| value.is_none()) && slot_coordinates.is_empty() {
2212                continue;
2213            }
2214
2215            rows.push(DiscreteMaxWeightRow {
2216                contribution: contribution_display_field(contribution, integration_state),
2217                component_sign: styled_component_sign(component, sign, positive, view),
2218                slot_values,
2219                slot_coordinates,
2220            });
2221        }
2222        if !rows.is_empty() {
2223            row_groups.push(rows);
2224        }
2225    }
2226
2227    if row_groups.is_empty() {
2228        return None;
2229    }
2230
2231    Some(DiscreteMaxWeightDetailsSection {
2232        contribution_header: contribution_header(integration_state, true),
2233        slot_headers: integration_state
2234            .slot_metas
2235            .iter()
2236            .map(|slot_meta| styled_colored(slot_meta.key(), TextStyle::blue().bold()))
2237            .collect(),
2238        row_groups,
2239    })
2240}
2241
2242pub(crate) fn build_status_update(request: StatusUpdateBuildRequest<'_>) -> StatusUpdate {
2243    let target_accuracy_status = evaluate_target_accuracy(
2244        request.integration_state,
2245        request.total_points_display,
2246        request.elapsed_time,
2247        request.targets,
2248        request.render_options.training_phase_display,
2249        request.render_options.target_relative_accuracy,
2250        request.render_options.target_absolute_accuracy,
2251    );
2252    StatusUpdate {
2253        kind: request.kind,
2254        meta: StatusMeta {
2255            elapsed_time: request.elapsed_time,
2256            iteration_elapsed_time: request.iteration_elapsed_time,
2257            iteration: request.integration_state.iter,
2258            current_iteration_points: request.cur_points,
2259            total_points: request.total_points_display,
2260            n_samples_evaluated: request.n_samples_evaluated,
2261            cores: request.cores,
2262            training_slot: request.render_options.training_slot,
2263            training_phase_display: request.render_options.training_phase_display,
2264            live_progress: request.live_progress,
2265            show_eta_to_target: request.render_options.target_relative_accuracy.is_some()
2266                || request.render_options.target_absolute_accuracy.is_some(),
2267            eta_to_target_specification: format_eta_to_target_specification(
2268                request.render_options.target_relative_accuracy,
2269                request.render_options.target_absolute_accuracy,
2270            ),
2271            eta_to_target: target_accuracy_status.eta_to_target,
2272        },
2273        targets: request.targets.to_vec(),
2274        slot_training_phase_displays: request.render_options.slot_training_phase_displays.clone(),
2275        per_slot_training_phase: request.render_options.per_slot_training_phase,
2276        main_results: build_main_results_section(&request),
2277        max_weight_details: build_max_weight_details_section(
2278            request.integration_state,
2279            request.render_options,
2280            IntegralView::Signed,
2281        ),
2282        absolute_max_weight_details: build_max_weight_details_section(
2283            request.integration_state,
2284            request.render_options,
2285            IntegralView::Absolute,
2286        ),
2287        discrete_max_weight_details: build_discrete_max_weight_details_section(
2288            request.integration_state,
2289            request.render_options,
2290            IntegralView::Signed,
2291        ),
2292        absolute_discrete_max_weight_details: build_discrete_max_weight_details_section(
2293            request.integration_state,
2294            request.render_options,
2295            IntegralView::Absolute,
2296        ),
2297        statistics: Some(StatisticsSection {
2298            global: request.integration_state.stats.clone(),
2299            slot_counters: request.integration_state.slot_stats.clone(),
2300            slot_labels: request
2301                .integration_state
2302                .slot_metas
2303                .iter()
2304                .map(|slot_meta| slot_meta.key())
2305                .collect(),
2306        }),
2307    }
2308}
2309
2310pub(crate) fn build_saved_status_update(
2311    integration_state: &IntegrationState,
2312    targets: &[Option<Complex<F<f64>>>],
2313    render_options: &IntegrationStatusViewOptions,
2314) -> StatusUpdate {
2315    let final_render_options = render_options.clone().for_final();
2316    build_status_update(StatusUpdateBuildRequest {
2317        kind: IntegrationStatusKind::Final,
2318        integration_state,
2319        cores: integration_state.n_cores.max(1),
2320        elapsed_time: utils::duration_from_secs_f64_saturating(integration_state.elapsed_seconds),
2321        iteration_elapsed_time: Duration::ZERO,
2322        cur_points: 0,
2323        total_points_display: integration_state.num_points,
2324        n_samples_evaluated: integration_state.num_points,
2325        targets,
2326        render_options: &final_render_options,
2327        live_progress: None,
2328    })
2329}