autoemulate 0.3.2__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {autoemulate-0.3.2 → autoemulate-0.3.3}/PKG-INFO +3 -1
- autoemulate-0.3.3/autoemulate/mcmc.py +348 -0
- autoemulate-0.3.3/autoemulate/mcmc_dashboard.py +818 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/simulations/epidemic.py +30 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/simulations/projectile.py +30 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/pyproject.toml +3 -2
- {autoemulate-0.3.2 → autoemulate-0.3.3}/LICENSE +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/README.md +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/__init__.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/compare.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/cross_validate.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/data_splitting.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/datasets.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/__init__.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/conditional_neural_process.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/conditional_neural_process_attn.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/gaussian_process.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/gaussian_process_mogp.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/gaussian_process_mt.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/gaussian_process_sklearn.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/gaussian_process_utils/__init__.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/gaussian_process_utils/early_stopping_criterion.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/gaussian_process_utils/poly_mean.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/gaussian_process_utils/polynomial_features.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/gradient_boosting.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/light_gbm.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/neural_net_sk.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/neural_networks/__init__.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/neural_networks/cnp_module.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/neural_networks/cnp_module_attn.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/neural_networks/datasets.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/neural_networks/gp_module.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/neural_networks/losses.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/polynomials.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/radial_basis_functions.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/random_forest.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/emulators/support_vector_machines.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/__init__.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/compare.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/data/preprocessors.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/data/utils.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/device.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/emulators/__init__.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/emulators/base.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/emulators/gaussian_process/__init__.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/emulators/gaussian_process/exact.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/emulators/lightgbm.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/emulators/neural_processes/conditional_neural_process.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/emulators/random_forest.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/emulators/svm.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/exploratory/active_matter/README.md +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/exploratory/active_matter/active_matter.ipynb +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/learners/__init__.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/learners/base.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/learners/membership.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/learners/pool.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/learners/stream.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/model_selection.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/simulations/__init__.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/tuner.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental/types.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/experimental_design.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/history_matching.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/history_matching_dashboard.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/hyperparam_searching.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/logging_config.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/metrics.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/model_processing.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/model_registry.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/plotting.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/preprocess_target.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/printing.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/save.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/sensitivity_analysis.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/simulations/__init__.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/simulations/base.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/simulations/circ_utils.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/simulations/flow_functions.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/simulations/naghavi_cardiac_ModularCirc.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/simulations/reaction_diffusion.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/autoemulate/utils.py +0 -0
- {autoemulate-0.3.2 → autoemulate-0.3.3}/misc/AE_logo_final.png +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: autoemulate
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: A python package for semi-automated emulation
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: AutoEmulate contributors (see our GitHub page)
|
|
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Provides-Extra: docs
|
|
14
14
|
Requires-Dist: anytree (>=2.12.1,<3.0.0)
|
|
15
|
+
Requires-Dist: corner (>=2.2.3,<3.0.0)
|
|
15
16
|
Requires-Dist: gpytorch (>=1.12,<2.0)
|
|
16
17
|
Requires-Dist: iprogress (>=0.4,<0.5)
|
|
17
18
|
Requires-Dist: ipywidgets (>=8.1.2,<9.0.0)
|
|
@@ -21,6 +22,7 @@ Requires-Dist: matplotlib (>=3.7.2,<4.0.0)
|
|
|
21
22
|
Requires-Dist: mogp-emulator (>=0.7.2,<0.8.0)
|
|
22
23
|
Requires-Dist: numpy (>=1.24,<2.0)
|
|
23
24
|
Requires-Dist: pandas (>=2.1,<3.0)
|
|
25
|
+
Requires-Dist: pyro-ppl (>=1.9.1,<2.0.0)
|
|
24
26
|
Requires-Dist: salib (>=1.5.1,<2.0.0)
|
|
25
27
|
Requires-Dist: scikit-learn (>=1.3.0,<1.6.0)
|
|
26
28
|
Requires-Dist: scipy (>=1.11.3,<2.0.0)
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
from typing import List
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import pyro
|
|
9
|
+
import pyro.distributions as dist
|
|
10
|
+
import torch
|
|
11
|
+
from pyro.infer import MCMC
|
|
12
|
+
from pyro.infer import NUTS
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MCMCCalibrator:
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
emulator,
|
|
19
|
+
sensitivity_results: pd.DataFrame,
|
|
20
|
+
observations: Dict[str, Tuple[float, float]],
|
|
21
|
+
parameter_bounds: Dict[str, List[float]],
|
|
22
|
+
nroy_samples: Optional[np.ndarray] = None,
|
|
23
|
+
nroy_indices: Optional[List[int]] = None,
|
|
24
|
+
all_samples: Optional[np.ndarray] = None,
|
|
25
|
+
top_n_params: int = 5,
|
|
26
|
+
device: str = "cpu",
|
|
27
|
+
):
|
|
28
|
+
self.emulator = emulator
|
|
29
|
+
self.observations = observations
|
|
30
|
+
self.device = device
|
|
31
|
+
self.parameter_bounds = parameter_bounds
|
|
32
|
+
self.nroy_samples = nroy_samples
|
|
33
|
+
self.nroy_indices = nroy_indices
|
|
34
|
+
self.all_samples = all_samples
|
|
35
|
+
|
|
36
|
+
# Get important parameters from SA and set up bounds
|
|
37
|
+
self.important_params = self._get_important_parameters(
|
|
38
|
+
sensitivity_results, top_n_params
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
self.param_names = list(parameter_bounds.keys())
|
|
42
|
+
self.important_param_indices = [
|
|
43
|
+
self.param_names.index(p) for p in self.important_params
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
# Set up parameter bounds (refined by NROY if available)
|
|
47
|
+
self.calibration_bounds = self._setup_calibration_bounds()
|
|
48
|
+
|
|
49
|
+
print(f"Calibrating top {len(self.important_params)} parameters:")
|
|
50
|
+
print("\n".join(f" - {param}" for param in self.important_params))
|
|
51
|
+
|
|
52
|
+
def _get_important_parameters(
|
|
53
|
+
self, sensitivity_results: pd.DataFrame, top_n: int
|
|
54
|
+
) -> List[str]:
|
|
55
|
+
"""Get top parameters based on total Sobol indices."""
|
|
56
|
+
st_results = sensitivity_results[sensitivity_results["index"] == "ST"]
|
|
57
|
+
return (
|
|
58
|
+
st_results.groupby("parameter")["value"]
|
|
59
|
+
.mean()
|
|
60
|
+
.nlargest(top_n)
|
|
61
|
+
.index.tolist()
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def _setup_calibration_bounds(self) -> Dict[str, List[float]]:
|
|
65
|
+
"""Set up calibration bounds, refining with NROY samples if available."""
|
|
66
|
+
if not self._has_valid_nroy_samples():
|
|
67
|
+
print("No valid NROY samples. Using original parameter bounds.")
|
|
68
|
+
return {p: self.parameter_bounds[p] for p in self.important_params}
|
|
69
|
+
|
|
70
|
+
print(f"Using {len(self.nroy_samples)} NROY samples to refine bounds")
|
|
71
|
+
return self._refine_bounds_from_nroy()
|
|
72
|
+
|
|
73
|
+
def _has_valid_nroy_samples(self) -> bool:
|
|
74
|
+
"""Check if NROY samples are valid and usable."""
|
|
75
|
+
if self.nroy_samples is None:
|
|
76
|
+
return False
|
|
77
|
+
if len(self.nroy_samples) == 0:
|
|
78
|
+
print("Warning: Empty NROY samples provided.")
|
|
79
|
+
return False
|
|
80
|
+
if self.nroy_samples.shape[1] != len(self.parameter_bounds):
|
|
81
|
+
print(
|
|
82
|
+
f"Warning: NROY samples has {self.nroy_samples.shape[1]} parameters, "
|
|
83
|
+
f"but expected {len(self.parameter_bounds)}."
|
|
84
|
+
)
|
|
85
|
+
return False
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
def _refine_bounds_from_nroy(self) -> Dict[str, List[float]]:
|
|
89
|
+
"""Refine parameter bounds using NROY samples."""
|
|
90
|
+
refined_bounds = {}
|
|
91
|
+
|
|
92
|
+
for i, param_name in enumerate(self.param_names):
|
|
93
|
+
if param_name not in self.important_params:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
nroy_values = self.nroy_samples[:, i]
|
|
98
|
+
if len(nroy_values) == 0:
|
|
99
|
+
raise ValueError("No NROY values")
|
|
100
|
+
|
|
101
|
+
# Calculate refined bounds with buffer
|
|
102
|
+
min_val = max(np.min(nroy_values), self.parameter_bounds[param_name][0])
|
|
103
|
+
max_val = min(np.max(nroy_values), self.parameter_bounds[param_name][1])
|
|
104
|
+
|
|
105
|
+
if min_val >= max_val:
|
|
106
|
+
raise ValueError("Degenerate bounds")
|
|
107
|
+
|
|
108
|
+
buffer = (max_val - min_val) * 0.05
|
|
109
|
+
refined_bounds[param_name] = [
|
|
110
|
+
max(min_val - buffer, self.parameter_bounds[param_name][0]),
|
|
111
|
+
min(max_val + buffer, self.parameter_bounds[param_name][1]),
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
print(
|
|
115
|
+
f"Parameter {param_name}: {self.parameter_bounds[param_name]} -> {refined_bounds[param_name]}"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
print(
|
|
120
|
+
f"Error refining bounds for {param_name}: {e}. Using original bounds."
|
|
121
|
+
)
|
|
122
|
+
refined_bounds[param_name] = self.parameter_bounds[param_name]
|
|
123
|
+
|
|
124
|
+
return refined_bounds
|
|
125
|
+
|
|
126
|
+
def _get_initial_values(self) -> Optional[Dict[str, torch.Tensor]]:
|
|
127
|
+
"""Get initial MCMC values, preferring NROY samples if available."""
|
|
128
|
+
if not self._has_valid_nroy_samples():
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
# Sample from NROY points
|
|
133
|
+
sample_idx = np.random.choice(len(self.nroy_samples))
|
|
134
|
+
selected_sample = self.nroy_samples[sample_idx]
|
|
135
|
+
|
|
136
|
+
init_values = {}
|
|
137
|
+
for param_name in self.important_params:
|
|
138
|
+
param_idx = self.param_names.index(param_name)
|
|
139
|
+
# Convert to tensor and ensure it's on the correct device
|
|
140
|
+
init_values[param_name] = torch.tensor(
|
|
141
|
+
float(selected_sample[param_idx]),
|
|
142
|
+
device=self.device,
|
|
143
|
+
dtype=torch.float32,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return init_values
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
print(
|
|
150
|
+
f"Error getting NROY initial values: {e}. Using default initialization."
|
|
151
|
+
)
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
def _create_full_params(self, reduced_params: torch.Tensor) -> torch.Tensor:
|
|
155
|
+
"""Create full parameter vector with defaults for non-calibrated params."""
|
|
156
|
+
full_params = torch.zeros(len(self.parameter_bounds), device=self.device)
|
|
157
|
+
|
|
158
|
+
# Set calibrated parameters
|
|
159
|
+
for i, param_name in enumerate(self.important_params):
|
|
160
|
+
param_idx = self.param_names.index(param_name)
|
|
161
|
+
full_params[param_idx] = reduced_params[i]
|
|
162
|
+
|
|
163
|
+
# Set non-calibrated parameters to midpoint
|
|
164
|
+
for i, param_name in enumerate(self.param_names):
|
|
165
|
+
if param_name not in self.important_params:
|
|
166
|
+
bounds = self.parameter_bounds[param_name]
|
|
167
|
+
full_params[i] = (bounds[0] + bounds[1]) / 2
|
|
168
|
+
|
|
169
|
+
return full_params
|
|
170
|
+
|
|
171
|
+
def model(self):
|
|
172
|
+
"""Pyro model for MCMC calibration."""
|
|
173
|
+
reduced_params = []
|
|
174
|
+
|
|
175
|
+
for param_name in self.important_params:
|
|
176
|
+
bounds = self.calibration_bounds[param_name]
|
|
177
|
+
param_value = pyro.sample(
|
|
178
|
+
param_name,
|
|
179
|
+
dist.Uniform(
|
|
180
|
+
torch.tensor(bounds[0], device=self.device),
|
|
181
|
+
torch.tensor(bounds[1], device=self.device),
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
reduced_params.append(param_value)
|
|
185
|
+
|
|
186
|
+
# Create full parameter vector and get predictions
|
|
187
|
+
reduced_params_tensor = torch.stack(reduced_params)
|
|
188
|
+
full_params = self._create_full_params(reduced_params_tensor)
|
|
189
|
+
|
|
190
|
+
with torch.no_grad():
|
|
191
|
+
pred_mean = torch.tensor(
|
|
192
|
+
self.emulator.predict(
|
|
193
|
+
full_params.cpu().numpy().reshape(1, -1)
|
|
194
|
+
).flatten(),
|
|
195
|
+
device=self.device,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# setup likelihood from observations
|
|
199
|
+
|
|
200
|
+
# Pre-convert observations to tensors
|
|
201
|
+
obs_means = torch.tensor(
|
|
202
|
+
[obs[0] for obs in self.observations.values()], device=self.device
|
|
203
|
+
)
|
|
204
|
+
obs_stds = torch.tensor(
|
|
205
|
+
[obs[1] for obs in self.observations.values()], device=self.device
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# One-liner comparison
|
|
209
|
+
[
|
|
210
|
+
pyro.sample(
|
|
211
|
+
f"obs_{name}", dist.Normal(pred_mean[i], obs_stds[i]), obs=obs_means[i]
|
|
212
|
+
)
|
|
213
|
+
for i, name in enumerate(self.observations.keys())
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
def run_mcmc(
|
|
217
|
+
self,
|
|
218
|
+
num_samples: int = 1000,
|
|
219
|
+
warmup_steps: int = 500,
|
|
220
|
+
num_chains: int = 1,
|
|
221
|
+
use_nroy_init: bool = True,
|
|
222
|
+
) -> Dict:
|
|
223
|
+
"""Run MCMC sampling with optional NROY initialization."""
|
|
224
|
+
nuts_kernel = NUTS(self.model)
|
|
225
|
+
|
|
226
|
+
# Get initial parameters if requested and available
|
|
227
|
+
initial_params = None
|
|
228
|
+
if use_nroy_init:
|
|
229
|
+
initial_params = self._get_initial_values()
|
|
230
|
+
if initial_params is None:
|
|
231
|
+
print("Using default MCMC initialization.")
|
|
232
|
+
else:
|
|
233
|
+
print("Initializing MCMC chains from NROY samples...")
|
|
234
|
+
|
|
235
|
+
mcmc = MCMC(
|
|
236
|
+
nuts_kernel,
|
|
237
|
+
num_samples=num_samples,
|
|
238
|
+
warmup_steps=warmup_steps,
|
|
239
|
+
num_chains=num_chains,
|
|
240
|
+
initial_params=initial_params,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
print("Running MCMC...")
|
|
244
|
+
mcmc.run()
|
|
245
|
+
|
|
246
|
+
# Store and summarize results
|
|
247
|
+
self.mcmc_results = {
|
|
248
|
+
p: samples.cpu().numpy() for p, samples in mcmc.get_samples().items()
|
|
249
|
+
}
|
|
250
|
+
self.mcmc_summary = self._create_summary()
|
|
251
|
+
|
|
252
|
+
print("MCMC completed!\nPosterior Summary:")
|
|
253
|
+
print(self.mcmc_summary)
|
|
254
|
+
return self.mcmc_results
|
|
255
|
+
|
|
256
|
+
def _create_summary(self) -> pd.DataFrame:
|
|
257
|
+
"""Create summary statistics for MCMC results."""
|
|
258
|
+
summary_data = []
|
|
259
|
+
for param, samples in self.mcmc_results.items():
|
|
260
|
+
summary_data.append(
|
|
261
|
+
{
|
|
262
|
+
"parameter": param,
|
|
263
|
+
"mean": np.mean(samples),
|
|
264
|
+
"std": np.std(samples),
|
|
265
|
+
**{
|
|
266
|
+
f"q{p}": np.percentile(samples, p)
|
|
267
|
+
for p in [2.5, 25, 50, 75, 97.5]
|
|
268
|
+
},
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
return pd.DataFrame(summary_data).round(4)
|
|
272
|
+
|
|
273
|
+
def get_calibrated_parameters(self) -> Dict[str, float]:
|
|
274
|
+
"""Get posterior means of calibrated parameters."""
|
|
275
|
+
return {param: np.mean(samples) for param, samples in self.mcmc_results.items()}
|
|
276
|
+
|
|
277
|
+
def predict_with_uncertainty(
|
|
278
|
+
self, X_test: np.ndarray, n_posterior_samples: int = 100
|
|
279
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
280
|
+
"""Make predictions using posterior uncertainty."""
|
|
281
|
+
n_samples = len(next(iter(self.mcmc_results.values())))
|
|
282
|
+
sample_indices = np.random.choice(
|
|
283
|
+
n_samples, min(n_posterior_samples, n_samples), False
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
predictions = []
|
|
287
|
+
for sample_idx in sample_indices:
|
|
288
|
+
# Get calibrated parameters for this sample
|
|
289
|
+
calibrated_params = {
|
|
290
|
+
p: samples[sample_idx] for p, samples in self.mcmc_results.items()
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
# Make predictions for all test points
|
|
294
|
+
sample_predictions = []
|
|
295
|
+
for x_test in X_test:
|
|
296
|
+
param_vector = self._create_param_vector(x_test, calibrated_params)
|
|
297
|
+
pred = self.emulator.predict(param_vector.reshape(1, -1))
|
|
298
|
+
sample_predictions.append(pred)
|
|
299
|
+
|
|
300
|
+
predictions.append(np.vstack(sample_predictions))
|
|
301
|
+
|
|
302
|
+
predictions = np.array(predictions)
|
|
303
|
+
return np.mean(predictions, axis=0), np.std(predictions, axis=0)
|
|
304
|
+
|
|
305
|
+
def _create_param_vector(
|
|
306
|
+
self, x_test: np.ndarray, calibrated: Dict[str, float]
|
|
307
|
+
) -> np.ndarray:
|
|
308
|
+
"""Create parameter vector mixing test values and calibrated parameters."""
|
|
309
|
+
params = np.zeros(len(self.parameter_bounds))
|
|
310
|
+
|
|
311
|
+
for i, param_name in enumerate(self.param_names):
|
|
312
|
+
if param_name in calibrated:
|
|
313
|
+
params[i] = calibrated[param_name]
|
|
314
|
+
elif i < len(x_test):
|
|
315
|
+
params[i] = x_test[i]
|
|
316
|
+
else:
|
|
317
|
+
# Default to midpoint for missing parameters
|
|
318
|
+
bounds = self.parameter_bounds[param_name]
|
|
319
|
+
params[i] = (bounds[0] + bounds[1]) / 2
|
|
320
|
+
|
|
321
|
+
return params
|
|
322
|
+
|
|
323
|
+
def compare_with_nroy(self) -> Optional[pd.DataFrame]:
|
|
324
|
+
"""Compare MCMC results with NROY bounds."""
|
|
325
|
+
if not self._has_valid_nroy_samples():
|
|
326
|
+
print("No valid NROY samples available for comparison")
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
comparison_data = []
|
|
330
|
+
for param_name in self.important_params:
|
|
331
|
+
param_idx = self.param_names.index(param_name)
|
|
332
|
+
nroy_values = self.nroy_samples[:, param_idx]
|
|
333
|
+
mcmc_values = self.mcmc_results[param_name]
|
|
334
|
+
|
|
335
|
+
comparison_data.append(
|
|
336
|
+
{
|
|
337
|
+
"parameter": param_name,
|
|
338
|
+
"nroy_min": np.min(nroy_values),
|
|
339
|
+
"nroy_max": np.max(nroy_values),
|
|
340
|
+
"nroy_mean": np.mean(nroy_values),
|
|
341
|
+
"mcmc_mean": np.mean(mcmc_values),
|
|
342
|
+
"mcmc_std": np.std(mcmc_values),
|
|
343
|
+
"mcmc_q2.5": np.percentile(mcmc_values, 2.5),
|
|
344
|
+
"mcmc_q97.5": np.percentile(mcmc_values, 97.5),
|
|
345
|
+
}
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
return pd.DataFrame(comparison_data)
|