geoloop 0.0.1__py3-none-any.whl → 1.0.0b1__py3-none-any.whl

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.
Files changed (47) hide show
  1. geoloop/axisym/AxisymetricEL.py +751 -0
  2. geoloop/axisym/__init__.py +3 -0
  3. geoloop/bin/Flowdatamain.py +89 -0
  4. geoloop/bin/Lithologymain.py +84 -0
  5. geoloop/bin/Loadprofilemain.py +100 -0
  6. geoloop/bin/Plotmain.py +250 -0
  7. geoloop/bin/Runbatch.py +81 -0
  8. geoloop/bin/Runmain.py +86 -0
  9. geoloop/bin/SingleRunSim.py +928 -0
  10. geoloop/bin/__init__.py +3 -0
  11. geoloop/cli/__init__.py +0 -0
  12. geoloop/cli/batch.py +106 -0
  13. geoloop/cli/main.py +105 -0
  14. geoloop/configuration.py +946 -0
  15. geoloop/constants.py +112 -0
  16. geoloop/geoloopcore/CoaxialPipe.py +503 -0
  17. geoloop/geoloopcore/CustomPipe.py +727 -0
  18. geoloop/geoloopcore/__init__.py +3 -0
  19. geoloop/geoloopcore/b2g.py +739 -0
  20. geoloop/geoloopcore/b2g_ana.py +535 -0
  21. geoloop/geoloopcore/boreholedesign.py +683 -0
  22. geoloop/geoloopcore/getloaddata.py +112 -0
  23. geoloop/geoloopcore/pyg_ana.py +280 -0
  24. geoloop/geoloopcore/pygfield_ana.py +519 -0
  25. geoloop/geoloopcore/simulationparameters.py +130 -0
  26. geoloop/geoloopcore/soilproperties.py +152 -0
  27. geoloop/geoloopcore/strat_interpolator.py +194 -0
  28. geoloop/lithology/__init__.py +3 -0
  29. geoloop/lithology/plot_lithology.py +277 -0
  30. geoloop/lithology/process_lithology.py +697 -0
  31. geoloop/loadflowdata/__init__.py +3 -0
  32. geoloop/loadflowdata/flow_data.py +161 -0
  33. geoloop/loadflowdata/loadprofile.py +325 -0
  34. geoloop/plotting/__init__.py +3 -0
  35. geoloop/plotting/create_plots.py +1137 -0
  36. geoloop/plotting/load_data.py +432 -0
  37. geoloop/utils/RunManager.py +164 -0
  38. geoloop/utils/__init__.py +0 -0
  39. geoloop/utils/helpers.py +841 -0
  40. geoloop-1.0.0b1.dist-info/METADATA +112 -0
  41. geoloop-1.0.0b1.dist-info/RECORD +46 -0
  42. geoloop-1.0.0b1.dist-info/entry_points.txt +2 -0
  43. geoloop-0.0.1.dist-info/licenses/LICENSE → geoloop-1.0.0b1.dist-info/licenses/LICENSE.md +2 -1
  44. geoloop-0.0.1.dist-info/METADATA +0 -10
  45. geoloop-0.0.1.dist-info/RECORD +0 -6
  46. {geoloop-0.0.1.dist-info → geoloop-1.0.0b1.dist-info}/WHEEL +0 -0
  47. {geoloop-0.0.1.dist-info → geoloop-1.0.0b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,432 @@
1
+ from pathlib import Path
2
+
3
+ import pandas as pd
4
+ import xarray as xr
5
+
6
+ from geoloop.configuration import PlotInputConfig
7
+
8
+
9
+ class PlotInput:
10
+ """
11
+ A class to handle the settings from the plotting configuration file and load the simulation dataset that requires plotting.
12
+
13
+ Attributes
14
+ ----------
15
+ base_dir : str or Path object
16
+ Base directory for simulation results.
17
+ run_names : list
18
+ Names of the simulation runs.
19
+ model_types : list
20
+ Type of Geoloop model used in the simulations (i.e., ANALYTICAL, FINVOL).
21
+ run_types : list
22
+ Type of simulation runs (i.e., target power (run_type=TIN) or target fluid temperatures (run_type=POWER)).
23
+ run_modes : list
24
+ Modes of runs (i.e., single run (SR), Monte Carlo (MC)).
25
+ plot_names : list
26
+ Names of simulation runs that is used in the legend of the plots. Optional.
27
+ plot_nPipes : list
28
+ Index for pipes dimension (nPipes) to use in the data selection for plotting of the results.
29
+ plot_layer_k_ss : list
30
+ Index for dimension layer_k_s to use in the data selection for plotting of the input parameters.
31
+ plot_layer_kgs : list
32
+ Index for dimension layer_kg to use in the data selection for plotting of the input parameters.
33
+ plot_layer_Tgs : list
34
+ Index for dimension layer_Tg to use in the data selection for plotting of the input parameters.
35
+ plot_nzs : list
36
+ Index for dimension z to use in the data selection for plotting of the results.
37
+ plot_ntimes : list
38
+ Index for dimension time to use in the data selection for plotting of the results.
39
+ plot_nzsegs : list
40
+ Index for dimension zseg to use in the data selection for plotting of the results.
41
+ plot_times : list
42
+ Timesteps in the simulation results for which the depth-profiles are plotted.
43
+ plot_temperature_field : bool
44
+ Flag to plot temperature fields in case of plotting numerical simulation.
45
+ plot_time_depth : bool
46
+ Flag to plot time-depth profiles.
47
+ plot_time_parameters : list
48
+ Parameters to plot in a timeseries plot. Options: dploop, qloop, flowrate, T_fi, T_fo, T_bave, Q_b, COP, Delta_T.
49
+ Where Delta_T = T_fo - T_fi and COP = Q_b/qloop
50
+ plot_depth_parameters : list
51
+ Parameters to plot in the depth plots. Options: T_b, Tg, T_f, Delta_T
52
+ plot_crossplot_barplot : bool
53
+ Flag to plot scatter crossplots and bar sensitivity plot in case of a Monte Carlo simulation.
54
+ newplot : bool
55
+ Flag for new plot for every listed simulation in the config, or overlay the plots of the listed simulations.
56
+ crossplot_vars : list
57
+ Data variables to plot on the y-axis of the cross plots in case of a Monte Carlo simulation.
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ base_dir: str | Path,
63
+ run_names: list[str],
64
+ model_types: list[str],
65
+ run_types: list[str],
66
+ run_modes: list[str],
67
+ plot_names: list[str | None],
68
+ plot_nPipes: list[int],
69
+ plot_layer_k_ss: list[int],
70
+ plot_layer_kgs: list[int],
71
+ plot_layer_Tgs: list[int],
72
+ plot_nzs: list[int],
73
+ plot_ntimes: list[int],
74
+ plot_nzsegs: list[int],
75
+ plot_times: list[int],
76
+ plot_temperature_field: bool,
77
+ plot_time_depth: bool,
78
+ plot_crossplot_barplot: bool,
79
+ newplot: bool,
80
+ crossplot_vars: list[str],
81
+ plot_time_parameters: list[str] | bool,
82
+ plot_depth_parameters: list[str] | bool,
83
+ plot_borehole_temp: list[int],
84
+ ):
85
+ self.base_dir = base_dir
86
+ self.run_names = run_names
87
+ self.model_types = model_types
88
+ self.run_types = run_types
89
+ self.run_modes = run_modes
90
+ self.plot_names = plot_names
91
+ self.plot_nPipes = plot_nPipes
92
+ self.plot_layer_k_ss = plot_layer_k_ss
93
+ self.plot_layer_kgs = plot_layer_kgs
94
+ self.plot_layer_Tgs = plot_layer_Tgs
95
+ self.plot_nzs = plot_nzs
96
+ self.plot_ntimes = plot_ntimes
97
+ self.plot_nzsegs = plot_nzsegs
98
+ self.plot_times = plot_times
99
+ self.plot_temperature_field = plot_temperature_field
100
+ self.plot_time_depth = plot_time_depth
101
+ self.plot_crossplot_barplot = plot_crossplot_barplot
102
+ self.newplot = newplot
103
+ self.crossplot_vars = crossplot_vars
104
+ self.plot_time_parameters = plot_time_parameters
105
+ self.plot_depth_parameters = plot_depth_parameters
106
+ self.plot_borehole_temp = plot_borehole_temp
107
+
108
+ @classmethod
109
+ def from_config(cls, config: PlotInputConfig) -> "PlotInput":
110
+ """
111
+ Create a PlotInput instance from a configuration dictionary.
112
+
113
+ Parameters
114
+ ----------
115
+ config : PlotInputConfig
116
+ Configuration object containing parameters for plotting.
117
+
118
+ Returns
119
+ -------
120
+ PlotInput
121
+ """
122
+ # Handle optional plot_names
123
+ plot_names = (
124
+ config.plot_names
125
+ if config.plot_names is not None
126
+ else [False] * len(config.run_names)
127
+ )
128
+
129
+ return cls(
130
+ base_dir=config.base_dir,
131
+ run_names=config.run_names,
132
+ model_types=config.model_types,
133
+ run_types=config.run_types,
134
+ run_modes=config.run_modes,
135
+ plot_names=plot_names,
136
+ plot_nPipes=config.plot_nPipes,
137
+ plot_layer_k_ss=config.plot_layer_k_s,
138
+ plot_layer_kgs=config.plot_layer_kg,
139
+ plot_layer_Tgs=config.plot_layer_Tg,
140
+ plot_nzs=config.plot_nz,
141
+ plot_ntimes=config.plot_ntime,
142
+ plot_nzsegs=config.plot_nzseg,
143
+ plot_times=config.plot_times,
144
+ plot_temperature_field=config.plot_temperature_field,
145
+ plot_time_depth=config.plot_time_depth,
146
+ plot_crossplot_barplot=config.plot_crossplot_barplot,
147
+ newplot=config.newplot,
148
+ crossplot_vars=config.crossplot_vars,
149
+ plot_time_parameters=config.plot_time_parameters,
150
+ plot_depth_parameters=config.plot_depth_parameters,
151
+ plot_borehole_temp=config.plot_borehole_temp,
152
+ )
153
+
154
+ def list_filenames(self) -> None:
155
+ """
156
+ Construct filenames for loading the .h5 files with simulation results.
157
+ """
158
+ self.file_names = []
159
+ for i in range(len(self.run_names)):
160
+ file_name = (
161
+ self.run_names[i]
162
+ + "_"
163
+ + self.model_types[i][0]
164
+ + "_"
165
+ + self.run_types[i][0]
166
+ + "_"
167
+ + self.run_modes[i]
168
+ )
169
+ self.file_names.append(file_name)
170
+
171
+ def load_params_result_data(self) -> tuple:
172
+ """
173
+ Load the .h5 file(s) for the simulations that are plotted.
174
+
175
+ Returns
176
+ -------
177
+ Tuple[List[xr.Dataset], List[xr.Dataset]]
178
+ A tuple containing simulation input parameter dataset and simulation results dataset.
179
+ """
180
+ params_ds = []
181
+ results_ds = []
182
+
183
+ for i in range(len(self.run_names)):
184
+ out_path = self.base_dir / self.run_names[i] / self.file_names[i]
185
+ datasets_h5path = out_path.with_name(out_path.stem + ".h5")
186
+
187
+ # Open and close datasets immediately after reading them
188
+ with xr.open_dataset(
189
+ datasets_h5path, group="parameters", engine="h5netcdf"
190
+ ) as param_ds_i:
191
+ params_ds.append(param_ds_i.load()) # Load into memory, then close file
192
+
193
+ with xr.open_dataset(
194
+ datasets_h5path, group="results", engine="h5netcdf"
195
+ ) as results_ds_i:
196
+ results_ds.append(results_ds_i.load())
197
+
198
+ return (
199
+ params_ds,
200
+ results_ds,
201
+ ) # Now these are fully loaded and detached from file
202
+
203
+ def load_temperature_field_data(self) -> list[xr.DataArray]:
204
+ """
205
+ Only compatible with results of numerical (FINVOL model) simulations, that explicitly saved the calculated
206
+ temperature grid around the borehole.
207
+ Load the .h5 file(s) with temperature grid data.
208
+
209
+ Returns
210
+ -------
211
+ List[xr.DataArray]
212
+ List of temperature field DataArrays.
213
+ """
214
+
215
+ temperature_field_da = []
216
+
217
+ for i in range(len(self.run_names)):
218
+ if self.model_types[i] != "FINVOL":
219
+ print(
220
+ "Model type does not allow for plotting numerical temperature field."
221
+ )
222
+ continue
223
+
224
+ self.file_names_T = []
225
+ file_name_T = f"{self.run_names[i]}_{self.model_types[0][i]}_{self.run_types[0][i]}_FINVOL_T"
226
+ self.file_names_T.append(file_name_T)
227
+
228
+ out_path = self.base_dir / self.run_names[i] / file_name_T
229
+ Tfield_res_h5path = out_path.with_name(out_path.stem + ".h5")
230
+
231
+ # Open and close the dataset immediately after reading
232
+ with xr.open_dataarray(
233
+ Tfield_res_h5path, group="Temperature", engine="h5netcdf"
234
+ ) as temperature_field_da_i:
235
+ temperature_field_da.append(
236
+ temperature_field_da_i.load()
237
+ ) # Load into memory
238
+
239
+ return temperature_field_da
240
+
241
+
242
+ class DataTotal:
243
+ """
244
+ A class to store simulation input parameter and result datasets.
245
+
246
+ Attributes
247
+ ----------
248
+ results_ds : xarray.Dataset or list of xarray.Dataset
249
+ Dataset (or list of Datasets) containing simulation results.
250
+ params_ds : xarray.Dataset or list of xarray.Dataset
251
+ Dataset (or list of Datasets) containing simulation input parameters.
252
+ Tresult_da : xarray.Dataset or list of xarray.Dataset
253
+ DataArray containing 3D (z=len(1)) grid of calculated temperature values around the borehole.
254
+ """
255
+
256
+ def __init__(self, results_ds, params_ds, temperature_field_da):
257
+ self.results_ds = results_ds
258
+ self.params_ds = params_ds
259
+ self.temperature_field_da = temperature_field_da
260
+
261
+
262
+ class DataSelection:
263
+ """
264
+ A class to select datasets required for plotting. It stores subsets of simulation input parameters and simulation
265
+ results for the different plot types.
266
+
267
+ Attributes
268
+ ----------
269
+ crossplot_params_df : pd.DataFrame or list of pd.DataFrame
270
+ DataFrame (or list of DataFrames) of simulation input parameters for the cross-plots.
271
+ crossplot_results_df : pd.DataFrame or list of pd.DataFrame
272
+ DataFrame (or list of DataFrames) of simulation results for the cross-plots.
273
+ timeplot_results_df : pd.DataFrame or list of pd.DataFrame
274
+ DataFrame (or list of DataFrames) of simulation results for the time-plots.
275
+ depthplot_params_ds : xarray.Dataset or list of xarray.Dataset
276
+ Datasets (or list of Datasets) of simulation input parameters for the depth-plots.
277
+ depthplot_results_ds : xarray.Dataset or list of xarray.Dataset
278
+ Datasets (or list of Datasets) of simulation results for the depth-plots.
279
+ """
280
+
281
+ def __init__(
282
+ self,
283
+ crossplot_params_df: list[pd.DataFrame],
284
+ crossplot_results_df: list[pd.DataFrame],
285
+ timeplot_results_df: list[pd.DataFrame],
286
+ depthplot_params_ds: list[xr.Dataset],
287
+ depthplot_results_ds: list[xr.Dataset],
288
+ ):
289
+ self.crossplot_params_df = crossplot_params_df
290
+ self.crossplot_results_df = crossplot_results_df
291
+ self.timeplot_results_df = timeplot_results_df
292
+ self.depthplot_params_ds = depthplot_params_ds
293
+ self.depthplot_results_ds = depthplot_results_ds
294
+
295
+ @classmethod
296
+ def select_sub_datasets(
297
+ cls, plotinput: PlotInput, datatotal: DataTotal
298
+ ) -> "DataSelection":
299
+ """
300
+ Selects and aggregates subsets of datasets for plotting, based on PlotInput settings.
301
+
302
+ Parameters
303
+ ----------
304
+ plotinput : PlotInput
305
+ Plotting configuration.
306
+ datatotal : DataTotal
307
+ Object containing simulation datasets (including simulation results and input parameters).
308
+
309
+ Returns
310
+ -------
311
+ DataSelection
312
+ Object containing selected subsets of datasets ready for plotting.
313
+ """
314
+ crossplot_params_df_total = []
315
+ crossplot_results_df_total = []
316
+ timeplot_results_df_total = []
317
+ depthplot_params_ds_total = []
318
+ depthplot_results_ds_total = []
319
+
320
+ for i in range(len(plotinput.run_names)):
321
+ plot_nPipe = plotinput.plot_nPipes[i]
322
+ plot_layer_k_s = plotinput.plot_layer_k_ss[i]
323
+ plot_layer_kg = plotinput.plot_layer_kgs[i]
324
+ plot_layer_Tg = plotinput.plot_layer_Tgs[i]
325
+ plot_nz = plotinput.plot_nzs[i]
326
+ plot_ntime = plotinput.plot_ntimes[i]
327
+ plot_nzseg = plotinput.plot_nzsegs[i]
328
+ file_name = plotinput.file_names[i]
329
+ plot_name = plotinput.plot_names[i]
330
+
331
+ param_ds = datatotal.params_ds[i]
332
+ results_ds = datatotal.results_ds[i]
333
+
334
+ # For plotting, add run_name dummy in all dims to the results and param datasets
335
+ # Use plot_name if defined, otherwise fallback to file_name
336
+ name_to_use = plot_name if plot_name else file_name
337
+
338
+ # Add to param_ds
339
+ param_ds["file_name"] = xr.DataArray(
340
+ data=name_to_use,
341
+ dims=("samples", "nPipes", "layer_k_s", "layer_k_g", "layer_Tg"),
342
+ coords=param_ds.coords,
343
+ )
344
+
345
+ # Add to results_ds based on run mode
346
+ if plotinput.run_modes[i] == "SR":
347
+ results_ds["file_name"] = xr.DataArray(
348
+ data=name_to_use,
349
+ dims=("samples", "time", "nPipes", "zseg", "z"),
350
+ coords=results_ds.coords,
351
+ )
352
+ elif plotinput.run_modes[i] == "MC":
353
+ results_ds["file_name"] = xr.DataArray(
354
+ data=name_to_use,
355
+ dims=("samples", "time", "nPipes"),
356
+ coords=(results_ds.samples, results_ds.time, results_ds.nPipes),
357
+ )
358
+
359
+ # Select crossplot datasets
360
+ # Parameters
361
+ crossplot_params_ds = param_ds.isel(
362
+ layer_k_s=plot_layer_k_s,
363
+ layer_k_g=plot_layer_kg,
364
+ layer_Tg=plot_layer_Tg,
365
+ xy=-1,
366
+ nPipes=plot_nPipe,
367
+ )
368
+ crossplot_params_df = crossplot_params_ds.to_dataframe(["samples"])
369
+ crossplot_params_df["samples"] = crossplot_params_df.index
370
+ crossplot_params_df = crossplot_params_df.reset_index(drop=True)
371
+ crossplot_params_df_total.append(crossplot_params_df)
372
+
373
+ # Results
374
+ # for cross-plotting, calculate average of the results over all dimensions except samples and time
375
+ crossplot_k_s_avg = results_ds.isel(
376
+ time=plot_ntime, z=plot_nz, nPipes=plot_nPipe
377
+ )["k_s"].mean(dim="zseg")
378
+ if "z" in results_ds["k_g"].dims:
379
+ crossplot_k_g_avg = results_ds.isel(
380
+ time=plot_ntime, zseg=plot_nzseg, nPipes=plot_nPipe
381
+ )["k_g"].mean(dim="z")
382
+ else:
383
+ crossplot_k_g_avg = results_ds.isel(time=plot_ntime, nPipes=plot_nPipe)[
384
+ "k_g"
385
+ ].mean(dim="zseg")
386
+ crossplot_T_b_avg = results_ds.isel(
387
+ time=plot_ntime, z=plot_nz, nPipes=plot_nPipe
388
+ )["T_b"].mean(dim="zseg")
389
+ crossplot_qzb_avg = results_ds.isel(
390
+ time=plot_ntime, z=plot_nz, nPipes=plot_nPipe
391
+ )["qzb"].mean(dim="zseg")
392
+ crossplot_results_ds = results_ds.isel(
393
+ time=plot_ntime, zseg=plot_nzseg, z=plot_nz, nPipes=plot_nPipe
394
+ )
395
+ crossplot_results_df = crossplot_results_ds.to_dataframe(["samples"])
396
+
397
+ # Add calculated averages over depth to the datafame for plotting
398
+ crossplot_results_df["k_s_res"] = crossplot_k_s_avg.values
399
+ crossplot_results_df["k_g_res"] = crossplot_k_g_avg.values
400
+ crossplot_results_df["T_b"] = crossplot_T_b_avg.values
401
+ crossplot_results_df["qzb"] = crossplot_qzb_avg.values
402
+ crossplot_results_df["samples"] = crossplot_results_df.index
403
+ crossplot_results_df = crossplot_results_df.reset_index(drop=True)
404
+ crossplot_results_df_total.append(crossplot_results_df)
405
+
406
+ # Select timeplot datasets
407
+ # Select the desired dimensions from the param dataset to convert to a dataframe for plotting
408
+ timeplot_results_ds = results_ds.isel(
409
+ samples=-1, zseg=plot_nzseg, z=plot_nz, nPipes=plot_nPipe
410
+ )
411
+ timeplot_results_df = timeplot_results_ds.to_dataframe(["time"])
412
+ timeplot_results_df["time"] = timeplot_results_df.index
413
+ timeplot_results_df = timeplot_results_df.reset_index(drop=True)
414
+ timeplot_results_df_total.append(timeplot_results_df)
415
+
416
+ # Selects depthplot datasets
417
+ # For MC runs, select here another sample to plot a depth plot with another thermal conductivity profile
418
+ # than the basecase
419
+ depthplot_params_ds = param_ds.isel(samples=-1)
420
+ depthplot_results_ds = results_ds.isel(samples=-1)
421
+ depthplot_results_ds_total.append(depthplot_results_ds)
422
+ depthplot_params_ds_total.append(depthplot_params_ds)
423
+
424
+ dataselection = cls(
425
+ crossplot_params_df_total,
426
+ crossplot_results_df_total,
427
+ timeplot_results_df_total,
428
+ depthplot_params_ds_total,
429
+ depthplot_results_ds_total,
430
+ )
431
+
432
+ return dataselection
@@ -0,0 +1,164 @@
1
+ import time
2
+
3
+ import numpy as np
4
+ from scipy.stats import lognorm, norm, triang, uniform
5
+ from tqdm import tqdm
6
+
7
+ from geoloop.bin.SingleRunSim import SingleRun, optimize_forkeys
8
+ from geoloop.configuration import SingleRunConfig
9
+ from geoloop.geoloopcore.simulationparameters import SimulationParameters
10
+ from geoloop.utils.helpers import get_param_names
11
+
12
+
13
+ def sample_parameter_space(config: SingleRunConfig) -> tuple[dict, list[str]]:
14
+ """
15
+ Sample the model parameter space based on definitions provided in `config`.
16
+
17
+ Parameters
18
+ ----------
19
+ config : SingleRunConfig
20
+ Object containing parameter definitions. Must include:
21
+ ``n_samples`` : int
22
+ Number of Monte Carlo samples to generate.
23
+ Remaining keys should define either:
24
+ * A fixed numerical value, or
25
+ * A distribution definition list: [dist_type, p1, p2, (optional p3)]
26
+ where dist_type ∈ {"normal", "uniform", "lognormal", "triangular"}.
27
+
28
+ Returns
29
+ -------
30
+ sample_space : dict of array-like
31
+ Dictionary mapping parameter names → sampled values with shape ``(n_samples,)``.
32
+ varying_parameters : list of str
33
+ List of parameter names that were sampled from statistical distributions.
34
+
35
+ Notes
36
+ -----
37
+ Locked parameters (non-varying) are broadcast to all samples.
38
+ """
39
+ variables_config = config.variables_config
40
+ n_samples = variables_config.n_samples
41
+
42
+ sample_space = dict()
43
+
44
+ variable_param_names, locked_param_names = get_param_names(config)
45
+
46
+ np.random.seed(1)
47
+ varying_parameters = []
48
+
49
+ for param_name in variable_param_names:
50
+ dist = getattr(variables_config, param_name)
51
+
52
+ if isinstance(dist, tuple):
53
+ dist_name = dist[0].lower()
54
+ varying_parameters.append(param_name)
55
+
56
+ if dist_name == "normal":
57
+ mean, std_dev = dist[1], dist[2]
58
+ samples = norm(loc=mean, scale=std_dev).rvs(size=n_samples)
59
+ elif dist_name == "uniform":
60
+ min, max = dist[1], dist[2]
61
+ samples = uniform(loc=min, scale=max - min).rvs(size=n_samples)
62
+
63
+ elif dist_name == "lognormal":
64
+ mu, sigma = dist[1], dist[2]
65
+ samples = lognorm(s=sigma, scale=np.exp(mu)).rvs(n_samples)
66
+
67
+ elif dist_name == "triangular":
68
+ min, peak, max = dist[1], dist[2], dist[3]
69
+ samples = triang(
70
+ loc=min, scale=max - min, c=(peak - min) / (max - min)
71
+ ).rvs(size=n_samples)
72
+
73
+ else:
74
+ raise ValueError(f"Unknown distribution '{dist_name}' in {param_name}")
75
+
76
+ sample_space[param_name] = samples
77
+ continue
78
+
79
+ # Case 2: No distribution → lock to fixed value in main config
80
+ fixed_value = getattr(config, param_name)
81
+ sample_space[param_name] = np.full(n_samples, float(fixed_value))
82
+
83
+ # broadcast locked (non-varying) parameters
84
+ for param_name in locked_param_names:
85
+ sample_space[param_name] = [
86
+ getattr(config, param_name) for _ in range(n_samples)
87
+ ]
88
+
89
+ return sample_space, varying_parameters
90
+
91
+
92
+ def run_models(config: SingleRunConfig) -> tuple[dict, list]:
93
+ """
94
+ Execute Monte Carlo model runs based on sampled parameters.
95
+
96
+ Parameters
97
+ ----------
98
+ config : SingleRunConfig
99
+
100
+ Model configuration. Must include:
101
+ ``n_samples`` : int
102
+ Number of stochastic simulations.
103
+ Must also be compatible with ``sample_parameter_space`` and
104
+ ``SingleRun.from_config``.
105
+
106
+ Returns
107
+ -------
108
+ parameters : dict
109
+ Sampled input parameter space with arrays sized ``(n_samples,)``.
110
+ results : list
111
+ List of SingleRunResult model result objects, one per sample.
112
+ """
113
+ parameters, varying_param = sample_parameter_space(
114
+ config
115
+ ) # parameters = sample space
116
+
117
+ n_samples = config.variables_config.n_samples
118
+ print(f"Running {n_samples} models")
119
+ print("Varying: {}".format(", ".join(varying_param)))
120
+ time.sleep(0.1) # For pretty printing
121
+
122
+ base_dict = config.model_dump(serialize_as_any=True)
123
+
124
+ results = []
125
+ for a in tqdm(range(n_samples)):
126
+ # Inject sampled parameters
127
+ for key in parameters:
128
+ try:
129
+ base_dict[key] = parameters[key][a]
130
+ except:
131
+ print("Parameter assignment error for:", key)
132
+
133
+ # Create SimulationParameters and update time in the base_dict
134
+ sim_params = SimulationParameters.from_config(config)
135
+ base_dict["time"] = sim_params.time
136
+
137
+ # Optional optimization
138
+ if config.dooptimize:
139
+ cop_crit = config.copcrit
140
+ optimize_keys = config.optimize_keys
141
+ optimize_bounds = config.optimize_keys_bounds
142
+
143
+ print(f"Optimizing flow rate for COP: {cop_crit}")
144
+ if config.dploopcrit:
145
+ print(f"Maximum pressure constraint: {config.dploopcrit}")
146
+ print(f"Optimizing keys: {optimize_keys}")
147
+ print(f"Bounds: {optimize_bounds}")
148
+
149
+ # find optimized config parameters
150
+ _, optimized_params = optimize_forkeys(
151
+ base_dict, cop_crit, optimize_keys, optimize_bounds, a
152
+ )
153
+ # Update the parameters dict with the optimized values
154
+ for key in optimize_keys:
155
+ parameters[key][a] = optimized_params[key]
156
+
157
+ sample_config = SingleRunConfig(**base_dict)
158
+
159
+ # Run full model
160
+ single_run = SingleRun.from_config(sample_config)
161
+ run_result = single_run.run(a)
162
+ results.append(run_result)
163
+
164
+ return parameters, results
File without changes