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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. geoloop/axisym/AxisymetricEL.py +751 -0
  2. geoloop/axisym/__init__.py +3 -0
  3. geoloop/bin/Flowdatamain.py +89 -0
  4. geoloop/bin/Lithologymain.py +84 -0
  5. geoloop/bin/Loadprofilemain.py +100 -0
  6. geoloop/bin/Plotmain.py +250 -0
  7. geoloop/bin/Runbatch.py +81 -0
  8. geoloop/bin/Runmain.py +86 -0
  9. geoloop/bin/SingleRunSim.py +928 -0
  10. geoloop/bin/__init__.py +3 -0
  11. geoloop/cli/__init__.py +0 -0
  12. geoloop/cli/batch.py +106 -0
  13. geoloop/cli/main.py +105 -0
  14. geoloop/configuration.py +946 -0
  15. geoloop/constants.py +112 -0
  16. geoloop/geoloopcore/CoaxialPipe.py +503 -0
  17. geoloop/geoloopcore/CustomPipe.py +727 -0
  18. geoloop/geoloopcore/__init__.py +3 -0
  19. geoloop/geoloopcore/b2g.py +739 -0
  20. geoloop/geoloopcore/b2g_ana.py +535 -0
  21. geoloop/geoloopcore/boreholedesign.py +683 -0
  22. geoloop/geoloopcore/getloaddata.py +112 -0
  23. geoloop/geoloopcore/pyg_ana.py +280 -0
  24. geoloop/geoloopcore/pygfield_ana.py +519 -0
  25. geoloop/geoloopcore/simulationparameters.py +130 -0
  26. geoloop/geoloopcore/soilproperties.py +152 -0
  27. geoloop/geoloopcore/strat_interpolator.py +194 -0
  28. geoloop/lithology/__init__.py +3 -0
  29. geoloop/lithology/plot_lithology.py +277 -0
  30. geoloop/lithology/process_lithology.py +697 -0
  31. geoloop/loadflowdata/__init__.py +3 -0
  32. geoloop/loadflowdata/flow_data.py +161 -0
  33. geoloop/loadflowdata/loadprofile.py +325 -0
  34. geoloop/plotting/__init__.py +3 -0
  35. geoloop/plotting/create_plots.py +1137 -0
  36. geoloop/plotting/load_data.py +432 -0
  37. geoloop/utils/RunManager.py +164 -0
  38. geoloop/utils/__init__.py +0 -0
  39. geoloop/utils/helpers.py +841 -0
  40. geoloop-1.0.0b1.dist-info/METADATA +112 -0
  41. geoloop-1.0.0b1.dist-info/RECORD +46 -0
  42. geoloop-1.0.0b1.dist-info/entry_points.txt +2 -0
  43. geoloop-0.0.1.dist-info/licenses/LICENSE → geoloop-1.0.0b1.dist-info/licenses/LICENSE.md +2 -1
  44. geoloop-0.0.1.dist-info/METADATA +0 -10
  45. geoloop-0.0.1.dist-info/RECORD +0 -6
  46. {geoloop-0.0.1.dist-info → geoloop-1.0.0b1.dist-info}/WHEEL +0 -0
  47. {geoloop-0.0.1.dist-info → geoloop-1.0.0b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,519 @@
1
+ import numpy as np
2
+ import pygfunction as gt
3
+ from matplotlib import pyplot as plt
4
+ from scipy.constants import pi
5
+
6
+ from geoloop.geoloopcore.boreholedesign import BoreholeDesign
7
+ from geoloop.geoloopcore.CoaxialPipe import CoaxialPipe
8
+ from geoloop.geoloopcore.CustomPipe import CustomPipe
9
+ from geoloop.geoloopcore.simulationparameters import SimulationParameters
10
+ from geoloop.geoloopcore.soilproperties import SoilProperties
11
+
12
+
13
+ def inclination_with_depth(
14
+ depth: float, max_depth: float, initial_inclination: float, final_inclination: float
15
+ ) -> float:
16
+ """
17
+ Calculate inclination angle with depth.
18
+
19
+ Parameters
20
+ ----------
21
+ depth : float
22
+ Depth at which to calculate the inclination (m).
23
+ max_depth : float
24
+ Maximum depth of the borehole (m).
25
+ initial_inclination : float
26
+ Inclination angle at the surface (radians).
27
+ final_inclination : float
28
+ Inclination angle at maximum depth (radians).
29
+
30
+ Returns
31
+ -------
32
+ inclination : float
33
+ Inclination angle at the given depth (radians).
34
+ """
35
+ inclination = initial_inclination + (final_inclination - initial_inclination) * (
36
+ depth / max_depth
37
+ )
38
+ return inclination
39
+
40
+
41
+ def create_curved_borehole(
42
+ H: float,
43
+ D: float,
44
+ x: float,
45
+ y: float,
46
+ initial_tilt: float,
47
+ final_tilt: float,
48
+ orientation: float,
49
+ num_segments: int = 10,
50
+ ):
51
+ """
52
+ Create a borehole with varying inclination by approximating it with straight segments.
53
+
54
+ Parameters
55
+ ----------
56
+ H : float
57
+ Borehole length (m).
58
+ D : float
59
+ Borehole burial depth (m)
60
+ x : float
61
+ x-coordinate of the borehole.
62
+ y : float
63
+ y-coordinate of the borehole.
64
+ initial_tilt : float
65
+ Initial borehole inclination (radians).
66
+ final_tilt : float
67
+ Final borehole inclination (radians).
68
+ orientation : float
69
+ Borehole orientation angle (radians).
70
+ num_segments : int, optional
71
+ Number of segments to approximate the varying inclination. The default is 10.
72
+
73
+ Returns
74
+ -------
75
+ segments : list
76
+ List of segment coordinates representing the borehole.
77
+ (x_start, y_start, z_start, x_end, y_end, z_end, tilt, orientation)
78
+ """
79
+ segment_length = H / num_segments
80
+ segments = []
81
+
82
+ # starting point
83
+ x_current, y_current = x, y
84
+ z_current = D
85
+
86
+ for i in range(num_segments):
87
+ # Depth at midpoint of the segment
88
+ depth = (i + 0.5) * segment_length
89
+
90
+ # local tilt at this depth
91
+ tilt = inclination_with_depth(depth, H, initial_tilt, final_tilt)
92
+
93
+ # compute offsets in 3D
94
+ x_offset = segment_length * np.sin(tilt) * np.cos(orientation)
95
+ y_offset = segment_length * np.sin(tilt) * np.sin(orientation)
96
+ z_offset = segment_length * np.cos(tilt)
97
+
98
+ # avoid degeneracy for perfectly vertical segments
99
+ if (x_offset == 0) and (y_offset == 0):
100
+ x_offset = max(1, x_offset)
101
+
102
+ x_end = x_current + x_offset
103
+ y_end = y_current + y_offset
104
+ z_end = z_current + z_offset
105
+
106
+ segments.append(
107
+ (x_current, y_current, z_current, x_end, y_end, z_end, tilt, orientation)
108
+ )
109
+
110
+ # Move to next segment start
111
+ x_current, y_current, z_current = x_end, y_end, z_end
112
+
113
+ return segments
114
+
115
+
116
+ def visualize_3d_borehole_field(borefield: list) -> "Figure":
117
+ """
118
+ Produce a simple 3D matplotlib visualization of a borehole field.
119
+
120
+ Parameters
121
+ ----------
122
+ borefield : list
123
+ List of boreholes, each itself a list of segments
124
+ defined by (x_start, y_start, z_start, x_end, y_end, z_end, tilt, orientation).
125
+
126
+ Returns
127
+ -------
128
+ matplotlib.figure.Figure
129
+ The resulting 3D figure.
130
+ """
131
+ fig = plt.figure()
132
+ ax = fig.add_subplot(111, projection="3d")
133
+
134
+ for segments in borefield:
135
+ for (
136
+ x_start,
137
+ y_start,
138
+ z_start,
139
+ x_end,
140
+ y_end,
141
+ z_end,
142
+ tilt,
143
+ orientation,
144
+ ) in segments:
145
+ ax.plot([x_start, x_end], [y_start, y_end], [-z_start, -z_end], "bo-")
146
+
147
+ ax.set_xlabel("X (m)")
148
+ ax.set_ylabel("Y (m)")
149
+ ax.set_zlabel("Depth (m)")
150
+ ax.set_title("3D Visualization of Borehole Field")
151
+
152
+ return fig
153
+
154
+
155
+ def visualize_gfunc(gfunc: gt.gfunction) -> "Figure":
156
+ """
157
+ Visualize a g-function using the built-in pygfunction plotting utility.
158
+
159
+ Parameters
160
+ ----------
161
+ gfunc : gt.gfunction.gFunction
162
+ Computed g-function object.
163
+
164
+ Returns
165
+ -------
166
+ matplotlib.figure.Figure
167
+ The plotted g-function.
168
+ """
169
+ fig = gfunc.visualize_g_function()
170
+ fig.suptitle("g-function of borehole field")
171
+ fig.tight_layout()
172
+ return fig
173
+
174
+
175
+ class PYGFIELD_ana:
176
+ """
177
+ Field-level simulation driver using pygfunction and an analytical
178
+ approximation for inclined / curved boreholes.
179
+
180
+ This class builds a borehole field from a BoreholeDesign (which may include
181
+ inclined/curved boreholes approximated by segments), computes the g-function,
182
+ runs load aggregation and finally evaluates pipe temperatures using a
183
+ CustomPipe / CoaxialPipe model.
184
+ """
185
+
186
+ def __init__(
187
+ self,
188
+ bh_design: BoreholeDesign,
189
+ custom_pipe: CustomPipe,
190
+ soil_props: SoilProperties,
191
+ sim_params: SimulationParameters,
192
+ ):
193
+ """
194
+ Initialize the PYGFIELD_ana model with a given borehole field configuration.
195
+
196
+ Parameters
197
+ ----------
198
+ bh_design : BoreholeDesign
199
+ Design describing field layout, inclinations and borehole parameters.
200
+ custom_pipe : CustomPipe or CoaxialPipe
201
+ Pipe/borehole model used for temperature calculations.
202
+ soil_props : SoilProperties
203
+ Soil parameters and temperature profile provider.
204
+ sim_params : SimulationParameters
205
+ Simulation control parameters (time vector, loads, flow, run type).
206
+ """
207
+ self.bh_design = bh_design
208
+ self.custom_pipe = custom_pipe
209
+ self.soil_props = soil_props
210
+ self.sim_params = sim_params
211
+
212
+ def runsimulation(self):
213
+ """
214
+ Dispatch to the appropriate simulation routine based on run_type.
215
+ """
216
+ if self.sim_params.run_type == SimulationParameters.POWER:
217
+ return self.runsimulation_power()
218
+
219
+ else:
220
+ # TIN mode is not implemented in this bhe field driver
221
+ raise NotImplementedError("run_type 'TIN' is not supported by PYGFIELD_ana")
222
+
223
+ def runsimulation_power(self) -> tuple:
224
+ """
225
+ Run a power-driven simulation (POWER) for the borehole field.
226
+
227
+ Notes
228
+ ------
229
+ The method:
230
+ - Converts the provided pipe model to a pygfunction-compatible object
231
+ (if necessary),
232
+ - Builds a borehole field according to BoreholeDesign (inclined/curved
233
+ boreholes are approximated by segments),
234
+ - Computes the g-function for the field,
235
+ - Initializes load aggregation (Claesson-Javed) and applies the simulated
236
+ loads, and
237
+ - Calls the pipe model to obtain fluid temperatures and extraction rates.
238
+
239
+ Returns
240
+ -------
241
+ tuple
242
+ (hours, Q_b, flowrate, qsign, T_fi, T_fo, T_bave, z, zseg, T_b,
243
+ T_ftimes, -qbzseg, h_fpipes)
244
+ """
245
+ sim_params = self.sim_params
246
+ custom_pipe = self.custom_pipe
247
+ soil_props = self.soil_props
248
+
249
+ # Extract fluid properties from custom_pipe object
250
+ cp_f = custom_pipe.cp_f
251
+
252
+ # Convert the custompipe to the pyg singleutube design with adopted parameters
253
+ if isinstance(custom_pipe, CoaxialPipe):
254
+ coaxial = custom_pipe.create_coaxial()
255
+ custom_pipe = coaxial
256
+ elif isinstance(custom_pipe, CustomPipe):
257
+ multiUTube = custom_pipe.create_multi_u_tube()
258
+ custom_pipe = multiUTube
259
+
260
+ # only one segment simulated because no depthvar in pyg
261
+ sim_params.nsegments = 1
262
+
263
+ nsegments = sim_params.nsegments
264
+
265
+ # Load aggregation scheme
266
+ LoadAgg = gt.load_aggregation.ClaessonJaved(sim_params.dt, sim_params.tmax)
267
+
268
+ # Geometry and time arrays
269
+ H = custom_pipe.b.H
270
+ D = custom_pipe.b.D
271
+ Nt = sim_params.Nt
272
+ time = sim_params.time
273
+ m_flow = sim_params.m_flow[0]
274
+ scaleflow = sim_params.m_flow / m_flow
275
+
276
+ dz = H / nsegments
277
+ zmin = D + 0.5 * dz
278
+ zmax = D + H - 0.5 * dz
279
+ zseg = np.linspace(zmin, zmax, nsegments)
280
+ zz = np.linspace(D, D + H, nsegments + 1)
281
+ k_s = soil_props.get_k_s(zz[0:-1], zz[1:], sim_params.isample)
282
+
283
+ # Build borefield from BoreholeDesign
284
+ borehole_field = []
285
+ N = self.bh_design.N
286
+ M = self.bh_design.M
287
+ R = self.bh_design.R
288
+ num_segments = self.bh_design.num_tiltedsegments
289
+
290
+ print(
291
+ "N = total nr. of boreholes, "
292
+ "M = nr. of boreholes per side of the field, "
293
+ "R = Distance between boreholes",
294
+ N,
295
+ M,
296
+ R,
297
+ )
298
+
299
+ # Circular / radial arrangement when M <= 0
300
+ if M <= 0:
301
+ initial_tilt = np.deg2rad(self.bh_design.inclination_start)
302
+ final_tilt = np.deg2rad(self.bh_design.inclination_end)
303
+
304
+ # Create curved boreholes and flatten to pyg Borehole objects
305
+ for i in range(N):
306
+ angle = 2 * np.pi * i / N
307
+ x = R * np.cos(angle)
308
+ y = R * np.sin(angle)
309
+
310
+ segments = create_curved_borehole(
311
+ custom_pipe.b.H,
312
+ custom_pipe.b.D,
313
+ x,
314
+ y,
315
+ initial_tilt,
316
+ final_tilt,
317
+ angle,
318
+ num_segments=num_segments,
319
+ )
320
+ borehole_field.append(segments)
321
+
322
+ self.borefield = borehole_field
323
+
324
+ borehole_field_flat = [
325
+ gt.boreholes.Borehole(
326
+ custom_pipe.b.H / num_segments,
327
+ z,
328
+ custom_pipe.b.r_b,
329
+ x,
330
+ y,
331
+ tilt=tilt,
332
+ orientation=orientation,
333
+ )
334
+ for segments in borehole_field
335
+ for (x, y, z, _, _, _, tilt, orientation) in segments
336
+ ]
337
+
338
+ else:
339
+ # Rectangular field arrangement (N must be multiple of M)
340
+ Ng = int(N / M)
341
+ if (Ng * M) != N:
342
+ print("N must be a multiple of M for agrid arrangement")
343
+ exit()
344
+
345
+ B = R
346
+ tilt = 0.5 * (
347
+ np.deg2rad(self.bh_design.inclination_start)
348
+ + np.deg2rad(self.bh_design.inclination_end)
349
+ )
350
+
351
+ borehole_field_flat = gt.borefield.Borefield.rectangle_field(
352
+ Ng,
353
+ M,
354
+ B,
355
+ B,
356
+ custom_pipe.b.H,
357
+ custom_pipe.b.D,
358
+ custom_pipe.b.r_b,
359
+ tilt=tilt,
360
+ )
361
+ gt.borefield.Borefield.visualize_field(borehole_field_flat)
362
+
363
+ self.borefield = borehole_field_flat
364
+
365
+ # Compute g-function for the built field
366
+ alpha = soil_props.alfa
367
+ method = "similarities"
368
+ options = {"nSegments": 1}
369
+
370
+ time_req = LoadAgg.get_times_for_simulation()
371
+
372
+ # Compute g-function (newer pygfunction versions accept keyword args alpha=, time=)
373
+ np.seterr(under="ignore")
374
+ stringbc = "UBWT"
375
+ gFunc = gt.gfunction.gFunction(
376
+ borehole_field_flat,
377
+ alpha,
378
+ time=time_req,
379
+ options=options,
380
+ method=method,
381
+ boundary_condition=stringbc,
382
+ )
383
+ np.seterr(under="warn")
384
+
385
+ # Store for later inspection / plotting
386
+ self.gfunc = gFunc
387
+
388
+ # Initialize load aggregation scheme
389
+ LoadAgg.initialize(gFunc.gFunc / (2 * pi * k_s))
390
+
391
+ Qabsmin = H * 0.1 # assume at least 0.1 W /m to avoid division by zero
392
+
393
+ # Delta temperatures are calculated at the segments
394
+ deltaT_b = np.zeros(Nt)
395
+ deltaT_bk = np.zeros((Nt, nsegments))
396
+ power = np.zeros(Nt)
397
+ Q_b = sim_params.Q / N # per-borehole load
398
+
399
+ for i, (t, Q_b_i) in enumerate(zip(time, Q_b)):
400
+ # Increment time step by (1)
401
+ LoadAgg.next_time_step(t)
402
+
403
+ # avoid the Q_B_i to be zero, it is at least the 10% of H in watts
404
+ if abs(Q_b_i) < Qabsmin:
405
+ Q_b_i = Qabsmin * np.sign(Q_b_i)
406
+
407
+ # Apply current load
408
+ LoadAgg.set_current_load(Q_b_i / H)
409
+
410
+ deltaT_bk[i] = LoadAgg.temporal_superposition()
411
+ deltaT_b[i] = LoadAgg.temporal_superposition()
412
+ power[i] = Q_b_i
413
+
414
+ # Prepare storage arrays
415
+ T_b_top = np.zeros(Nt)
416
+ T_bave = np.zeros(Nt)
417
+ T_fi = np.zeros(Nt)
418
+ T_fo = np.zeros(Nt)
419
+
420
+ Q_b = np.zeros(Nt)
421
+ deltaT_b_ref = np.zeros(Nt)
422
+ flowrate = np.zeros(Nt)
423
+ Rs = np.zeros(Nt)
424
+ qsign = np.zeros(Nt)
425
+
426
+ imax = -1
427
+ for i, t in enumerate(time):
428
+ if (i > imax) and (i < Nt):
429
+ p = power[i]
430
+ Rs[i] = deltaT_b[i] / (p / H)
431
+
432
+ minR = 0.01
433
+ Rs = np.where(np.logical_and(Rs < minR, Rs > -0.5), minR, Rs)
434
+
435
+ # Iterative refinement to compute pipe & borehole temperatures
436
+ iter = 0
437
+ niter = 3
438
+ # Rsz not filled because not used in pyg-function
439
+ qbzseg = np.zeros((len(Rs), nsegments))
440
+ T_b = np.zeros((len(Rs), nsegments))
441
+ h_fpipes = np.zeros(
442
+ (len(Rs), (custom_pipe.nInlets + custom_pipe.nOutlets) * custom_pipe.nPipes)
443
+ )
444
+
445
+ # nPipes is defined differently for standard pyg and the analytical model; in pyg nPipes defines the number of utubes,
446
+ # in the analytical model nPipes defines the nr of pipes
447
+ nz = 20
448
+ z = np.linspace(0.0, H, num=nz)
449
+ T_ftimes = np.zeros(
450
+ (
451
+ len(Rs),
452
+ nz,
453
+ (custom_pipe.nInlets + custom_pipe.nOutlets) * custom_pipe.nPipes,
454
+ )
455
+ )
456
+
457
+ # soil temperature
458
+ T_g = soil_props.getTg(zseg)
459
+
460
+ while iter < niter:
461
+ iter += 1
462
+ imax = -1
463
+
464
+ for i, t in enumerate(time):
465
+ if (i > imax) and (i < Nt):
466
+ p = power[i]
467
+ h_f = custom_pipe.h_f
468
+
469
+ T_b[i] = np.asarray(T_g - deltaT_b[i])
470
+
471
+ T_f_in = custom_pipe.get_inlet_temperature(
472
+ p, T_b[i], scaleflow[i] * m_flow, cp_f
473
+ )
474
+ T_f_out = custom_pipe.get_outlet_temperature(
475
+ T_f_in, T_b[i], scaleflow[i] * m_flow, cp_f
476
+ )
477
+
478
+ # To compare between depth variation between pyg and ana; Evaluate temperatures fro pyg at nz evenly spaced depths along the borehole
479
+ # at the (it+1)-th time step
480
+ T_f = custom_pipe.get_temperature(
481
+ z, T_f_in, T_b[i], scaleflow[i] * m_flow, cp_f
482
+ )
483
+
484
+ qbz = custom_pipe.get_total_heat_extraction_rate(
485
+ T_f_in, T_b[i], scaleflow[i] * m_flow, cp_f
486
+ )
487
+
488
+ # Store outputs per time-step
489
+ Q_b[i] = power[i]
490
+ T_b_top[i] = T_b[i][0]
491
+ deltaT_b_ref[i] = deltaT_b[i]
492
+ T_bave[i] = np.average(T_b[i, :])
493
+ T_fi[i] = T_f_in
494
+ T_fo[i] = T_f_out
495
+ h_fpipes[i] = h_f
496
+ flowrate[i] = scaleflow[i] * m_flow
497
+ T_ftimes[i] = T_f # stored index time, depth, pipe
498
+ # zseg 1 value less than z
499
+ qbzseg[i] = qbz / custom_pipe.b.H
500
+ qsign[i] = np.sign(qbz)
501
+ imax = i
502
+
503
+ hours = time / 3600.0
504
+
505
+ return (
506
+ hours,
507
+ Q_b,
508
+ flowrate,
509
+ qsign,
510
+ T_fi,
511
+ T_fo,
512
+ T_bave,
513
+ z,
514
+ zseg,
515
+ T_b,
516
+ T_ftimes,
517
+ -qbzseg,
518
+ h_fpipes,
519
+ )
@@ -0,0 +1,130 @@
1
+ import numpy as np
2
+
3
+ from geoloop.configuration import SingleRunConfig
4
+ from geoloop.geoloopcore.getloaddata import GetLoadData
5
+
6
+
7
+ class SimulationParameters:
8
+ """
9
+ Class for simulation settings, time discretization,
10
+ load data, and model selection for BHE simulations.
11
+ """
12
+
13
+ # model types
14
+ FINVOL = "FINVOL" # finite volume approach with borehole heat exchanger system, can only run with run_type TIN
15
+ ANALYTICAL = (
16
+ "ANALYTICAL" # semi-analytical approach, can run with both TIN and POWER
17
+ )
18
+ PYG = "PYG" # 'standard' pyg approach, only supports run type POWER
19
+ PYGFIELD = (
20
+ "PYGFIELD" # pyg curved borehole array approach, only supports run type POWER
21
+ )
22
+
23
+ # run types
24
+ TIN = "TIN" # input temperature
25
+ POWER = "POWER" # input power
26
+
27
+ def __init__(
28
+ self,
29
+ nyear: int,
30
+ nled: int,
31
+ model_type: str,
32
+ run_type: str,
33
+ nsegments: int,
34
+ nr: int = None,
35
+ rmax: float = None,
36
+ loaddata: GetLoadData = None,
37
+ ) -> None:
38
+ """
39
+ Constructor for the SimulationParameters class.
40
+
41
+ Parameters
42
+ ----------
43
+ nyear : int
44
+ Number of simulation years.
45
+ nled : int
46
+ Time resolution factor (dt = 3600 * nled).
47
+ model_type : str
48
+ One of {FINVOL, ANALYTICAL, PYG, PYGFIELD}.
49
+ run_type : str
50
+ Either TIN or POWER.
51
+ nsegments : int
52
+ Number of depth segments used in the borehole solver.
53
+ nr : int, optional
54
+ Number of radial nodes (FINVOL only).
55
+ rmax : float, optional
56
+ Outer radius for radial simulation domain (FINVOL only).
57
+ loaddata : GetLoadData
58
+ Load data object providing Tin, Q, m_flow, and time arrays.
59
+ """
60
+ # time discretization
61
+ self.nyear = nyear
62
+ self.tmax = nyear * 8760.0 * 3600.0 # Maximum time (s)
63
+ self.nled = nled
64
+ self.dt = 3600 * nled # Time step (s) , 100h
65
+ self.Nt = int(np.ceil(self.tmax / self.dt)) # Number of time steps
66
+ self.time = self.dt * np.arange(1, self.Nt + 1) # time in seconds
67
+
68
+ # Geometry parameters only used in the FINVOL model
69
+ self.nr = nr
70
+ self.rmax = rmax
71
+
72
+ # load data
73
+ self.loaddata = loaddata.data
74
+
75
+ self.m_flow = loaddata.m_flow
76
+ self.Tin = loaddata.Tin
77
+ self.Q = loaddata.Q
78
+
79
+ # model configuration
80
+ self.model_type = model_type
81
+ self.run_type = run_type
82
+ self.nsegments = nsegments
83
+
84
+ # set the efficency of the pump to 0.65
85
+ self.eff = 0.65
86
+
87
+ @property
88
+ def isample(self):
89
+ """Index of the current simulation timestep."""
90
+ return self._isample
91
+
92
+ @isample.setter
93
+ def isample(self, value: int) -> None:
94
+ self._isample = value
95
+
96
+ def modelsupported(self) -> None:
97
+ """Prints a warning for unsupported model/run combinations."""
98
+ print("Model not supported ", self.model_type, " with mode ", self)
99
+
100
+ @classmethod
101
+ def from_config(cls, config: SingleRunConfig) -> "SimulationParameters":
102
+ """
103
+ Construct a SimulationParameters object from a configuration object.
104
+
105
+ Parameters
106
+ ----------
107
+ config : SingleRunConfig
108
+ Configuration object.
109
+
110
+ Returns
111
+ -------
112
+ SimulationParameters
113
+ Fully initialized simulation parameter object.
114
+ """
115
+ nr = config.nr if config.model_type == cls.FINVOL else None
116
+ rmax = config.r_sim if config.model_type == cls.FINVOL else None
117
+
118
+ # Build GetLoadData only if present
119
+ load_data_obj = GetLoadData.from_config(config)
120
+
121
+ return cls(
122
+ nyear=config.nyear,
123
+ nled=config.nled,
124
+ model_type=config.model_type,
125
+ run_type=config.run_type,
126
+ nsegments=config.nsegments,
127
+ nr=nr,
128
+ rmax=rmax,
129
+ loaddata=load_data_obj,
130
+ )