Skip to content

Commit 81c1ae2

Browse files
hmonroeddCopilot
andcommitted
sensitivity analysis scripts
Co-authored-by: Copilot <copilot@github.com>
1 parent 3ef0d66 commit 81c1ae2

3 files changed

Lines changed: 605 additions & 0 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,13 @@ __marimo__/
207207

208208
.vscode/
209209
misc_scripts/
210+
training/
210211
*.yaml
211212
*.csv
212213
mwe.py
213214
*.json
214215
!standard_input.json
215216
*.ipynb
217+
!sensitivity.ipynb
216218
*.code-workspace
217219
*.pkl

sensitivity.ipynb

Lines changed: 404 additions & 0 deletions
Large diffs are not rendered by default.

sensitivity.py

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
from datetime import datetime
2+
from pathlib import Path
3+
4+
from sparging.inputs import (
5+
LIBRA_PI_GEOM,
6+
LIBRA_PI_MAT,
7+
LIBRA_PI_OPERATING_PARAMS,
8+
LIBRA_PI_SPARGING_PARAMS,
9+
SimulationInput,
10+
)
11+
from sparging.model import Simulation, SimulationResults
12+
from sparging.config import ureg
13+
import logging
14+
from dataclasses import replace
15+
16+
import matplotlib.pyplot as plt
17+
import sparging.postprocess as pp
18+
import numpy as np
19+
20+
from autoemulate.simulations.base import Simulator
21+
from autoemulate import AutoEmulate
22+
from autoemulate.core.sensitivity_analysis import SensitivityAnalysis
23+
24+
import torch
25+
import pandas as pd
26+
import json
27+
28+
29+
logger = logging.getLogger(__name__)
30+
logging.basicConfig(level=logging.WARNING)
31+
32+
FOLDER = Path("training") / datetime.now().strftime("%Y%m%d_%H%M%S")
33+
FOLDER.mkdir(exist_ok=True, parents=True)
34+
FOLDER_SAMPLES = FOLDER / "samples"
35+
FOLDER_SAMPLES.mkdir(exist_ok=True, parents=True)
36+
FOLDER_PP = FOLDER / "postprocessing"
37+
FOLDER_PP.mkdir(exist_ok=True)
38+
39+
outputs = []
40+
t_irr = 8 * ureg.hour
41+
t_sparging = 7 * ureg.day
42+
43+
librapi = SimulationInput.from_parameters(
44+
LIBRA_PI_GEOM.copy(),
45+
LIBRA_PI_MAT.copy(),
46+
LIBRA_PI_OPERATING_PARAMS.copy(),
47+
LIBRA_PI_SPARGING_PARAMS.copy(),
48+
)
49+
50+
# for i, (P, T) in enumerate(samples):
51+
# librapi = SimulationInput.from_parameters(
52+
# LIBRA_PI_GEOM.copy(),
53+
# LIBRA_PI_MAT.copy(),
54+
# replace(LIBRA_PI_OPERATING_PARAMS, temperature=T, P_top=P),
55+
# LIBRA_PI_SPARGING_PARAMS.copy(),
56+
# )
57+
58+
# tau = librapi.get_tau()
59+
# librapi.signal_irr = lambda t: 1 if t <= t_irr else 0
60+
# librapi.signal_sparging = lambda t: 0 if t <= t_irr else 1
61+
62+
# my_simulation = Simulation(
63+
# librapi,
64+
# t_final=t_irr + t_sparging,
65+
# )
66+
# print(f"Running simulation {i} with P={P}, T={T}, tau={tau.to('hour')}")
67+
# outputs.append(my_simulation.solve(dt=8 * ureg.hour))
68+
69+
# get sim results from JSON file
70+
# for i in range(len(samples)):
71+
# outputs.append(SimulationResults.from_json(f"output_{i}.json"))
72+
73+
# fig, ax = plt.subplots()
74+
# residuals = []
75+
76+
# for i in range(len(outputs)):
77+
# res = pp.get_residual_fraction(
78+
# outputs[i].inventories_T2_salt, outputs[i].times, t_irr, t_irr + 1 * ureg.week
79+
# )
80+
# residuals.append(res)
81+
# print(res)
82+
83+
84+
class SpargingProblem(Simulator):
85+
def __init__(self, parameters_range, output_names):
86+
self.counter = 0
87+
super().__init__(parameters_range, output_names)
88+
89+
def _forward(self, x: torch.Tensor) -> torch.Tensor:
90+
# construct simulation input
91+
sim_input = librapi.copy()
92+
sim_input.h_l = np.power(10, x[0, 0].item()) * ureg("m/s")
93+
sim_input.eps_g = np.power(10, x[0, 1].item()) * ureg.dimensionless
94+
sim_input.a = x[0, 2].item() * ureg("m**-1")
95+
sim_input.temperature = x[0, 3].item() * ureg.celsius
96+
sim_input.K_s = np.power(10, x[0, 4].item()) * ureg(
97+
"mol/m**3/Pa"
98+
) # express in molT2 ?
99+
sim_input.u_g0 = x[0, 5].item() * ureg("m/s")
100+
sim_input.signal_irr = lambda t: 1 if t <= t_irr else 0
101+
sim_input.signal_sparging = lambda t: 0 if t <= t_irr else 1
102+
103+
# breakpoint()
104+
105+
full_model = Simulation(
106+
sim_input,
107+
t_final=t_irr + t_sparging,
108+
)
109+
sim_output = full_model.solve(dt=0.2 * ureg.hour)
110+
sim_output.to_json(
111+
FOLDER_SAMPLES / f"sample_{self.counter}.json"
112+
) # for debugging
113+
self.counter += 1
114+
115+
residual_fraction = pp.get_residual_fraction(
116+
sim_output.inventories_T2_salt,
117+
sim_output.times,
118+
t_irr,
119+
t_irr + 1 * ureg.week,
120+
)
121+
PP_numbers.append(sim_input.get_PP_number().to("dimensionless").magnitude)
122+
y = torch.tensor(
123+
[[np.log10(residual_fraction.magnitude)]],
124+
dtype=torch.float64,
125+
)
126+
return y
127+
128+
129+
simulator = SpargingProblem(
130+
# parameters_range={
131+
# "log(h_l)": tuple(np.log10((1e-6, 1e-4))),
132+
# "log(eps_g)": tuple(np.log10((1e-4, 2e-1))),
133+
# "a": (0.05, 0.5),
134+
# "temperature": (450, 800),
135+
# "log(K_s)": tuple(np.log10((1e-6, 1e-1))),
136+
# "u_g0": (0.02, 0.4),
137+
# },
138+
parameters_range={
139+
"log(h_l)": tuple(np.log10((1e-3, 1e-2))),
140+
"log(eps_g)": tuple(np.log10((1e-4, 2e-1))),
141+
"a": (0.05, 0.3),
142+
"temperature": (700, 800),
143+
"log(K_s)": tuple(np.log10((1e-3, 1e-1))),
144+
"u_g0": (0.02, 0.1),
145+
},
146+
output_names=["log(residual_1week)"],
147+
)
148+
149+
n_samples = 50
150+
151+
X = simulator.sample_inputs(n_samples)
152+
153+
PP_numbers = []
154+
Y, _ = simulator.forward_batch(X, allow_failures=False)
155+
156+
# save training data
157+
pd.DataFrame(Y, columns=simulator.output_names).to_csv(
158+
FOLDER / "simulator_outputs.csv", index=False
159+
)
160+
pd.DataFrame(X, columns=simulator.param_names).to_csv(
161+
FOLDER / "simulator_inputs.csv", index=False
162+
)
163+
pd.DataFrame(PP_numbers, columns=["PP_number"]).to_csv(
164+
FOLDER / "PP_numbers.csv", index=False
165+
)
166+
167+
# Run AutoEmulate with default settings
168+
ae = AutoEmulate(X, Y, log_level="WARNING")
169+
ae.summarise()
170+
171+
# pick best model
172+
emulator = ae.best_result()
173+
print(f"Selected model: {emulator.model_name} with id: {emulator.id}")
174+
175+
# The use_timestamp paramater ensures a new result is saved each time the save method is called
176+
best_result_filepath = ae.save(emulator, FOLDER, use_timestamp=False)
177+
print("Model and metadata saved to: ", best_result_filepath)
178+
179+
ae.plot_preds(
180+
emulator,
181+
output_names=simulator.output_names,
182+
fname=FOLDER_PP / "predictions.png",
183+
)
184+
185+
186+
# === Sensitivity analysis ===
187+
problem = {
188+
"num_vars": simulator.in_dim,
189+
"names": simulator.param_names,
190+
"bounds": simulator.param_bounds,
191+
"output_names": simulator.output_names,
192+
}
193+
194+
with open(FOLDER / "problem.json", "w") as f:
195+
json.dump(problem, f, indent=4)
196+
197+
sa = SensitivityAnalysis(emulator.model, problem=problem)
198+
sobol_df = sa.run("sobol")
199+
sa.plot_sobol(sobol_df, index="ST", fname=FOLDER_PP / "sobol.png")

0 commit comments

Comments
 (0)