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}