wolfhece 2.2.2__py3-none-any.whl → 2.2.3__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.
@@ -0,0 +1,1019 @@
1
+ import numpy as np
2
+ from numpy import random as rnd
3
+ import math
4
+ from scipy.stats import beta
5
+ from scipy.optimize import fsolve
6
+ import random
7
+ from wolfgpu.glsimulation import ResultsStore
8
+ from pathlib import Path
9
+ import json
10
+ import wx
11
+ import queue
12
+ import os
13
+ import logging
14
+
15
+ EPS = 0.15
16
+ G = [0, 0, -9.81]
17
+ P_ATM = 101325 # Pa
18
+ RHO_F = 1000
19
+
20
+ def beta_find(x, *data):
21
+ """
22
+ Iterative function that finds the parameters alpha and beta defining a beta distribution and scales it in a range.
23
+ The function fits the parameters based on two given percentiles of the data.
24
+
25
+ :param x: Array containing initial guesses for alpha and beta.
26
+ :param data: Tuple containing (down, up, mini, maxi, perc1, perc2).
27
+ :return: Array containing the differences between the calculated and given percentiles.
28
+ """
29
+
30
+ ##Conversion of data into understandable parameters
31
+ down = data[0]
32
+ up = data[1]
33
+ mini = data[2]
34
+ maxi = data[3]
35
+ perc1 = data[4]
36
+ perc2 = data[5]
37
+ data = (down, up, mini, maxi, perc1, perc2)
38
+
39
+ ## Initialisation of the iterative array with alpha and beta values into F[0] and F[1]
40
+ F = np.zeros((2))
41
+
42
+ ## Calculation of the beta distribution with the given parameters
43
+ F[0] = beta.cdf((down - mini) / (maxi - mini), x[0], x[1]) - perc1
44
+ F[1] = beta.cdf((up - mini) / (maxi - mini), x[0], x[1]) - perc2
45
+
46
+ return F
47
+
48
+ def Body_motion(a_RK, batch_turb, CFL, Delta, epsilon, H_pre, H_post, Human, k, NbX, NbY, Pos_bp, resurface, sinking, time_b, t_Wolf_pre, t_Wolf_post, time_goal, U_bp, Ux_pre, Ux_post, Uy_pre, Uy_post, Z_param):
49
+ """
50
+ Function calculating the motion of the body at each time step using a Runge-Kutta method.
51
+ From body position, velocity and flow environment, the function determines the flow velocity at the body position and calculates its new velocities, checking for collisions with walls.
52
+
53
+ :param a_RK: Runge-Kutta coefficient.
54
+ :param batch_turb: Batch turbulence.
55
+ :param CFL: Courant-Friedrichs-Lewy number.
56
+ :param Delta: Array of delta values.
57
+ :param epsilon: Epsilon value.
58
+ :param H_pre: Pre-update water depth.
59
+ :param H_post: Post-update water depth.
60
+ :param Human: Human parameters array.
61
+ :param k: Turbulence kinetic energy.
62
+ :param NbX: Number of cells in X direction.
63
+ :param NbY: Number of cells in Y direction.
64
+ :param Pos_bp: Body position array for calculations.
65
+ :param resurface: Resurface array.
66
+ :param sinking: Sinking array.
67
+ :param time_b: Time array for savings.
68
+ :param t_Wolf_pre: Pre-update Wolf time.
69
+ :param t_Wolf_post: Post-update Wolf time.
70
+ :param time_goal: Time goal.
71
+ :param U_bp: Body velocity array for calculations.
72
+ :param Ux_pre: Pre-update X velocity.
73
+ :param Ux_post: Post-update X velocity.
74
+ :param Uy_pre: Pre-update Y velocity.
75
+ :param Uy_post: Post-update Y velocity.
76
+ :param Z_param: Z parameters array.
77
+ :return: Updated Human, Pos_b, resurface, sinking, time_b, U_b arrays.
78
+ """
79
+
80
+ ##Global parameters and conersion of some parameters to give them an explicit name
81
+
82
+ vertical = Z_param[0,3]
83
+ turb_type = 1
84
+ z_0 = Z_param[0,2]
85
+ T_w = Z_param[:,5]
86
+
87
+ dt_Wolf = t_Wolf_post - t_Wolf_pre
88
+ t_Wolf_perc = (time_b - t_Wolf_pre)/dt_Wolf
89
+ t_Wolf_perc_insta = t_Wolf_perc + Delta[3]/dt_Wolf
90
+
91
+ ## Body parameters
92
+
93
+ m_b = Human[:,16] + Human[:,7]
94
+
95
+ BSA_Ken = 2.4631 #[m²]
96
+
97
+ CAM = Human[:,3]
98
+ CDA = Human[:,4] * Human[:,2]/BSA_Ken
99
+ CLA = Human[:,5] * Human[:,2]/BSA_Ken
100
+ CSA = np.random.choice([-1, 1], size=len(Human[:,23]))*Human[:,23] * Human[:,2]/BSA_Ken
101
+
102
+ mu_stat = Z_param[:,3]
103
+
104
+ ## Initisalisation of the Runge-Kutta variables as variables at the time t for both columns
105
+
106
+ Pos_RK = np.array([Pos_bp.copy(),Pos_bp.copy()])
107
+ U_RK = np.array([U_bp.copy(),U_bp.copy()])
108
+ acc_RK = np.zeros((2,U_bp.shape[0],U_bp.shape[1]))
109
+
110
+ for i in range(2): #RK loop
111
+
112
+ j = abs(i-1)
113
+
114
+ index_bp = ((np.floor(Pos_RK[j,:,:].T / Delta[0:3,None])).T).astype(int) #Body position converted into index in the input matrix of U and H
115
+
116
+ ## Get the velocity at exact time and horizontal position of each run, most time consuming function of the drowning calculation
117
+ [du_insta,_,_,H_f,k_f,U_x_vec,U_y_vec,walls] = interp_mailles_mat(Delta,epsilon,H_pre,H_post,index_bp,k,NbX,NbY,Pos_RK[j,:,:],t_Wolf_perc,t_Wolf_perc_insta,Ux_pre,Ux_post,Uy_pre,Uy_post)
118
+
119
+ ## Parameters and calculations to have the velocity at the body vertical position and its relative velocity
120
+ l_tilde = EPS/H_f
121
+ z = np.clip(Pos_RK[j, :, -1],EPS, H_f)
122
+ z_tilde = np.maximum(z/H_f,l_tilde)
123
+ mu = np.minimum(mu_stat * ((1-z_tilde)/z_tilde * l_tilde/(1-l_tilde))**3,mu_stat)
124
+
125
+ U_max = np.sqrt(U_x_vec**2 + U_y_vec**2)
126
+ dt = np.clip(np.divide(CFL * Delta[4],np.where(U_max!=0,U_max,Delta[3])), 0.00001, Delta[3]) #to be used if turb_type == 2 or 3 in Flow_time_t
127
+ U_x_dif,U_x_sign,U_y_dif,U_y_sign = Flow_time_t(batch_turb,dt,EPS,H_f,k_f,turb_type,U_RK[j,:,:],U_x_vec,U_y_vec,z,z_0)
128
+ norm_U = np.sqrt((U_x_dif**2 + U_y_dif**2))
129
+
130
+ dt = np.clip(np.divide(CFL * Delta[4],np.where(norm_U!=0, norm_U, Delta[3])), 0.0001, Delta[3]).T
131
+
132
+ V_b = Body_volume_variation(Z_param[:,7],Human[:,2],Human[:,14],H_f,Human[:,16],time_b,T_w,1.5*Human[:,15],Human[:,18],Human[:,20],Human[:,21],z)
133
+
134
+ acc_RK[i,:,:],U_RK[i,:,:] = Motion_equations(CAM,CDA,CLA,CSA,dt,du_insta,m_b,mu,U_RK[j,:,:],U_x_dif,U_x_sign,U_y_dif,U_y_sign,V_b,vertical)
135
+ Pos_RK[i,:,:] += U_RK[i,:,:]*dt[:,None]
136
+
137
+ mask_bottom = ((U_RK[i, :, 2] < 0) & (Pos_RK[i, :, 2] < EPS)) #Mask to consider if the body goes down while it is on the bottom
138
+ Pos_RK[i, :, 2][mask_bottom] = EPS #Prevents the body from going under the bottom
139
+ U_RK[i, :, 2][mask_bottom] = 0
140
+
141
+ mask_up = ((U_RK[i, :, 2] > 0) & (Pos_RK[i, :, 2] > H_f)) #Mask to consider if the body goes down while it is on the bottom
142
+ Pos_RK[i, :, 2][mask_up] = H_f[mask_up] #Prevents the body from going under the bottom
143
+ U_RK[i, :, 2][mask_up] = 0
144
+
145
+ t_Wolf_perc += dt/dt_Wolf
146
+
147
+
148
+ U_b = (1-a_RK)*U_RK[0,:,:] + a_RK*U_RK[1,:,:]
149
+ Pos_b = (1-a_RK)*Pos_RK[0,:,:] + a_RK*Pos_RK[1,:,:]
150
+
151
+ time_b += dt
152
+
153
+ ## Check and corrections for collisions with walls
154
+ index_b = ((np.floor(Pos_b[:,:2].T / Delta[0:2,None])).T).astype(int)
155
+ walls = (H_pre[index_b[:,1], index_b[:,0]] < EPS)
156
+ ind_walls = np.where(walls)[0]
157
+ if ind_walls.size!=0:
158
+ [Pos_b[ind_walls,0:2],U_b[ind_walls,0:2]] = Collision(Delta,Pos_b[ind_walls,0:2],Pos_bp[ind_walls,0:2],U_b[ind_walls,0:2],walls)
159
+ index_b = ((np.floor(Pos_b[:,:2].T / Delta[0:2,None])).T).astype(int)
160
+ walls = (H_pre[index_b[:,1], index_b[:,0]] < EPS)
161
+ ind_walls = np.where(walls)[0]
162
+ U_b[ind_walls,0:2] = 0
163
+
164
+
165
+ ## Storage of times when bodies sank and resurfaced
166
+ sinking[:,0] = sinking[:,0] + (sinking[:,0]==0)*(Pos_b[:,2] == EPS)*(U_b[:,2]==0) * Pos_b[:,0]
167
+ sinking[:,1] = sinking[:,1] + (sinking[:,1]==0)*(Pos_b[:,2] == EPS)*(U_b[:,2]==0) * time_b
168
+
169
+ H_f = H_f.ravel()
170
+ V_b = V_b.ravel()
171
+ resurface[:,0] = resurface[:,0] + (resurface[:,0]==0)*(Pos_b[:,2] >= H_f-0.5)*(m_b/V_b/RHO_F<1) * Pos_b[:,0]
172
+ resurface[:,1] = resurface[:,1] + (resurface[:,1]==0)*(Pos_b[:,2] >= H_f-0.5)*(m_b/V_b/RHO_F<1) * time_b
173
+
174
+ return Human,Pos_b,resurface,sinking,time_b,U_b
175
+
176
+ def Body_temperature(BSA, mass, T_w, t):
177
+ """
178
+ Gives the body temperature at time t considering ONLY the conduction.
179
+
180
+ :param BSA: Body surface area [m²].
181
+ :param mass: Body mass [kg].
182
+ :param T_w: Water temperature [°C].
183
+ :param t: Time from the beginning.
184
+ :return: ADD (Accumulated Degree Days) and body temperature.
185
+ """
186
+ T_body_ini = 37
187
+
188
+ # c_b = 3500 # J/kg/K
189
+ # t_skin = 4.6e-3 # Average thickness of the skin in meters
190
+ # k_skin = 0.21 # Thermal conductivity of the skin W/m K
191
+
192
+ # R_cond = t_skin / (k_skin * BSA)
193
+
194
+ # Res = R_cond * mass * c_b
195
+
196
+ Res = 3000
197
+
198
+ T_b = T_w + (T_body_ini - T_w)*np.exp(-t/Res)
199
+
200
+ ADD = ((T_body_ini - T_w) * Res * (1 - np.exp(-t / Res)) + T_w * t)/3600/24 #Integral of the body temperature as a function of time, ADD in [°C.day]
201
+
202
+ return ADD,T_b
203
+
204
+ def Body_volume_variation(alpha_1, BSA, FRC, H_f, m_b, time_b, T_w, TLC, V_b0, V_clothes1, V_clothes2, z):
205
+ """
206
+ Function calculating the body volume variation due to the putrefaction gases.
207
+ The method is described in Delhez et al. 2025, "Predicting the buoyancy and Postmortem Submersion Interval of victims of river drowning".
208
+
209
+ :param alpha_1: Alpha parameter.
210
+ :param BSA: Body surface area.
211
+ :param FRC: Functional residual capacity.
212
+ :param H_f: Water depth.
213
+ :param m_b: Body mass.
214
+ :param time_b: Time array for savings.
215
+ :param T_w: Water temperature.
216
+ :param TLC: Total lung capacity.
217
+ :param V_b0: Initial body volume.
218
+ :param V_clothes1: Clothes volume for temperature < 15°C.
219
+ :param V_clothes2: Clothes volume for temperature >= 15°C.
220
+ :param z: Depth array.
221
+ :return: Body volume.
222
+ """
223
+
224
+ p_hydro = np.maximum(H_f - z,0.00001)*(-G[2])*1000
225
+ p_ext = P_ATM+p_hydro
226
+ V_s = V_b0 - FRC #fraction of FRC on the whole body volume
227
+ n_0 = P_ATM*FRC/(8.3144621*(37+273.15)) #initial amount of gas in the lungs, perfect gas law
228
+
229
+ ADD = time_b/60/60/24 * T_w
230
+ _,T_b = Body_temperature(BSA,m_b,T_w,time_b) #Consideration of the actual body temperature
231
+ ratio_ADD = ADD/alpha_1
232
+
233
+ n_1 = 2*P_ATM * TLC/(8.3144621*(T_w+273.15))
234
+ coef_eta = [10,-15,6]
235
+ eta = n_1*(coef_eta[2]*(ratio_ADD)**5+coef_eta[1]*(ratio_ADD)**4+coef_eta[0]*(ratio_ADD)**3)
236
+ eta = np.minimum(eta,n_1)
237
+
238
+ V_clothes = np.where(T_w < 15, V_clothes1, V_clothes2)
239
+
240
+ V_comp = (n_0+eta/n_1*(n_1-n_0))*8.3144621*(T_b+273.15)/(p_ext) #Compressible Volume
241
+ V_b = V_s + V_comp + V_clothes
242
+
243
+ return V_b
244
+
245
+ def Collision(Delta, Pos, Pos_p, U, walls):
246
+ """
247
+ Correct the bodies that hit a wall by correcting its velocity and replace it on the wall (+marge).
248
+
249
+ :param Delta: Array of delta values.
250
+ :param Pos: Body position array for calculations.
251
+ :param Pos_p: Previous body position array.
252
+ :param U: Body velocity array for calculations.
253
+ :param walls: Walls array.
254
+ :return: Corrected position and velocity arrays.
255
+ """
256
+ marge = Delta[0:2]/100 * (25)
257
+ elasticite = 0.1
258
+
259
+ Correction_pos_val = Pos%Delta[0:2]-Delta[0:2]/2 #Detection of body position in the cell
260
+ Correction_pos_sign = ((Pos%Delta[0:2]>Delta[0:2]/2) != (Pos_p%Delta[0:2]>Delta[0:2]/2)) #1 for right or below, 0 for left and above
261
+ # which_direction = (U>0 and walls[1:3]==0) #if 1, collision with wall right and/or above; if 0, collision with a wall left and/or below
262
+
263
+ adjust_sign = (1-2*(Correction_pos_sign))
264
+
265
+ #Correction of body position and velocity
266
+ U = (U * adjust_sign)*(elasticite)*Correction_pos_sign + U * (Correction_pos_sign==0)
267
+ Pos += (np.sign(Correction_pos_val)*(Correction_pos_sign==1)*((Delta[0:2]/2-np.abs(Correction_pos_val))+marge))
268
+
269
+ return Pos,U
270
+
271
+ def Flow_time_t(batch_turb, dt, epsilon, H_f, k, turb_type, U_bp, U_x_vec, U_y_vec, z, z_0):
272
+ """
273
+ Calculates the difference between the body velocity and the flow velocity (-> relative body velocity) and its sign before and after evaluating the turbulences.
274
+
275
+ :param batch_turb: Batch turbulence.
276
+ :param dt: Time step.
277
+ :param epsilon: Epsilon value.
278
+ :param H_f: Water depth.
279
+ :param k: Turbulence kinetic energy.
280
+ :param turb_type: Turbulence type.
281
+ :param U_bp: Body velocity array for calculations.
282
+ :param U_x_vec: X velocity vector.
283
+ :param U_y_vec: Y velocity vector.
284
+ :param z: Depth array.
285
+ :param z_0: Roughness length.
286
+ :return: U_x_dif, U_x_sign, U_y_dif, U_y_sign arrays.
287
+ """
288
+ U_x_dif = (U_x_vec-U_bp[:,0])
289
+ U_y_dif = (U_y_vec-U_bp[:,1])
290
+ U_x_vec,U_y_vec = U_turbulence(batch_turb,dt,epsilon,H_f,k,turb_type,U_x_vec,U_x_dif,U_y_vec,U_y_dif,z,z_0)
291
+ U_x_dif = (U_x_vec-U_bp[:,0])
292
+ U_y_dif = (U_y_vec-U_bp[:,1])
293
+ U_x_sign = np.sign(U_x_dif)
294
+ U_y_sign = np.sign(U_y_dif)
295
+
296
+ return U_x_dif,U_x_sign,U_y_dif,U_y_sign
297
+
298
+ def interp_mailles_mat(Delta, epsilon, H_0, H_1, index_b, k, NbX, NbY, Pos_b, t_WOLF_perc, t_Wolf_perc_insta, Ux_0, Ux_1, Uy_0, Uy_1):
299
+ """
300
+ Determines the flow velocity and height at the body position based on the value of the cells in which the body is and the next cells in x, y and xy.
301
+ It is a spatial bi-linear and temporal linear interpolation.
302
+ Method described in Delhez et al. 2025, Lagrangian modelling of the drift of a victim of river drowning.
303
+
304
+ :param Delta: Array of delta values.
305
+ :param epsilon: Epsilon value.
306
+ :param H_0: Pre-update water depth.
307
+ :param H_1: Post-update water depth.
308
+ :param index_b: Body position indices.
309
+ :param k: Turbulence kinetic energy.
310
+ :param NbX: Number of cells in X direction.
311
+ :param NbY: Number of cells in Y direction.
312
+ :param Pos_b: Body position array for savings.
313
+ :param t_WOLF_perc: Percentage of Wolf time.
314
+ :param t_Wolf_perc_insta: Instantaneous percentage of Wolf time.
315
+ :param Ux_0: Pre-update X velocity.
316
+ :param Ux_1: Post-update X velocity.
317
+ :param Uy_0: Pre-update Y velocity.
318
+ :param Uy_1: Post-update Y velocity.
319
+ :return: du_insta, epsilon_v, ind_walls, H_v, k_v, Ux_v, Uy_v, walls arrays.
320
+ """
321
+
322
+ # Calculate the position within the cell
323
+ x_rel = (Pos_b[:, 0] % Delta[0]) - Delta[0] / 2
324
+ y_rel = (Pos_b[:, 1] % Delta[1]) - Delta[1] / 2
325
+
326
+ # Determine next cell indices
327
+ index_bo_x = np.array(index_b[:, 0] + np.sign(x_rel),dtype=int)
328
+ index_bo_y = np.array(index_b[:, 1] + np.sign(y_rel),dtype=int)
329
+
330
+ # Normalize x and y to the range [0, 0.5]
331
+ x = np.abs(x_rel) / Delta[0]
332
+ y = np.abs(y_rel) / Delta[1]
333
+
334
+ # Check for non-zero Ux values
335
+ walls = np.column_stack((Ux_0[index_b[:,1], index_b[:,0]] != 0.0,Ux_0[index_b[:,1], index_bo_x] != 0.0,Ux_0[index_bo_y, index_b[:,0]] != 0.0,Ux_0[index_bo_y, index_bo_x] != 0.0))
336
+ ind_walls = np.where(~np.all(walls, axis=1))[0]
337
+
338
+ fact_cell = (1 - x) * (1 - y)
339
+ fact_next_x = x * (1 - y)
340
+ fact_next_y = (1 - x) * y
341
+ fact_next_xy = x * y
342
+
343
+ # Interpolate values (spatially)
344
+ def interp(var):
345
+ return (var[index_b[:,1],index_b[:,0]] * fact_cell +
346
+ var[index_b[:,1],index_bo_x] * fact_next_x +
347
+ var[index_bo_y,index_b[:,0]] * fact_next_y +
348
+ var[index_bo_y,index_bo_x] * fact_next_xy)
349
+
350
+ interp_Ux_0 = interp(Ux_0)
351
+ interp_Ux_1 = interp(Ux_1)
352
+ interp_Uy_0 = interp(Uy_0)
353
+ interp_Uy_1 = interp(Uy_1)
354
+
355
+ # Temporal interpolation
356
+ Ux_v = interp_Ux_0*(1-t_WOLF_perc) + interp_Ux_1*t_WOLF_perc
357
+ Uy_v = interp_Uy_0*(1-t_WOLF_perc) + interp_Uy_1*t_WOLF_perc
358
+ H_v = interp(H_0)*(1-t_WOLF_perc) + interp(H_1)*t_WOLF_perc
359
+ k_v = 0 #interp(k)
360
+ epsilon_v = 0 #interp(epsilon)
361
+
362
+ first_part = np.array(Ux_v - (interp_Ux_0*(1 - t_Wolf_perc_insta) + interp_Ux_1*t_Wolf_perc_insta))
363
+ second_part = np.array(Uy_v - (interp_Uy_0*(1 - t_Wolf_perc_insta) + interp_Uy_1*t_Wolf_perc_insta))
364
+ du_insta = np.column_stack((first_part, second_part))
365
+
366
+ # Variant if a body is next to a wall
367
+ if ind_walls.size != 0:
368
+ active_cells = np.maximum(np.sum(walls, axis=1), 1)
369
+
370
+ def sum_vars(var):
371
+ return (var[index_b[ind_walls,1],index_b[ind_walls,0]] +
372
+ var[index_b[ind_walls,1],index_bo_x[ind_walls]] +
373
+ var[index_bo_y[ind_walls],index_b[ind_walls,0]] +
374
+ var[index_bo_y[ind_walls],index_bo_x[ind_walls]])
375
+
376
+ # sum_vars = lambda var: (var[index_b[ind_walls,1],index_b[ind_walls,0]] +
377
+ # var[index_b[ind_walls,1],index_bo_x[ind_walls]] +
378
+ # var[index_bo_y[ind_walls],index_b[ind_walls,0]] +
379
+ # var[index_bo_y[ind_walls],index_bo_x[ind_walls]))
380
+
381
+ Ux_v[ind_walls] = sum_vars(Ux_0) / active_cells[ind_walls]
382
+ Uy_v[ind_walls] = sum_vars(Uy_0) / active_cells[ind_walls]
383
+ H_v[ind_walls] = sum_vars(H_0) / active_cells[ind_walls]
384
+ # k_v[ind_walls] = sum_vars(k) / active_cells[ind_walls]
385
+ # epsilon_v[ind_walls] = sum_vars(epsilon) / active_cells[ind_walls]
386
+
387
+ # limit_layer = lambda var: (var[index_cell[ind_walls]]*(0.5-x[ind_walls])*(walls[ind_walls,1]==0) +
388
+ # var[index_cell[ind_walls]]*(0.5-y[ind_walls])*(walls[ind_walls,2]==0))
389
+
390
+ # Ux_v[ind_walls] = limit_layer(Ux)
391
+ # Uy_v[ind_walls] = limit_layer(Uy)
392
+ # H_v[ind_walls] = limit_layer(H)
393
+ # H_v[ind_walls] = (H_v[ind_walls]!=0)*H_v[ind_walls] + (H_v[ind_walls]==0)*1
394
+ # k_v[ind_walls] = limit_layer(k)
395
+ # epsilon_v[ind_walls] = limit_layer(epsilon)
396
+
397
+ H_v = np.maximum(H_v,EPS*2)
398
+
399
+ ind_walls = np.where(walls[:, 0] == 0)[0]
400
+
401
+ return du_insta,epsilon_v,ind_walls, H_v, k_v, Ux_v, Uy_v,walls[ind_walls,:]
402
+
403
+ def known_1(g, mini, maxi, down, up, perc1, perc2):
404
+ """
405
+ Used in the fit of the beta parameters alpha and beta by iteration.
406
+
407
+ :param g: Guess array.
408
+ :param mini: Minimum value.
409
+ :param maxi: Maximum value.
410
+ :param down: Lower percentile value.
411
+ :param up: Upper percentile value.
412
+ :param perc1: Lower percentile.
413
+ :param perc2: Upper percentile.
414
+ :return: Tuple containing alpha and beta.
415
+ """
416
+ x0 = np.array([1, 1])
417
+ data = (down, up, mini, maxi, perc1, perc2)
418
+ x = fsolve(beta_find, x0, args=data)
419
+ a = x[0]
420
+ b = x[1]
421
+ return (a, b)
422
+
423
+ def Loading(Path_loading, Pos_b, time_b, U_b):
424
+ """
425
+ Loads the results of a previous simulation and returns the data needed to start from these results.
426
+
427
+ :param Path_loading: Path to the loading file.
428
+ :param Pos_b: Body position array for savings.
429
+ :param time_b: Time array for savings.
430
+ :param U_b: Body velocity array for savings.
431
+ :return: count_initial, Human, n_loaded, Pos_b, time_b, U_b, Z_param arrays.
432
+ """
433
+ n_b_wanted = np.size(Pos_b,0)
434
+ n_wanted = np.size(Pos_b,2)
435
+
436
+ Human = data["Human"]
437
+ Pos_b = data["Pos_b"]
438
+ time_b = data["time_b"]
439
+ U_b = data["U_b"]
440
+ Z_param = data["Z_param"]
441
+
442
+ n_b_loaded = np.size(Pos_b,0)
443
+ n_loaded = np.size(Pos_b,2)
444
+
445
+ count_initial = n_loaded-1
446
+
447
+ data = np.load(Path_loading)
448
+
449
+ if n_b_wanted != n_b_loaded:
450
+ print(f"Error: Size of the sample loaded is different from the new one, the new one must be made of {n_b_loaded} bodies")
451
+ return
452
+
453
+ Pos_b[:][:][n_loaded+1] = np.zeros((n_b_wanted,3,int(n_wanted-n_loaded)))
454
+ time_b[:][:][n_loaded+1] = np.zeros((n_b_wanted,3,int(n_wanted-n_loaded)))
455
+ U_b[:][:][n_loaded+1] = np.zeros((n_b_wanted,3,int(n_wanted-n_loaded)))
456
+
457
+ return count_initial,Human,n_loaded,Pos_b,time_b,U_b,Z_param
458
+
459
+ def Loop_management(progress_queue, process_id, a_RK, BC_cells, count, count_Wolf, CFL, Delta, Human_np, i_initial, n_b, n_saved, n_t, NbX, NbY, Path_Saving, Path_Wolf, Pos, Pos_b, resurface, sinking, time, time_b, time_goal, U, U_b, wanted_time, wanted_Wolf, Z_param_np):
460
+ """
461
+ Main loop of the code. Calculates the motion of each body at each time in the loop and updates the flow when needed.
462
+ Everything is based on the array "still" which contains the index of all the bodies that need more calculations, as we work with variable time step for each body.
463
+ If a body is out of the domain, it is out of still; if a body reaches a time for which we need a save, it is out of still;
464
+ if a body reaches a time for which the flow needs to be updated, it is out of still.
465
+
466
+ :param progress_queue: Queue for progress updates.
467
+ :param process_id: Process ID.
468
+ :param a_RK: Runge-Kutta coefficient.
469
+ :param BC_cells: Boundary condition cells.
470
+ :param count: Count of saved states.
471
+ :param count_Wolf: Count of Wolf states.
472
+ :param CFL: Courant-Friedrichs-Lewy number.
473
+ :param Delta: Array of delta values.
474
+ :param Human_np: Human parameters array.
475
+ :param i_initial: Initial index.
476
+ :param n_b: Number of bodies.
477
+ :param n_saved: Number of saved states.
478
+ :param n_t: Number of time steps.
479
+ :param NbX: Number of cells in X direction.
480
+ :param NbY: Number of cells in Y direction.
481
+ :param Path_Saving: Path to save results.
482
+ :param Path_Wolf: Path to Wolf results.
483
+ :param Pos: Body position array for calculations.
484
+ :param Pos_b: Body position array for savings.
485
+ :param resurface: Resurface array.
486
+ :param sinking: Sinking array.
487
+ :param time: Time array for calculations.
488
+ :param time_b: Time array for savings.
489
+ :param time_goal: Time goal.
490
+ :param U: Body velocity array for calculations.
491
+ :param U_b: Body velocity array for savings.
492
+ :param wanted_time: Array of wanted times.
493
+ :param wanted_Wolf: Array of wanted Wolf times.
494
+ :param Z_param_np: Z parameters array.
495
+ :return: Updated Pos_b, resurface, sinking, time_b, U_b arrays.
496
+ """
497
+
498
+ # Initialisation of some of the function variables
499
+ batch_turb = rnd.normal(0,1,n_b) # Vector with random values following a normal distribution used to avoid using rnd.normal at each iteration
500
+ still = np.arange(n_b)
501
+ i = i_initial+1
502
+ count_Wolf_ini = count_Wolf
503
+
504
+ # Loading of the first flow between t_initial and t_initial + dt_Wolf (often 1h)
505
+ H_pre,Ux_pre,Uy_pre = Read_Wolf_GPU_mat(count_Wolf+1,Path_Wolf)
506
+ H_post,Ux_post,Uy_post = Read_Wolf_GPU_mat(count_Wolf+2,Path_Wolf)
507
+
508
+ epsilon = H_pre*0
509
+ k = H_pre*0
510
+
511
+ # Main loop of the model
512
+ while count<(n_saved):
513
+ t_for = wanted_time[count]
514
+ t_Wolf = wanted_Wolf[count_Wolf]
515
+ s = 1 - i%2
516
+ sp = 0 + i%2
517
+
518
+ # Body position calculation at each time step
519
+ (Human_np[still,:],Pos[still,:,s],resurface[still,:],sinking[still,:],time[still],U[still,:,s]) = Body_motion(a_RK,batch_turb,CFL,Delta,epsilon,H_pre,H_post,Human_np[still,:],k,NbX,NbY,Pos[still,:,sp],resurface[still,:],sinking[still,:],time[still],t_Wolf-wanted_Wolf[count_Wolf_ini],wanted_Wolf[count_Wolf+1]-wanted_Wolf[count_Wolf_ini],t_for,U[still,:,sp],Ux_pre,Ux_post,Uy_pre,Uy_post,Z_param_np[still,:])
520
+ index = np.floor_divide(Pos[:, :2, s], Delta[:2]).astype(int)
521
+ still = np.where((time < t_for) & (time < t_Wolf) & (~np.any(np.all(index[:, None] == BC_cells, axis=2), axis=1)))[0]
522
+
523
+ # Save of calculations from the working variables (size(n,n,2) with index s being the time t and sp time t-dt to size(n,n,n_saved)) or need to update the flow
524
+ if still.size==0:
525
+ if np.any(time<t_Wolf-wanted_Wolf[count_Wolf_ini]):#Save data
526
+ Pos_b[:,:,count] = Pos[:,:,s]
527
+ U_b[:,:,count] = U[:,:,s]
528
+ time_b[:,count] = time
529
+
530
+ count += 1
531
+ t_for = wanted_time[count]
532
+ still = np.where((time < t_for) & (time < t_Wolf) & (~np.any(np.all(index[:, None] == BC_cells, axis=2), axis=1)))[0]
533
+ elif np.any(time<t_for): # Update flow
534
+ H_pre,Ux_pre,Uy_pre = Read_Wolf_GPU_mat(count_Wolf+1,Path_Wolf)
535
+ H_post,Ux_post,Uy_post = Read_Wolf_GPU_mat(count_Wolf+2,Path_Wolf)
536
+ count_Wolf += 1
537
+
538
+ t_Wolf = wanted_Wolf[count_Wolf]
539
+ still = np.where((time < t_for) & (time < t_Wolf) & (~np.any(np.all(index[:, None] == BC_cells, axis=2), axis=1)))[0]
540
+ else: # Save data and update flow
541
+ Pos_b[:,:,count] = Pos[:,:,s]
542
+ U_b[:,:,count] = U[:,:,s]
543
+ time_b[:,count] = time
544
+
545
+ count += 1
546
+ t_for = wanted_time[count]
547
+ if t_for > wanted_Wolf[count_Wolf+1]-wanted_Wolf[count_Wolf_ini]:
548
+ count_Wolf += 1
549
+ t_Wolf = wanted_Wolf[count_Wolf]
550
+ H_pre,Ux_pre,Uy_pre = Read_Wolf_GPU_mat(count_Wolf,Path_Wolf)
551
+ H_post,Ux_post,Uy_post = Read_Wolf_GPU_mat(count_Wolf+1,Path_Wolf)
552
+ still = np.where((time < t_for) & (time < t_Wolf) & (~np.any(np.all(index[:, None] == BC_cells, axis=2), axis=1)))[0]
553
+
554
+ if process_id != -1: #To not enter when we do not work in multiprocess
555
+ progress_queue.put((process_id, time[0])) #Sends the id and the time of this process to the main one
556
+ else:
557
+ Path_save = os.path.join(Path_Saving,'Results')
558
+ os.makedirs(Path_save,exist_ok=True)
559
+ Save_wanted_time(Path_save,Pos[:,:,s],U[:,:,s],count-1)
560
+
561
+ if still.size==0: #End of the loop
562
+ Pos_b[:, :, count:] = np.repeat(Pos[:, :, 0:1], repeats=n_saved-count, axis=2)
563
+ # time_b[:, count:] = np.repeat(time[:], repeats=n_saved-count, axis=2)
564
+ print(f"[Process {process_id}] End of simulation")
565
+ count = n_saved + 1
566
+ break
567
+
568
+ # pbar.update(int(time[0] - pbar.n))
569
+ i += 1
570
+
571
+ return Pos_b,resurface,sinking,time_b,U_b
572
+
573
+ def Motion_equations(CAM, CDA, CLA, CSA, dt, du_insta, m_b, mu, U_bp, U_x_dif, U_x_sign, U_y_dif, U_y_sign, V_b, vertical):
574
+ """
575
+ Calculates the body acceleration and velocity based on the motion equation with the Flow to the body forces (Drag, Side and Lift), Gravity and Buoyancy, Friction with the bottom and added mass effect.
576
+ Equations described in Delhez et al., 2025 "Lagrangian modelling of the drift of a victim of river drowning"
577
+
578
+ :param CAM: Added mass coefficient.
579
+ :param CDA: Drag coefficient.
580
+ :param CLA: Lift coefficient.
581
+ :param CSA: Side force coefficient.
582
+ :param dt: Time step.
583
+ :param du_insta: Instantaneous velocity difference.
584
+ :param m_b: Body mass.
585
+ :param mu: Friction coefficient.
586
+ :param U_bp: Body velocity array for calculations.
587
+ :param U_x_dif: X velocity difference.
588
+ :param U_x_sign: X velocity sign.
589
+ :param U_y_dif: Y velocity difference.
590
+ :param U_y_sign: Y velocity sign.
591
+ :param V_b: Body volume.
592
+ :param vertical: Vertical position.
593
+ :return: Body acceleration and velocity arrays.
594
+ """
595
+
596
+ n_b = len(m_b)
597
+ half_rho_f = 0.5 * RHO_F
598
+ zeros_n_b_3 = np.zeros((n_b, 3))
599
+
600
+ m_added = (CAM * RHO_F * V_b).T
601
+ # m_added = ((1+CAM) * RHO_F * V_b).T
602
+
603
+ # Initialisation of the forces arrays
604
+ F_fb = zeros_n_b_3.copy()
605
+ F_g = zeros_n_b_3.copy()
606
+ F_fr = zeros_n_b_3.copy()
607
+ F_A = zeros_n_b_3.copy()
608
+
609
+ #Rotation matrix to apply to the hydrodynamic coefficients, see article Delhez et al., 2025 for the detail computation of the resultant matrix
610
+ angle_rel = np.arctan2(U_y_dif, U_x_dif)
611
+ angle_rel[(U_y_dif == 0) & (U_x_dif == 0)] = 0
612
+
613
+ cos_angle_rel_2 = np.cos(2*angle_rel)
614
+ sin_angle_rel_2 = np.sin(2*angle_rel)
615
+
616
+ #Application of the rotation matrix to the hydrodynamic coefficients. To be way more efficient in time, we calculate the values of the matrix analytically instead of algebrically
617
+ C = np.zeros((n_b,2,2))
618
+ C[:,0,0] = (CDA - sin_angle_rel_2*CSA)#*0+CDA
619
+ C[:,0,1] = (CSA*cos_angle_rel_2)#*0+CSA
620
+ C[:,1,0] = C[:,0,1]
621
+ C[:,1,1] = (CDA + sin_angle_rel_2*CSA)#*0+CDA
622
+
623
+ # Fluid forces (hydrodynamic forces: drag, side and lift)
624
+ F_fb[:, 0] = U_x_sign * half_rho_f * C[:,0,0] * U_x_dif**2 + U_y_sign * half_rho_f * C[:,0,1] * U_y_dif**2
625
+ F_fb[:, 1] = U_y_sign * half_rho_f * C[:,1,1] * U_y_dif**2 + U_x_sign * half_rho_f * C[:,1,0] * U_x_dif**2
626
+ F_fb[:, 2] = half_rho_f * CLA * (U_x_dif**2 + U_y_dif**2) * vertical
627
+
628
+ F_D_z = -np.sign(U_bp[:,2])*half_rho_f*(CDA*2)*U_bp[:,2]**2 * vertical #Consider the vertical drag to be consistent (if we consider the lift, we have to consider the drag)
629
+ F_fb[:,2] += F_D_z
630
+
631
+ # Gravitational forces
632
+ F_g[:, 2] = (m_b - RHO_F * V_b) * G[2] * vertical
633
+
634
+ #Instationnary flow added mass force
635
+ F_A[:,:2] = m_added[:,None] * du_insta
636
+
637
+ Forces_but_friction = F_fb + F_g + F_A #Used to reproduce the threshold of the friction force (as it can't create motion)
638
+ abs_Forces_but_friction = np.hypot(Forces_but_friction[:, 0], Forces_but_friction[:, 1])
639
+ sign_U_bp = np.sign(U_bp[:,:2])
640
+
641
+ # Friction forces, calculation of value and direction
642
+ F_fr_tot = mu * np.abs(Forces_but_friction[:,2]) * (Forces_but_friction[:,2]<0) #mu depends on the depth (activating the friction only at the bottom)
643
+ angle = np.arctan2(U_bp[:,1], U_bp[:,0])
644
+ angle[(U_bp[:, 1] == 0) & (U_bp[:, 0] == 0)] = np.arctan2(Forces_but_friction[(U_bp[:, 1] == 0) & (U_bp[:, 0] == 0), 1], Forces_but_friction[(U_bp[:, 1] == 0) & (U_bp[:, 0] == 0), 0]) #Force the angle to be 0 also when both speed are -0.0 which gives -pi by convention
645
+ cos_angle = np.abs(np.cos(angle))# + 1*(angle*180/pi==90)
646
+ sin_angle = np.abs(np.sin(angle))# + 1*(angle*180/pi==0)
647
+ pourcentage = np.divide(F_fr_tot, np.where(abs_Forces_but_friction != 0, abs_Forces_but_friction, 1))
648
+ cos_angle = cos_angle*(pourcentage<=1) + (pourcentage>1)
649
+ sin_angle = sin_angle*(pourcentage<=1) + (pourcentage>1)
650
+ F_fr[:, 0] = np.abs(Forces_but_friction[:,0])*pourcentage*cos_angle * -(sign_U_bp[:, 0] + (sign_U_bp[:, 0] == 0) * np.sign(Forces_but_friction[:, 0]))
651
+ F_fr[:, 1] = np.abs(Forces_but_friction[:,1])*pourcentage*sin_angle * -(sign_U_bp[:, 1] + (sign_U_bp[:, 1] == 0) * np.sign(Forces_but_friction[:, 1]))
652
+
653
+ # Newton second law and motion equation
654
+ acc_b = (Forces_but_friction + F_fr) / (m_b + m_added)[:,None]
655
+
656
+ U_b = U_bp + acc_b*dt[:,None]
657
+ corr_friction = (np.abs(F_fr[:, :2]) < np.abs(Forces_but_friction[:, :2]))
658
+ U_b[:,:2] *= corr_friction
659
+
660
+ return acc_b,U_b
661
+
662
+ def Parallel_loop(args):
663
+ """
664
+ Used to run the code in Multiprocess.
665
+
666
+ :param args: Arguments for the Loop_management function.
667
+ :return: Result of the Loop_management function.
668
+ """
669
+ result = Loop_management(*args)
670
+ return result
671
+
672
+ def Preparation_parallelisation(progress_queue, a_RK, BC_cells, count, count_Wolf, CFL, Delta, Human_np, i_initial, n_b, n_saved, n_parallel, n_t, NbX, NbY, Path_saving, Path_Wolf, Pos, Pos_b, resurface, sinking, time, time_b, time_goal, U, U_b, wanted_time, wanted_Wolf, Z_param_np):
673
+ """
674
+ Splits the arrays in the number we want to be ran in MultiProcess.
675
+
676
+ :param progress_queue: Queue for progress updates.
677
+ :param a_RK: Runge-Kutta coefficient.
678
+ :param BC_cells: Boundary condition cells.
679
+ :param count: Count of saved states.
680
+ :param count_Wolf: Count of Wolf states.
681
+ :param CFL: Courant-Friedrichs-Lewy number.
682
+ :param Delta: Array of delta values.
683
+ :param Human_np: Human parameters array.
684
+ :param i_initial: Initial index.
685
+ :param n_b: Number of bodies.
686
+ :param n_saved: Number of saved states.
687
+ :param n_parallel: Number of parallel processes.
688
+ :param n_t: Number of time steps.
689
+ :param NbX: Number of cells in X direction.
690
+ :param NbY: Number of cells in Y direction.
691
+ :param Path_saving: Path to save results.
692
+ :param Path_Wolf: Path to Wolf results.
693
+ :param Pos: Body position array for calculations.
694
+ :param Pos_b: Body position array for savings.
695
+ :param resurface: Resurface array.
696
+ :param sinking: Sinking array.
697
+ :param time: Time array for calculations.
698
+ :param time_b: Time array for savings.
699
+ :param time_goal: Time goal.
700
+ :param U: Body velocity array for calculations.
701
+ :param U_b: Body velocity array for savings.
702
+ :param wanted_time: Array of wanted times.
703
+ :param wanted_Wolf: Array of wanted Wolf times.
704
+ :param Z_param_np: Z parameters array.
705
+ :return: List of tasks for parallel processing.
706
+ """
707
+ chunk_size = n_b // n_parallel # Taille des sous-ensembles
708
+ TASKS = []
709
+
710
+ if n_parallel > 1:
711
+ for i in range(n_parallel):
712
+ start_idx = i * chunk_size
713
+ end_idx = (i + 1) * chunk_size if i != n_parallel - 1 else n_b # Prend tout jusqu'à la fin au dernier
714
+
715
+ # Préparation des arguments pour chaque processus avec sous-vecteurs
716
+ task = (progress_queue,i,a_RK,BC_cells, count,count_Wolf, CFL, Delta, Human_np[start_idx:end_idx,:], i_initial, chunk_size, n_saved, n_t, NbX, NbY,
717
+ Path_saving,Path_Wolf, Pos[start_idx:end_idx,:,:], Pos_b[start_idx:end_idx,:,:], resurface[start_idx:end_idx],
718
+ sinking[start_idx:end_idx,:], time[start_idx:end_idx], time_b[start_idx:end_idx,:], time_goal,
719
+ U[start_idx:end_idx,:,:], U_b[start_idx:end_idx,:,:], wanted_time, wanted_Wolf, Z_param_np[start_idx:end_idx,:])
720
+ TASKS.append(task)
721
+
722
+ return TASKS
723
+
724
+ def Read_Wolf_GPU_mat(i_Wolf, Path_Wolf):
725
+ """
726
+ Reads the results of WolfGPU at a particular time and returns the water depth and velocities' matrix.
727
+
728
+ :param i_Wolf: Index of the WolfGPU result.
729
+ :param Path_Wolf: Path to the WolfGPU results.
730
+ :return: Water depth and velocities' matrix.
731
+ """
732
+ dir_sim = Path(Path_Wolf)
733
+ dir_sim_gpu = Path(dir_sim)# / 'simul/simulations/sim_Hydrogram'
734
+
735
+ store = ResultsStore(dir_sim_gpu / 'Results', mode='r')
736
+
737
+ _,_,_, _, h_store,qx_store,qy_store = store.get_result(i_Wolf)
738
+
739
+ h_store = np.where(h_store == 0, 10**-7, h_store)
740
+
741
+ Ux = np.nan_to_num(qx_store/h_store, nan=0)
742
+
743
+ Uy = np.nan_to_num(qy_store/h_store, nan=0)
744
+
745
+ return h_store,Ux,Uy
746
+
747
+ def Read_Wolf_GPU_metadata(Path_Wolf):
748
+ """
749
+ Reads the parameters of the WolfGPU simulation and returns the relevant ones.
750
+
751
+ :param Path_Wolf: Path to the WolfGPU results.
752
+ :return: Boundary condition cells, Wolf time step, grid spacing, water depth, number of cells in X and Y directions, and total simulation time.
753
+ """
754
+ dir_sim = Path(Path_Wolf)
755
+ dir_sim_gpu = Path(dir_sim)# / 'simul/simulations/sim_Hydrogram'
756
+
757
+ with open(dir_sim_gpu / 'parameters.json','r') as file:
758
+ parameters = json.load(file)
759
+
760
+ param = parameters["parameters"]
761
+ DX = param["dx"]
762
+ DY = param["dy"]
763
+ NbX = param["nx"]
764
+ NbY = param["ny"]
765
+
766
+ dur = param["duration"]
767
+ t_tot = dur["duration"]
768
+
769
+ dur_dt = param["report_period"]
770
+ dt_WOLF = dur_dt["duration"]
771
+
772
+ BC = parameters["boundary_conditions"]
773
+
774
+ BC_cells = np.array([(bc['i'], bc['j']) for bc in BC])
775
+
776
+ store = ResultsStore(dir_sim_gpu / 'Results', mode='r')
777
+
778
+ _,_,_, _, h_store,_,_ = store.get_result(1)
779
+
780
+ return BC_cells,dt_WOLF,DX,DY,h_store,NbX,NbY,t_tot
781
+
782
+ def Save_wanted_time(Path, Pos_b, U_b, count):
783
+ """
784
+ Saves the body positions and velocities at a given time.
785
+
786
+ :param Path: Path to save the results.
787
+ :param Pos_b: Body position array for savings.
788
+ :param U_b: Body velocity array for savings.
789
+ :param count: Count of saved states.
790
+ """
791
+ Path_Pos = os.path.join(Path,f'Pos_{count:04d}')
792
+ Path_U = os.path.join(Path,f'U_{count:04d}')
793
+ np.savez(Path_Pos,Pos_b=Pos_b)
794
+ np.savez(Path_U,U=U_b)
795
+
796
+ def Skinfold(n_b, known, Human):
797
+ """
798
+ Determines the body density based on its age and BMI using Siri's equation. An error percentage body fat is also set, according to Meeuwsen et al., 2010.
799
+
800
+ :param n_b: Number of bodies.
801
+ :param known: Known parameter.
802
+ :param Human: Human parameters array.
803
+ :return: Updated Human parameters and error percentage body fat.
804
+ """
805
+ std_perc_fat_m = [8.1, 7.6, 7.0, 6.4, 6.2, 6.7, 6.8] #Meeuwsen et al
806
+ std_perc_fat_w = [8, 8.1, 8.4, 8.4, 8.4, 8.3, 8.3, 9.4]
807
+ error_perc_fat = np.zeros((n_b))
808
+
809
+ ind_20_m = np.where((Human.Age.to_numpy()<=20) & (Human.gender.to_numpy()==1))
810
+ ind_30_m = np.where((Human.Age.to_numpy()<=30)&(Human.Age.to_numpy()>20) & (Human.gender.to_numpy()==1))
811
+ ind_40_m = np.where((Human.Age.to_numpy()<=40)&(Human.Age.to_numpy()>30) & (Human.gender.to_numpy()==1))
812
+ ind_50_m = np.where((Human.Age.to_numpy()<=50)&(Human.Age.to_numpy()>40) & (Human.gender.to_numpy()==1))
813
+ ind_max_m = np.where((Human.Age.to_numpy()>50) & (Human.gender.to_numpy() ==1))
814
+
815
+ ind_20_w = np.where((Human.Age.to_numpy()<=20) & (Human.gender.to_numpy()==2))
816
+ ind_30_w = np.where((Human.Age.to_numpy()<=30)&(Human.Age.to_numpy()>20) & (Human.gender.to_numpy()==2))
817
+ ind_40_w = np.where((Human.Age.to_numpy()<=40)&(Human.Age.to_numpy()>30) & (Human.gender.to_numpy()==2))
818
+ ind_50_w = np.where((Human.Age.to_numpy()<=50)&(Human.Age.to_numpy()>40) & (Human.gender.to_numpy()==2))
819
+ ind_max_w = np.where((Human.Age.to_numpy()>50) & (Human.gender.to_numpy() ==2))
820
+
821
+ ind_20 = np.where(Human.Age.to_numpy()<=24)
822
+ ind_30 = np.where((Human.Age.to_numpy()<=34)&(Human.Age.to_numpy()>24))
823
+ ind_40 = np.where((Human.Age.to_numpy()<=44)&(Human.Age.to_numpy()>34))
824
+ ind_50 = np.where((Human.Age.to_numpy()<=54)&(Human.Age.to_numpy()>44))
825
+ ind_60 = np.where((Human.Age.to_numpy()<=64)&(Human.Age.to_numpy()>54))
826
+ ind_70 = np.where((Human.Age.to_numpy()<=74)&(Human.Age.to_numpy()>64))
827
+ ind_max = np.where(Human.Age.to_numpy()>74)
828
+
829
+ BMI_25 = [20, 21.3, 22.5, 23.3, 22.9, 23.7, 23.1]
830
+ BMI_50 = [21.7, 23.4, 24.8, 25.7, 25.9, 26.3, 25.3]
831
+ BMI_75 = [24.3, 26.4, 28, 29, 29.1, 29.7, 28]
832
+
833
+
834
+ change = [ind_20,ind_30,ind_40,ind_50,ind_60,ind_70,ind_max]
835
+
836
+ ind_Age = np.minimum(math.floor(np.mean(Human.Age.to_numpy())/10)-1,6)
837
+
838
+ if known == 0:
839
+
840
+ for i in range(7):
841
+ ind = np.array((change[i]))
842
+ (aBMI,bBMI) = known_1(3,16,40,BMI_25[i],BMI_50[i],0.25,0.5)
843
+ Human.loc[ind[0,:],'BMI'] = rnd.beta(aBMI,bBMI,size=(len(ind[:][0])))*(40-16)+16
844
+
845
+ error_perc_fat = (rnd.beta(2,2,size=(n_b))*(2*std_perc_fat_m[ind_Age]+2*std_perc_fat_m[ind_Age])-2*std_perc_fat_m[ind_Age])*(Human.gender.to_numpy()==1) + (rnd.beta(2,2,size=(n_b))*(2*std_perc_fat_w[ind_Age]+2*std_perc_fat_w[ind_Age])-2*std_perc_fat_w[ind_Age])*(Human.gender.to_numpy()==2)
846
+
847
+ Human.loc[:,'mass'] = Human.BMI * Human.height**2
848
+
849
+ perc_fat = -32.515 + 12.409*(Human.gender.to_numpy()-1) + 3.306*Human.BMI.to_numpy() - 0.03*Human.BMI.to_numpy()**2 - 0.006*Human.Age.to_numpy() + 0.033*Human.Age.to_numpy()*(Human.gender.to_numpy()-1) - 0.001*Human.Age.to_numpy()*Human.BMI.to_numpy() #Meeuwsen et al
850
+ perc_fat = perc_fat*(perc_fat+error_perc_fat<45) + (perc_fat-(perc_fat+error_perc_fat-45-random.randint(-100,200))/100)*(perc_fat+error_perc_fat>=45)
851
+ perc_fat = np.maximum(perc_fat+error_perc_fat,8)
852
+ Human.rho = 4.95/(perc_fat/100+4.5) * 1000#Siri's equation
853
+ Human.Volume = Human.mass/Human.rho
854
+
855
+ else: #if known != 3:
856
+
857
+ ind_Age = np.minimum(math.floor(np.mean(Human.Age.to_numpy())/10)-1,6)
858
+
859
+ error_perc_fat = rnd.beta(2,2,size=(n_b))*(2*std_perc_fat_m[ind_Age]+2*std_perc_fat_m[ind_Age])-(2*std_perc_fat_m[ind_Age])*(Human.gender.to_numpy()==1) + (rnd.beta(2,2,size=(n_b))*(2*std_perc_fat_w[ind_Age]+2*std_perc_fat_w[ind_Age])-2*std_perc_fat_w[ind_Age])*(Human.gender.to_numpy()==2)
860
+
861
+ perc_fat = -32.515 + 12.409*(Human.gender.to_numpy()-1) + 3.306*Human.BMI.to_numpy() - 0.03*Human.BMI.to_numpy()**2 - 0.006*Human.Age.to_numpy() + 0.033*Human.Age.to_numpy()*(Human.gender.to_numpy()-1) - 0.001*Human.Age.to_numpy()*Human.BMI.to_numpy() #Meeuwsen et al
862
+ perc_fat = perc_fat*(perc_fat+error_perc_fat<45) + (perc_fat-(perc_fat+error_perc_fat-45-random.randint(-100,200))/100)*(perc_fat+error_perc_fat>=45)
863
+ perc_fat = np.maximum(perc_fat+error_perc_fat,8)
864
+ Human.rho = 4.95/(perc_fat/100+4.5) * 1000;#Siri's equation
865
+ Human.Volume = Human.mass.to_numpy()/Human.rho.to_numpy()
866
+
867
+ return Human,error_perc_fat
868
+
869
+ def state_of_run(progress_queue, frame, interval):
870
+ """
871
+ Monitoring function that displays progress every `interval` seconds.
872
+
873
+ :param progress_queue: Queue for progress updates.
874
+ :param frame: Frame object for GUI updates.
875
+ :param interval: Time interval for updates.
876
+ """
877
+ progress_dict = {i: None for i in range(frame.n_processes)} # Initialize the progress dictionary
878
+ while True:
879
+ # Get the progress updates from the queue
880
+ try:
881
+ progress_update = progress_queue.get(timeout=interval) # Timeout after interval second if no updates
882
+ process_id, progress = progress_update
883
+ progress_dict[process_id] = progress
884
+ wx.CallAfter(frame.update_progress,progress_dict) # Update GUI safely from a thread
885
+ except queue.Empty:
886
+ continue # Pas de mise à jour dispo, on continue
887
+ except (EOFError, BrokenPipeError):
888
+ break
889
+
890
+ # Close window when all processes are done
891
+ # if all(value is not None for value in progress_dict.values()):
892
+ # wx.CallAfter(frame.Close) # Close the frame after all tasks are complete
893
+ # break
894
+
895
+ def U_turbulence(batch, dt, epsilon, H, k, turb_type, U_x, U_x_dif, U_y, U_y_dif, z, z_0):
896
+ """
897
+ Adjust the flow velocity with turbulence. 4 different evaluations are proposed but each uses a log law of the wall to go from depth-averaged velocity to velocity related to the body vertical position.
898
+
899
+ :param batch: Batch turbulence.
900
+ :param dt: Time step.
901
+ :param epsilon: Epsilon value.
902
+ :param H: Water depth.
903
+ :param k: Turbulence kinetic energy.
904
+ :param turb_type: Turbulence type.
905
+ :param U_x: X velocity.
906
+ :param U_x_dif: X velocity difference.
907
+ :param U_y: Y velocity.
908
+ :param U_y_dif: Y velocity difference.
909
+ :param z: Depth array.
910
+ :param z_0: Roughness length.
911
+ :return: Adjusted X and Y velocities.
912
+ """
913
+ n = 0.027 #To be taken from WOLF
914
+ kappa = 0.41 #Von Karman constant
915
+
916
+ U_shear_x = np.zeros_like(H)
917
+ U_shear_y = np.zeros_like(H)
918
+ U_x_sign = np.zeros_like(H)
919
+ U_y_sign = np.zeros_like(H)
920
+ ln_z_z0 = np.zeros_like(H)
921
+
922
+ mask_zero = (H >= 0) & (z >= 0)
923
+
924
+ U_shear_x[mask_zero] = np.abs(U_x[mask_zero])*n*np.sqrt(9.81)/(H[mask_zero]**(1/6)) #based on the combination of Manning equation and definition of bed shear stress and shear velocity
925
+ U_shear_y[mask_zero] = np.abs(U_y[mask_zero])*n*np.sqrt(9.81)/(H[mask_zero]**(1/6))
926
+
927
+ U_x_sign[mask_zero] = np.sign(U_x[mask_zero])
928
+ U_y_sign[mask_zero] = np.sign(U_y[mask_zero])
929
+
930
+ ln_z_z0[mask_zero] = np.log(z[mask_zero] / z_0)
931
+
932
+ if turb_type == 0: #No turbulence
933
+
934
+ ln_H_z0 = np.log(H / z_0)
935
+ factor = ln_H_z0# + z / H - 1 #see Eq. 7.2.12 of Principles of sediment transport in rivers, estuaries and coastal seas (Leo van Rijn)
936
+
937
+ U_x = (U_x / factor) * ln_z_z0
938
+ U_y = (U_y / factor) * ln_z_z0
939
+
940
+ elif turb_type==1: #Based on a random parameter
941
+
942
+ percentage = 5 *10**-2
943
+
944
+ n_b = len(U_x)
945
+
946
+ index_x = np.random.randint(0,len(batch), n_b) #Trick to not make a rnd.normal at each iteration (faster)
947
+ index_y = np.roll(index_x,-1) #Trick to avoid using two randint
948
+ # index = np.random.choice(len(batch), n_b) # Numba
949
+ R_x = batch[index_x] #Number choosen randomly from a gaussian distribution of mean 0 and std 1
950
+ R_y = batch[index_y]
951
+
952
+ U_xy = np.hypot(U_shear_x,U_shear_y)/kappa * ln_z_z0
953
+
954
+ U_x = U_shear_x/ kappa * ln_z_z0 #Formula derived from the log velocity profile used in boundary layer theory for turbulent open-channel flow
955
+ U_x_turb = R_x*percentage*U_x
956
+ U_x = U_x_sign*U_x + U_x_turb
957
+
958
+ U_y = U_shear_y/ kappa * ln_z_z0
959
+ U_y_turb = R_y*percentage*U_y
960
+ U_y = U_y_sign*U_y + U_y_turb
961
+
962
+ elif turb_type==2: #See Garcia et al. (2013), page 214, based on the shear velocity, I think this is false as it is applied on floating particles and on the position itself, can't work applied on the velocity and with friction with the bottom
963
+
964
+ n_b = len(U_x)
965
+
966
+ index = np.random.randint(0,len(batch), n_b) #Trick to not make a rnd.normal at each iteration
967
+ # index = np.random.choice(len(batch), n_b) # Numba
968
+ R = batch[index] #Constant used in the definition of U_prime
969
+
970
+ #U_shear_glob = np.sqrt(U_x**2+U_y**2)*n*np.sqrt(9.81)/(H**(1/6))
971
+
972
+
973
+ #U_x_shear = (U_x / factor) * ln_z_z0 #Log law of the wall
974
+ K_H = 0.6*H*np.abs(U_shear_x)#np.abs(U_x_shear) #Turbulent diffusion coefficient (or turbulent diffusivity coefficient)
975
+ U_x_turb = R*np.sqrt(2*K_H*dt) #In the original formula (Garcia et al. (2013), Eq. 6), we have R*np.sqrt(2*K_H*dt) but as we multiply by dt after that, we have to divide by dt before
976
+ U_x = U_shear_x/ kappa * ln_z_z0 + U_x_turb/dt
977
+
978
+ #U_y_shear = (U_y / factor) * ln_z_z0 #Log law of the wall
979
+ K_H = 0.6*H*np.abs(U_shear_y)#np.abs(U_y_shear) #Turbulent diffusion coefficient (or turbulent diffusivity coefficient)
980
+ U_y_turb = R*np.sqrt(2*K_H*dt)
981
+ U_y = U_shear_y/ kappa * ln_z_z0 + U_y_turb/dt
982
+
983
+ elif turb_type ==3: #See Bocksell & Loth (2001), Eqs. 16 to 21, based on k-eps
984
+
985
+ n_b = len(U_x)
986
+ epsilon = np.maximum(epsilon,10**-10) #Because the correction of collision is done after, trick to avoid to divide by 0 when out of the domain
987
+
988
+ index = np.random.randint(0,len(batch), n_b) #Trick to not make a rnd.normal at each iteration
989
+ # index = np.random.choice(len(batch), n_b) # Numba
990
+ R = batch[index] #Same as psi in the reference
991
+ eps_01_09 = index/n_b/10*8+0.1 #Explanation just after Eq. 17
992
+
993
+ C_c_x = 1 + np.sign(U_x_dif) #Eq. 17
994
+ C_c_y = 1 + np.sign(U_y_dif) #Eq. 17
995
+ C_delta = 1.6 #Table 2 of the reference
996
+ C_mu = 0.09 #After Eq. 17 of reference
997
+ C_tau = 0.27 #Table 2 of the reference
998
+
999
+ Delta = np.array([C_c_x,C_c_y])*C_delta*C_mu**(3/4)*(k**(3/2)/epsilon) #Eq. 17
1000
+
1001
+ tau_delta = C_tau*eps_01_09*(k/epsilon) #Eq. 17
1002
+ tau_t_abs = np.abs(Delta/np.array([U_x_dif,U_y_dif])) #Eq. 17
1003
+ tau_t_sign = np.sign(Delta/np.array([U_x_dif,U_y_dif])) #Eq. 17
1004
+ tau_t_sign[tau_t_sign==0]=1
1005
+ tau_int = np.maximum(np.maximum(tau_delta,tau_t_abs),10**-10) #Eq. 16, problem, if we keep min, we have 0 and so U_y = inf, we test with max
1006
+
1007
+ alpha = np.exp(-dt/tau_int)*tau_t_sign #Eq. 20
1008
+ sigma_u = np.sqrt(2/3*k) #Eq. 20
1009
+
1010
+ U_x = alpha[0,:]*U_x + np.sqrt((1-alpha[0,:]**2)*sigma_u**2)*R# + U_x #Eq. 18
1011
+ U_y = alpha[1,:]*U_y + np.sqrt((1-alpha[1,:]**2)*sigma_u**2)*R# + U_y #Eq. 18
1012
+
1013
+ ##At this time, compatible only with Wolf CPU because no k-eps given by GPU
1014
+
1015
+ return U_x,U_y
1016
+
1017
+
1018
+
1019
+