Coverage for src/gwtransport/advection.py: 55%

51 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-22 18:30 +0000

1""" 

2Advection Analysis for 1D Aquifer Systems. 

3 

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. 

7 

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'). 

12 

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""" 

18 

19import warnings 

20 

21import numpy as np 

22import pandas as pd 

23 

24from gwtransport import gamma 

25from gwtransport.residence_time import residence_time 

26from gwtransport.utils import compute_time_edges, interp_series, linear_interpolate 

27 

28 

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. 

32 

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. 

36 

37 This function represents a forward operation (equivalent to convolution). 

38 

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`. 

54 

55 Returns 

56 ------- 

57 numpy.ndarray 

58 Concentration of the compound in the extracted water [ng/m3]. 

59 """ 

60 # Create flow tedges from the flow series index (assuming it's at the end of bins) 

61 flow_tedges = compute_time_edges(tedges=None, tstart=None, tend=flow_series.index, number_of_bins=len(flow_series)) 

62 rt_series = residence_time( 

63 flow=flow_series, 

64 flow_tedges=flow_tedges, 

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 ) 

71 

72 rt = pd.to_timedelta(rt_series.values, unit="D", errors="coerce") 

73 index = cin_series.index + rt 

74 

75 cout = pd.Series(data=cin_series.values, index=index, name="cout") 

76 

77 if cout_index == "cin": 

78 return interp_series(cout, cin_series.index) 

79 if cout_index == "flow": 

80 # If cout_index is 'flow', we need to resample cout to the flow index 

81 return interp_series(cout, flow_series.index) 

82 if cout_index == "cout": 

83 # If cout_index is 'cout', we return the cout as is 

84 return cout.values 

85 

86 msg = f"Invalid cout_index: {cout_index}. Must be 'cin', 'flow', or 'cout'." 

87 raise ValueError(msg) 

88 

89 

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. 

93 

94 This function represents a backward operation (equivalent to deconvolution). 

95 

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]. 

104 

105 Returns 

106 ------- 

107 numpy.ndarray 

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) 

112 

113 

114def gamma_forward( 

115 *, 

116 cin, 

117 cin_tedges, 

118 cout_tedges, 

119 flow, 

120 flow_tedges, 

121 alpha=None, 

122 beta=None, 

123 mean=None, 

124 std=None, 

125 n_bins=100, 

126 retardation_factor=1.0, 

127): 

128 """ 

129 Compute the concentration of the extracted water by shifting cin with its residence time. 

130 

131 The compound is retarded in the aquifer with a retardation factor. The residence 

132 time is computed based on the flow rate of the water in the aquifer and the pore volume 

133 of the aquifer. The aquifer pore volume is approximated by a gamma distribution, with 

134 parameters alpha and beta. 

135 

136 This function represents a forward operation (equivalent to convolution). 

137 

138 Provide either alpha and beta or mean and std. 

139 

140 Parameters 

141 ---------- 

142 cin : pandas.Series 

143 Concentration of the compound in infiltrating water or temperature of infiltrating 

144 water. 

145 cin_tedges : pandas.DatetimeIndex 

146 Time edges for the concentration data. Used to compute the cumulative concentration. 

147 Has a length of one more than `cin`. 

148 cout_tedges : pandas.DatetimeIndex 

149 Time edges for the output data. Used to compute the cumulative concentration. 

150 Has a length of one more than `flow`. 

151 flow : pandas.Series 

152 Flow rate of water in the aquifer [m3/day]. 

153 flow_tedges : pandas.DatetimeIndex 

154 Time edges for the flow data. Used to compute the cumulative flow. 

155 Has a length of one more than `flow`. 

156 alpha : float, optional 

157 Shape parameter of gamma distribution of the aquifer pore volume (must be > 0) 

158 beta : float, optional 

159 Scale parameter of gamma distribution of the aquifer pore volume (must be > 0) 

160 mean : float, optional 

161 Mean of the gamma distribution. 

162 std : float, optional 

163 Standard deviation of the gamma distribution. 

164 n_bins : int 

165 Number of bins to discretize the gamma distribution. 

166 retardation_factor : float 

167 Retardation factor of the compound in the aquifer. 

168 

169 Returns 

170 ------- 

171 numpy.ndarray 

172 Concentration of the compound in the extracted water [ng/m3] or temperature. 

173 """ 

174 bins = gamma.bins(alpha=alpha, beta=beta, mean=mean, std=std, n_bins=n_bins) 

175 return distribution_forward( 

176 cin=cin, 

177 cin_tedges=cin_tedges, 

178 cout_tedges=cout_tedges, 

179 flow=flow, 

180 flow_tedges=flow_tedges, 

181 aquifer_pore_volumes=bins["expected_value"], 

182 retardation_factor=retardation_factor, 

183 ) 

184 

185 

186def gamma_backward(cout, flow, alpha, beta, n_bins=100, retardation_factor=1.0): 

187 """ 

188 Compute the concentration of the infiltrating water by shifting cout with its residence time. 

189 

190 This function represents a backward operation (equivalent to deconvolution). 

191 

192 Parameters 

193 ---------- 

194 cout : pandas.Series 

195 Concentration of the compound in the extracted water [ng/m3]. 

196 flow : pandas.Series 

197 Flow rate of water in the aquifer [m3/day]. 

198 alpha : float 

199 Shape parameter of gamma distribution of the aquifer pore volume (must be > 0) 

200 beta : float 

201 Scale parameter of gamma distribution of the aquifer pore volume (must be > 0) 

202 n_bins : int 

203 Number of bins to discretize the gamma distribution. 

204 retardation_factor : float 

205 Retardation factor of the compound in the aquifer. 

206 

207 Returns 

208 ------- 

209 NotImplementedError 

210 This function is not yet implemented. 

211 """ 

212 msg = "Backward advection gamma (deconvolution) is not implemented yet" 

213 raise NotImplementedError(msg) 

214 

215 

216def distribution_forward( 

217 *, 

218 cin, 

219 cin_tedges, 

220 cout_tedges, 

221 flow, 

222 flow_tedges, 

223 aquifer_pore_volumes, 

224 retardation_factor=1.0, 

225): 

226 """ 

227 Similar to forward_advection, but with a distribution of aquifer pore volumes. 

228 

229 Parameters 

230 ---------- 

231 cin : pandas.Series 

232 Concentration of the compound in infiltrating water or temperature of infiltrating 

233 water. 

234 cin_tedges : pandas.DatetimeIndex 

235 Time edges for the concentration data. Used to compute the cumulative concentration. 

236 Has a length of one more than `cin`. 

237 cout_tedges : pandas.DatetimeIndex 

238 Time edges for the output data. Used to compute the cumulative concentration. 

239 Has a length of one more than `flow`. 

240 flow : pandas.Series 

241 Flow rate of water in the aquifer [m3/day]. 

242 flow_tedges : pandas.DatetimeIndex 

243 Time edges for the flow data. Used to compute the cumulative flow. 

244 Has a length of one more than `flow`. 

245 aquifer_pore_volumes : array-like 

246 Array of aquifer pore volumes [m3] representing the distribution. 

247 retardation_factor : float 

248 Retardation factor of the compound in the aquifer. 

249 

250 Returns 

251 ------- 

252 numpy.ndarray 

253 Concentration of the compound in the extracted water or temperature. Same units as cin. 

254 """ 

255 cin_tedges = pd.DatetimeIndex(cin_tedges) 

256 cout_tedges = pd.DatetimeIndex(cout_tedges) 

257 if len(cin_tedges) != len(cin) + 1: 

258 msg = "cin_tedges must have one more element than cin" 

259 raise ValueError(msg) 

260 if len(cout_tedges) != len(flow) + 1: 

261 msg = "cout_tedges must have one more element than flow" 

262 raise ValueError(msg) 

263 

264 cout_time = cout_tedges[:-1] + (cout_tedges[1:] - cout_tedges[:-1]) / 2 

265 

266 # Use residence time at cout_time for all pore volumes 

267 rt_array = residence_time( 

268 flow=flow, 

269 flow_tedges=flow_tedges, 

270 index=cout_time, 

271 aquifer_pore_volume=aquifer_pore_volumes, 

272 retardation_factor=retardation_factor, 

273 direction="extraction", 

274 ).astype("timedelta64[D]") 

275 day_of_infiltration_array = cout_time.values[None] - rt_array 

276 

277 cin_sum = np.concat(([0.0], cin.cumsum())) # Add a zero at the beginning for cumulative sum 

278 cin_sum_interpolated = linear_interpolate(cin_tedges, cin_sum, day_of_infiltration_array) 

279 n_measurements = linear_interpolate(cin_tedges, np.arange(cin_tedges.size), day_of_infiltration_array) 

280 cout_arr = np.diff(cin_sum_interpolated, axis=0) / np.diff(n_measurements, axis=0) 

281 

282 with warnings.catch_warnings(): 

283 warnings.filterwarnings(action="ignore", message="Mean of empty slice") 

284 return np.nanmean(cout_arr, axis=0) 

285 

286 

287def distribution_backward(cout, flow, aquifer_pore_volume_edges, retardation_factor=1.0): 

288 """ 

289 Compute the concentration of the infiltrating water from the extracted water concentration considering a distribution of aquifer pore volumes. 

290 

291 This function represents a backward operation (equivalent to deconvolution). 

292 

293 Parameters 

294 ---------- 

295 cout : pandas.Series 

296 Concentration of the compound in the extracted water [ng/m3]. 

297 flow : pandas.Series 

298 Flow rate of water in the aquifer [m3/day]. 

299 aquifer_pore_volume_edges : array-like 

300 Edges of the bins that define the distribution of the aquifer pore volume. 

301 Of size nbins + 1 [m3]. 

302 retardation_factor : float 

303 Retardation factor of the compound in the aquifer. 

304 

305 Returns 

306 ------- 

307 NotImplementedError 

308 This function is not yet implemented. 

309 """ 

310 msg = "Backward advection distribution (deconvolution) is not implemented yet" 

311 raise NotImplementedError(msg)