Coverage for src/gwtransport/advection.py: 83%
52 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-06 15:45 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-06 15:45 +0000
1"""
2Advection Analysis for 1D Aquifer Systems.
4This module provides functions to analyze compound transport by advection
5in aquifer systems. It includes tools for computing concentrations of the extracted water
6based on the concentration of the infiltrating water, extraction data and aquifer properties.
8The model assumes requires the groundwaterflow to be reduced to a 1D system. On one side,
9water with a certain concentration infiltrates ('cin'), the water flows through the aquifer and
10the compound of interest flows through the aquifer with a retarded velocity. The water is
11extracted ('cout').
13Main functions:
14- forward: Compute the concentration of the extracted water by shifting cin with its residence time. This corresponds to a convolution operation.
15- gamma_forward: Similar to forward, but for a gamma distribution of aquifer pore volumes.
16- distribution_forward: Similar to forward, but for an arbitrairy distribution of aquifer pore volumes.
17"""
19import warnings
21import numpy as np
22import pandas as pd
24from gwtransport import gamma
25from gwtransport.residence_time import residence_time
26from gwtransport.utils import compute_time_edges, interp_series, linear_interpolate
29def forward(cin_series, flow_series, aquifer_pore_volume, retardation_factor=1.0, cout_index="cin"):
30 """
31 Compute the concentration of the extracted water by shifting cin with its residence time.
33 The compound is retarded in the aquifer with a retardation factor. The residence
34 time is computed based on the flow rate of the water in the aquifer and the pore volume
35 of the aquifer.
37 This function represents a forward operation (equivalent to convolution).
39 Parameters
40 ----------
41 cin_series : pandas.Series
42 Concentration of the compound in the extracted water [ng/m3]. The cin_series should be the average concentration of a time bin. The index should be a pandas.DatetimeIndex
43 and is labeled at the end of the time bin.
44 flow_series : pandas.Series
45 Flow rate of water in the aquifer [m3/day]. The flow_series should be the average flow of a time bin. The index should be a pandas.DatetimeIndex
46 and is labeled at the end of the time bin.
47 aquifer_pore_volume : float
48 Pore volume of the aquifer [m3].
49 cout_index : str, optional
50 The index of the output series. Can be 'cin', 'flow', or 'cout'. Default is 'cin'.
51 - 'cin': The output series will have the same index as `cin_series`.
52 - 'flow': The output series will have the same index as `flow_series`.
53 - 'cout': The output series will have the same index as `cin_series + residence_time`.
55 Returns
56 -------
57 pandas.Series
58 Concentration of the compound in the extracted water [ng/m3].
59 """
60 rt_series = residence_time(
61 flow=flow_series,
62 flow_tedges=None,
63 flow_tstart=None,
64 flow_tend=flow_series.index,
65 index=cin_series.index,
66 aquifer_pore_volume=aquifer_pore_volume,
67 retardation_factor=retardation_factor,
68 direction="infiltration",
69 return_pandas_series=True,
70 )
72 rt = pd.to_timedelta(rt_series.values, unit="D", errors="coerce")
73 index = cin_series.index + rt
75 cout = pd.Series(data=cin_series.values, index=index, name="cout")
77 if cout_index == "cin":
78 return pd.Series(interp_series(cout, cin_series.index), index=cin_series.index, name="cout")
79 if cout_index == "flow":
80 # If cout_index is 'flow', we need to resample cout to the flow index
81 return pd.Series(interp_series(cout, flow_series.index), index=flow_series.index, name="cout")
82 if cout_index == "cout":
83 # If cout_index is 'cout', we return the cout as is
84 return cout
86 msg = f"Invalid cout_index: {cout_index}. Must be 'cin', 'flow', or 'cout'."
87 raise ValueError(msg)
90def backward(cout, flow, aquifer_pore_volume, retardation_factor=1.0, resample_dates=None):
91 """
92 Compute the concentration of the infiltrating water by shifting cout with its residence time.
94 This function represents a backward operation (equivalent to deconvolution).
96 Parameters
97 ----------
98 cout : pandas.Series
99 Concentration of the compound in the extracted water [ng/m3].
100 flow : pandas.Series
101 Flow rate of water in the aquifer [m3/day].
102 aquifer_pore_volume : float
103 Pore volume of the aquifer [m3].
105 Returns
106 -------
107 pandas.Series
108 Concentration of the compound in the infiltrating water [ng/m3].
109 """
110 msg = "Backward advection (deconvolution) is not implemented yet"
111 raise NotImplementedError(msg)
114def gamma_forward(
115 *,
116 cin,
117 cin_tedges=None,
118 cin_tstart=None,
119 cin_tend=None,
120 cout_tedges=None,
121 cout_tstart=None,
122 cout_tend=None,
123 flow,
124 flow_tedges=None,
125 flow_tstart=None,
126 flow_tend=None,
127 alpha=None,
128 beta=None,
129 mean=None,
130 std=None,
131 n_bins=100,
132 retardation_factor=1.0,
133):
134 """
135 Compute the concentration of the extracted water by shifting cin with its residence time.
137 The compound is retarded in the aquifer with a retardation factor. The residence
138 time is computed based on the flow rate of the water in the aquifer and the pore volume
139 of the aquifer. The aquifer pore volume is approximated by a gamma distribution, with
140 parameters alpha and beta.
142 This function represents a forward operation (equivalent to convolution).
144 Provide:
145 - either alpha and beta or mean and std.
146 - either cin_tedges or cin_tstart or cin_tend.
147 - either cout_tedges or cout_tstart or cout_tend.
148 - either flow_tedges or flow_tstart or flow_tend.
150 Parameters
151 ----------
152 cin : pandas.Series
153 Concentration of the compound in infiltrating water or temperature of infiltrating
154 water.
155 cin_tedges : pandas.DatetimeIndex, optional
156 Time edges for the concentration data. If provided, it is used to compute the cumulative concentration.
157 cin_tstart : pandas.DatetimeIndex, optional
158 Timestamps aligned to the start of the concentration measurement intervals. Preferably use cin_tedges,
159 but if not available this approach can be used for convenience.
160 cin_tend : pandas.DatetimeIndex, optional
161 Timestamps aligned to the end of the concentration measurement intervals. Preferably use cin_tedges,
162 but if not available this approach can be used for convenience.
163 cout_tedges : pandas.DatetimeIndex, optional
164 Time edges for the output data. If provided, it is used to compute the cumulative concentration.
165 cout_tstart : pandas.DatetimeIndex, optional
166 Timestamps aligned to the start of the output measurement intervals. Preferably use cout_tedges,
167 but if not available this approach can be used for convenience.
168 cout_tend : pandas.DatetimeIndex, optional
169 Timestamps aligned to the end of the output measurement intervals. Preferably use cout_tedges,
170 but if not available this approach can be used for convenience.
171 flow : pandas.Series
172 Flow rate of water in the aquifer [m3/day].
173 flow_tedges : pandas.DatetimeIndex, optional
174 Time edges for the flow data. If provided, it is used to compute the cumulative flow.
175 If left to None, the index of `flow` is used. Has a length of one more than `flow`. Default is None.
176 flow_tstart : pandas.DatetimeIndex, optional
177 Timestamps aligned to the start of the flow measurement intervals. Preferably use flow_tedges,
178 but if not available this approach can be used for convenience. Has the same length as `flow`.
179 flow_tend : pandas.DatetimeIndex, optional
180 Timestamps aligned to the end of the flow measurement intervals. Preferably use flow_tedges,
181 but if not available this approach can be used for convenience. Has the same length as `flow`.
182 alpha : float, optional
183 Shape parameter of gamma distribution of the aquifer pore volume (must be > 0)
184 beta : float, optional
185 Scale parameter of gamma distribution of the aquifer pore volume (must be > 0)
186 mean : float, optional
187 Mean of the gamma distribution.
188 std : float, optional
189 Standard deviation of the gamma distribution.
190 n_bins : int
191 Number of bins to discretize the gamma distribution.
192 retardation_factor : float
193 Retardation factor of the compound in the aquifer.
195 Returns
196 -------
197 pandas.Series
198 Concentration of the compound in the extracted water [ng/m3] or temperature.
199 """
200 bins = gamma.bins(alpha=alpha, beta=beta, mean=mean, std=std, n_bins=n_bins)
201 return distribution_forward(
202 cin=cin,
203 cin_tedges=cin_tedges,
204 cin_tstart=cin_tstart,
205 cin_tend=cin_tend,
206 cout_tedges=cout_tedges,
207 cout_tstart=cout_tstart,
208 cout_tend=cout_tend,
209 flow=flow,
210 flow_tedges=flow_tedges,
211 flow_tstart=flow_tstart,
212 flow_tend=flow_tend,
213 aquifer_pore_volume_edges=bins["edges"],
214 retardation_factor=retardation_factor,
215 )
218def gamma_backward(cout, flow, alpha, beta, n_bins=100, retardation_factor=1.0):
219 """
220 Compute the concentration of the infiltrating water by shifting cout with its residence time.
222 This function represents a backward operation (equivalent to deconvolution).
224 Parameters
225 ----------
226 cout : pandas.Series
227 Concentration of the compound in the extracted water [ng/m3].
228 flow : pandas.Series
229 Flow rate of water in the aquifer [m3/day].
230 alpha : float
231 Shape parameter of gamma distribution of the aquifer pore volume (must be > 0)
232 beta : float
233 Scale parameter of gamma distribution of the aquifer pore volume (must be > 0)
234 n_bins : int
235 Number of bins to discretize the gamma distribution.
236 retardation_factor : float
237 Retardation factor of the compound in the aquifer.
239 Returns
240 -------
241 NotImplementedError
242 This function is not yet implemented.
243 """
244 msg = "Backward advection gamma (deconvolution) is not implemented yet"
245 raise NotImplementedError(msg)
248def distribution_forward(
249 *,
250 cin,
251 cin_tedges=None,
252 cin_tstart=None,
253 cin_tend=None,
254 cout_tedges=None,
255 cout_tstart=None,
256 cout_tend=None,
257 flow,
258 flow_tedges=None,
259 flow_tstart=None,
260 flow_tend=None,
261 aquifer_pore_volume_edges=None,
262 retardation_factor=1.0,
263):
264 """
265 Similar to forward_advection, but with a distribution of aquifer pore volumes.
267 Provide:
268 - either cin_tedges or cin_tstart or cin_tend.
269 - either cout_tedges or cout_tstart or cout_tend.
270 - either flow_tedges or flow_tstart or flow_tend.
272 Parameters
273 ----------
274 cin : pandas.Series
275 Concentration of the compound in infiltrating water or temperature of infiltrating
276 water.
277 cin_tedges : pandas.DatetimeIndex, optional
278 Time edges for the concentration data. If provided, it is used to compute the cumulative concentration.
279 cin_tstart : pandas.DatetimeIndex, optional
280 Timestamps aligned to the start of the concentration measurement intervals. Preferably use cin_tedges,
281 but if not available this approach can be used for convenience.
282 cin_tend : pandas.DatetimeIndex, optional
283 Timestamps aligned to the end of the concentration measurement intervals. Preferably use cin_tedges,
284 but if not available this approach can be used for convenience.
285 cout_tedges : pandas.DatetimeIndex, optional
286 Time edges for the output data. If provided, it is used to compute the cumulative concentration.
287 cout_tstart : pandas.DatetimeIndex, optional
288 Timestamps aligned to the start of the output measurement intervals. Preferably use cout_tedges,
289 but if not available this approach can be used for convenience.
290 cout_tend : pandas.DatetimeIndex, optional
291 Timestamps aligned to the end of the output measurement intervals. Preferably use cout_tedges,
292 but if not available this approach can be used for convenience.
293 flow : pandas.Series
294 Flow rate of water in the aquifer [m3/day].
295 flow_tedges : pandas.DatetimeIndex, optional
296 Time edges for the flow data. If provided, it is used to compute the cumulative flow.
297 If left to None, the index of `flow` is used. Has a length of one more than `flow`. Default is None.
298 flow_tstart : pandas.DatetimeIndex, optional
299 Timestamps aligned to the start of the flow measurement intervals. Preferably use flow_tedges,
300 but if not available this approach can be used for convenience. Has the same length as `flow`.
301 flow_tend : pandas.DatetimeIndex, optional
302 Timestamps aligned to the end of the flow measurement intervals. Preferably use flow_tedges,
303 but if not available this approach can be used for convenience. Has the same length as `flow`.
304 aquifer_pore_volume_edges : array-like
305 Edges of the bins that define the distribution of the aquifer pore volume.
306 Of size nbins + 1 [m3].
307 retardation_factor : float
308 Retardation factor of the compound in the aquifer.
310 Returns
311 -------
312 pandas.Series
313 Concentration of the compound in the extracted water or temperature. Same units as cin.
314 """
315 cin_tedges = compute_time_edges(tedges=cin_tedges, tstart=cin_tstart, tend=cin_tend, number_of_bins=len(cin))
316 cout_tedges = compute_time_edges(tedges=cout_tedges, tstart=cout_tstart, tend=cout_tend, number_of_bins=len(flow))
318 if cout_tstart is not None:
319 cout_time = cout_tstart
320 elif cout_tend is not None:
321 cout_time = cout_tend
322 elif cout_tedges is not None:
323 cout_time = cout_tedges[:-1] + (cout_tedges[1:] - cout_tedges[:-1]) / 2
324 else:
325 msg = "Either cout_tedges, cout_tstart, or cout_tend must be provided."
326 raise ValueError(msg)
328 # Use residence time at cout_tedges
329 rt_edges = residence_time(
330 flow=flow,
331 flow_tedges=flow_tedges,
332 flow_tstart=flow_tstart,
333 flow_tend=flow_tend,
334 index=cout_time,
335 aquifer_pore_volume=aquifer_pore_volume_edges,
336 retardation_factor=retardation_factor,
337 direction="extraction",
338 ).astype("timedelta64[D]")
339 day_of_infiltration_edges = cout_time.values[None] - rt_edges
341 cin_sum = np.concat(([0.0], cin.cumsum())) # Add a zero at the beginning for cumulative sum
342 cin_sum_edges = linear_interpolate(cin_tedges, cin_sum, day_of_infiltration_edges)
343 n_measurements = linear_interpolate(cin_tedges, np.arange(cin_tedges.size), day_of_infiltration_edges)
344 cout_arr = np.diff(cin_sum_edges, axis=0) / np.diff(n_measurements, axis=0)
346 with warnings.catch_warnings():
347 warnings.filterwarnings(action="ignore", message="Mean of empty slice")
348 cout_data = np.nanmean(cout_arr, axis=0)
350 return pd.Series(data=cout_data, index=cout_time, name="cout")
353def distribution_backward(cout, flow, aquifer_pore_volume_edges, retardation_factor=1.0):
354 """
355 Compute the concentration of the infiltrating water from the extracted water concentration considering a distribution of aquifer pore volumes.
357 This function represents a backward operation (equivalent to deconvolution).
359 Parameters
360 ----------
361 cout : pandas.Series
362 Concentration of the compound in the extracted water [ng/m3].
363 flow : pandas.Series
364 Flow rate of water in the aquifer [m3/day].
365 aquifer_pore_volume_edges : array-like
366 Edges of the bins that define the distribution of the aquifer pore volume.
367 Of size nbins + 1 [m3].
368 retardation_factor : float
369 Retardation factor of the compound in the aquifer.
371 Returns
372 -------
373 NotImplementedError
374 This function is not yet implemented.
375 """
376 msg = "Backward advection distribution (deconvolution) is not implemented yet"
377 raise NotImplementedError(msg)