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,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
|
geoloop/bin/Plotmain.py
ADDED
|
@@ -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])
|
geoloop/bin/Runbatch.py
ADDED
|
@@ -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])
|