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,112 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from geoloop.configuration import FlowDataConfig, LoadProfileConfig, SingleRunConfig
|
|
4
|
+
from geoloop.loadflowdata.flow_data import FlowData
|
|
5
|
+
from geoloop.loadflowdata.loadprofile import LoadProfile
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GetLoadData:
|
|
9
|
+
"""
|
|
10
|
+
Class to generate time series of flow rates, inlet temperatures, and heat/power loads
|
|
11
|
+
for simulations.
|
|
12
|
+
|
|
13
|
+
It can use constant input values or optional time-dependent variations from either
|
|
14
|
+
a FlowData object (variable flow rates) or a LoadProfile object (variable heat load).
|
|
15
|
+
|
|
16
|
+
Attributes
|
|
17
|
+
----------
|
|
18
|
+
Tin : np.ndarray
|
|
19
|
+
Inlet temperature array (°C) over the simulation time (constant through time).
|
|
20
|
+
m_flow : np.ndarray
|
|
21
|
+
Mass flow rate array (kg/s) over the simulation time.
|
|
22
|
+
Q : np.ndarray
|
|
23
|
+
Heat load array (W) over the simulation time.
|
|
24
|
+
time : np.ndarray
|
|
25
|
+
Time array in seconds.
|
|
26
|
+
data : FlowData or LoadProfile, optional
|
|
27
|
+
Optional object providing time-dependent flow or load.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# model types
|
|
31
|
+
FINVOL = "FINVOL" # finite volume approach with borehole heat exchanger system, can only run with run_type TIN
|
|
32
|
+
ANALYTICAL = (
|
|
33
|
+
"ANALYTICAL" # semi-analytical approach, can run with both TIN and POWER
|
|
34
|
+
)
|
|
35
|
+
PYG = "PYG" # 'standard' pyg approach,
|
|
36
|
+
|
|
37
|
+
# run types
|
|
38
|
+
TIN = "TIN" # input temperature
|
|
39
|
+
POWER = "POWER" # input power
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
Tin: float,
|
|
44
|
+
m_flow: float,
|
|
45
|
+
Q: float,
|
|
46
|
+
time: np.ndarray,
|
|
47
|
+
loaddata: LoadProfile | FlowData = None,
|
|
48
|
+
):
|
|
49
|
+
"""
|
|
50
|
+
Initialize the GetLoadData object.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
Tin : float
|
|
55
|
+
Constant inlet temperature (°C) if no LoadData is provided.
|
|
56
|
+
m_flow : float
|
|
57
|
+
Constant mass flow rate (kg/s) if no LoadData is provided.
|
|
58
|
+
Q : float
|
|
59
|
+
Constant heat load (W) if no LoadData is provided.
|
|
60
|
+
time : np.ndarray
|
|
61
|
+
Array of time points (seconds) for the simulation.
|
|
62
|
+
loaddata : FlowData or LoadProfile, optional
|
|
63
|
+
Optional object providing variable flow or load data.
|
|
64
|
+
"""
|
|
65
|
+
self.data = loaddata
|
|
66
|
+
|
|
67
|
+
if isinstance(loaddata, FlowData):
|
|
68
|
+
self.Tin = np.ones_like(time) * Tin
|
|
69
|
+
self.Q = np.ones_like(time) * Q
|
|
70
|
+
self.m_flow = loaddata.getflow(time / 3600.0)
|
|
71
|
+
elif isinstance(loaddata, LoadProfile):
|
|
72
|
+
self.Tin = np.ones_like(time) * Tin
|
|
73
|
+
self.Q, self.m_flow = loaddata.getloadflow(time / 3600.0, m_flow)
|
|
74
|
+
elif loaddata is None:
|
|
75
|
+
self.Tin = np.ones_like(time) * Tin
|
|
76
|
+
self.Q = np.ones_like(time) * Q
|
|
77
|
+
self.m_flow = np.ones_like(time) * m_flow
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_config(cls, config: SingleRunConfig) -> "GetLoadData":
|
|
81
|
+
"""
|
|
82
|
+
Factory method to create a GetLoadData object from a configuration dictionary.
|
|
83
|
+
|
|
84
|
+
This method constructs the time array and optionally initializes either
|
|
85
|
+
a FlowData or LoadProfile object depending on the run type.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
config : SingleRunConfig
|
|
90
|
+
Configuration object of main simulation module
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
GetLoadData
|
|
95
|
+
Initialized object with time series for flow, temperature, and heat load.
|
|
96
|
+
"""
|
|
97
|
+
# Build time array
|
|
98
|
+
tmax = config.nyear * 8760 * 3600
|
|
99
|
+
dt = 3600 * config.nled
|
|
100
|
+
Nt = int(np.ceil(tmax / dt))
|
|
101
|
+
time = dt * np.arange(1, Nt + 1)
|
|
102
|
+
|
|
103
|
+
# Determine optional subobject
|
|
104
|
+
data = None
|
|
105
|
+
if config.run_type == "POWER" and config.loadprofile:
|
|
106
|
+
loadprofile_config = LoadProfileConfig(**config.loadprofile)
|
|
107
|
+
data = LoadProfile.from_config(loadprofile_config)
|
|
108
|
+
elif config.run_type == "TIN" and config.flow_data:
|
|
109
|
+
flow_data_config = FlowDataConfig(**config.flow_data)
|
|
110
|
+
data = FlowData.from_config(flow_data_config)
|
|
111
|
+
|
|
112
|
+
return cls(config.Tin, config.m_flow, config.Q, time, loaddata=data)
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pygfunction as gt
|
|
3
|
+
from scipy.constants import pi
|
|
4
|
+
|
|
5
|
+
from geoloop.geoloopcore.CoaxialPipe import CoaxialPipe
|
|
6
|
+
from geoloop.geoloopcore.CustomPipe import CustomPipe
|
|
7
|
+
from geoloop.geoloopcore.simulationparameters import SimulationParameters
|
|
8
|
+
from geoloop.geoloopcore.soilproperties import SoilProperties
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PYG_ana:
|
|
12
|
+
"""
|
|
13
|
+
Class to simulate a borehole heat exchanger using pygfunction for the determination of the thermal resistivity
|
|
14
|
+
network of the borehole and analytical g-function.
|
|
15
|
+
|
|
16
|
+
Attributes
|
|
17
|
+
----------
|
|
18
|
+
custom_pipe : CustomPipe
|
|
19
|
+
Depth-dependent borehole configuration and pipe properties.
|
|
20
|
+
soil_props : SoilProperties
|
|
21
|
+
Depth-dependent soil properties (thermal conductivity, temperature profile).
|
|
22
|
+
sim_params : SimulationParameters
|
|
23
|
+
Simulation parameters including time, flow, and input power or temperature.
|
|
24
|
+
|
|
25
|
+
Notes
|
|
26
|
+
-----
|
|
27
|
+
The simulation can be run for power and calls the appropriate function.
|
|
28
|
+
`runsimulation_power` is called for power simulations.
|
|
29
|
+
|
|
30
|
+
References
|
|
31
|
+
----------
|
|
32
|
+
.. [#Cimmino2022] Cimmino, M., & Cook, J.C. (2022). pygfunction 2.2: New features and improvements in accuracy and computational efficiency.
|
|
33
|
+
In Research Conference Proceedings, IGSHPA Annual Conference 2022 (pp. 45-52).
|
|
34
|
+
International Ground Source Heat Pump Association. DOI: https://doi.org/10.22488/okstate.22.00001
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
custom_pipe: CustomPipe,
|
|
40
|
+
soil_props: SoilProperties,
|
|
41
|
+
sim_params: SimulationParameters,
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
Constructor for the PYG_ana class.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
custom_pipe : CustomPipe
|
|
49
|
+
CustomPipe object defining borehole and pipe properties.
|
|
50
|
+
soil_props : SoilProperties
|
|
51
|
+
SoilProperties object defining soil thermal properties.
|
|
52
|
+
sim_params : SimulationParameters
|
|
53
|
+
SimulationParameters object defining time step, flow rate, and load.
|
|
54
|
+
"""
|
|
55
|
+
self.custom_pipe = custom_pipe
|
|
56
|
+
self.soil_props = soil_props
|
|
57
|
+
self.sim_params = sim_params
|
|
58
|
+
|
|
59
|
+
def runsimulation(self):
|
|
60
|
+
"""
|
|
61
|
+
Main entry point to run the simulation.
|
|
62
|
+
"""
|
|
63
|
+
if self.sim_params.run_type == SimulationParameters.POWER:
|
|
64
|
+
return self.runsimulation_power()
|
|
65
|
+
else:
|
|
66
|
+
print("run_type 'TIN' not supported for pyg_ana model type")
|
|
67
|
+
|
|
68
|
+
def runsimulation_power(self):
|
|
69
|
+
"""
|
|
70
|
+
Run the simulation of the borehole to ground heat exchanger for an input heat demand.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
tuple
|
|
75
|
+
hours, Q_b, flowrate, qsign, T_fi, T_fo, T_bave, z, zseg, T_b, T_ftimes, -qbzseg, h_fpipes
|
|
76
|
+
"""
|
|
77
|
+
sim_params = self.sim_params
|
|
78
|
+
custom_pipe = self.custom_pipe
|
|
79
|
+
soil_props = self.soil_props
|
|
80
|
+
|
|
81
|
+
# Extract fluid properties from custom_pipe object
|
|
82
|
+
cp_f = custom_pipe.cp_f
|
|
83
|
+
|
|
84
|
+
# Convert the custompipe to the pyg singleutube design with adopted parameters
|
|
85
|
+
if isinstance(custom_pipe, CoaxialPipe):
|
|
86
|
+
coaxial = custom_pipe.create_coaxial()
|
|
87
|
+
custom_pipe = coaxial
|
|
88
|
+
elif isinstance(custom_pipe, CustomPipe):
|
|
89
|
+
multiUTube = custom_pipe.create_multi_u_tube()
|
|
90
|
+
custom_pipe = multiUTube
|
|
91
|
+
|
|
92
|
+
# only one segment simulated because no depth-variation in pyg
|
|
93
|
+
sim_params.nsegments = 1
|
|
94
|
+
nsegments = sim_params.nsegments
|
|
95
|
+
|
|
96
|
+
# Load aggregation scheme
|
|
97
|
+
LoadAgg = gt.load_aggregation.ClaessonJaved(sim_params.dt, sim_params.tmax)
|
|
98
|
+
|
|
99
|
+
# g-function set-up
|
|
100
|
+
H = custom_pipe.b.H
|
|
101
|
+
D = custom_pipe.b.D
|
|
102
|
+
Nt = sim_params.Nt
|
|
103
|
+
time = sim_params.time
|
|
104
|
+
m_flow = sim_params.m_flow[0]
|
|
105
|
+
scaleflow = sim_params.m_flow / m_flow
|
|
106
|
+
|
|
107
|
+
dz = H / nsegments
|
|
108
|
+
zmin = D + 0.5 * dz
|
|
109
|
+
zmax = D + H - 0.5 * dz
|
|
110
|
+
zseg = np.linspace(zmin, zmax, nsegments)
|
|
111
|
+
zz = np.linspace(D, D + H, nsegments + 1)
|
|
112
|
+
|
|
113
|
+
k_s = soil_props.get_k_s(zz[0:-1], zz[1:], sim_params.isample)
|
|
114
|
+
|
|
115
|
+
# The field contains only one borehole, but needs one extra at very large distance to be correct, ie. gfunc plateaus at 6.7)
|
|
116
|
+
boreField = [
|
|
117
|
+
gt.boreholes.Borehole(
|
|
118
|
+
custom_pipe.b.H,
|
|
119
|
+
custom_pipe.b.D,
|
|
120
|
+
custom_pipe.b.r_b,
|
|
121
|
+
x=0.0,
|
|
122
|
+
y=0.0,
|
|
123
|
+
tilt=np.radians(0.1),
|
|
124
|
+
),
|
|
125
|
+
gt.boreholes.Borehole(
|
|
126
|
+
custom_pipe.b.H,
|
|
127
|
+
custom_pipe.b.D,
|
|
128
|
+
custom_pipe.b.r_b,
|
|
129
|
+
x=1000.0,
|
|
130
|
+
y=0.0,
|
|
131
|
+
tilt=np.radians(0.1),
|
|
132
|
+
),
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
# Get time values needed for g-function evaluation
|
|
136
|
+
time_req = LoadAgg.get_times_for_simulation()
|
|
137
|
+
|
|
138
|
+
# Calculate g-function
|
|
139
|
+
# g-Function calculation options
|
|
140
|
+
options = {"nSegments": 8, "disp": False}
|
|
141
|
+
np.seterr(under="ignore")
|
|
142
|
+
|
|
143
|
+
alpha = soil_props.alfa
|
|
144
|
+
gFunc = gt.gfunction.gFunction(
|
|
145
|
+
boreField, alpha, time=time_req, options=options, method="similarities"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Initialize load aggregation scheme
|
|
149
|
+
LoadAgg.initialize(gFunc.gFunc / (2 * pi * k_s))
|
|
150
|
+
|
|
151
|
+
Qabsmin = H * 0.1 # assume at least 0.1 W /m to avoid division by zero
|
|
152
|
+
|
|
153
|
+
# Delta temperatures are calculated at the segments
|
|
154
|
+
deltaT_b = np.zeros(Nt)
|
|
155
|
+
deltaT_bk = np.zeros((Nt, nsegments))
|
|
156
|
+
power = np.zeros(Nt)
|
|
157
|
+
Q_b = sim_params.Q
|
|
158
|
+
|
|
159
|
+
for i, (t, Q_b_i) in enumerate(zip(time, Q_b)):
|
|
160
|
+
# Increment time step by (1)
|
|
161
|
+
LoadAgg.next_time_step(t)
|
|
162
|
+
|
|
163
|
+
# avoid the Q_B_i to be zero, it is at least the 10% of H in watts
|
|
164
|
+
if abs(Q_b_i) < Qabsmin:
|
|
165
|
+
Q_b_i = Qabsmin * np.sign(Q_b_i)
|
|
166
|
+
|
|
167
|
+
# Apply current load
|
|
168
|
+
LoadAgg.set_current_load(Q_b_i / H)
|
|
169
|
+
|
|
170
|
+
deltaT_bk[i] = LoadAgg.temporal_superposition()
|
|
171
|
+
deltaT_b[i] = LoadAgg.temporal_superposition()
|
|
172
|
+
|
|
173
|
+
power[i] = Q_b_i
|
|
174
|
+
|
|
175
|
+
ntlow = Nt
|
|
176
|
+
T_b_top = np.zeros(ntlow)
|
|
177
|
+
T_bave = np.zeros(ntlow)
|
|
178
|
+
T_fi = np.zeros(ntlow)
|
|
179
|
+
T_fo = np.zeros(ntlow)
|
|
180
|
+
|
|
181
|
+
Q_b = np.zeros(ntlow)
|
|
182
|
+
deltaT_b_ref = np.zeros(ntlow)
|
|
183
|
+
flowrate = np.zeros(ntlow)
|
|
184
|
+
Rs = np.zeros(ntlow)
|
|
185
|
+
qsign = np.zeros(ntlow)
|
|
186
|
+
|
|
187
|
+
imax = -1
|
|
188
|
+
for i, t in enumerate(time):
|
|
189
|
+
if (i > imax) and (i < ntlow):
|
|
190
|
+
p = power[i]
|
|
191
|
+
Rs[i] = deltaT_b[i] / (p / H)
|
|
192
|
+
|
|
193
|
+
minR = 0.01
|
|
194
|
+
Rs = np.where(np.logical_and(Rs < minR, Rs > -0.5), minR, Rs)
|
|
195
|
+
|
|
196
|
+
iter = 0
|
|
197
|
+
niter = 3
|
|
198
|
+
# Rsz not filled because not used in pyg-function
|
|
199
|
+
qbzseg = np.zeros((len(Rs), nsegments))
|
|
200
|
+
T_b = np.zeros((len(Rs), nsegments))
|
|
201
|
+
h_fpipes = np.zeros(
|
|
202
|
+
(len(Rs), (custom_pipe.nInlets + custom_pipe.nOutlets) * custom_pipe.nPipes)
|
|
203
|
+
)
|
|
204
|
+
# nPipes is defined differently for standard pyg and the analytical model; in pyg nPipes defines the number of utubes,
|
|
205
|
+
# in the analytical model nPipes defines the nr of pipes
|
|
206
|
+
nz = 20
|
|
207
|
+
z = np.linspace(0.0, H, num=nz)
|
|
208
|
+
T_ftimes = np.zeros(
|
|
209
|
+
(
|
|
210
|
+
len(Rs),
|
|
211
|
+
nz,
|
|
212
|
+
(custom_pipe.nInlets + custom_pipe.nOutlets) * custom_pipe.nPipes,
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
T_g = soil_props.getTg(zseg)
|
|
216
|
+
|
|
217
|
+
while iter < niter:
|
|
218
|
+
iter += 1
|
|
219
|
+
imax = -1
|
|
220
|
+
|
|
221
|
+
for i, t in enumerate(time):
|
|
222
|
+
if (i > imax) and (i < ntlow):
|
|
223
|
+
# print(il)
|
|
224
|
+
p = power[i]
|
|
225
|
+
|
|
226
|
+
h_f = custom_pipe.h_f
|
|
227
|
+
|
|
228
|
+
T_b[i] = np.asarray(T_g - deltaT_b[i])
|
|
229
|
+
|
|
230
|
+
T_f_in = custom_pipe.get_inlet_temperature(
|
|
231
|
+
p, T_b[i], scaleflow[i] * m_flow, cp_f
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
T_f_out = custom_pipe.get_outlet_temperature(
|
|
235
|
+
T_f_in, T_b[i], scaleflow[i] * m_flow, cp_f
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# To compare between depth variation between pyg and ana; Evaluate temperatures fro pyg at nz evenly spaced depths along the borehole
|
|
239
|
+
# at the (it+1)-th time step
|
|
240
|
+
T_f = custom_pipe.get_temperature(
|
|
241
|
+
z, T_f_in, T_b[i], scaleflow[i] * m_flow, cp_f
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
qbz = custom_pipe.get_total_heat_extraction_rate(
|
|
245
|
+
T_f_in, T_b[i], scaleflow[i] * m_flow, cp_f
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
Q_b[i] = power[i]
|
|
249
|
+
T_b_top[i] = T_b[i][0]
|
|
250
|
+
deltaT_b_ref[i] = deltaT_b[i]
|
|
251
|
+
|
|
252
|
+
T_bave[i] = np.average(T_b[i, :])
|
|
253
|
+
|
|
254
|
+
T_fi[i] = T_f_in
|
|
255
|
+
T_fo[i] = T_f_out
|
|
256
|
+
h_fpipes[i] = h_f
|
|
257
|
+
flowrate[i] = scaleflow[i] * m_flow
|
|
258
|
+
T_ftimes[i] = T_f # stored index time, depth, pipe
|
|
259
|
+
# zseg 1 value less than z
|
|
260
|
+
qbzseg[i] = qbz / custom_pipe.b.H
|
|
261
|
+
qsign[i] = np.sign(qbz)
|
|
262
|
+
imax = i
|
|
263
|
+
|
|
264
|
+
hours = time / 3600.0
|
|
265
|
+
|
|
266
|
+
return (
|
|
267
|
+
hours,
|
|
268
|
+
Q_b,
|
|
269
|
+
flowrate,
|
|
270
|
+
qsign,
|
|
271
|
+
T_fi,
|
|
272
|
+
T_fo,
|
|
273
|
+
T_bave,
|
|
274
|
+
z,
|
|
275
|
+
zseg,
|
|
276
|
+
T_b,
|
|
277
|
+
T_ftimes,
|
|
278
|
+
-qbzseg,
|
|
279
|
+
h_fpipes,
|
|
280
|
+
)
|