Coverage for src / monte_neo / monte_carlo / utils.py: 82%
22 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
1"""Utility functions for Monte Carlo simulations."""
3from __future__ import annotations
5import numpy as np
8def summarize_metrics(results: list[dict]) -> dict:
9 """Summarize metrics across all scenarios.
11 Args:
12 results: List of scenario results.
14 Returns:
15 Summary statistics.
16 """
17 if not results:
18 return {}
20 # Collect all metric values
21 metric_values: dict[str, list] = {}
22 for result in results:
23 for name, value in result.get("metrics", {}).items():
24 if name not in metric_values:
25 metric_values[name] = []
26 metric_values[name].append(value)
28 # Calculate summary stats
29 summary = {}
30 for name, values in metric_values.items():
31 arr = np.array(values, dtype=float)
33 # Handle infinite values which cause warnings in std calculation
34 # We replace inf with nan and use nan-aware functions
35 is_inf = np.isinf(arr)
36 if np.any(is_inf):
37 arr[is_inf] = np.nan
39 # Check if we have any valid data left
40 if np.all(np.isnan(arr)):
41 summary[name] = {
42 "mean": 0.0,
43 "std": 0.0,
44 "min": 0.0,
45 "max": 0.0,
46 "median": 0.0,
47 "p5": 0.0,
48 "p95": 0.0,
49 }
50 continue
52 summary[name] = {
53 "mean": float(np.nanmean(arr)),
54 "std": float(np.nanstd(arr)),
55 "min": float(np.nanmin(arr)),
56 "max": float(np.nanmax(arr)),
57 "median": float(np.nanmedian(arr)),
58 "p5": float(np.nanpercentile(arr, 5)),
59 "p95": float(np.nanpercentile(arr, 95)),
60 }
62 return summary