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.
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 +516 -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 +695 -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 +1142 -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.0.dist-info/METADATA +120 -0
  41. geoloop-1.0.0.dist-info/RECORD +46 -0
  42. geoloop-1.0.0.dist-info/entry_points.txt +2 -0
  43. geoloop-0.0.1.dist-info/licenses/LICENSE → geoloop-1.0.0.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.0.dist-info}/WHEEL +0 -0
  47. {geoloop-0.0.1.dist-info → geoloop-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,3 @@
1
+ """
2
+ This package contains a module with a class for simulation of a 3D Grid.
3
+ """
@@ -0,0 +1,89 @@
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ import matplotlib.pyplot as plt
5
+ import numpy as np
6
+
7
+ from geoloop.configuration import FlowDataConfig, load_single_config
8
+ from geoloop.loadflowdata.flow_data import FlowData
9
+
10
+
11
+ def main_flow_data(config_path: str | Path):
12
+ """
13
+ Command-line entry point to calculate, plot and optionally save a time-profile of flow rate.
14
+
15
+ Parameters
16
+ ----------
17
+ config_path : str or Path
18
+ Path to a JSON config file.
19
+
20
+ Notes
21
+ -----
22
+ This function:
23
+ • Loads a FlowData configuration file
24
+ • Builds a FlowData instance
25
+ • Computes 1 year of hourly flow data
26
+ • Plots instantaneous and cumulative flow
27
+ • Saves the plot in the configured output directory
28
+ """
29
+
30
+ # 1. Load configuration
31
+ config_dict = load_single_config(config_path)
32
+ config = FlowDataConfig(**config_dict)
33
+
34
+ flow = FlowData.from_config(config)
35
+
36
+ # 2. Simulation time setup (1 hour step)
37
+ dt = 3600.0 # seconds
38
+ tmax = 8760.0 * 3600 # one year in seconds
39
+ Nt = int(np.ceil(tmax / dt))
40
+
41
+ time = dt * np.arange(1, Nt + 1)
42
+ hours = time / 3600
43
+
44
+ # Compute flow data
45
+ Q_flow = flow.getflow(hours)
46
+
47
+ # 3. Plotting
48
+ fig, (ax1, ax2) = plt.subplots(2, figsize=(9, 9))
49
+
50
+ # Instantaneous flow
51
+ ax1.set_xlabel(r"$t$ [hours]")
52
+ ax1.set_ylabel(r"$\dot{m}$ [kg/s]")
53
+ ax1.plot(hours, Q_flow)
54
+ ax1.set_title("Instantaneous Flow Rate")
55
+
56
+ # Cumulative flow (kg)
57
+ Qcum = np.cumsum(Q_flow) * dt # dt is seconds → sum(kg/s * s) = kg
58
+ ax2.set_xlabel(r"$t$ [hours]")
59
+ ax2.set_ylabel(r"Cumulative flow [kg]")
60
+ ax2.plot(hours, Qcum)
61
+ ax2.set_title("Cumulative Flow")
62
+
63
+ plt.tight_layout()
64
+
65
+ # 4. Save figure
66
+ plot_dir = Path(config.fp_outdir)
67
+
68
+ if config.fp_type == "FROMFILE":
69
+ if config.fp_smoothing is not None:
70
+ fig_name = f"{config.fp_filename[:-4]}_{config.fp_smoothing}_flow"
71
+ else:
72
+ fig_name = f"{config.fp_filename[:-4]}_flow"
73
+ else:
74
+ fig_name = f"{config.fp_type}_flow_profile"
75
+
76
+ fig_path = plot_dir / fig_name
77
+ plt.savefig(fig_path)
78
+
79
+ # 5. Print summary
80
+ print("Flow profile summary:")
81
+ print("---------------------")
82
+ print("Mean flow rate [kg/s]:", np.mean(Q_flow))
83
+ print("Min flow rate [kg/s]:", np.min(Q_flow))
84
+ print("Max flow rate [kg/s]:", np.max(Q_flow))
85
+ print("Total cumulative flow [kg]:", Qcum[-1])
86
+
87
+
88
+ if __name__ == "__main__":
89
+ main_flow_data(sys.argv[1])
@@ -0,0 +1,84 @@
1
+ from pathlib import Path
2
+
3
+ import numpy as np
4
+ import xarray as xr
5
+
6
+ from geoloop.configuration import LithologyConfig, load_single_config
7
+ from geoloop.geoloopcore.strat_interpolator import StratInterpolator
8
+ from geoloop.lithology.plot_lithology import plot_lithology_and_thermcon
9
+ from geoloop.lithology.process_lithology import ProcessLithologyToThermalConductivity
10
+
11
+
12
+ def main_lithology(config_path: str | Path) -> None:
13
+ """
14
+ Command-line entry point to calculate and optionally save lithology to thermal conductivity
15
+ conversions and produce and save plots.
16
+
17
+ Parameters
18
+ ----------
19
+ config_path : str
20
+ Path to a JSON configuration file.
21
+
22
+ Notes
23
+ -----
24
+ Expected JSON configuration fields (minimum):
25
+ ``litho_k_param``
26
+ Defines parameter distributions for stochastic evaluation.
27
+
28
+ Returns
29
+ -------
30
+ None
31
+ """
32
+ config_dict = load_single_config(config_path)
33
+ config = LithologyConfig(**config_dict) # validated Pydantic object
34
+
35
+ # initiate object for lithology_to_k simulation and create (stochastic) thermcon-depth profiles
36
+ lithology_to_k = ProcessLithologyToThermalConductivity.from_config(config)
37
+ if lithology_to_k.read_from_table:
38
+ pass
39
+ else:
40
+ lithology_to_k.create_multi_thermcon_profiles()
41
+ lithology_to_k.save_thermcon_sample_profiles()
42
+
43
+ # Create plots
44
+ # load in the h5 dataset again for convenient plotting
45
+ lithology_h5path = lithology_to_k.out_dir / lithology_to_k.out_table
46
+
47
+ lithology_k_ds = xr.open_dataset(
48
+ lithology_h5path, group="litho_k", engine="h5netcdf"
49
+ )
50
+ # select only basecase
51
+ lithology_k_base_case = lithology_k_ds.sel(n_samples=0)
52
+
53
+ z = lithology_k_base_case.depth.values
54
+ zval = lithology_k_base_case.kh_bulk.values
55
+
56
+ zend = z
57
+ zstart = z[:-1] * 1
58
+
59
+ # Append 0 at the beginning of zstart
60
+ zstart = np.insert(zstart, 0, 0)
61
+
62
+ # Interpolate basecase thermal conductivity values
63
+ interp_obj = StratInterpolator(zend, zval)
64
+ kh_bulk_plotting_base_case = interp_obj.interp_plot(zstart, zend)
65
+
66
+ # Extract max and min thermal conductivity profiles and interpolate
67
+ kh_bulk_max = lithology_k_ds.kh_bulk.max(dim="n_samples").values
68
+ interp_obj.zval = kh_bulk_max
69
+ kh_bulk_plotting_max = interp_obj.interp_plot(zstart, zend)
70
+
71
+ kh_bulk_min = lithology_k_ds.kh_bulk.min(dim="n_samples").values
72
+ interp_obj.zval = kh_bulk_min
73
+ kh_bulk_plotting_min = interp_obj.interp_plot(zstart, zend)
74
+
75
+ # Create the layer cake plot
76
+ plot_lithology_and_thermcon(
77
+ lithology_k_base_case,
78
+ kh_bulk_plotting_base_case,
79
+ interp_obj,
80
+ kh_bulk_plotting_max,
81
+ kh_bulk_plotting_min,
82
+ lithology_h5path,
83
+ lithology_to_k.out_dir,
84
+ )
@@ -0,0 +1,100 @@
1
+ from pathlib import Path
2
+
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+
6
+ from geoloop.configuration import LoadProfileConfig, load_single_config
7
+ from geoloop.loadflowdata.loadprofile import LoadProfile
8
+
9
+
10
+ def main_load_profile(config_path: str | Path):
11
+ """
12
+ Command-line entry point to calculate, plot and optionally save a time-profile of heat load.
13
+
14
+ Parameters
15
+ ----------
16
+ config_path : str or Path
17
+ The path to a JSON configuration file.
18
+
19
+ Notes
20
+ -----
21
+ The function:
22
+ • loads a configuration file
23
+ • builds a LoadProfile instance
24
+ • computes 1 year of hourly load data
25
+ • plots instantaneous and cumulative energy
26
+ • writes the plot to the configured output directory
27
+ """
28
+ # Parse the configuration file and combine with standard configuration
29
+ config_dict = load_single_config(config_path)
30
+ config = LoadProfileConfig(**config_dict) # validated Pydantic object
31
+
32
+ loadprof = LoadProfile.from_config(config)
33
+
34
+ # Simulation parameters
35
+ dt = 3600.0 # Time step (s)
36
+ tmax = 1.0 * 8760.0 * 3600.0 # Maximum time (s)
37
+ Nt = int(np.ceil(tmax / dt)) # Number of time steps
38
+ time = dt * np.arange(1, Nt + 1)
39
+
40
+ # Load function expects hours
41
+ Q = loadprof.getload(time / 3600)
42
+
43
+ # plot results
44
+ fig, (ax1, ax2) = plt.subplots(2, figsize=(9, 9))
45
+
46
+ # Instantaneous load
47
+ ax1.set_xlabel(r"$t$ [hours]")
48
+ ax1.set_ylabel(r"$Q_b$ [W]")
49
+ hours = np.arange(1, Nt + 1) * dt / 3600.0
50
+ ax1.plot(hours, Q)
51
+
52
+ # Cumulative energy
53
+ ax2.set_xlabel(r"$t$ [hours]")
54
+ ax2.set_ylabel(r"$Q_b$ [MWh]")
55
+
56
+ # Combined cumulative (heating + cooling)
57
+ Qcum_all = np.cumsum(Q) * (dt / 3600) * 1e-6
58
+ ax2.plot(hours, Qcum_all, label="Cumulative heating + cooling load")
59
+
60
+ # Heating only
61
+ Q_heating = Q * 1
62
+ Q_heating[Q_heating <= 0] = 0
63
+ Qcum_heating = np.cumsum(Q_heating) * (dt / 3600) * 1e-6
64
+ ax2.plot(hours, Qcum_heating, label="Cumulative heating load")
65
+
66
+ # Cooling only
67
+ Q_cooling = Q * 1
68
+ Q_cooling[Q_cooling > 0] = 0
69
+ Qcum_cooling = np.cumsum(Q_cooling) * (dt / 3600) * 1e-6
70
+ ax2.plot(hours, Qcum_cooling, label="Cumulative cooling load")
71
+
72
+ plt.legend()
73
+ plt.tight_layout()
74
+
75
+ # Save figure
76
+ plot_dir = config.lp_outdir
77
+ if config.lp_type == "FROMFILE":
78
+ if config.lp_smoothing is not None:
79
+ fig_name = f"{config.lp_filename[:-4]}_{config.lp_smoothing}_smoothing"
80
+ else:
81
+ fig_name = config.lp_filename[:-3]
82
+ else:
83
+ fig_name = f"{config.lp_type}_load_profile"
84
+
85
+ fig_path = plot_dir / fig_name
86
+ plt.savefig(fig_path)
87
+
88
+ # Print results summary
89
+ print(
90
+ "total energy consumed [MWh]:", np.sum(Q) * 1e-6
91
+ ) # total net energy demand in one year
92
+ print(
93
+ "total energy heating [MWh]:", np.sum(Q[Q > 0]) * 1e-6
94
+ ) # total heating demand in one year
95
+ print(
96
+ "total energy cooling [MWh]:", np.sum(Q[Q <= 0]) * 1e-6
97
+ ) # total cooling demand in one year
98
+ print(
99
+ "yearly average energy consumed [W]:", np.mean(Q)
100
+ ) # yearly average energy consumed
@@ -0,0 +1,250 @@
1
+ import sys
2
+ import time
3
+ from pathlib import Path
4
+
5
+ import pandas as pd
6
+
7
+ from geoloop.bin.SingleRunSim import SingleRun
8
+ from geoloop.configuration import (
9
+ LithologyConfig,
10
+ PlotInputConfig,
11
+ SingleRunConfig,
12
+ load_nested_config,
13
+ load_single_config,
14
+ )
15
+ from geoloop.lithology.process_lithology import ProcessLithologyToThermalConductivity
16
+ from geoloop.plotting.create_plots import PlotResults
17
+ from geoloop.plotting.load_data import DataSelection, DataTotal, PlotInput
18
+
19
+
20
+ def main_plotmain(config_path: str | Path) -> None:
21
+ """
22
+ Main entry point for plotting BHE analysis.
23
+
24
+ This function loads simulation parameters and results, organizes them into
25
+ plot-ready data structures, and generates figures for multiple combinations
26
+ of deterministic and Monte-Carlo simulation types. Supports plotting either:
27
+
28
+ * Multiple **deterministic** (SR) runs, or
29
+ * Multiple **stochastic** (MC) runs
30
+
31
+ Mixed SR/MC plotting is not supported. Plotting more than two runs is possible
32
+ but not optimally formatted.
33
+
34
+ Parameters
35
+ ----------
36
+ config_path : str
37
+ Path to a JSON configuration file.
38
+
39
+ Returns
40
+ -------
41
+ None
42
+ """
43
+
44
+ start_time = time.time()
45
+
46
+ config_dict = load_single_config(config_path)
47
+ config = PlotInputConfig(**config_dict) # validated Pydantic object
48
+
49
+ print("Processing results and visualizing...")
50
+
51
+ # Build data input object
52
+ plotinput = PlotInput.from_config(config)
53
+ plotinput.list_filenames()
54
+ param_ds, results_ds = plotinput.load_params_result_data()
55
+
56
+ temperature_field_da = None
57
+ if plotinput.plot_temperature_field:
58
+ temperature_field_da = plotinput.load_temperature_field_data()
59
+
60
+ datatotal = DataTotal(results_ds, param_ds, temperature_field_da)
61
+ dataselection = DataSelection.select_sub_datasets(plotinput, datatotal)
62
+
63
+ # NEW individual run plotting
64
+ if plotinput.newplot:
65
+ for i in range(len(plotinput.run_names)):
66
+ out_path = (
67
+ plotinput.base_dir / plotinput.run_names[i] / plotinput.file_names[i]
68
+ )
69
+
70
+ # -------- Single Run (SR) --------
71
+ if plotinput.run_modes[i] == "SR":
72
+ if plotinput.plot_time_depth:
73
+ # Only create time and depth plots if the data is from a single run
74
+ PlotResults.create_timeseriesplot(
75
+ dataselection.timeplot_results_df[i],
76
+ out_path,
77
+ plotinput.plot_time_parameters,
78
+ )
79
+ PlotResults.create_depthplot(
80
+ plotinput.plot_depth_parameters,
81
+ plotinput.plot_times,
82
+ dataselection.depthplot_params_ds[i],
83
+ dataselection.depthplot_results_ds[i],
84
+ out_path,
85
+ )
86
+
87
+ if config.plot_borehole_temp:
88
+ run_name = plotinput.run_names[i]
89
+ runjson = run_name + ".json"
90
+ keysneeded = []
91
+ keysoptional = [
92
+ "litho_k_param",
93
+ "loadprofile",
94
+ "borefield",
95
+ "variables_config",
96
+ "flow_data",
97
+ ]
98
+ config_sim = load_nested_config(
99
+ runjson, keysneeded, keysoptional
100
+ )
101
+
102
+ run_config = SingleRunConfig(**config_sim)
103
+
104
+ run_config.lithology_to_k = None
105
+ if run_config.litho_k_param:
106
+ # in a single run always set the base case to True
107
+ run_config.litho_k_param["basecase"] = True
108
+ lithology_to_k = (
109
+ ProcessLithologyToThermalConductivity.from_config(
110
+ LithologyConfig(**run_config.litho_k_param)
111
+ )
112
+ )
113
+ lithology_to_k.create_multi_thermcon_profiles()
114
+ run_config.lithology_to_k = lithology_to_k
115
+ single_run = SingleRun.from_config(run_config)
116
+ PlotResults.create_borehole_temp_plot(
117
+ plotinput.plot_borehole_temp,
118
+ plotinput.plot_times,
119
+ dataselection.depthplot_params_ds[i],
120
+ dataselection.depthplot_results_ds[i],
121
+ single_run,
122
+ out_path,
123
+ )
124
+
125
+ # T-field movie & snapshots
126
+ if plotinput.plot_temperature_field:
127
+ figure_folder = "Tfield_time"
128
+
129
+ out_path = plotinput.base_dir / plotinput.run_names[i]
130
+
131
+ # Create the figure folder if it doesn't exist
132
+ figure_folder_path = out_path / figure_folder
133
+ figure_folder_path.mkdir(parents=True, exist_ok=True)
134
+
135
+ # Full path for the file
136
+ out_path = figure_folder_path / plotinput.file_names[i]
137
+
138
+ Tmin = datatotal.temperature_field_da[i].values.min()
139
+ Tmax = datatotal.temperature_field_da[i].values.max()
140
+ for j in range(len(datatotal.temperature_field_da[i].time)):
141
+ PlotResults.plot_temperature_field(
142
+ j, datatotal.temperature_field_da[i], Tmin, Tmax, out_path
143
+ )
144
+ in_path = figure_folder_path
145
+ PlotResults.create_temperature_field_movie(in_path)
146
+
147
+ # -------- Monte-Carlo (MC) --------
148
+ elif plotinput.run_modes[i] == "MC":
149
+ if plotinput.plot_crossplot_barplot:
150
+ # Only make scatter and bar plots is the simulation is a MC run
151
+ # Make plots for target parameter vs the other results and params
152
+ if plotinput.run_types[i] == "TIN":
153
+ target = "Q_b"
154
+ elif plotinput.run_types[i] == "POWER":
155
+ target = "T_fi"
156
+ else:
157
+ target = None
158
+ print("The run_type is not recognized")
159
+ plotinput.crossplot_vars.append(target)
160
+
161
+ for y_var_name in plotinput.crossplot_vars:
162
+ PlotResults.create_scatterplots(
163
+ dataselection.crossplot_results_df[i],
164
+ dataselection.crossplot_params_df[i],
165
+ y_var_name,
166
+ out_path,
167
+ )
168
+ PlotResults.create_barplot(
169
+ dataselection.crossplot_results_df[i],
170
+ dataselection.crossplot_params_df[i],
171
+ y_var_name,
172
+ out_path,
173
+ )
174
+
175
+ else:
176
+ print("Run_mode not recognized")
177
+ pass
178
+
179
+ # COMBINED multi-simulation plotting
180
+ elif not plotinput.newplot:
181
+ # below make sure that the figure name is shorter so that more than three simulations can be plotted together
182
+ combined_fig_folder = "combined_plots"
183
+ figure_name = "_".join([str(i) for i in plotinput.file_names])
184
+ subfolder = figure_name
185
+
186
+ # Build the full path
187
+ combined_path = plotinput.base_dir / combined_fig_folder / subfolder
188
+
189
+ # Create the folder if it doesn't exist
190
+ combined_path.mkdir(parents=True, exist_ok=True)
191
+
192
+ # Full output path for the figure
193
+ out_path = combined_path / figure_name
194
+
195
+ # Define runtype and create combined scatter and bar plots
196
+ for run_type in plotinput.run_types:
197
+ # Make plots for target parameter vs the other results and params
198
+ if run_type == "TIN":
199
+ target = "Q_b"
200
+ elif run_type == "POWER":
201
+ target = "T_fi"
202
+ else:
203
+ target = None
204
+ print("The run_type is not recognized")
205
+ plotinput.crossplot_vars.append(target)
206
+
207
+ # Only create time and depth plots if the data is from a single run
208
+ if (
209
+ all(mode == "SR" for mode in plotinput.run_modes)
210
+ and plotinput.plot_time_depth
211
+ ):
212
+ PlotResults.create_timeseriesplot(
213
+ dataselection.timeplot_results_df,
214
+ out_path,
215
+ plotinput.plot_time_parameters,
216
+ )
217
+ PlotResults.create_depthplot(
218
+ plotinput.plot_depth_parameters,
219
+ plotinput.plot_times,
220
+ dataselection.depthplot_params_ds,
221
+ dataselection.depthplot_results_ds,
222
+ out_path,
223
+ )
224
+
225
+ # Only make scatter and bar plots is the simulation is a MC run
226
+ elif (
227
+ all(mode == "MC" for mode in plotinput.run_modes)
228
+ and plotinput.plot_crossplot_barplot
229
+ ):
230
+ # Combine dataframes for crossplots per run_name
231
+ results_df = pd.concat(
232
+ dataselection.crossplot_results_df, ignore_index=True
233
+ )
234
+ param_df = pd.concat(dataselection.crossplot_params_df, ignore_index=True)
235
+
236
+ for y_var_name in plotinput.crossplot_vars:
237
+ PlotResults.create_scatterplots(
238
+ results_df, param_df, y_var_name, out_path
239
+ )
240
+ PlotResults.create_barplot(results_df, param_df, y_var_name, out_path)
241
+
242
+ else:
243
+ print("Can not plot a single run and MC simulation together")
244
+
245
+ # Runtime log
246
+ print(f"Total runtime: {(time.time() - start_time) / 60.0:.2f} minutes")
247
+
248
+
249
+ if __name__ == "__main__":
250
+ main_plotmain(sys.argv[1])
@@ -0,0 +1,81 @@
1
+ import json
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ from geoloop.bin.Flowdatamain import main_flow_data
6
+ from geoloop.bin.Lithologymain import main_lithology
7
+ from geoloop.bin.Loadprofilemain import main_load_profile
8
+ from geoloop.bin.Plotmain import main_plotmain
9
+ from geoloop.bin.Runmain import main_runmain
10
+ from geoloop.bin.SingleRunSim import main_single_run_sim
11
+
12
+ command_dict = {
13
+ "SingleRunSim": main_single_run_sim,
14
+ "Runmain": main_runmain,
15
+ "Plotmain": main_plotmain,
16
+ "Loadprofile": main_load_profile,
17
+ "Lithology": main_lithology,
18
+ "FlowData": main_flow_data,
19
+ }
20
+
21
+
22
+ def run_batch_from_json(config_file: str) -> None:
23
+ """
24
+ Execute internal registered command functions defined in a JSON batch file.
25
+
26
+ Parameters
27
+ ----------
28
+ config_file : str
29
+ Path to a JSON file specifying commands to be executed.
30
+
31
+ Notes
32
+ -----
33
+ Expected JSON format::
34
+
35
+ {
36
+ "commands": [
37
+ {"command": "Plotmain", "args": ["config.json"]},
38
+ ...
39
+ ]
40
+ }
41
+
42
+ Raises
43
+ ------
44
+ ValueError
45
+ If an unknown command is encountered.
46
+
47
+ Returns
48
+ -------
49
+ None
50
+ """
51
+ with open(config_file) as file:
52
+ config_path = Path(config_file).resolve()
53
+ base_dir = config_path.parent # directory of the batch file
54
+
55
+ config = json.load(file)
56
+
57
+ for command_spec in config["commands"]:
58
+ command = command_spec["command"]
59
+ args_json = Path(command_spec.get("args", []))
60
+
61
+ if args_json.is_absolute():
62
+ command_config_path = args_json
63
+ else:
64
+ command_config_path = base_dir / args_json
65
+
66
+ if command in command_dict:
67
+ print("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
68
+ print("Running command", command)
69
+ print("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
70
+ command_dict[command](command_config_path)
71
+ else:
72
+ raise ValueError(f"Command {command} not recognized")
73
+
74
+
75
+ def main(args):
76
+ config_file = args[0]
77
+ run_batch_from_json(config_file)
78
+
79
+
80
+ if __name__ == "__main__":
81
+ main(sys.argv[1:])
geoloop/bin/Runmain.py ADDED
@@ -0,0 +1,86 @@
1
+ import sys
2
+ import time
3
+ from pathlib import Path
4
+
5
+ from geoloop.configuration import (
6
+ LithologyConfig,
7
+ SingleRunConfig,
8
+ StochasticRunConfig,
9
+ load_nested_config,
10
+ )
11
+ from geoloop.lithology.process_lithology import ProcessLithologyToThermalConductivity
12
+ from geoloop.utils.helpers import save_MCrun_results
13
+ from geoloop.utils.RunManager import run_models
14
+
15
+
16
+ def main_runmain(config_file_path: str | Path) -> None:
17
+ """
18
+ Run a **stochastic BHE analysis** and write results to disk.
19
+
20
+ This function loads a combined configuration, optionally generates
21
+ lithology-based thermal conductivity samples, executes stochastic BHE
22
+ model runs, and stores output files.
23
+
24
+ Parameters
25
+ ----------
26
+ config_file_path : str or Path
27
+ The path to a JSON configuration file.
28
+
29
+ Notes
30
+ -----
31
+ Expected JSON configuration fields (minimum):
32
+ `variables_config`
33
+ Defines parameter distributions for stochastic evaluation.
34
+
35
+ Returns
36
+ -------
37
+ None
38
+ """
39
+ start_time = time.time()
40
+
41
+ # Load configuration
42
+ keysneeded = ["variables_config"]
43
+ keysoptional = ["litho_k_param", "loadprofile", "borefield", "flow_data"]
44
+ config_dict = load_nested_config(config_file_path, keysneeded, keysoptional)
45
+
46
+ config = SingleRunConfig(**config_dict)
47
+ # load configuration for variable parameters
48
+ config.variables_config = StochasticRunConfig(**config.variables_config)
49
+
50
+ # Optional lithology-based thermal conductivity sample generation
51
+ config.lithology_to_k = None
52
+ if config.litho_k_param:
53
+ lithology_to_k = ProcessLithologyToThermalConductivity.from_config(
54
+ LithologyConfig(**config.litho_k_param)
55
+ )
56
+ lithology_to_k.create_multi_thermcon_profiles()
57
+ config.lithology_to_k = lithology_to_k
58
+
59
+ # Model execution
60
+ parameters, results = run_models(config)
61
+
62
+ print("Saving results and visualizing...")
63
+
64
+ # output directory setup
65
+ if config.run_name:
66
+ runfolder = config.run_name
67
+ else:
68
+ runfolder = Path(config_file_path).stem
69
+ config.run_name = runfolder
70
+
71
+ out_dir = (
72
+ config.base_dir / runfolder
73
+ ) # Check if the runfolder already exists, and create it if not
74
+ out_dir.mkdir(parents=True, exist_ok=True) # creates all missing directories
75
+
76
+ basename = f"{runfolder}_{config.model_type[0]}_{config.run_type[0]}"
77
+ outpath = out_dir / basename
78
+
79
+ # Write results to disk
80
+ save_MCrun_results(config, parameters, results, outpath)
81
+
82
+ print(f"Total runtime: {(time.time() - start_time) / 60.0:.2f} minutes")
83
+
84
+
85
+ if __name__ == "__main__":
86
+ main_runmain(sys.argv[1])