geoloop 0.0.1__py3-none-any.whl → 1.0.0__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.
- geoloop/axisym/AxisymetricEL.py +751 -0
- geoloop/axisym/__init__.py +3 -0
- geoloop/bin/Flowdatamain.py +89 -0
- geoloop/bin/Lithologymain.py +84 -0
- geoloop/bin/Loadprofilemain.py +100 -0
- geoloop/bin/Plotmain.py +250 -0
- geoloop/bin/Runbatch.py +81 -0
- geoloop/bin/Runmain.py +86 -0
- geoloop/bin/SingleRunSim.py +928 -0
- geoloop/bin/__init__.py +3 -0
- geoloop/cli/__init__.py +0 -0
- geoloop/cli/batch.py +106 -0
- geoloop/cli/main.py +105 -0
- geoloop/configuration.py +946 -0
- geoloop/constants.py +112 -0
- geoloop/geoloopcore/CoaxialPipe.py +503 -0
- geoloop/geoloopcore/CustomPipe.py +727 -0
- geoloop/geoloopcore/__init__.py +3 -0
- geoloop/geoloopcore/b2g.py +739 -0
- geoloop/geoloopcore/b2g_ana.py +516 -0
- geoloop/geoloopcore/boreholedesign.py +683 -0
- geoloop/geoloopcore/getloaddata.py +112 -0
- geoloop/geoloopcore/pyg_ana.py +280 -0
- geoloop/geoloopcore/pygfield_ana.py +519 -0
- geoloop/geoloopcore/simulationparameters.py +130 -0
- geoloop/geoloopcore/soilproperties.py +152 -0
- geoloop/geoloopcore/strat_interpolator.py +194 -0
- geoloop/lithology/__init__.py +3 -0
- geoloop/lithology/plot_lithology.py +277 -0
- geoloop/lithology/process_lithology.py +695 -0
- geoloop/loadflowdata/__init__.py +3 -0
- geoloop/loadflowdata/flow_data.py +161 -0
- geoloop/loadflowdata/loadprofile.py +325 -0
- geoloop/plotting/__init__.py +3 -0
- geoloop/plotting/create_plots.py +1142 -0
- geoloop/plotting/load_data.py +432 -0
- geoloop/utils/RunManager.py +164 -0
- geoloop/utils/__init__.py +0 -0
- geoloop/utils/helpers.py +841 -0
- geoloop-1.0.0.dist-info/METADATA +120 -0
- geoloop-1.0.0.dist-info/RECORD +46 -0
- geoloop-1.0.0.dist-info/entry_points.txt +2 -0
- geoloop-0.0.1.dist-info/licenses/LICENSE → geoloop-1.0.0.dist-info/licenses/LICENSE.md +2 -1
- geoloop-0.0.1.dist-info/METADATA +0 -10
- geoloop-0.0.1.dist-info/RECORD +0 -6
- {geoloop-0.0.1.dist-info → geoloop-1.0.0.dist-info}/WHEEL +0 -0
- {geoloop-0.0.1.dist-info → geoloop-1.0.0.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
|