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