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,161 @@
1
+ from pathlib import Path
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+
6
+ from geoloop.configuration import FlowDataConfig
7
+ from geoloop.utils.helpers import apply_smoothing
8
+
9
+
10
+ class FlowData:
11
+ """
12
+ Class to handle flow rate data for simulations, either as constant, variable, or from a CSV file.
13
+
14
+ This class provides time-dependent flow data (in kg/s) for simulations. It supports
15
+ three types of flow profiles:
16
+
17
+ - CONSTANT: fixed flow rate over time.
18
+ - VARIABLE: synthetic sinusoidal variation over the year.
19
+ - FROMFILE: flow rates read from a CSV file, optionally smoothed.
20
+
21
+ Parameters
22
+ ----------
23
+ type : str
24
+ Flow type: "CONSTANT", "VARIABLE", or "FROMFILE".
25
+ base_flow : float, optional
26
+ Base flow rate (kg/s), default is 1.0.
27
+ peak_flow : float, optional
28
+ Peak flow rate (kg/s) for VARIABLE type, default is 1.0.
29
+ filepath : str, optional
30
+ Path to CSV file with flow data, required for FROMFILE type.
31
+ outdir : str, optional
32
+ Directory where smoothed CSVs are saved.
33
+ scale : float, optional
34
+ Scaling factor for flow data, default is 1.0.
35
+ inputcolumn : str, optional
36
+ Column name in CSV with flow data, default is "m_flow".
37
+ smoothing : int or str, optional
38
+ Smoothing for FROMFILE data:
39
+ - int: rolling average window in samples.
40
+ - "D": daily average (requires 'local_time' column).
41
+ - "M": monthly average (requires 'local_time' column).
42
+ - None or "none": no smoothing.
43
+
44
+ """
45
+
46
+ CONSTANT = "CONSTANT"
47
+ VARIABLE = "VARIABLE"
48
+ FROMFILE = "FROMFILE"
49
+
50
+ def __init__(
51
+ self,
52
+ type: str,
53
+ base_flow: float = 1.0,
54
+ peak_flow: float = 1.0,
55
+ filepath: str | Path | None = None,
56
+ outdir: str | Path | None = None,
57
+ inputdir: str | Path | None = None,
58
+ scale: float = 1.0,
59
+ inputcolumn: str = "m_flow",
60
+ smoothing: int | str | None = None,
61
+ ) -> None:
62
+ """
63
+ Initialize the FlowData object.
64
+ """
65
+ self.type = type
66
+ self.base_flow = base_flow
67
+ self.peak_flow = peak_flow
68
+ self.filepath = filepath
69
+ self.outdir = outdir
70
+ self.inputdir = inputdir
71
+ self.scale = scale
72
+ self.inputcolumn = inputcolumn
73
+ self.smoothing = smoothing
74
+
75
+ # Load CSV if needed
76
+ if self.type == FlowData.FROMFILE:
77
+ self.flow_data = pd.read_csv(filepath)
78
+ if self.smoothing is not None:
79
+ self.flow_data = apply_smoothing(
80
+ self.flow_data,
81
+ column=self.inputcolumn,
82
+ smoothing=self.smoothing,
83
+ outdir=self.outdir,
84
+ prefix="flow",
85
+ )
86
+
87
+ def getflow(self, x: np.ndarray) -> np.ndarray:
88
+ """
89
+ Return flow values at specified times.
90
+
91
+ Parameters
92
+ ----------
93
+ x : array-like
94
+ Array of time points in hours at which to get flow values.
95
+
96
+ Returns
97
+ -------
98
+ np.ndarray
99
+ Flow rates (kg/s) (interpolated or analytically computed) at the requested time points.
100
+ """
101
+ if self.type == FlowData.CONSTANT:
102
+ return np.ones_like(x) * self.peak_flow
103
+
104
+ elif self.type == FlowData.VARIABLE:
105
+ # One-year sinusoidal fluctuation (period = 8760 hours)
106
+ arg = 2 * np.pi * x / 8760
107
+ return (self.peak_flow - self.base_flow) / 2 * (
108
+ 1 + np.cos(arg)
109
+ ) + self.base_flow
110
+
111
+ elif self.type == FlowData.FROMFILE:
112
+ flowdata = np.asarray(self.flow_data[self.inputcolumn])
113
+ hours = np.arange(len(flowdata))
114
+ return np.interp(x, hours, flowdata, period=len(hours)) * self.scale
115
+
116
+ else:
117
+ raise ValueError(f"Unsupported FlowData type: {self.type}")
118
+
119
+ @classmethod
120
+ def from_config(cls, config: FlowDataConfig) -> "FlowData | None":
121
+ """
122
+ Create a FlowData instance from a configuration dictionary.
123
+
124
+ The configuration dictionary must include keys defining the flow type
125
+ and file paths if applicable.
126
+
127
+ Parameters
128
+ ----------
129
+ config : FlowDataConfig
130
+ Configuration object with keys.
131
+
132
+ Returns
133
+ -------
134
+ FlowData
135
+ Initialized FlowData object.
136
+ """
137
+ if config.fp_type == "CONSTANT":
138
+ return cls(
139
+ config.fp_type, peak_flow=config.fp_peak, outdir=config.fp_outdir
140
+ )
141
+
142
+ elif config.fp_type == "VARIABLE":
143
+ return cls(
144
+ config.fp_type,
145
+ base_flow=config.fp_base,
146
+ peak_flow=config.fp_peak,
147
+ outdir=config.fp_outdir,
148
+ )
149
+
150
+ elif config.fp_type == "FROMFILE":
151
+ return cls(
152
+ config.fp_type,
153
+ filepath=config.fp_filepath,
154
+ outdir=config.fp_outdir,
155
+ inputdir=config.fp_inputdir,
156
+ scale=config.fp_scale,
157
+ inputcolumn=config.fp_inputcolumn,
158
+ smoothing=config.fp_smoothing,
159
+ )
160
+ else:
161
+ print("No type of flow data profile not supported")
@@ -0,0 +1,325 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ from geoloop.configuration import LoadProfileConfig
5
+ from geoloop.utils.helpers import apply_smoothing
6
+
7
+
8
+ class LoadProfile:
9
+ """
10
+ Class to generate heat load profiles for simulations. Supports constant, variable, Bernier synthetic,
11
+ or from-file time series of heat demand.
12
+
13
+ Attributes
14
+ ----------
15
+ type : str
16
+ Type of load profile: 'BERNIER', 'CONSTANT', 'VARIABLE', 'FROMFILE'.
17
+ base : float
18
+ Base heat load (W) for VARIABLE type.
19
+ peak : float
20
+ Peak heat load (W) for VARIABLE type.
21
+ filepath : str
22
+ Path to CSV file if FROMFILE type is used.
23
+ outdir : str
24
+ Directory to save processed outputs (e.g., smoothed data).
25
+ scale : float
26
+ Scaling factor for loads read from file.
27
+ minQ : float
28
+ Minimum heat load (W) when using FROMFILE type.
29
+ minscaleflow : float
30
+ Minimum scaling factor for flow computation.
31
+ inputcolumn : str
32
+ Name of the column in CSV file containing heat demand.
33
+ smoothing : int or str, optional
34
+ Smoothing applied to time series: int for rolling window, 'D' or 'M' for daily/monthly.
35
+ table : pd.DataFrame
36
+ Loaded table when using FROMFILE type.
37
+ """
38
+
39
+ BERNIER = "BERNIER" # finite volume approach, can only run with run_type TIN
40
+ CONSTANT = "CONSTANT" # constant load, base mis
41
+ VARIABLE = "VARIABLE"
42
+ FROMFILE = "FROMFILE"
43
+
44
+ def __init__(
45
+ self,
46
+ type,
47
+ base=4000,
48
+ peak=4000,
49
+ filepath=None,
50
+ outdir=None,
51
+ inputdir=None,
52
+ scale=1,
53
+ minQ=0,
54
+ minscaleflow=1,
55
+ inputcolumn="heating_demand",
56
+ smoothing=None,
57
+ ):
58
+ """
59
+ Initialize the LoadProfile object.
60
+ """
61
+ self.type = type
62
+ self.peak = peak
63
+ self.base = base
64
+ self.filepath = filepath
65
+ self.outdir = outdir
66
+ self.inputdir = inputdir
67
+ self.minscaleflow = minscaleflow
68
+ self.smoothing = smoothing
69
+ self.inputcolumn = inputcolumn
70
+
71
+ if self.type == LoadProfile.FROMFILE:
72
+ self.scale = scale
73
+ self.minQ = minQ
74
+ self.load_data = self.getloadtable()
75
+
76
+ # Apply smoothing if requested
77
+ if self.smoothing is not None and str(self.smoothing).lower() != "none":
78
+ self.load_data = apply_smoothing(
79
+ self.load_data,
80
+ column=self.inputcolumn,
81
+ smoothing=self.smoothing,
82
+ outdir=self.outdir,
83
+ prefix="load",
84
+ )
85
+
86
+ def getloadtable(self):
87
+ """
88
+ Load the time-variable heat demand table from CSV file.
89
+
90
+ Returns
91
+ -------
92
+ pd.DataFrame
93
+ Data frame containing the heat demand table.
94
+ """
95
+ load_data = pd.read_csv(self.filepath, header=3, index_col=0, parse_dates=True)
96
+ return load_data
97
+
98
+ def getload(self, times: np.ndarray) -> np.ndarray:
99
+ """
100
+ Compute heat load for given times.
101
+
102
+ Parameters
103
+ ----------
104
+ times : np.ndarray
105
+ Array of times in hours.
106
+
107
+ Returns
108
+ -------
109
+ np.ndarray
110
+ Heat load values (W) at the specified times.
111
+ """
112
+ if self.type == LoadProfile.BERNIER:
113
+ return self.bernier(times)
114
+
115
+ if self.type == LoadProfile.CONSTANT:
116
+ return self.constant(times)
117
+
118
+ if self.type == LoadProfile.VARIABLE:
119
+ return self.variable(times)
120
+
121
+ if self.type == LoadProfile.FROMFILE:
122
+ return self.fromfile(times)
123
+
124
+ return np.zeros_like(times)
125
+
126
+ def getloadflow(self, times: np.ndarray, m_flow: float) -> tuple:
127
+ """
128
+ Compute heat load and flow_data rate, scaling flow_data based on maximum load.
129
+
130
+ Parameters
131
+ ----------
132
+ times : np.ndarray
133
+ Times in hours.
134
+ m_flow : float
135
+ Maximum flow_data rate for scaling.
136
+
137
+ Returns
138
+ -------
139
+ tuple
140
+ Tuple of (heat load array, scaled flow_data rate array)
141
+ """
142
+ load_data = self.getload(times)
143
+ abs_load_data = np.absolute(load_data)
144
+
145
+ # Prevent division by zero and enforce minimum flow_data
146
+ scaleflow = np.maximum(self.minscaleflow, abs_load_data / np.max(abs_load_data))
147
+ flow_data = scaleflow * m_flow
148
+
149
+ return load_data, flow_data
150
+
151
+ def variable(self, times: np.ndarray) -> np.ndarray:
152
+ """
153
+ Generate a variable load profile using cosine function.
154
+
155
+ Parameters
156
+ ----------
157
+ times : np.ndarray
158
+ Times in hours.
159
+
160
+ Returns
161
+ -------
162
+ np.ndarray
163
+ Load values (W).
164
+ """
165
+ peak_load = self.peak
166
+ base_load = self.base
167
+
168
+ arg = 2 * np.pi * times / 8760
169
+ load_data = (peak_load - base_load) * np.cos(arg) + base_load
170
+ return load_data
171
+
172
+ def constant(self, times: np.ndarray) -> np.ndarray:
173
+ """
174
+ Generate a constant load profile.
175
+
176
+ Parameters
177
+ ----------
178
+ times : np.ndarray
179
+ Times in hours.
180
+
181
+ Returns
182
+ -------
183
+ np.ndarray
184
+ Constant load array (W).
185
+ """
186
+ load_data = times * 0 + self.peak
187
+ return load_data
188
+
189
+ def bernier(self, times: np.ndarray) -> np.ndarray:
190
+ """
191
+ Generate synthetic load profile of Bernier et al. (2004).
192
+
193
+ Parameters
194
+ ----------
195
+ times : np.ndarray
196
+ Times in hours.
197
+
198
+ Returns
199
+ -------
200
+ np.ndarray
201
+ Synthetic Bernier load array (W).
202
+ """
203
+ A = 2000.0
204
+ B = 2190.0
205
+ C = 80.0
206
+ D = 2.0
207
+ E = 0.01
208
+ F = 0.0
209
+ G = 0.95
210
+
211
+ pi = np.pi
212
+
213
+ # Start with base Bernier series function
214
+ func = (168.0 - C) / 168.0
215
+
216
+ # Sum harmonics
217
+ for i in [1, 2, 3]:
218
+ func += (
219
+ 1.0
220
+ / (i * pi)
221
+ * (np.cos(C * pi * i / 84.0) - 1.0)
222
+ * (np.sin(pi * i / 84.0 * (times - B)))
223
+ )
224
+
225
+ # Modulate with seasonal and daily terms
226
+ func = (
227
+ func
228
+ * A
229
+ * np.sin(pi / 12.0 * (times - B))
230
+ * np.sin(pi / 4380.0 * (times - B))
231
+ )
232
+
233
+ # Apply Bernier sign adjustments
234
+ load_data = (
235
+ func
236
+ + (-1.0) ** np.floor(D / 8760.0 * (times - B)) * abs(func)
237
+ + E
238
+ * (-1.0) ** np.floor(D / 8760.0 * (times - B))
239
+ / np.sign(np.cos(D * pi / 4380.0 * (times - F)) + G)
240
+ )
241
+
242
+ return -load_data
243
+
244
+ def fromfile(self, times: np.ndarray) -> np.ndarray:
245
+ """
246
+ Interpolate heat load from CSV table.
247
+
248
+ Parameters
249
+ ----------
250
+ times : np.ndarray
251
+ Times in hours.
252
+
253
+ Returns
254
+ -------
255
+ np.ndarray
256
+ Interpolated and scaled heat load array (W).
257
+ """
258
+ heatdemand = np.asarray(self.load_data[self.inputcolumn])
259
+ hours = np.arange(len(heatdemand))
260
+
261
+ # Periodic interpolation across full year
262
+ load_data = np.interp(times, hours, heatdemand, period=(len(hours)))
263
+
264
+ # Apply kW→W (1000x) and user scale
265
+ scaled_load_data = load_data * 1000 * self.scale
266
+
267
+ if self.minQ >= 0:
268
+ scaled_load_data = np.where(
269
+ scaled_load_data >= 0,
270
+ np.maximum(self.minQ, scaled_load_data),
271
+ np.minimum(-self.minQ, scaled_load_data),
272
+ )
273
+ else:
274
+ # Replace with average if minQ flag is negative
275
+ average_scaled_load_data = np.average(scaled_load_data)
276
+ scaled_load_data = scaled_load_data * 0 + average_scaled_load_data
277
+
278
+ return scaled_load_data
279
+
280
+ @classmethod
281
+ def from_config(cls, config: LoadProfileConfig) -> "LoadProfile":
282
+ """
283
+ Create LoadProfile instance from configuration dictionary.
284
+
285
+ Parameters
286
+ ----------
287
+ config : LoadProfileConfig
288
+ Configuration object containing load profile type and parameters.
289
+
290
+ Returns
291
+ -------
292
+ LoadProfile
293
+ Initialized load profile object.
294
+ """
295
+ if config.lp_type == LoadProfile.CONSTANT:
296
+ base = config.lp_base
297
+ peak = base
298
+ return cls(config.lp_type, base=base, peak=peak, outdir=config.lp_outdir)
299
+
300
+ elif config.lp_type == LoadProfile.VARIABLE:
301
+ return cls(
302
+ config.lp_type,
303
+ base=config.lp_base,
304
+ peak=config.lp_peak,
305
+ minscaleflow=config.lp_minscaleflow,
306
+ outdir=config.lp_outdir,
307
+ )
308
+
309
+ elif config.lp_type == LoadProfile.FROMFILE:
310
+ inputcolumn = getattr(config, "lp_inputcolumn", "heating_demand")
311
+ smoothing = getattr(config, "lp_smoothing", None)
312
+ return cls(
313
+ config.lp_type,
314
+ filepath=config.lp_filepath,
315
+ outdir=config.lp_outdir,
316
+ inputdir=config.lp_inputdir,
317
+ scale=config.lp_scale,
318
+ minQ=config.lp_minQ,
319
+ minscaleflow=config.lp_minscaleflow,
320
+ inputcolumn=inputcolumn,
321
+ smoothing=smoothing,
322
+ )
323
+
324
+ else:
325
+ return cls(config.lp_type)
@@ -0,0 +1,3 @@
1
+ """
2
+ This package contains modules with the main classes and functions for the plotting of Geoloop simulation results.
3
+ """