wolfhece 2.2.43__py3-none-any.whl → 2.2.45__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.
- wolfhece/ChatwWOLF.py +200 -0
- wolfhece/Model1D.py +1785 -11
- wolfhece/PyCrosssections.py +1539 -702
- wolfhece/PyDraw.py +61 -8
- wolfhece/PyVertexvectors.py +64 -22
- wolfhece/RatingCurve_xml.py +15 -1
- wolfhece/analyze_poly.py +198 -4
- wolfhece/apps/version.py +1 -1
- wolfhece/dike.py +267 -19
- wolfhece/eikonal.py +1 -0
- wolfhece/wolf_array.py +251 -33
- {wolfhece-2.2.43.dist-info → wolfhece-2.2.45.dist-info}/METADATA +2 -2
- {wolfhece-2.2.43.dist-info → wolfhece-2.2.45.dist-info}/RECORD +16 -15
- {wolfhece-2.2.43.dist-info → wolfhece-2.2.45.dist-info}/WHEEL +0 -0
- {wolfhece-2.2.43.dist-info → wolfhece-2.2.45.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.2.43.dist-info → wolfhece-2.2.45.dist-info}/top_level.txt +0 -0
wolfhece/Model1D.py
CHANGED
@@ -33,7 +33,8 @@ from matplotlib import animation ,rcParams
|
|
33
33
|
from matplotlib.axes import Axes
|
34
34
|
from matplotlib.figure import Figure
|
35
35
|
from matplotlib.lines import Line2D
|
36
|
-
from matplotlib.ticker import MultipleLocator
|
36
|
+
from matplotlib.ticker import MultipleLocator,FuncFormatter
|
37
|
+
from PIL import Image
|
37
38
|
from shapely.ops import substring, split, LineString, Point
|
38
39
|
from subprocess import Popen, PIPE
|
39
40
|
from tqdm import tqdm
|
@@ -47,8 +48,9 @@ from .pylogging import create_wxlogwindow
|
|
47
48
|
from .PyParams import Wolf_Param
|
48
49
|
from .PyTranslate import _
|
49
50
|
from .PyVertexvectors import Zones, zone, vector, wolfvertex
|
50
|
-
from .wolf_array import WolfArray
|
51
|
+
from .wolf_array import WolfArray, header_wolf
|
51
52
|
from .wolf_vrt import crop_vrt
|
53
|
+
from .xyz_file import xyz_scandir
|
52
54
|
|
53
55
|
# --- Constants ---
|
54
56
|
# __________________
|
@@ -1576,6 +1578,10 @@ class Creator_1D:
|
|
1576
1578
|
nby = int(round(y/dy))
|
1577
1579
|
else:
|
1578
1580
|
array1.dy= array2.dy = dx
|
1581
|
+
# Added
|
1582
|
+
# ----------------
|
1583
|
+
dy = dx
|
1584
|
+
nby = int(round(y/dy))
|
1579
1585
|
|
1580
1586
|
if array1.dz == array2.dz:
|
1581
1587
|
dz= array1.dz
|
@@ -1850,6 +1856,121 @@ class Creator_1D:
|
|
1850
1856
|
vecz_file = self.initialize_file(save_as,'.vecz')
|
1851
1857
|
new_zones.saveas(vecz_file)
|
1852
1858
|
|
1859
|
+
def wolfarray_from_xyz(self,
|
1860
|
+
xyz_directory: str,
|
1861
|
+
coordinate_origin: tuple,
|
1862
|
+
coordinate_extent: tuple,
|
1863
|
+
dx: float= 1,
|
1864
|
+
dy: float= 1,
|
1865
|
+
save_as: str = '',
|
1866
|
+
nullvalue: float = -99999.,
|
1867
|
+
mask_nullvalue = True) -> WolfArray:
|
1868
|
+
"""
|
1869
|
+
return a WolfArray from a .XYZ file.
|
1870
|
+
|
1871
|
+
.. notes:: After scanning the directory and concatenating all the the xyz files into one file,
|
1872
|
+
a wolfarry with the given extent is created. All values are first set to -99999.
|
1873
|
+
Then, the array is filled with the values from the xyz file and mask is placed wherever values are not available.
|
1874
|
+
|
1875
|
+
:param wyz_directory: computer path to the folder containing the .XYZ files
|
1876
|
+
:type wyz_directory: str
|
1877
|
+
:param coordinate_origin: Origin of the WolfArray. (x,y) of the lower left corner.
|
1878
|
+
:type coordinate_origin: tuple
|
1879
|
+
:param coordinate_extent: Extent of the WolfArray. (x,y) of the upper right corner.
|
1880
|
+
:type coordinate_extent: tuple
|
1881
|
+
:param dx: Cell size in x direction, defaults to 1
|
1882
|
+
:type dx: float, optional
|
1883
|
+
:param dy: Cell size in y direction, defaults to 1
|
1884
|
+
:type dy: float, optional
|
1885
|
+
:param save_as: computer path of the file where new WolfArray will be stored, defaults to ''
|
1886
|
+
:type save_as: str, optional
|
1887
|
+
:return: WolfArray
|
1888
|
+
|
1889
|
+
"""
|
1890
|
+
|
1891
|
+
header = self.create_header_wolf_array(dx = dx,
|
1892
|
+
dy = dy,
|
1893
|
+
originx = coordinate_origin[0],
|
1894
|
+
originy = coordinate_origin[1],
|
1895
|
+
extentx = coordinate_extent[0],
|
1896
|
+
extenty = coordinate_extent[1]
|
1897
|
+
)
|
1898
|
+
|
1899
|
+
cropini = [[float(coordinate_origin[0]), float(coordinate_extent[0])],
|
1900
|
+
[float(coordinate_origin[1]), float(coordinate_extent[1])]]
|
1901
|
+
|
1902
|
+
|
1903
|
+
|
1904
|
+
file_xyz = xyz_scandir(xyz_directory, cropini)
|
1905
|
+
|
1906
|
+
new_array = WolfArray()
|
1907
|
+
new_array.init_from_header(header)
|
1908
|
+
new_array.nullvalue = nullvalue
|
1909
|
+
new_array.array.data[:,:] = nullvalue
|
1910
|
+
new_array.fillin_from_xyz(file_xyz)
|
1911
|
+
if mask_nullvalue:
|
1912
|
+
new_array.mask_data(new_array.nullvalue)
|
1913
|
+
if save_as:
|
1914
|
+
new_array.write_all(save_as)
|
1915
|
+
return new_array
|
1916
|
+
|
1917
|
+
def create_header_wolf_array(self,
|
1918
|
+
dx: float,
|
1919
|
+
dy: float,
|
1920
|
+
originx: float,
|
1921
|
+
originy: float,
|
1922
|
+
extentx:float = None,
|
1923
|
+
extenty:float = None,
|
1924
|
+
nbx: int = None,
|
1925
|
+
nby: int = None) -> header_wolf:
|
1926
|
+
""" Fill and return a header of a wolf_2D array (header_wolf object).
|
1927
|
+
|
1928
|
+
:param dx: Cell size in x direction
|
1929
|
+
:type dx: float
|
1930
|
+
:param dy: Cell size in y direction
|
1931
|
+
:type dy: float
|
1932
|
+
:param originx: x coordinate of the origin of the WolfArray (lower left corner).
|
1933
|
+
:type originx: float
|
1934
|
+
:param originy: y coordinate of the origin of the WolfArray (lower left corner).
|
1935
|
+
:type originy: float
|
1936
|
+
:param extentx: x coordinate of the upper right corner, defaults to None
|
1937
|
+
:type extentx: float, optional
|
1938
|
+
:param extenty: y coordinate of the upper right corner, defaults to None
|
1939
|
+
:type extenty: float, optional
|
1940
|
+
:param nbx: Number of cells in x direction, defaults to None
|
1941
|
+
:type nbx: int, optional
|
1942
|
+
:param nby: Number of cells in y direction, defaults to None
|
1943
|
+
:type nby: int, optional
|
1944
|
+
:return: Header of the WolfArray
|
1945
|
+
:rtype: header_wolf
|
1946
|
+
"""
|
1947
|
+
assert isinstance(originx, (float, int)), 'originx should be a float'
|
1948
|
+
assert isinstance(originy,( float,int)), 'originy should be a float'
|
1949
|
+
assert isinstance(dx, (float,int)), 'dx should be a float'
|
1950
|
+
assert isinstance(dy, (float,int)), 'dy should be a float'
|
1951
|
+
|
1952
|
+
if extentx is not None and extenty is not None:
|
1953
|
+
assert isinstance(extentx, (float,int)), 'extentx should be a float'
|
1954
|
+
assert isinstance(extenty, (float,int)), 'extenty should be a float'
|
1955
|
+
nbx = int((extentx- originx)/dx)
|
1956
|
+
nby = int((extenty- originy)/dy)
|
1957
|
+
|
1958
|
+
if nbx is not None and nby is not None:
|
1959
|
+
assert isinstance(nbx, int), 'nbx should be an integer'
|
1960
|
+
assert isinstance(nby, int), 'nby should be an integer'
|
1961
|
+
nbx = nbx
|
1962
|
+
nby = nby
|
1963
|
+
|
1964
|
+
myhead = header_wolf()
|
1965
|
+
myhead.origx = originx
|
1966
|
+
myhead.origy = originy
|
1967
|
+
myhead.dx = dx
|
1968
|
+
myhead.dy = dy
|
1969
|
+
myhead.nbx = nbx
|
1970
|
+
myhead.nby = nby
|
1971
|
+
|
1972
|
+
return myhead
|
1973
|
+
|
1853
1974
|
|
1854
1975
|
# --- Cross sections ---
|
1855
1976
|
#_______________________
|
@@ -1857,7 +1978,7 @@ class Creator_1D:
|
|
1857
1978
|
|
1858
1979
|
def save_as_1D_crossections(self,
|
1859
1980
|
zones: Zones,
|
1860
|
-
format ='
|
1981
|
+
format ='vecz',
|
1861
1982
|
save_as: str='',
|
1862
1983
|
return_list = True) -> list[crosssections]: # FIXME list of cross sections
|
1863
1984
|
r"""
|
@@ -2168,7 +2289,7 @@ class Creator_1D:
|
|
2168
2289
|
new_zones.add_zone(new_zone)
|
2169
2290
|
new_zone.myvectors = profiles
|
2170
2291
|
new_zones.find_minmax(True)
|
2171
|
-
new_crossections = crosssections(new_zones, '
|
2292
|
+
new_crossections = crosssections(new_zones, 'vecz')
|
2172
2293
|
return new_crossections
|
2173
2294
|
|
2174
2295
|
def extrapolate_extremities(self,
|
@@ -2416,6 +2537,89 @@ class Creator_1D:
|
|
2416
2537
|
relations = np.array(all_relations)
|
2417
2538
|
return relations, (zne_id_skeleton - 1)
|
2418
2539
|
|
2540
|
+
def test_presence_section_name(self, section: vector, list_of_names:list[str]):
|
2541
|
+
"""Check if a section is in a list of names. Return True if it's not.
|
2542
|
+
|
2543
|
+
:param section: section (profile)
|
2544
|
+
:type section: vector
|
2545
|
+
:param list_of_names: list of section names to check
|
2546
|
+
:type list_of_names: list[str]
|
2547
|
+
:return: bool
|
2548
|
+
"""
|
2549
|
+
name = section.myname
|
2550
|
+
if isinstance(name, str):
|
2551
|
+
if "copy" in name.lower():
|
2552
|
+
name = name.split('_')[0]
|
2553
|
+
return not name in list_of_names
|
2554
|
+
|
2555
|
+
def delete_sections_from_cross_sections(self,
|
2556
|
+
cross_sections: crosssections,
|
2557
|
+
list_to_delete: list[str],
|
2558
|
+
save_as = '',
|
2559
|
+
id_zone: int= 0) -> crosssections:
|
2560
|
+
"""Delete a list of sections from a cross_sections object based on their names and
|
2561
|
+
return a new cross_sections object.
|
2562
|
+
|
2563
|
+
:param cross_sections: cross sections object
|
2564
|
+
:type cross_sections: crosssections
|
2565
|
+
:param list_to_delete: list of section names to delete
|
2566
|
+
:type list_to_delete: list[str]
|
2567
|
+
:param id_zone: index of the zone from which vectors are selected, defaults to 0
|
2568
|
+
:return: crosssections where the sections are deleted.
|
2569
|
+
:rtype: crosssections
|
2570
|
+
"""
|
2571
|
+
# 1. Get all sections as vector from cross_sections
|
2572
|
+
# all_sections = cross_sections.myzones.deepcopy_zones(add_suffix = False) # add_suffix deepcopy_zones is not needed anymore
|
2573
|
+
all_sections = cross_sections.myzones.deepcopy_zones()
|
2574
|
+
sections = all_sections.myzones[id_zone].myvectors
|
2575
|
+
# indices_to_delete = []
|
2576
|
+
sections_filtered = list(filter(lambda section: self.test_presence_section_name(section, list_to_delete), sections))
|
2577
|
+
all_sections.myzones[id_zone].myvectors = sections_filtered
|
2578
|
+
|
2579
|
+
|
2580
|
+
# 3. Updating the zone of sections
|
2581
|
+
all_sections.find_minmax(update=True)
|
2582
|
+
# 4. Create a new cross_sections
|
2583
|
+
new_cross_sections = crosssections(all_sections, format='vecz')
|
2584
|
+
if save_as != '':
|
2585
|
+
new_cross_sections.saveas(save_as)
|
2586
|
+
# Return the new cross_sections
|
2587
|
+
return new_cross_sections
|
2588
|
+
|
2589
|
+
def select_sections_from_cross_sections(self,
|
2590
|
+
cross_sections: crosssections,
|
2591
|
+
list_to_select: list[str],
|
2592
|
+
save_as:str ='',
|
2593
|
+
id_zone: int= 0) -> crosssections:
|
2594
|
+
"""Select a list of sections from a cross_sections object based on their names and
|
2595
|
+
return a new cross_sections object.
|
2596
|
+
|
2597
|
+
:param cross_sections: cross sections object
|
2598
|
+
:type cross_sections: crosssections
|
2599
|
+
:param list_to_select: list of section names to select
|
2600
|
+
:type list_to_select: list[str]
|
2601
|
+
:param id_zone: index of the zone from which vectors are selected, defaults to 0
|
2602
|
+
:return: crosssections where the sections are selected.
|
2603
|
+
:rtype: crosssections
|
2604
|
+
"""
|
2605
|
+
|
2606
|
+
all_sections = cross_sections.myzones.myzones[id_zone].myvectors
|
2607
|
+
names = [section.myname for section in all_sections]
|
2608
|
+
selection = []
|
2609
|
+
for name in list_to_select:
|
2610
|
+
if name in names:
|
2611
|
+
selection.append(all_sections[names.index(name)])
|
2612
|
+
zones = Zones()
|
2613
|
+
zne = zone(parent = zones)
|
2614
|
+
zones.myzones.append(zne)
|
2615
|
+
zones.myzones[0].myvectors = selection
|
2616
|
+
zones.find_minmax(update=True)
|
2617
|
+
new_cross_sections = crosssections(zones, format='vecz')
|
2618
|
+
if save_as != '':
|
2619
|
+
new_cross_sections.saveas(save_as)
|
2620
|
+
return new_cross_sections
|
2621
|
+
|
2622
|
+
|
2419
2623
|
# --- Graph cross sections ---
|
2420
2624
|
#______________________________
|
2421
2625
|
|
@@ -4192,7 +4396,14 @@ class Creator_1D:
|
|
4192
4396
|
val1 = prof_indices[0] # FIXME check whether it should start from 0 or from 1
|
4193
4397
|
val2 = prof_indices[1]
|
4194
4398
|
# Writing the condition.
|
4195
|
-
cl.write(f'{val1:12d}{sep}{val2:12d}{sep}{loc:12d}{sep}{condition_type:12d}{sep}{condition_value:20.14f}' +'\n')
|
4399
|
+
# cl.write(f'{val1:12d}{sep}{val2:12d}{sep}{loc:12d}{sep}{condition_type:12d}{sep}{condition_value:20.14f}' +'\n')
|
4400
|
+
|
4401
|
+
# FIXME testing the location
|
4402
|
+
if loc == 1:
|
4403
|
+
cl.write(f'{np.int32(val1)}{sep}{np.int32(val2)}{sep}{np.int32(loc)}{sep}{np.int32(condition_type)}{sep}{np.float32(condition_value)}{sep}zone, vecteur, amont=1 & aval=-1, type, valeur' +'\n')
|
4404
|
+
else:
|
4405
|
+
cl.write(f'{np.int32(val1)}{sep}{np.int32(val2)}{sep}{np.int32(loc)}{sep}{np.int32(condition_type)}{sep}{np.float32(condition_value)}' +'\n')
|
4406
|
+
|
4196
4407
|
# Log messages
|
4197
4408
|
else:
|
4198
4409
|
if self.wx_exists:
|
@@ -5991,7 +6202,7 @@ class Creator_1D:
|
|
5991
6202
|
# check wolfresults_1D update ini file or find another way to update the file.
|
5992
6203
|
|
5993
6204
|
|
5994
|
-
# --- Outdated methods ---
|
6205
|
+
# --- Outdated methods (deprecated) ---
|
5995
6206
|
#_________________________
|
5996
6207
|
|
5997
6208
|
def __write_batch_file(self,
|
@@ -6810,6 +7021,7 @@ class Wolfresults_1D:
|
|
6810
7021
|
|
6811
7022
|
.. todo:: FIXME Implement a consitency check of the files
|
6812
7023
|
.. to do:: FIXME Use a multiprocess to speed up the creation of `.gif` files.
|
7024
|
+
.. to do: FIXME: find a way to implement an initial time for the simulation
|
6813
7025
|
"""
|
6814
7026
|
# Useful properties for other methds
|
6815
7027
|
self.directory = simulation_directory
|
@@ -6844,6 +7056,11 @@ class Wolfresults_1D:
|
|
6844
7056
|
elif file.endswith('banks.vecz'):
|
6845
7057
|
self.bank_file = os.path.join(simulation_directory,file)
|
6846
7058
|
|
7059
|
+
elif file.endswith('_banksbed.vecz'):
|
7060
|
+
self.banksbed_file = os.path.join(simulation_directory,file)
|
7061
|
+
|
7062
|
+
|
7063
|
+
|
6847
7064
|
# Reading the results
|
6848
7065
|
# # head file
|
6849
7066
|
file_size = os.path.getsize(self.head_file) # size of head file in bytes
|
@@ -6870,7 +7087,8 @@ class Wolfresults_1D:
|
|
6870
7087
|
|
6871
7088
|
# Extraction of time steps
|
6872
7089
|
all_times = head_data[nb_coordinates_data + 1::]
|
6873
|
-
self.simulated_times = all_times[1::2]
|
7090
|
+
self.simulated_times = all_times[1::2] # FIXME why does it start from 1?
|
7091
|
+
# self.simulated_times = all_times[::2]
|
6874
7092
|
self.real_times = all_times[::2]
|
6875
7093
|
|
6876
7094
|
# Formula for available results
|
@@ -6945,8 +7163,76 @@ class Wolfresults_1D:
|
|
6945
7163
|
self.froudes_max = self.find_max(self.froudes)
|
6946
7164
|
self.water_level_ymax = self.depths_max + self.find_max(self.topo) # FIXME
|
6947
7165
|
|
7166
|
+
if self.banksbed_file is not None:
|
7167
|
+
self._river_banksbed(self.banksbed_file)
|
7168
|
+
# FIXME delete the unnecessary call of this function (self._river_banksbed) in the plotting methods
|
7169
|
+
|
6948
7170
|
# print('done')
|
6949
7171
|
|
7172
|
+
def get_closest_simulated_time_index(self, time:float)-> int:
|
7173
|
+
"""Return the index of the closest time step to a given time.
|
7174
|
+
|
7175
|
+
The closest time step is obtained by subtracting
|
7176
|
+
the provided time from all simulated times steps.
|
7177
|
+
The smallest difference is then used to find the index of
|
7178
|
+
closest time step.
|
7179
|
+
|
7180
|
+
:param time: the desired time
|
7181
|
+
:type time: float
|
7182
|
+
:return: the index of the closest time step
|
7183
|
+
:rtype: int
|
7184
|
+
"""
|
7185
|
+
assert time >= 0 and time <= self.simulated_times[-1],\
|
7186
|
+
f'The time ({time}) should be greater than 0 and less than the last simulated time - ({self.simulated_times[-1]})'
|
7187
|
+
dif_array = np.absolute(self.simulated_times - time)
|
7188
|
+
id = np.argmin(dif_array)
|
7189
|
+
return id
|
7190
|
+
|
7191
|
+
def get_closest_simulated_results(self,
|
7192
|
+
time:float,
|
7193
|
+
node:int,
|
7194
|
+
which_results: Literal['water level',
|
7195
|
+
'water depth',
|
7196
|
+
'discharge',
|
7197
|
+
'velocity',
|
7198
|
+
'froude',
|
7199
|
+
'wetted section',
|
7200
|
+
'top width',
|
7201
|
+
'all'] = 'all'
|
7202
|
+
)-> Union[float, dict]:
|
7203
|
+
"""
|
7204
|
+
Return the simulated conditions (water level, water depth, discharge, velocity, froude, wetted section, top width
|
7205
|
+
) at the closest time step to a given time.
|
7206
|
+
|
7207
|
+
:param time: the desired time
|
7208
|
+
:type time: float
|
7209
|
+
:param node: the desired node
|
7210
|
+
:type node: int
|
7211
|
+
:return: the conditions at the closest time step
|
7212
|
+
:rtype: np.ndarray
|
7213
|
+
"""
|
7214
|
+
id = self.get_closest_simulated_time_index(time)
|
7215
|
+
water_level = self.water_levels[node,id]
|
7216
|
+
depth = self.depths[node][id]
|
7217
|
+
disharge = self.discharges[node][id]
|
7218
|
+
velocity = self.velocities[node][id]
|
7219
|
+
froude = self.froudes[node][id]
|
7220
|
+
wetted_section = self.wetted_sections[node][id]
|
7221
|
+
top_width = self.widths[node][id]
|
7222
|
+
results = {'water level': water_level,
|
7223
|
+
'water depth': depth,
|
7224
|
+
'discharge': disharge,
|
7225
|
+
'velocity': velocity,
|
7226
|
+
'froude': froude,
|
7227
|
+
'wetted section': wetted_section,
|
7228
|
+
'top width': top_width}
|
7229
|
+
|
7230
|
+
if which_results == 'all':
|
7231
|
+
|
7232
|
+
return results
|
7233
|
+
else:
|
7234
|
+
return results[which_results]
|
7235
|
+
|
6950
7236
|
def update_ic_from_time_steps(self, time_step:int = -1) -> np.ndarray:
|
6951
7237
|
"""Return the simulated conditions (a,q,h, r) at a given time step as a `np.ndarray`.
|
6952
7238
|
- a: Wetted section (index 0),
|
@@ -7004,6 +7290,77 @@ class Wolfresults_1D:
|
|
7004
7290
|
for i in range(lgth_values):
|
7005
7291
|
f.write(f"{df['skeleton'][i]}{sep}{df['zone'][i]}{sep}{df['segment'][i]}{sep}{str(df['value'][i])}\n")
|
7006
7292
|
|
7293
|
+
def get_one_node_evolution(self,
|
7294
|
+
node:int = 0,
|
7295
|
+
variable:Literal['water level', 'discharge', 'water depth', 'velocity', 'wetted section', 'froude'] = 'discharge',
|
7296
|
+
first_time_step = 0,
|
7297
|
+
last_time_step = -1 )-> np.ndarray:
|
7298
|
+
"""Return the discharges at a given node (cross section) for all time steps.
|
7299
|
+
|
7300
|
+
:param node: the desired node, defaults to 0
|
7301
|
+
:type node: int, optional
|
7302
|
+
:return: the discharges at the specified node for all time steps
|
7303
|
+
:rtype: np.ndarray
|
7304
|
+
"""
|
7305
|
+
assert isinstance(node, int),\
|
7306
|
+
f"The node should be an integer greater than 0, not {node}."
|
7307
|
+
assert isinstance(first_time_step, int) and isinstance(last_time_step, int),\
|
7308
|
+
f"The time steps should be integers, not {first_time_step} and {last_time_step}."
|
7309
|
+
|
7310
|
+
if variable == 'discharge':
|
7311
|
+
variable_data = self.discharges
|
7312
|
+
elif variable == 'water depth':
|
7313
|
+
variable_data = self.depths
|
7314
|
+
elif variable == 'water level':
|
7315
|
+
variable_data = self.water_levels
|
7316
|
+
elif variable == 'velocity':
|
7317
|
+
variable_data = self.velocities
|
7318
|
+
elif variable == 'wetted section':
|
7319
|
+
variable_data = self.wetted_sections
|
7320
|
+
elif variable == 'froude':
|
7321
|
+
variable_data = self.froudes
|
7322
|
+
else:
|
7323
|
+
raise ValueError(f"The variable {variable} is not available in results.")
|
7324
|
+
|
7325
|
+
real_first_time_step = self.convert_time_step(first_time_step)
|
7326
|
+
real_last_time_step = self.convert_time_step(last_time_step)
|
7327
|
+
assert real_first_time_step < real_last_time_step,\
|
7328
|
+
f"The first time step should be less than the last time step, not {real_first_time_step} and {real_last_time_step}."
|
7329
|
+
|
7330
|
+
if last_time_step == -1:
|
7331
|
+
data = variable_data[node,first_time_step:]
|
7332
|
+
time = self.simulated_times[first_time_step:]
|
7333
|
+
elif last_time_step < 0 and last_time_step != -1:
|
7334
|
+
data = variable_data[node,first_time_step: (last_time_step +1)]
|
7335
|
+
time = self.simulated_times[first_time_step: (last_time_step +1)]
|
7336
|
+
else:
|
7337
|
+
data = variable_data[node,first_time_step:last_time_step]
|
7338
|
+
time = self.simulated_times[first_time_step:last_time_step]
|
7339
|
+
node_data = pd.Series(data, index = time)
|
7340
|
+
return node_data
|
7341
|
+
|
7342
|
+
def get_one_node_evolution_on_hydrograph_format(self,
|
7343
|
+
node:int = 0,
|
7344
|
+
variable: Literal['water level', 'discharge', 'water depth',
|
7345
|
+
'velocity', 'wetted section', 'froude'] = 'discharge',
|
7346
|
+
first_time_step = 0,
|
7347
|
+
last_time_step = -1,
|
7348
|
+
) -> Hydrograph:
|
7349
|
+
"""Return the hydrograph of a given node for a speiciified range of time steps.
|
7350
|
+
|
7351
|
+
:param node: the desired node, defaults to 0
|
7352
|
+
:type node: int, optional
|
7353
|
+
:param first_time_step: the first time step, defaults to 0
|
7354
|
+
:type first_time_step: int, optional
|
7355
|
+
:param last_time_step: the last time step, defaults to -1
|
7356
|
+
:type last_time_step: int, optional
|
7357
|
+
:return: the hydrograph of the specified node
|
7358
|
+
:rtype: Hydrograph
|
7359
|
+
"""
|
7360
|
+
|
7361
|
+
node_data = self.get_one_node_evolution(node=node,variable=variable,first_time_step=first_time_step,last_time_step=last_time_step)
|
7362
|
+
return Hydrograph(node_data)
|
7363
|
+
|
7007
7364
|
def plot_water_level(self,
|
7008
7365
|
figax:tuple = None,
|
7009
7366
|
time_step:int = 1,
|
@@ -8735,6 +9092,216 @@ class Wolfresults_1D:
|
|
8735
9092
|
:rtype: list
|
8736
9093
|
"""
|
8737
9094
|
|
9095
|
+
animation_axes = self.plot_variables(figures,
|
9096
|
+
time_step,
|
9097
|
+
banksbed,
|
9098
|
+
landmark= landmark,
|
9099
|
+
figsize=figsize,
|
9100
|
+
alpha=alpha,
|
9101
|
+
grid_x_m = grid_x,
|
9102
|
+
grid_y_m = grid_y,
|
9103
|
+
show=False)
|
9104
|
+
for charactetristic in animation_axes:
|
9105
|
+
if len(charactetristic) == 3:
|
9106
|
+
fig = charactetristic[2]
|
9107
|
+
break
|
9108
|
+
real_time_step = self.convert_time_step(time_step)
|
9109
|
+
new_axes = self.create_axis(figures)
|
9110
|
+
if banksbed != '':
|
9111
|
+
minimum_banks = np.minimum(self.z_bankleft,self.z_bankright)
|
9112
|
+
maximum_banks = np.maximum(self.z_bankleft,self.z_bankright)
|
9113
|
+
|
9114
|
+
# animation_axes = figure_charateristics[0]
|
9115
|
+
# fig = figure_charateristics[-1]
|
9116
|
+
def _update_plots(i:int):
|
9117
|
+
index = i + real_time_step -1
|
9118
|
+
h = self.depths[:, index]
|
9119
|
+
wl= self.z_coords + h
|
9120
|
+
q = self.discharges[:, index]
|
9121
|
+
a = self.wetted_sections[:, index]
|
9122
|
+
v = self.velocities[:, index]
|
9123
|
+
# if self.froudes != None:
|
9124
|
+
fr = self.froudes[:, index]
|
9125
|
+
|
9126
|
+
if 'water level' in figures:
|
9127
|
+
wl_id= figures.index('water level')
|
9128
|
+
water_level = animation_axes[wl_id][0]
|
9129
|
+
ax_wl = animation_axes[wl_id][1]
|
9130
|
+
water_level.set_data(self.s_coords,wl)
|
9131
|
+
for coll in ax_wl.collections:
|
9132
|
+
coll.remove()
|
9133
|
+
# ax_wl.collections.clear()
|
9134
|
+
# ax_wl.fill_between(self.s_coords,
|
9135
|
+
# self.z_coords,
|
9136
|
+
# wl,
|
9137
|
+
# where=self.z_coords <= wl,
|
9138
|
+
# facecolor='cyan',
|
9139
|
+
# alpha=0.3,
|
9140
|
+
# interpolate=True )
|
9141
|
+
|
9142
|
+
if banksbed != '':
|
9143
|
+
|
9144
|
+
ax_wl.fill_between(self.s_coords, self.z_coords, wl, where = minimum_banks[:-1] <= wl,
|
9145
|
+
color = Colors.RIVER_COLOR.value,
|
9146
|
+
alpha = Constants.TRANSPARENCY_RIVER.value,
|
9147
|
+
interpolate=True, label='Below banks')
|
9148
|
+
ax_wl.fill_between(self.s_coords, self.z_coords, wl, where = wl <= maximum_banks[:-1],
|
9149
|
+
color = Colors.RIVER_COLOR.value,
|
9150
|
+
alpha = Constants.TRANSPARENCY_RIVER.value,
|
9151
|
+
interpolate=True)
|
9152
|
+
|
9153
|
+
|
9154
|
+
ax_wl.fill_between(self.s_coords, self.z_bankright[:-1], wl, where = self.z_bankright[:-1] < wl,
|
9155
|
+
color =Colors.FLOODED_RIGHT.value, alpha=Constants.TRANSPARENCY_FLOOD.value,
|
9156
|
+
interpolate=True, label='Right flooded')
|
9157
|
+
|
9158
|
+
ax_wl.fill_between(self.s_coords, self.z_bankleft[:-1], wl, where = self.z_bankleft[:-1] < wl,
|
9159
|
+
color =Colors.FLOODED_LEFT.value, alpha=Constants.TRANSPARENCY_FLOOD.value,
|
9160
|
+
interpolate=True, label='left flooded')
|
9161
|
+
|
9162
|
+
ax_wl.fill_between(self.s_coords, maximum_banks[:-1], wl, where = maximum_banks[:-1] <= wl,
|
9163
|
+
color =Colors.FLOODED_ALL.value,
|
9164
|
+
alpha=Constants.TRANSPARENCY_FLOOD.value,interpolate=True, label='All flooded')
|
9165
|
+
|
9166
|
+
ax_wl.fill_between(self.s_coords,
|
9167
|
+
self.z_coords,
|
9168
|
+
y2 = self.z_min,
|
9169
|
+
color='black',
|
9170
|
+
label ='topo',
|
9171
|
+
alpha =0.3
|
9172
|
+
)
|
9173
|
+
|
9174
|
+
# # to be verified
|
9175
|
+
if banksbed != '':
|
9176
|
+
ax_wl.fill_between(self.s_coords, wl, self.z_bankleft[:-1], where = wl >= self.z_bankleft[:-1],
|
9177
|
+
color ='red', alpha=alpha, interpolate=True, label='Left flooded')
|
9178
|
+
ax_wl.fill_between(self.s_coords, wl, self.z_bankright[:-1], where = wl >= self.z_bankright[:-1],
|
9179
|
+
color ='magenta', alpha=alpha, interpolate=True, label='Right flooded')
|
9180
|
+
|
9181
|
+
if landmark != '':
|
9182
|
+
self._landmark(landmark,ax_wl,index, variable='water level', text= False)
|
9183
|
+
# ax_wl.grid()
|
9184
|
+
|
9185
|
+
if 'water depth' in figures:
|
9186
|
+
h_id= figures.index('water depth')
|
9187
|
+
water_depth = animation_axes[h_id][0]
|
9188
|
+
ax_h = animation_axes[h_id][1]
|
9189
|
+
water_depth.set_data(self.s_coords, h)
|
9190
|
+
for coll in ax_h.collections:
|
9191
|
+
coll.remove()
|
9192
|
+
# ax_h.collections.clear()
|
9193
|
+
ax_h.fill_between(self.s_coords, h, facecolor= 'cyan', alpha= 0.3, interpolate=True)
|
9194
|
+
if landmark != '':
|
9195
|
+
self._landmark(landmark,ax_h,index, variable='water depth', text= False)
|
9196
|
+
|
9197
|
+
if 'discharge' in figures:
|
9198
|
+
q_id= figures.index('discharge')
|
9199
|
+
discharge = animation_axes[q_id][0]
|
9200
|
+
ax_q = animation_axes[q_id][1]
|
9201
|
+
discharge.set_data(self.s_coords,q)
|
9202
|
+
for coll in ax_q.collections:
|
9203
|
+
coll.remove()
|
9204
|
+
# ax_q.collections.clear()
|
9205
|
+
ax_q.fill_between(self.s_coords, q, facecolor= 'red', alpha= 0.3, interpolate=True)
|
9206
|
+
if landmark != '':
|
9207
|
+
self._landmark(landmark,ax_q,index, variable='discharge', text= False)
|
9208
|
+
|
9209
|
+
if 'wetted section' in figures:
|
9210
|
+
a_id= figures.index('wetted section')
|
9211
|
+
wetted_section= animation_axes[a_id][0]
|
9212
|
+
ax_a = animation_axes[a_id][1]
|
9213
|
+
wetted_section.set_data(self.s_coords, a)
|
9214
|
+
for coll in ax_a.collections:
|
9215
|
+
coll.remove()
|
9216
|
+
# ax_a.collections.clear()
|
9217
|
+
ax_a.fill_between(self.s_coords, a, facecolor= 'blue', alpha= 0.3, interpolate=True)
|
9218
|
+
if landmark != '':
|
9219
|
+
self._landmark(landmark,ax_a,index, variable='wetted sections', text= False)
|
9220
|
+
|
9221
|
+
if 'velocity' in figures:
|
9222
|
+
v_id= figures.index('velocity')
|
9223
|
+
velocity = animation_axes[v_id][0]
|
9224
|
+
ax_v = animation_axes[v_id][1]
|
9225
|
+
velocity.set_data(self.s_coords, v)
|
9226
|
+
for coll in ax_v.collections:
|
9227
|
+
coll.remove()
|
9228
|
+
# ax_v.collections.clear()
|
9229
|
+
ax_v.fill_between(self.s_coords, v, facecolor= 'red', alpha= 0.3, interpolate=True)
|
9230
|
+
if landmark != '':
|
9231
|
+
self._landmark(landmark,ax_v,index, variable='velocity', text= False)
|
9232
|
+
|
9233
|
+
if 'froude' in figures:
|
9234
|
+
fr_id= figures.index('froude')
|
9235
|
+
froude = animation_axes[fr_id][0]
|
9236
|
+
ax_fr = animation_axes[fr_id][1]
|
9237
|
+
froude.set_data(self.s_coords, fr)
|
9238
|
+
for coll in ax_fr.collections:
|
9239
|
+
coll.remove()
|
9240
|
+
# ax_fr.collections.clear()
|
9241
|
+
# ax_fr.fill_between(self.s_coords, fr, facecolor= 'blue', alpha= 0.3, interpolate=True)
|
9242
|
+
|
9243
|
+
ax_fr.fill_between(self.s_coords, fr,
|
9244
|
+
color ='blue', alpha=alpha, interpolate=True, label='Subcritical')
|
9245
|
+
ax_fr.fill_between(self.s_coords, fr,y2 =1, where= 1<= fr,
|
9246
|
+
color ='blue', alpha=1, interpolate=True, label='Supercritical')
|
9247
|
+
|
9248
|
+
ax_fr.hlines([1], 0, self.s_max, colors='red',linestyles= 'dotted')
|
9249
|
+
if landmark != '':
|
9250
|
+
self._landmark(landmark,ax_fr,index, variable='froude', text= False)
|
9251
|
+
|
9252
|
+
|
9253
|
+
fig.suptitle(f'Results - 1D model\n$Time: (step: {index+1} - $simulated: {self.simulated_times[index+1]:#.1f}s$ - real: {self.real_times[index+1]:#.1e} s)$',
|
9254
|
+
fontsize= 'x-large', fontweight= 'bold')
|
9255
|
+
|
9256
|
+
|
9257
|
+
rcParams['animation.embed_limit'] = 2**128 # Maximum size gif
|
9258
|
+
ani = animation.FuncAnimation(fig,
|
9259
|
+
_update_plots,
|
9260
|
+
interval = 200,
|
9261
|
+
blit = False,
|
9262
|
+
frames = self.simulated_times.shape[0] - real_time_step,
|
9263
|
+
repeat_delay = 100)
|
9264
|
+
|
9265
|
+
if save_as != '':
|
9266
|
+
writergif = animation.PillowWriter(fps=5)
|
9267
|
+
ani.save(save_as, writer=writergif)
|
9268
|
+
|
9269
|
+
return HTML(ani.to_jshtml())
|
9270
|
+
|
9271
|
+
def ____FIXMEanimate_1D_plots(self,
|
9272
|
+
figures:list[Literal['water level', 'discharge', 'wetted section','water depth', 'velocity', 'froude']] =['water level', 'discharge', 'wetted section','water depth', 'velocity', 'froude'],
|
9273
|
+
time_step:int = 1,
|
9274
|
+
banksbed: Union[str, Zones] = '',
|
9275
|
+
landmark: Union[str,Zones]= '',
|
9276
|
+
figsize:tuple = (20,10),
|
9277
|
+
alpha:float = 0.3,
|
9278
|
+
save_as:str='',
|
9279
|
+
grid_x = 1000,
|
9280
|
+
grid_y = 10):
|
9281
|
+
"""
|
9282
|
+
Animate the selected variables in figures, save them as a file and
|
9283
|
+
return the information associated with their axes.
|
9284
|
+
|
9285
|
+
FIXME: implement multiprocesses to speed up the process.
|
9286
|
+
|
9287
|
+
:param figures: List of figures
|
9288
|
+
:type figures: list
|
9289
|
+
:param time_step: Time step, defaults to 1
|
9290
|
+
:type time_step: int, optional
|
9291
|
+
:param banksbed: Banksbed, defaults to ''
|
9292
|
+
:type banksbed: Union[str, Zones], optional
|
9293
|
+
:param landmark: Landmark, defaults to ''
|
9294
|
+
:type landmark: Union[str,Zones], optional
|
9295
|
+
:param figsize: Figsize, defaults to (20,10)
|
9296
|
+
:type figsize: tuple, optional
|
9297
|
+
:param alpha: Alpha, defaults to 0.3
|
9298
|
+
:type alpha: float, optional
|
9299
|
+
:param save_as: Save as, defaults to ''
|
9300
|
+
:type save_as: str, optional
|
9301
|
+
:return: List of axes
|
9302
|
+
:rtype: list
|
9303
|
+
"""
|
9304
|
+
|
8738
9305
|
animation_axes = self.plot_variables(figures,
|
8739
9306
|
time_step,
|
8740
9307
|
banksbed,
|
@@ -8884,6 +9451,7 @@ class Wolfresults_1D:
|
|
8884
9451
|
fig.suptitle(f'Results - 1D model\n$Time: (step: {index+1} - $simulated: {self.simulated_times[index+1]:#.1f}s$ - real: {self.real_times[index+1]:#.1e} s)$',
|
8885
9452
|
fontsize= 'x-large', fontweight= 'bold')
|
8886
9453
|
|
9454
|
+
# pillow_image = Image.frombytes('RGB',fig.canvas.get_width_height(),fig.canvas.tostring_rgb())
|
8887
9455
|
|
8888
9456
|
rcParams['animation.embed_limit'] = 2**128 # Maximum size gif
|
8889
9457
|
ani = animation.FuncAnimation(fig,
|
@@ -9066,6 +9634,8 @@ class Wolfresults_1D:
|
|
9066
9634
|
real_timestep = self.results_length + time_step
|
9067
9635
|
elif time_step > 0 and time_step <= self.results_length:
|
9068
9636
|
real_timestep= time_step - 1 # Python counts from 0.
|
9637
|
+
elif time_step == 0:
|
9638
|
+
real_timestep = 0
|
9069
9639
|
else:
|
9070
9640
|
real_timestep = self.results_length - 1
|
9071
9641
|
warnings.warn(f'The input (time step) was not found; therefore, the last time step is plotted.', UserWarning)
|
@@ -9256,7 +9826,15 @@ class Wolfresults_1D:
|
|
9256
9826
|
:rtype: float
|
9257
9827
|
"""
|
9258
9828
|
|
9259
|
-
if y_max <
|
9829
|
+
if y_max < 0.0001:
|
9830
|
+
grid_y = .00001
|
9831
|
+
elif y_max < 0.001:
|
9832
|
+
grid_y = .0001
|
9833
|
+
elif y_max < 0.01:
|
9834
|
+
grid_y = .001
|
9835
|
+
elif y_max < 0.1:
|
9836
|
+
grid_y = .01
|
9837
|
+
elif y_max < 1:
|
9260
9838
|
grid_y = .1
|
9261
9839
|
elif y_max < 3:
|
9262
9840
|
grid_y = .2
|
@@ -9582,7 +10160,8 @@ class Wolfresults_1D:
|
|
9582
10160
|
save_as:str = '',
|
9583
10161
|
figure_size = (25,15),
|
9584
10162
|
linewidth = 2.,
|
9585
|
-
plotting_style:Literal['scatter','line', 'combined'] = 'line'
|
10163
|
+
plotting_style:Literal['scatter','line', 'combined'] = 'line',
|
10164
|
+
convert_to: Literal['days', 'hours', 'minutes', 'seconds'] = 'seconds'
|
9586
10165
|
):
|
9587
10166
|
"""
|
9588
10167
|
Plot the evolution in time of specified nodes for a variable.
|
@@ -9600,10 +10179,17 @@ class Wolfresults_1D:
|
|
9600
10179
|
:param plotting_style: Plotting style (matplotlib format), defaults to 'line'
|
9601
10180
|
:type plotting_style: Literal['scatter','line', 'combined'], optional
|
9602
10181
|
"""
|
10182
|
+
coefficient = self.find_conversion_factor_from_seconds(convert_to)
|
10183
|
+
|
10184
|
+
def axis_as_time_unit(val, pos):
|
10185
|
+
value = val/coefficient
|
10186
|
+
return f'{value:#.1F}'
|
9603
10187
|
|
9604
10188
|
fig = plt.figure(figsize = figure_size, facecolor='white')
|
9605
10189
|
ax1 = fig.add_subplot(121)
|
9606
10190
|
ax2 = fig.add_subplot(122)
|
10191
|
+
ax1.xaxis.set_major_formatter(FuncFormatter(axis_as_time_unit))
|
10192
|
+
ax2.xaxis.set_major_formatter(FuncFormatter(axis_as_time_unit))
|
9607
10193
|
|
9608
10194
|
# Define the variable
|
9609
10195
|
if variable_name == 'discharge':
|
@@ -9678,7 +10264,8 @@ class Wolfresults_1D:
|
|
9678
10264
|
ax1.set_xlim(np.min(self.simulated_times),np.max(self.simulated_times))
|
9679
10265
|
ax1.set_ylim(min(min_variable), (max(max_variable) + 0.1*max(max_variable)))
|
9680
10266
|
# ax1.set_ylabel('Discharge [$m^3$]', fontsize= 'x-large')
|
9681
|
-
ax1.set_xlabel('Simulated
|
10267
|
+
# ax1.set_xlabel('Simulated time [$s$]', fontsize= 'x-large')
|
10268
|
+
ax1.set_xlabel(f'Time [{convert_to}]')
|
9682
10269
|
ax1.legend()
|
9683
10270
|
# ax1.xaxis.set_ticks(np.arange(min(times),max(times),900))
|
9684
10271
|
x_max = max(self.simulated_times)
|
@@ -9696,7 +10283,8 @@ class Wolfresults_1D:
|
|
9696
10283
|
# max(max_variable) + 0.05* max(max_variable),
|
9697
10284
|
# f'$Phase-shift: {max_times[0]-max_times[-1]:#.2f}s$', fontsize =14)
|
9698
10285
|
ax2.set_ylim(min(min_variable), (max(max_variable) + 0.1*max(max_variable)))
|
9699
|
-
ax2.set_xlabel('Time from maximum [s]', fontsize= 'x-large')
|
10286
|
+
# ax2.set_xlabel('Time from maximum [s]', fontsize= 'x-large')
|
10287
|
+
ax2.set_xlabel(f'Time from maximum [{convert_to}]')
|
9700
10288
|
ax2.legend()
|
9701
10289
|
# ax2.xaxis.set_ticks(np.arange((-tmax/2),(tmax/2),900))
|
9702
10290
|
ax2.grid()
|
@@ -9799,6 +10387,1184 @@ class Wolfresults_1D:
|
|
9799
10387
|
fig.savefig(save_as, dpi =300)
|
9800
10388
|
plt.show()
|
9801
10389
|
|
10390
|
+
@staticmethod
|
10391
|
+
def find_conversion_factor_from_seconds(convert_to: Literal['days', 'hours', 'minutes', 'seconds'] = 'seconds') -> int:
|
10392
|
+
"""
|
10393
|
+
Find the conversion factor of a given time format to seconds.
|
10394
|
+
|
10395
|
+
:param convert_to: The time format to convert to.
|
10396
|
+
:type convert_to: Literal['days', 'hours', 'minutes', 'seconds']
|
10397
|
+
:return: The conversion factor.
|
10398
|
+
:rtype: int
|
10399
|
+
"""
|
10400
|
+
if convert_to == 'days':
|
10401
|
+
return 86400
|
10402
|
+
elif convert_to == 'hours':
|
10403
|
+
return 3600
|
10404
|
+
elif convert_to == 'minutes':
|
10405
|
+
return 60
|
10406
|
+
elif convert_to == 'seconds':
|
10407
|
+
return 1
|
10408
|
+
else:
|
10409
|
+
raise ValueError('The time format is not recognized')
|
10410
|
+
|
10411
|
+
def project_coordinates_on_bed(self, x:float, y:float, z:float = None) -> tuple[int, float]:
|
10412
|
+
"""
|
10413
|
+
Return the projection of the given point coorinates on the river bed.
|
10414
|
+
The return format is the following (x, y, s).
|
10415
|
+
Where,
|
10416
|
+
- x is the x coordinate of the projected point,
|
10417
|
+
- y is the y coordinate of the projected point,
|
10418
|
+
- s is the curviligne coordinate of the projected point (length from origin).
|
10419
|
+
|
10420
|
+
:param x: X coordinate
|
10421
|
+
:type x: float
|
10422
|
+
:param y: Y coordinate
|
10423
|
+
:type y: float
|
10424
|
+
:param z: Z coordinate, defaults to None
|
10425
|
+
:type z: float, optional
|
10426
|
+
:return: Projected coordinates
|
10427
|
+
:rtype: tuple[float,float,float]
|
10428
|
+
"""
|
10429
|
+
|
10430
|
+
if z is None:
|
10431
|
+
projection = self.mid_river_ls.project(Point(x,y))
|
10432
|
+
|
10433
|
+
else:
|
10434
|
+
projection = self.mid_river_ls.project(Point(x,y,z))
|
10435
|
+
|
10436
|
+
|
10437
|
+
closest_length, index_in_the_simulation = self.find_nearest(self.s_coords, projection)
|
10438
|
+
|
10439
|
+
return index_in_the_simulation, closest_length
|
10440
|
+
|
10441
|
+
def find_nearest(self, array: np.array, value: Literal[int, float]) -> tuple[float,int]:
|
10442
|
+
"Find the nearest value to the <value> and its index in an array."
|
10443
|
+
array = np.asarray(array)
|
10444
|
+
idx = (np.abs(array - value)).argmin()
|
10445
|
+
return array[idx], idx
|
10446
|
+
|
10447
|
+
def get_results_from_xy_coordinates(self, x:float, y:float, time:int) -> dict:
|
10448
|
+
"""
|
10449
|
+
Return the results of the closest point coordinates for a given time.
|
10450
|
+
|
10451
|
+
:param x: X coordinate
|
10452
|
+
:type x: float
|
10453
|
+
:param y: Y coordinate
|
10454
|
+
:type y: float
|
10455
|
+
:param time: Time step
|
10456
|
+
:type time: int
|
10457
|
+
:return: Results
|
10458
|
+
:rtype: dict
|
10459
|
+
"""
|
10460
|
+
assert isinstance(time, int), 'The time step must be an integer.'
|
10461
|
+
|
10462
|
+
index, s = self.project_coordinates_on_bed(x,y)
|
10463
|
+
# results = self.get_closest_simulated_results(time, index)
|
10464
|
+
# results['Distance'] = s
|
10465
|
+
return self.get_closest_simulated_results(time, index)
|
10466
|
+
|
10467
|
+
def get_simulated_velocity(self, node_index: int, time_index: int) -> float:
|
10468
|
+
"""Return the simulated velocity at a given node and time step."""
|
10469
|
+
# assert isinstance(time_index, (int, np.ndarray)), 'The time step must be an integer or an array of integers.'
|
10470
|
+
# assert isinstance(node_index, (int, np.ndarray)), 'The node index must be an integer or an array of integers.'
|
10471
|
+
return self.velocities[node_index][time_index]
|
10472
|
+
|
10473
|
+
def _get_buoy_trajectories(self,
|
10474
|
+
time:float,
|
10475
|
+
x:float,
|
10476
|
+
y:float,
|
10477
|
+
distance: float = None,
|
10478
|
+
number_cells = None) -> pd.DataFrame:
|
10479
|
+
assert time>=0 and time <= self.simulated_times[-1],\
|
10480
|
+
f'The time must be within the simulation time range. {self.simulated_times[0]} <= time <= {self.simulated_times[-1]}'
|
10481
|
+
|
10482
|
+
buoy_data = pd.DataFrame(columns = ['time',
|
10483
|
+
'distance',
|
10484
|
+
'water level',
|
10485
|
+
'water depth',
|
10486
|
+
'discharge',
|
10487
|
+
'velocity',
|
10488
|
+
'froude',
|
10489
|
+
'wetted section',
|
10490
|
+
'top width'])
|
10491
|
+
|
10492
|
+
|
10493
|
+
if distance is None and number_cells is None:
|
10494
|
+
all_cells = len(self.s_coords)
|
10495
|
+
index, s = self.project_coordinates_on_bed(x, y)
|
10496
|
+
distance = s
|
10497
|
+
|
10498
|
+
while tqdm(index < all_cells, desc='Computing buoy trajectories', unit='node', disable = True):
|
10499
|
+
|
10500
|
+
results = self.get_closest_simulated_results(time, index)
|
10501
|
+
|
10502
|
+
buoy_data.loc[len(buoy_data.index)] ={'time': time,
|
10503
|
+
'distance': distance,
|
10504
|
+
'water level': results['water level'],
|
10505
|
+
'water depth': results['water depth'],
|
10506
|
+
'discharge': results['discharge'],
|
10507
|
+
'velocity': results['velocity'],
|
10508
|
+
'froude': results['froude'],
|
10509
|
+
'wetted section': results['wetted section'],
|
10510
|
+
'top width': results['top width']}
|
10511
|
+
|
10512
|
+
new_index = index + 1
|
10513
|
+
if new_index < all_cells:
|
10514
|
+
distance = self.s_coords[new_index]
|
10515
|
+
distance_difference = self.s_coords[new_index] - self.s_coords[index]
|
10516
|
+
# time += distance_difference / results['velocity']
|
10517
|
+
time += distance_difference / abs(results['velocity']) # FIXME
|
10518
|
+
index = new_index
|
10519
|
+
|
10520
|
+
return buoy_data
|
10521
|
+
|
10522
|
+
def update_spatial_index_from_velocity(self, spatial_index:int, velocity:float,) -> int:
|
10523
|
+
"""
|
10524
|
+
Update the spatial index based on the velocity.
|
10525
|
+
"""
|
10526
|
+
if velocity > 0:
|
10527
|
+
new_spatial_index = spatial_index + 1
|
10528
|
+
elif velocity < 0:
|
10529
|
+
new_spatial_index = spatial_index - 1
|
10530
|
+
elif velocity == 0:
|
10531
|
+
raise ValueError(f'The velocity at node - {spatial_index} is null. The buoy is not moving.')
|
10532
|
+
else:
|
10533
|
+
raise ValueError(f'The velocity at node - {spatial_index} is not a number.')
|
10534
|
+
return new_spatial_index
|
10535
|
+
|
10536
|
+
def update_spatial_index_from_velocity_backwards(self, spatial_index:int, velocity:float,) -> int:
|
10537
|
+
"""
|
10538
|
+
Update the spatial index based on the velocity.
|
10539
|
+
"""
|
10540
|
+
if velocity > 0:
|
10541
|
+
new_spatial_index = spatial_index - 1
|
10542
|
+
elif velocity < 0:
|
10543
|
+
new_spatial_index = spatial_index + 1
|
10544
|
+
elif velocity == 0:
|
10545
|
+
raise ValueError(f'The velocity at node - {spatial_index} is null. The buoy is not moving.')
|
10546
|
+
else:
|
10547
|
+
raise ValueError(f'The velocity at node - {spatial_index} is not a number.')
|
10548
|
+
return new_spatial_index
|
10549
|
+
|
10550
|
+
def update_null_and_missing_velocities(self, velocity: float, treshold: float = 0.0001):
|
10551
|
+
"""
|
10552
|
+
If the velocity is null or a missing value,
|
10553
|
+
a treshold (preferably very small) is returned
|
10554
|
+
as the new velocity to avoid division by zero or nan.
|
10555
|
+
|
10556
|
+
@ Added during Gaia's internship
|
10557
|
+
to handle null and missing velocities.
|
10558
|
+
"""
|
10559
|
+
if velocity > 0:
|
10560
|
+
return velocity
|
10561
|
+
elif velocity < 0:
|
10562
|
+
return velocity
|
10563
|
+
elif velocity == 0:
|
10564
|
+
logging.warning(f'The velocity was zero. It has been changed to 0.0001m/s')
|
10565
|
+
return treshold
|
10566
|
+
else:
|
10567
|
+
logging.warning(f'The velocity was not a number. It has been changed to 0.0001m/s')
|
10568
|
+
return treshold
|
10569
|
+
|
10570
|
+
def _create_buoy_dataframe(self) -> pd.DataFrame:
|
10571
|
+
"""
|
10572
|
+
Create a DataFrame to store data from a drifter (buoy) trajectory.
|
10573
|
+
"""
|
10574
|
+
columns = ['time',
|
10575
|
+
'distance',
|
10576
|
+
'water level',
|
10577
|
+
'water depth',
|
10578
|
+
'discharge',
|
10579
|
+
'velocity',
|
10580
|
+
'froude',
|
10581
|
+
'wetted section',
|
10582
|
+
'top width']
|
10583
|
+
return pd.DataFrame(columns=columns)
|
10584
|
+
|
10585
|
+
def _compute_number_of_cells_on_curvilinear_vector(self) -> int:
|
10586
|
+
"""Return the number of cells on the curvilinear vector."""
|
10587
|
+
return len(self.s_coords)
|
10588
|
+
|
10589
|
+
def _get_results_velocity_for_buoy(self, results: dict) -> float:
|
10590
|
+
"""
|
10591
|
+
Extract the velocity result for the buoy from the results dictionary.
|
10592
|
+
"""
|
10593
|
+
assert isinstance(results, dict), 'The results must be a dictionary.'
|
10594
|
+
velocity = results.get('velocity', None)
|
10595
|
+
if velocity is None:
|
10596
|
+
raise Exception('Velocity not found')
|
10597
|
+
return self.update_null_and_missing_velocities(velocity)
|
10598
|
+
|
10599
|
+
def _get_distance_between_spatial_indices(self, from_index:int, to_index:int) -> float:
|
10600
|
+
"""
|
10601
|
+
Compute the distance between two spatial indices.
|
10602
|
+
"""
|
10603
|
+
return self.s_coords[to_index] - self.s_coords[from_index]
|
10604
|
+
|
10605
|
+
def _update_time_from_distance_and_velocity(self,
|
10606
|
+
time: float,
|
10607
|
+
distance: float,
|
10608
|
+
velocity:float) -> float:
|
10609
|
+
"""
|
10610
|
+
Return the updated time which is computed
|
10611
|
+
based on the provided distance and velocity.
|
10612
|
+
|
10613
|
+
!!! Mainly used in buoy applications (drifters)
|
10614
|
+
in the forward direction.
|
10615
|
+
|
10616
|
+
:param time: Initial time
|
10617
|
+
:type time: float
|
10618
|
+
:param distance: Distance travelled between two points.
|
10619
|
+
:type distance: float
|
10620
|
+
:param velocity: Velocity
|
10621
|
+
:type velocity: float
|
10622
|
+
:return: Updated time
|
10623
|
+
:rtype: float
|
10624
|
+
"""
|
10625
|
+
time_delta = abs(distance)/ abs(velocity)
|
10626
|
+
return time + time_delta
|
10627
|
+
|
10628
|
+
def _update_time_from_distance_and_velocity_backwards(self,
|
10629
|
+
time: float,
|
10630
|
+
distance: float,
|
10631
|
+
velocity:float) -> float:
|
10632
|
+
"""
|
10633
|
+
Return the updated time which is computed
|
10634
|
+
based on the provided distance and velocity.
|
10635
|
+
|
10636
|
+
!!! Mainly used in buoy applications (drifters)
|
10637
|
+
in the backward direction.
|
10638
|
+
|
10639
|
+
:param time: Initial time
|
10640
|
+
:type time: float
|
10641
|
+
:param distance: Distance travelled between two points.
|
10642
|
+
:type distance: float
|
10643
|
+
:param velocity: Velocity
|
10644
|
+
:type velocity: float
|
10645
|
+
:return: Updated time
|
10646
|
+
:rtype: float
|
10647
|
+
"""
|
10648
|
+
time_delta = abs(distance)/ abs(velocity)
|
10649
|
+
return time - time_delta
|
10650
|
+
|
10651
|
+
def _verify_and_correct_overtime(self, time: float):
|
10652
|
+
"""
|
10653
|
+
Ensure the time does not exceed the simulated time range.
|
10654
|
+
"""
|
10655
|
+
if time > self.simulated_times [-1]:
|
10656
|
+
return self.simulated_times[-1]
|
10657
|
+
return time
|
10658
|
+
|
10659
|
+
def _verify_and_correct_overtime_backwards(self, time: float):
|
10660
|
+
"""
|
10661
|
+
Return the starting time of the simulation
|
10662
|
+
if the provided time is less than the first simulated time.
|
10663
|
+
|
10664
|
+
! To ensure the time does not exceed the simulated time range,
|
10665
|
+
lowest boundary.
|
10666
|
+
"""
|
10667
|
+
if time < self.simulated_times[0]:
|
10668
|
+
return self.simulated_times[0]
|
10669
|
+
return time
|
10670
|
+
|
10671
|
+
def _verify_and_correct_overtime_smart_way(self,
|
10672
|
+
time_0: float,
|
10673
|
+
time_1: float,
|
10674
|
+
temporal_index_0: int,
|
10675
|
+
temporal_index_1: int,
|
10676
|
+
distance: float,
|
10677
|
+
spatial_index: int) -> tuple[float, int]:
|
10678
|
+
"""
|
10679
|
+
FIXME: examine this algorithm and check its impact on results.
|
10680
|
+
to ensure the time does not exceed the simulated time range.
|
10681
|
+
"""
|
10682
|
+
if time_1 > self.simulated_times [-1]:
|
10683
|
+
while time_1 > self.simulated_times[-1]:
|
10684
|
+
temporal_index_1 += 1
|
10685
|
+
results = self.get_closest_simulated_results(self.simulated_times[temporal_index_1], spatial_index)
|
10686
|
+
velocity = self._get_results_velocity_for_buoy(results)
|
10687
|
+
time = self._update_time_from_distance_and_velocity(time, distance, velocity)
|
10688
|
+
|
10689
|
+
time_delta = abs(distance) / abs(velocity)
|
10690
|
+
time_1 = time_0 + time_delta
|
10691
|
+
# To avoid repeating the same procedure at the next test.
|
10692
|
+
temporal_index_0 = temporal_index_1
|
10693
|
+
return time_1, temporal_index_0
|
10694
|
+
|
10695
|
+
def _verify_and_correct_overtime_smart_way_backwards(self,
|
10696
|
+
time_0: float,
|
10697
|
+
time_1: float,
|
10698
|
+
temporal_index_0: int,
|
10699
|
+
temporal_index_1: int,
|
10700
|
+
distance: float,
|
10701
|
+
spatial_index: int) -> tuple[float, int]:
|
10702
|
+
"""
|
10703
|
+
FIXME: examine this algorithm and check its impact on results.
|
10704
|
+
Ensure the time does not exceed the simulated time range.
|
10705
|
+
"""
|
10706
|
+
if time_1 < self.simulated_times [0]:
|
10707
|
+
while time_1 < self.simulated_times[0]:
|
10708
|
+
temporal_index_1 -= 1
|
10709
|
+
results = self.get_closest_simulated_results(self.simulated_times[temporal_index_1], spatial_index)
|
10710
|
+
velocity = self._get_results_velocity_for_buoy(results)
|
10711
|
+
time = self._update_time_from_distance_and_velocity(time, distance, velocity)
|
10712
|
+
|
10713
|
+
time_delta = abs(distance) / abs(velocity)
|
10714
|
+
time_1 = time_0 - time_delta
|
10715
|
+
# To avoid repeating the same procedure at the next test.
|
10716
|
+
temporal_index_0 = temporal_index_1
|
10717
|
+
return time_1, temporal_index_0
|
10718
|
+
|
10719
|
+
def _corrected_skipped_temporal_indices(self,
|
10720
|
+
time_0: float,
|
10721
|
+
time_1: float,
|
10722
|
+
temporal_index_0: int,
|
10723
|
+
temporal_index_1: int,
|
10724
|
+
distance: float,
|
10725
|
+
spatial_index: int,
|
10726
|
+
velocity: float) -> tuple[float, int]:
|
10727
|
+
|
10728
|
+
"""
|
10729
|
+
Correct the time and spatial index if some temporal indices were skipped.
|
10730
|
+
(! Mainly for forwards direction)
|
10731
|
+
|
10732
|
+
This can happen when the buoy moved faster
|
10733
|
+
than the first encountered flow velocity in the simulation.
|
10734
|
+
|
10735
|
+
For instance, if the buoy moved from temporal index 5 to 10,
|
10736
|
+
the maximum simulated velocity between indices 5 and 10 is searched.
|
10737
|
+
|
10738
|
+
:param time_0: Initial time
|
10739
|
+
:type time_0: float
|
10740
|
+
:param time_1: Updated time
|
10741
|
+
:type time_1: float
|
10742
|
+
:param temporal_index_0: Initial temporal index
|
10743
|
+
:type temporal_index_0: int
|
10744
|
+
:param temporal_index_1: Updated temporal index
|
10745
|
+
:type temporal_index_1: int
|
10746
|
+
:param distance: Distance travelled between two points.
|
10747
|
+
:type distance: float
|
10748
|
+
:param spatial_index: Spatial index
|
10749
|
+
:type spatial_index: int
|
10750
|
+
:param velocity: Velocity
|
10751
|
+
:type velocity: float
|
10752
|
+
:return: Updated time and spatial index
|
10753
|
+
:rtype: tuple[float, int]
|
10754
|
+
"""
|
10755
|
+
if temporal_index_1 - temporal_index_0 > 1:
|
10756
|
+
potential_temporal_indices = np.arange(temporal_index_0, temporal_index_1 + 1, 1)
|
10757
|
+
potential_velocities = self.get_simulated_velocity(spatial_index, potential_temporal_indices)
|
10758
|
+
|
10759
|
+
# 1. Getting the maximum velocity in the time range.
|
10760
|
+
maximum_velocity = np.max(potential_velocities)
|
10761
|
+
# 2. If the maximum velocity is not the previously extracted velocity which means the buoy moved faster,
|
10762
|
+
# the temporal index is updated to the index of the maximum velocity.
|
10763
|
+
if maximum_velocity != velocity:
|
10764
|
+
updated_temporal_index = potential_temporal_indices[np.argmax(potential_velocities)]
|
10765
|
+
# 3. The time and the results are updated.
|
10766
|
+
updated_time = self.simulated_times[updated_temporal_index]
|
10767
|
+
results = self.get_closest_simulated_results(updated_time, spatial_index)
|
10768
|
+
velocity = self._get_results_velocity_for_buoy(results)
|
10769
|
+
time_1 = self._update_time_from_distance_and_velocity(time_0, distance, velocity)
|
10770
|
+
# The spatial index is updated (mainly the sign)
|
10771
|
+
new_spatial_index = self.update_spatial_index_from_velocity(spatial_index, velocity)
|
10772
|
+
return time_1, new_spatial_index
|
10773
|
+
return time_1, spatial_index
|
10774
|
+
|
10775
|
+
def _corrected_skipped_temporal_indices_backwards(self,
|
10776
|
+
time_0: float,
|
10777
|
+
time_1: float,
|
10778
|
+
temporal_index_0: int,
|
10779
|
+
temporal_index_1: int,
|
10780
|
+
distance: float,
|
10781
|
+
spatial_index: int,
|
10782
|
+
velocity: float) -> tuple[float, int]:
|
10783
|
+
|
10784
|
+
"""
|
10785
|
+
Correct the time and spatial index if some temporal indices were skipped.
|
10786
|
+
(! Mainly for backwards direction)
|
10787
|
+
This can happen when the buoy moved faster
|
10788
|
+
than the first encountered flow velocity in the simulation.
|
10789
|
+
For instance, if the buoy moved from temporal index 10 to 5,
|
10790
|
+
the maximum simulated velocity between indices 5 and 10 is searched.
|
10791
|
+
:param time_0: Initial time
|
10792
|
+
:type time_0: float
|
10793
|
+
:param time_1: Updated time
|
10794
|
+
:type time_1: float
|
10795
|
+
:param temporal_index_0: Initial temporal index
|
10796
|
+
:type temporal_index_0: int
|
10797
|
+
:param temporal_index_1: Updated temporal index
|
10798
|
+
:type temporal_index_1: int
|
10799
|
+
:param distance: Distance travelled between two points.
|
10800
|
+
:type distance: float
|
10801
|
+
:param spatial_index: Spatial index
|
10802
|
+
:type spatial_index: int
|
10803
|
+
:param velocity: Velocity
|
10804
|
+
:type velocity: float
|
10805
|
+
:return: Updated time and spatial index
|
10806
|
+
:rtype: tuple[float, int]
|
10807
|
+
"""
|
10808
|
+
if temporal_index_0 - temporal_index_1> 1:
|
10809
|
+
potential_temporal_indices = np.arange(temporal_index_1,temporal_index_0 + 1, 1)
|
10810
|
+
potential_velocities = self.get_simulated_velocity(spatial_index, potential_temporal_indices)
|
10811
|
+
|
10812
|
+
# 1. Getting the maximum velocity in the time range.
|
10813
|
+
maximum_velocity = np.max(potential_velocities)
|
10814
|
+
# 2. If the maximum velocity is not the previously extracted velocity which means the buoy moved faster,
|
10815
|
+
# the temporal index is updated to the index of the maximum velocity.
|
10816
|
+
if maximum_velocity != velocity:
|
10817
|
+
updated_temporal_index = potential_temporal_indices[np.argmax(potential_velocities)]
|
10818
|
+
# 3. The time and the results are updated.
|
10819
|
+
updated_time = self.simulated_times[updated_temporal_index]
|
10820
|
+
results = self.get_closest_simulated_results(updated_time, spatial_index)
|
10821
|
+
velocity = self._get_results_velocity_for_buoy(results)
|
10822
|
+
time_1 = self._update_time_from_distance_and_velocity_backwards(time_0, distance, velocity)
|
10823
|
+
# The spatial index is updated (mainly the sign)
|
10824
|
+
new_spatial_index = self.update_spatial_index_from_velocity_backwards(spatial_index, velocity)
|
10825
|
+
return time_1, new_spatial_index
|
10826
|
+
return time_1, spatial_index
|
10827
|
+
|
10828
|
+
def get_buoy_forward_trajectory(self,
|
10829
|
+
time:float,
|
10830
|
+
x:float,
|
10831
|
+
y:float,
|
10832
|
+
debug = False) -> pd.DataFrame:
|
10833
|
+
"""
|
10834
|
+
Compute the trajectory of a buoy (drifter) in the forward direction.
|
10835
|
+
:param time: Initial time
|
10836
|
+
:type time: float
|
10837
|
+
:param x: X coordinate of the initial position
|
10838
|
+
:type x: float
|
10839
|
+
:param y: Y coordinate of the initial position
|
10840
|
+
:type y: float
|
10841
|
+
:param debug: If True, print debug information, defaults to False
|
10842
|
+
:type debug: bool, optional
|
10843
|
+
:return: DataFrame containing the buoy trajectory data
|
10844
|
+
:rtype: pd.DataFrame
|
10845
|
+
"""
|
10846
|
+
|
10847
|
+
# Step 1: We verify whether the time is within the simulated range.
|
10848
|
+
# ----------------------------------------------------------------
|
10849
|
+
assert time>=0 and time <= self.simulated_times[-1],\
|
10850
|
+
f'The time must be within the simulated time span.\
|
10851
|
+
{self.simulated_times[0]} <= time - {time} <= {self.simulated_times[-1]}'
|
10852
|
+
# Step 2: We create a DataFrame to store the buoy trajectory data.
|
10853
|
+
# ---------------------------------------------------------------
|
10854
|
+
buoy_data = self._create_buoy_dataframe()
|
10855
|
+
|
10856
|
+
# Step 3: Compute the curvilinear distance from the first simulation cell
|
10857
|
+
# and its index (spatial index). FInd the number of cells in the results
|
10858
|
+
# -------------------------------------------------------------------------
|
10859
|
+
spatial_index, distance = self.project_coordinates_on_bed(x, y)
|
10860
|
+
number_of_cells = self._compute_number_of_cells_on_curvilinear_vector()
|
10861
|
+
|
10862
|
+
# Step 4: Extraction of results
|
10863
|
+
# -----------------------------
|
10864
|
+
|
10865
|
+
# 4.1. Creation of loop to ensure that the computations are done within
|
10866
|
+
# the simulated time range and spatial limits.
|
10867
|
+
# -------------------------------------------------------------------
|
10868
|
+
if debug:
|
10869
|
+
# disable = False
|
10870
|
+
disable = True
|
10871
|
+
else:
|
10872
|
+
disable = True
|
10873
|
+
while tqdm(spatial_index < number_of_cells-1 and time < self.simulated_times[-1],\
|
10874
|
+
desc='Computing buoy trajectories', unit='cell', disable=disable):
|
10875
|
+
# 4.1.2. Get the closest simulated results in time at the provided coordinates.
|
10876
|
+
# -------------------------------------------------------------------------
|
10877
|
+
results = self.get_closest_simulated_results(time, spatial_index)
|
10878
|
+
time_0 = time # Is it necessary? doesnt look great, isn't it?
|
10879
|
+
|
10880
|
+
# 4.1.3. Extract the velocity from the results dictionary.
|
10881
|
+
# -------------------------------------------------------------------------
|
10882
|
+
velocity = self._get_results_velocity_for_buoy(results)
|
10883
|
+
|
10884
|
+
# 4.1.4. Update the spatial index based on the velocity extracted.
|
10885
|
+
# -------------------------------------------------------------------------
|
10886
|
+
new_spatial_index = self.update_spatial_index_from_velocity(spatial_index, velocity)
|
10887
|
+
|
10888
|
+
# 4.1.5. Compute the distance between the 2 spatial indices.
|
10889
|
+
# -------------------------------------------------------------------------
|
10890
|
+
distance_between_cells = self._get_distance_between_spatial_indices(spatial_index, new_spatial_index)
|
10891
|
+
|
10892
|
+
# 4.1.6. Update the time based on the distance and velocity.
|
10893
|
+
# -------------------------------------------------------------------------
|
10894
|
+
time_1 = self._update_time_from_distance_and_velocity(time_0, distance_between_cells, velocity)
|
10895
|
+
|
10896
|
+
# 4.1.7. Results verifications
|
10897
|
+
# -------------------------------------------------------------------------
|
10898
|
+
time_1 = self._verify_and_correct_overtime(time_1)
|
10899
|
+
|
10900
|
+
# 4.1.8. Get the temporal indices of the computed time
|
10901
|
+
# -------------------------------------------------------------------------
|
10902
|
+
temporal_index_0 = self.get_closest_simulated_time_index(time_0)
|
10903
|
+
temporal_index_1 = self.get_closest_simulated_time_index(time_1)
|
10904
|
+
|
10905
|
+
# 4.1.9. Another correction of times
|
10906
|
+
# ----------------------------------
|
10907
|
+
# FIXME: This step is probably useless.Check it in debug mode..
|
10908
|
+
time_1, temporal_index_0 = self._verify_and_correct_overtime_smart_way(time_0=time_0,
|
10909
|
+
time_1=time_1,
|
10910
|
+
temporal_index_0=temporal_index_0,
|
10911
|
+
temporal_index_1=temporal_index_1,
|
10912
|
+
distance=distance_between_cells,
|
10913
|
+
spatial_index=spatial_index,
|
10914
|
+
)
|
10915
|
+
|
10916
|
+
# 4.1.10. Case for temporal indices which have been skipped (not).
|
10917
|
+
# ----------------------------------------------------------------
|
10918
|
+
if temporal_index_1 - temporal_index_0 > 1:
|
10919
|
+
time_1, new_spatial_index = self._corrected_skipped_temporal_indices(time_0=time_0,
|
10920
|
+
time_1=time_1,
|
10921
|
+
temporal_index_0=temporal_index_0,
|
10922
|
+
temporal_index_1=temporal_index_1,
|
10923
|
+
distance=distance_between_cells,
|
10924
|
+
spatial_index=spatial_index,
|
10925
|
+
velocity=velocity)
|
10926
|
+
# 4.1.11. the results are added to the dataframe.
|
10927
|
+
# ----------------------------------------------------------------
|
10928
|
+
buoy_data.loc[len(buoy_data.index)] ={'time': time,
|
10929
|
+
'distance': distance,
|
10930
|
+
'water level': results['water level'],
|
10931
|
+
'water depth': results['water depth'],
|
10932
|
+
'discharge': results['discharge'],
|
10933
|
+
'velocity': results['velocity'],
|
10934
|
+
'froude': results['froude'],
|
10935
|
+
'wetted section': results['wetted section'],
|
10936
|
+
'top width': results['top width']}
|
10937
|
+
|
10938
|
+
# 4.1.12. Updating the time and spatial index for the next iteration.
|
10939
|
+
if new_spatial_index < number_of_cells:
|
10940
|
+
time = time_1
|
10941
|
+
spatial_index = new_spatial_index
|
10942
|
+
distance = self.s_coords[spatial_index]
|
10943
|
+
if debug:
|
10944
|
+
text_limit = '*'
|
10945
|
+
logging.info(f'\n{text_limit*50}\
|
10946
|
+
\n\tTime: {time:#.2F} s,\
|
10947
|
+
\n\tSpatial index: {spatial_index} out of {number_of_cells - 1},\
|
10948
|
+
\n\tDistance: {distance:#.2F} m,\
|
10949
|
+
\n\tVelocity: {velocity:#.2F} m/s\
|
10950
|
+
\n{text_limit*50}')
|
10951
|
+
# (print(f'New spatial index: {spatial_index} out of {number_of_cells}'))
|
10952
|
+
|
10953
|
+
return buoy_data
|
10954
|
+
|
10955
|
+
def get_buoy_backward_trajectory(self,
|
10956
|
+
time:float,
|
10957
|
+
x:float,
|
10958
|
+
y:float,
|
10959
|
+
debug = False) -> pd.DataFrame:
|
10960
|
+
"""
|
10961
|
+
Compute the trajectory of a buoy (drifter) in the backward direction.
|
10962
|
+
|
10963
|
+
:param time: Initial time
|
10964
|
+
:type time: float
|
10965
|
+
:param x: X coordinate of the initial position
|
10966
|
+
:type x: float
|
10967
|
+
:param y: Y coordinate of the initial position
|
10968
|
+
:type y: float
|
10969
|
+
:param debug: If True, print debug information, defaults to False
|
10970
|
+
:type debug: bool, optional
|
10971
|
+
:return: DataFrame containing the buoy trajectory data
|
10972
|
+
:rtype: pd.DataFrame
|
10973
|
+
"""
|
10974
|
+
|
10975
|
+
# Step 1: We verify whether the time is within the simulated range.
|
10976
|
+
# ----------------------------------------------------------------
|
10977
|
+
assert time>=0 and time <= self.simulated_times[-1],\
|
10978
|
+
f'The time must be within the simulated time span.\
|
10979
|
+
{self.simulated_times[0]} <= time - {time} <= {self.simulated_times[-1]}'
|
10980
|
+
# Step 2: We create a DataFrame to store the buoy trajectory data.
|
10981
|
+
# ---------------------------------------------------------------
|
10982
|
+
buoy_data = self._create_buoy_dataframe()
|
10983
|
+
|
10984
|
+
# Step 3: Compute the curvilinear distance from the first simulation cell
|
10985
|
+
# and its index (spatial index). FInd the number of cells in the results
|
10986
|
+
# -------------------------------------------------------------------------
|
10987
|
+
spatial_index, distance = self.project_coordinates_on_bed(x, y)
|
10988
|
+
number_of_cells = self._compute_number_of_cells_on_curvilinear_vector()
|
10989
|
+
|
10990
|
+
# Step 4: Extraction of results
|
10991
|
+
# -----------------------------
|
10992
|
+
|
10993
|
+
# 4.1. Creation of loop to ensure that the computations are done within
|
10994
|
+
# the simulated time range and spatial limits.
|
10995
|
+
# -------------------------------------------------------------------
|
10996
|
+
if debug:
|
10997
|
+
# disable = False
|
10998
|
+
disable = True
|
10999
|
+
else:
|
11000
|
+
disable = True
|
11001
|
+
while tqdm(spatial_index >= 0 and time >= self.simulated_times[0],\
|
11002
|
+
desc='Computing buoy trajectory backwards', unit='cell', disable=disable):
|
11003
|
+
# 4.1.2. Get the closest simulated results in time at the provided coordinates.
|
11004
|
+
# -------------------------------------------------------------------------
|
11005
|
+
results = self.get_closest_simulated_results(time, spatial_index)
|
11006
|
+
time_0 = time
|
11007
|
+
|
11008
|
+
# 4.1.3. Extract the velocity from the results dictionary.
|
11009
|
+
# -------------------------------------------------------------------------
|
11010
|
+
velocity = self._get_results_velocity_for_buoy(results)
|
11011
|
+
|
11012
|
+
# 4.1.4. Update the spatial index based on the velocity extracted.
|
11013
|
+
# -------------------------------------------------------------------------
|
11014
|
+
new_spatial_index = self.update_spatial_index_from_velocity_backwards(spatial_index, velocity)
|
11015
|
+
if new_spatial_index > 0:
|
11016
|
+
|
11017
|
+
# 4.1.5. Compute the distance between the 2 spatial indices.
|
11018
|
+
# -------------------------------------------------------------------------
|
11019
|
+
distance_between_cells = self._get_distance_between_spatial_indices(spatial_index, new_spatial_index)
|
11020
|
+
|
11021
|
+
# 4.1.6. Update the time based on the distance and velocity.
|
11022
|
+
# -------------------------------------------------------------------------
|
11023
|
+
time_1 = self._update_time_from_distance_and_velocity_backwards(time_0, distance_between_cells, velocity)
|
11024
|
+
|
11025
|
+
# 4.1.7. Results verifications
|
11026
|
+
# -------------------------------------------------------------------------
|
11027
|
+
time_1 = self._verify_and_correct_overtime_backwards(time_1)
|
11028
|
+
|
11029
|
+
# 4.1.8. Get the temporal indices of the computed time
|
11030
|
+
# -------------------------------------------------------------------------
|
11031
|
+
temporal_index_0 = self.get_closest_simulated_time_index(time_0)
|
11032
|
+
temporal_index_1 = self.get_closest_simulated_time_index(time_1)
|
11033
|
+
|
11034
|
+
# 4.1.9. Another correction of times
|
11035
|
+
# ----------------------------------
|
11036
|
+
# FIXME: This step is probably useless.Check it in debug mode..
|
11037
|
+
time_1, temporal_index_0 = self._verify_and_correct_overtime_smart_way_backwards(time_0=time_0,
|
11038
|
+
time_1=time_1,
|
11039
|
+
temporal_index_0=temporal_index_0,
|
11040
|
+
temporal_index_1=temporal_index_1,
|
11041
|
+
distance=distance_between_cells,
|
11042
|
+
spatial_index=spatial_index,
|
11043
|
+
)
|
11044
|
+
|
11045
|
+
# 4.1.10. Case for temporal indices which have been skipped (not).
|
11046
|
+
# ----------------------------------------------------------------
|
11047
|
+
if temporal_index_0 - temporal_index_1 > 1:
|
11048
|
+
time_1, new_spatial_index = self._corrected_skipped_temporal_indices_backwards(time_0=time_0,
|
11049
|
+
time_1=time_1,
|
11050
|
+
temporal_index_0=temporal_index_0,
|
11051
|
+
temporal_index_1=temporal_index_1,
|
11052
|
+
distance=distance_between_cells,
|
11053
|
+
spatial_index=spatial_index,
|
11054
|
+
velocity=velocity)
|
11055
|
+
# 4.1.11. the results are added to the dataframe.
|
11056
|
+
# ----------------------------------------------------------------
|
11057
|
+
buoy_data.loc[len(buoy_data.index)] ={'time': time,
|
11058
|
+
'distance': distance,
|
11059
|
+
'water level': results['water level'],
|
11060
|
+
'water depth': results['water depth'],
|
11061
|
+
'discharge': results['discharge'],
|
11062
|
+
'velocity': results['velocity'],
|
11063
|
+
'froude': results['froude'],
|
11064
|
+
'wetted section': results['wetted section'],
|
11065
|
+
'top width': results['top width']}
|
11066
|
+
|
11067
|
+
# 4.1.12. Updating the time and spatial index for the next iteration.
|
11068
|
+
if new_spatial_index >= 0:
|
11069
|
+
time = time_1
|
11070
|
+
spatial_index = new_spatial_index
|
11071
|
+
distance = self.s_coords[spatial_index]
|
11072
|
+
if debug:
|
11073
|
+
text_limit = '*'
|
11074
|
+
print(f'\n{text_limit*50}\
|
11075
|
+
\n\tTime: {time:#.2F} s,\
|
11076
|
+
\n\tSpatial index: {spatial_index} out of {number_of_cells - 1},\
|
11077
|
+
\n\tDistance: {distance:#.2F} m,\
|
11078
|
+
\n\tVelocity: {velocity:#.2F} m/s\
|
11079
|
+
\n{text_limit*50}')
|
11080
|
+
if spatial_index == 0:
|
11081
|
+
print('The spatial index has reached the first cell.')
|
11082
|
+
# (print(f'New spatial index: {spatial_index} out of {number_of_cells}'))
|
11083
|
+
|
11084
|
+
else:
|
11085
|
+
spatial_index = new_spatial_index
|
11086
|
+
if debug:
|
11087
|
+
print(f'\n{text_limit*50}\
|
11088
|
+
\n\tTime: {time:#.2F} s,\
|
11089
|
+
\n\tSpatial index: {spatial_index} out of {number_of_cells - 1},\
|
11090
|
+
\n\tDistance: {distance:#.2F} m,\
|
11091
|
+
\n\tVelocity: {velocity:#.2F} m/s\
|
11092
|
+
\n{text_limit*50}')
|
11093
|
+
|
11094
|
+
return buoy_data
|
11095
|
+
|
11096
|
+
def get_buoy_trajectories(self,
|
11097
|
+
time:float,
|
11098
|
+
x:float,
|
11099
|
+
y:float,
|
11100
|
+
distance: float = None,
|
11101
|
+
number_cells = None) -> pd.DataFrame:
|
11102
|
+
assert time>=0 and time <= self.simulated_times[-1],\
|
11103
|
+
f'The time must be within the simulation time range. {self.simulated_times[0]} <= time - {time} <= {self.simulated_times[-1]}'
|
11104
|
+
|
11105
|
+
buoy_data = pd.DataFrame(columns = ['time',
|
11106
|
+
'distance',
|
11107
|
+
'water level',
|
11108
|
+
'water depth',
|
11109
|
+
'discharge',
|
11110
|
+
'velocity',
|
11111
|
+
'froude',
|
11112
|
+
'wetted section',
|
11113
|
+
'top width'])
|
11114
|
+
|
11115
|
+
|
11116
|
+
|
11117
|
+
# 1. Definition of spatial and temporal indices
|
11118
|
+
# FIXME: What if only the distance is provided? or the opposite?
|
11119
|
+
# also what if one or both of them are wrong?
|
11120
|
+
# This should be a method make it robust.
|
11121
|
+
if distance is None and number_cells is None:
|
11122
|
+
# all_cells = len(self.s_coords)
|
11123
|
+
# index, s = self.project_coordinates_on_bed(x, y)
|
11124
|
+
spatial_index, distance = self.project_coordinates_on_bed(x, y)
|
11125
|
+
# time_0 = time
|
11126
|
+
number_of_cells = len(self.s_coords)
|
11127
|
+
|
11128
|
+
# 2. Extraction of results
|
11129
|
+
|
11130
|
+
# while tqdm(spatial_index >= 0 and spatial_index < number_of_cells, desc='Computing buoy trajectories', unit='cell', disable=True):
|
11131
|
+
while tqdm(spatial_index < number_of_cells-1 and time < self.simulated_times[-1],\
|
11132
|
+
desc='Computing buoy trajectories', unit='cell', disable=True):
|
11133
|
+
|
11134
|
+
results = self.get_closest_simulated_results(time, spatial_index)
|
11135
|
+
time_0 = time
|
11136
|
+
|
11137
|
+
# 2. 1. updating the spatial index (forard or backward movement)
|
11138
|
+
# FIXME: create a method for extracting and updating the velocity
|
11139
|
+
# because it's somehow recurrent throughout the code.
|
11140
|
+
velocity = results['velocity']
|
11141
|
+
# FIXME rethink this approach of changing null & missing values.
|
11142
|
+
velocity = self.update_null_and_missing_velocities(velocity)
|
11143
|
+
new_spatial_index = self.update_spatial_index_from_velocity(spatial_index, velocity)
|
11144
|
+
|
11145
|
+
# 2.2. Computing the distance between cells
|
11146
|
+
# FIXME: Make it a method and add assert for verifications.
|
11147
|
+
distance_between_cells = self.s_coords[new_spatial_index] - self.s_coords[spatial_index]
|
11148
|
+
|
11149
|
+
# 2.3. Updating the time
|
11150
|
+
# FIXME: Create a methods one for computing the time between successive cells and,
|
11151
|
+
# another for updating the time.
|
11152
|
+
time_delta = abs(distance_between_cells) / abs(velocity)
|
11153
|
+
time_1 = time_0 + time_delta
|
11154
|
+
|
11155
|
+
# 2.4. Correction of the results
|
11156
|
+
temporal_index_0 = self.get_closest_simulated_time_index(time_0)
|
11157
|
+
|
11158
|
+
if time_1 > self.simulated_times [-1]:
|
11159
|
+
time_1 = self.simulated_times[-1]
|
11160
|
+
temporal_index_1 = self.get_closest_simulated_time_index(time_1)
|
11161
|
+
|
11162
|
+
|
11163
|
+
# FIXME time -1 has a big issue.
|
11164
|
+
# In case the time is bigger than the simulation time range, the time is updated to the last time step.
|
11165
|
+
# This is the case for extremely low velocities which could lead to infinitely very long times.
|
11166
|
+
#FIXME: clean these steps (make them cleaner)
|
11167
|
+
if time_1 > self.simulated_times [-1] or time_1 < 0:
|
11168
|
+
while time_1 > self.simulated_times[-1]:
|
11169
|
+
temporal_index_1 += 1
|
11170
|
+
results = self.get_closest_simulated_results(self.simulated_times[temporal_index_1], spatial_index)
|
11171
|
+
velocity = results['velocity']
|
11172
|
+
velocity = self.update_null_and_missing_velocities(velocity)
|
11173
|
+
time_delta = abs(distance_between_cells) / abs(velocity)
|
11174
|
+
time_1 = time_0 + time_delta
|
11175
|
+
# To avoid repeating the same procedure at the next test.
|
11176
|
+
temporal_index_0 = temporal_index_1
|
11177
|
+
|
11178
|
+
# temporal_index_1 = self.get_closest_simulated_time_index(time_1)
|
11179
|
+
|
11180
|
+
# Case for temporal indices which have been skipped (not).
|
11181
|
+
if temporal_index_1 - temporal_index_0 > 1:
|
11182
|
+
potential_temporal_indices = np.arange(temporal_index_0, temporal_index_1+1, 1)
|
11183
|
+
potential_velocities = self.get_simulated_velocity(spatial_index, potential_temporal_indices) # FIXME check if it works for arrays.
|
11184
|
+
# 2.5. Checking whether the extracted velocities are rigoursly the same.
|
11185
|
+
# FIXME assert velocity in potential_velocities, \
|
11186
|
+
# f'The velocity extracted - {velocity} is not in the potential velocities - {potential_velocities}.'
|
11187
|
+
# assert potential_velocities[0]- velocity < 1e-5, 'The velocities extracted are not the same.'
|
11188
|
+
# 2.6. Getting the maximum velocity in the time range.
|
11189
|
+
maximum_velocity = np.max(potential_velocities) # FIXME find a better way to do this.
|
11190
|
+
# 2.7. If the maximum velocity is not the previously extracted velocity which means the buoy moved faster,
|
11191
|
+
# the temporal index is updated to the index of the maximum velocity.
|
11192
|
+
if maximum_velocity != velocity:
|
11193
|
+
updated_temporal_index = potential_temporal_indices[np.argmax(potential_velocities)]
|
11194
|
+
# 2.8. The time and the results are updated.
|
11195
|
+
updated_time = self.simulated_times[updated_temporal_index]
|
11196
|
+
results = self.get_closest_simulated_results(updated_time, spatial_index)
|
11197
|
+
velocity = results['velocity']
|
11198
|
+
velocity = self.update_null_and_missing_velocities(velocity)
|
11199
|
+
# The time increment is updated.
|
11200
|
+
new_time_delta = abs(distance_between_cells) / abs(velocity)
|
11201
|
+
# The spatial index is updated (mainly the sign)
|
11202
|
+
new_spatial_index = self.update_spatial_index_from_velocity(spatial_index, velocity)
|
11203
|
+
time_1 = time_0 + new_time_delta
|
11204
|
+
|
11205
|
+
# 2.9. The results are added to the dataframe.
|
11206
|
+
buoy_data.loc[len(buoy_data.index)] ={'time': time,
|
11207
|
+
'distance': distance,
|
11208
|
+
'water level': results['water level'],
|
11209
|
+
'water depth': results['water depth'],
|
11210
|
+
'discharge': results['discharge'],
|
11211
|
+
'velocity': results['velocity'],
|
11212
|
+
'froude': results['froude'],
|
11213
|
+
'wetted section': results['wetted section'],
|
11214
|
+
'top width': results['top width']}
|
11215
|
+
|
11216
|
+
# 2.10. Updating the time and spatial index for the next iteration.
|
11217
|
+
if new_spatial_index < number_of_cells:
|
11218
|
+
time = time_1
|
11219
|
+
spatial_index = new_spatial_index
|
11220
|
+
distance = self.s_coords[spatial_index]
|
11221
|
+
# (print(f'New spatial index: {spatial_index} out of {number_of_cells}'))
|
11222
|
+
|
11223
|
+
return buoy_data
|
11224
|
+
|
11225
|
+
def get_backwards_buoy_trajectories(self,
|
11226
|
+
time:float,
|
11227
|
+
x:float,
|
11228
|
+
y:float,
|
11229
|
+
distance: float = None,
|
11230
|
+
number_cells = None) -> pd.DataFrame:
|
11231
|
+
"""Deprecated: Use get_buoy_backward_trajectory instead."""
|
11232
|
+
assert time>=0 and time <= self.simulated_times[-1],\
|
11233
|
+
f'The time must be within the simulation time range. {self.simulated_times[0]} <= time - {time} <= {self.simulated_times[-1]}'
|
11234
|
+
|
11235
|
+
buoy_data = pd.DataFrame(columns = ['time',
|
11236
|
+
'distance',
|
11237
|
+
'water level',
|
11238
|
+
'water depth',
|
11239
|
+
'discharge',
|
11240
|
+
'velocity',
|
11241
|
+
'froude',
|
11242
|
+
'wetted section',
|
11243
|
+
'top width'])
|
11244
|
+
|
11245
|
+
|
11246
|
+
|
11247
|
+
# 1. Definition of spatial and temporal indices
|
11248
|
+
if distance is None and number_cells is None:
|
11249
|
+
# all_cells = len(self.s_coords)
|
11250
|
+
# index, s = self.project_coordinates_on_bed(x, y)
|
11251
|
+
spatial_index, distance = self.project_coordinates_on_bed(x, y)
|
11252
|
+
# time_0 = time
|
11253
|
+
number_of_cells = len(self.s_coords)
|
11254
|
+
|
11255
|
+
# 2. Extraction of results
|
11256
|
+
|
11257
|
+
# while tqdm(spatial_index >= 0 and spatial_index < number_of_cells, desc='Computing buoy trajectories', unit='cell', disable=True):
|
11258
|
+
while tqdm(spatial_index < number_of_cells-1 and time < self.simulated_times[-1],\
|
11259
|
+
desc='Computing buoy trajectories', unit='cell', disable=True):
|
11260
|
+
|
11261
|
+
results = self.get_closest_simulated_results(time, spatial_index)
|
11262
|
+
time_0 = time
|
11263
|
+
|
11264
|
+
# 2. 1. updating the spatial index (forard or backward movement)
|
11265
|
+
velocity = results['velocity']
|
11266
|
+
# FIXME rethink this approach of changing null & missing vaalues
|
11267
|
+
velocity = self.update_null_and_missing_velocities(velocity)
|
11268
|
+
new_spatial_index = self.update_spatial_index_from_velocity_backwards(spatial_index, velocity)
|
11269
|
+
|
11270
|
+
# 2.2. Computing the distance between cells
|
11271
|
+
distance_between_cells = self.s_coords[new_spatial_index] - self.s_coords[spatial_index]
|
11272
|
+
|
11273
|
+
# 2.3. Updating the time
|
11274
|
+
time_delta = abs(distance_between_cells) / abs(velocity)
|
11275
|
+
time_1 = time_0 + time_delta
|
11276
|
+
|
11277
|
+
# 2.4. Correction of the results
|
11278
|
+
temporal_index_0 = self.get_closest_simulated_time_index(time_0)
|
11279
|
+
|
11280
|
+
if time_1 > self.simulated_times[-1]:
|
11281
|
+
time_1 = self.simulated_times[-1]
|
11282
|
+
elif time_1 < self.simulated_times[0]:
|
11283
|
+
time_1 = self.simulated_times[0]
|
11284
|
+
temporal_index_1 = self.get_closest_simulated_time_index(time_1)
|
11285
|
+
|
11286
|
+
|
11287
|
+
|
11288
|
+
# FIXME time -1 has a big issue.
|
11289
|
+
# In case the time is bigger than the simulation time range, the time is updated to the last time step.
|
11290
|
+
# This is the case for extremely low velocities which could lead to infinitely very long times.
|
11291
|
+
if time_1 > self.simulated_times [-1] or time_1 < self.simulated_times[0]:
|
11292
|
+
while time_1 > self.simulated_times[-1]:
|
11293
|
+
temporal_index_1 -= 1
|
11294
|
+
results = self.get_closest_simulated_results(self.simulated_times[temporal_index_1], spatial_index)
|
11295
|
+
velocity = results['velocity']
|
11296
|
+
velocity = self.update_null_and_missing_velocities(velocity)
|
11297
|
+
time_delta = abs(distance_between_cells) / abs(velocity)
|
11298
|
+
time_1 = time_0 + time_delta
|
11299
|
+
# To avoid repeating the same procedure at the next test.
|
11300
|
+
temporal_index_0 = temporal_index_1
|
11301
|
+
|
11302
|
+
# temporal_index_1 = self.get_closest_simulated_time_index(time_1)
|
11303
|
+
|
11304
|
+
# Case for temporal indices which have been skipped (not).
|
11305
|
+
if temporal_index_0 - temporal_index_1 > 1:
|
11306
|
+
# potential_temporal_indices = np.arange(temporal_index_0, temporal_index_1+1, 1)
|
11307
|
+
potential_temporal_indices = np.arange(temporal_index_1, temporal_index_0+1, 1)
|
11308
|
+
potential_velocities = self.get_simulated_velocity(spatial_index, potential_temporal_indices) # FIXME check if it works for arrays.
|
11309
|
+
# 2.5. Checking whether the extracted velocities are rigoursly the same.
|
11310
|
+
# FIXME assert velocity in potential_velocities, \
|
11311
|
+
# f'The velocity extracted - {velocity} is not in the potential velocities - {potential_velocities}.'
|
11312
|
+
# assert potential_velocities[0]- velocity < 1e-5, 'The velocities extracted are not the same.'
|
11313
|
+
# 2.6. Getting the maximum velocity in the time range.
|
11314
|
+
maximum_velocity = np.max(potential_velocities) # FIXME find a better way to do this.
|
11315
|
+
# 2.7. If the maximum velocity is not the previously extracted velocity which means the buoy moved faster,
|
11316
|
+
# the temporal index is updated to the index of the maximum velocity.
|
11317
|
+
if maximum_velocity != velocity:
|
11318
|
+
updated_temporal_index = potential_temporal_indices[np.argmax(potential_velocities)]
|
11319
|
+
# 2.8. The time and the results are updated.
|
11320
|
+
updated_time = self.simulated_times[updated_temporal_index]
|
11321
|
+
results = self.get_closest_simulated_results(updated_time, spatial_index)
|
11322
|
+
velocity = results['velocity']
|
11323
|
+
velocity = self.update_null_and_missing_velocities(velocity)
|
11324
|
+
# The time increment is updated.
|
11325
|
+
new_time_delta = abs(distance_between_cells) / abs(velocity)
|
11326
|
+
# The spatial index is updated (mainly the sign)
|
11327
|
+
new_spatial_index = self.update_spatial_index_from_velocity_backwards(spatial_index, velocity)
|
11328
|
+
time_1 = time_0 + new_time_delta
|
11329
|
+
|
11330
|
+
# 2.9. The results are added to the dataframe.
|
11331
|
+
buoy_data.loc[len(buoy_data.index)] ={'time': time,
|
11332
|
+
'distance': distance,
|
11333
|
+
'water level': results['water level'],
|
11334
|
+
'water depth': results['water depth'],
|
11335
|
+
'discharge': results['discharge'],
|
11336
|
+
'velocity': results['velocity'],
|
11337
|
+
'froude': results['froude'],
|
11338
|
+
'wetted section': results['wetted section'],
|
11339
|
+
'top width': results['top width']}
|
11340
|
+
|
11341
|
+
# 2.10. Updating the time and spatial index for the next iteration.
|
11342
|
+
if new_spatial_index < number_of_cells and new_spatial_index >=0:
|
11343
|
+
time = time_1
|
11344
|
+
spatial_index = new_spatial_index
|
11345
|
+
distance = self.s_coords[spatial_index]
|
11346
|
+
# (print(f'New spatial index: {spatial_index} out of {number_of_cells}'))
|
11347
|
+
|
11348
|
+
return buoy_data
|
11349
|
+
|
11350
|
+
def get_mean_values_along_trajectory(self,
|
11351
|
+
time_1:float,
|
11352
|
+
x_1:float,
|
11353
|
+
y_1:float,
|
11354
|
+
time_2:float,
|
11355
|
+
x_2:float,
|
11356
|
+
y_2:float
|
11357
|
+
) -> pd.DataFrame:
|
11358
|
+
"""
|
11359
|
+
Return the mean of all simulated values along a trajectory defined by two points and two times.
|
11360
|
+
The distance in the returned dataframe is the curvilinear distance along the river bed
|
11361
|
+
for each reported time step.
|
11362
|
+
|
11363
|
+
:param time_1: Initial time
|
11364
|
+
:type time_1: float
|
11365
|
+
:param x_1: X coordinate of the initial position
|
11366
|
+
:type x_1: float
|
11367
|
+
:param y_1: Y coordinate of the initial position
|
11368
|
+
:type y_1: float
|
11369
|
+
:param time_2: Final time
|
11370
|
+
:type time_2: float
|
11371
|
+
:param x_2: X coordinate of the final position
|
11372
|
+
:type x_2: float
|
11373
|
+
:param y_2: Y coordinate of the final position
|
11374
|
+
:type y_2: float
|
11375
|
+
"""
|
11376
|
+
assert time_1 >= 0 and time_1 <= self.simulated_times[-1],\
|
11377
|
+
f'The time_1 must be within the simulation time range.\
|
11378
|
+
{self.simulated_times[0]} <= time_1 - {time_1} <= {self.simulated_times[-1]}'
|
11379
|
+
|
11380
|
+
assert time_2 >= 0 and time_2 <= self.simulated_times[-1],\
|
11381
|
+
f'The time_2 must be within the simulation time range.\
|
11382
|
+
{self.simulated_times[0]} <= time_2 - {time_2} <= {self.simulated_times[-1]}'
|
11383
|
+
assert time_2 > time_1, 'The time_2 must be greater than time_1.'
|
11384
|
+
|
11385
|
+
spatial_index_1, distance_1 = self.project_coordinates_on_bed(x_1, y_1)
|
11386
|
+
spatial_index_2, distance_2 = self.project_coordinates_on_bed(x_2, y_2)
|
11387
|
+
assert spatial_index_2 > spatial_index_1, 'The second point must be downstream the first point.'
|
11388
|
+
temporal_index_1 = self.get_closest_simulated_time_index(time_1)
|
11389
|
+
temporal_index_2 = self.get_closest_simulated_time_index(time_2)
|
11390
|
+
|
11391
|
+
results ={'distance': self.s_coords[spatial_index_1: spatial_index_2 + 1],
|
11392
|
+
'water level': np.mean(self.water_levels[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1], axis =1),
|
11393
|
+
'water depth': np.mean(self.depths[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1], axis =1),
|
11394
|
+
'discharge': np.mean(self.discharges[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1], axis =1),
|
11395
|
+
'velocity': np.mean(self.velocities[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1], axis =1),
|
11396
|
+
'froude': np.mean(self.froudes[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1], axis =1),
|
11397
|
+
'wetted section':np.mean(self.wetted_sections[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1], axis =1),
|
11398
|
+
'top width': np.mean(self.widths[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1], axis =1),
|
11399
|
+
}
|
11400
|
+
mean_values = pd.DataFrame(results)
|
11401
|
+
return mean_values
|
11402
|
+
|
11403
|
+
def get_mean_of_trajectory_variables(self,
|
11404
|
+
time_1:float,
|
11405
|
+
x_1:float,
|
11406
|
+
y_1:float,
|
11407
|
+
time_2:float,
|
11408
|
+
x_2:float,
|
11409
|
+
y_2:float
|
11410
|
+
) -> dict:
|
11411
|
+
"""
|
11412
|
+
Return all simulated values along a trajectory defined by two points and two times.
|
11413
|
+
The curvilinear distance along the river bed for each reported time step is not returned.
|
11414
|
+
|
11415
|
+
:param time_1: Initial time
|
11416
|
+
:type time_1: float
|
11417
|
+
:param x_1: X coordinate of the initial position
|
11418
|
+
:type x_1: float
|
11419
|
+
:param y_1: Y coordinate of the initial position
|
11420
|
+
:type y_1: float
|
11421
|
+
:param time_2: Final time
|
11422
|
+
:type time_2: float
|
11423
|
+
:param x_2: X coordinate of the final position
|
11424
|
+
:type x_2: float
|
11425
|
+
:param y_2: Y coordinate of the final position
|
11426
|
+
:type y_2: float
|
11427
|
+
:return: A dictionary containing the mean values of the simulated variables along the trajectory.
|
11428
|
+
:rtype: dict
|
11429
|
+
"""
|
11430
|
+
assert time_1 >= 0 and time_1 <= self.simulated_times[-1],\
|
11431
|
+
f'The time_1 must be within the simulation time range.\
|
11432
|
+
{self.simulated_times[0]} <= time_1 - {time_1} <= {self.simulated_times[-1]}'
|
11433
|
+
|
11434
|
+
assert time_2 >= 0 and time_2 <= self.simulated_times[-1],\
|
11435
|
+
f'The time_2 must be within the simulation time range.\
|
11436
|
+
{self.simulated_times[0]} <= time_2 - {time_2} <= {self.simulated_times[-1]}'
|
11437
|
+
assert time_2 > time_1, 'The time_2 must be greater than time_1.'
|
11438
|
+
|
11439
|
+
spatial_index_1, distance_1 = self.project_coordinates_on_bed(x_1, y_1)
|
11440
|
+
spatial_index_2, distance_2 = self.project_coordinates_on_bed(x_2, y_2)
|
11441
|
+
assert spatial_index_2 > spatial_index_1, 'The second point must be downstream the first point.'
|
11442
|
+
temporal_index_1 = self.get_closest_simulated_time_index(time_1)
|
11443
|
+
temporal_index_2 = self.get_closest_simulated_time_index(time_2)
|
11444
|
+
|
11445
|
+
results ={
|
11446
|
+
'water level': np.mean(self.water_levels[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1]),
|
11447
|
+
'water depth': np.mean(self.depths[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1]),
|
11448
|
+
'discharge': np.mean(self.discharges[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1]),
|
11449
|
+
'velocity': np.mean(self.velocities[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1]),
|
11450
|
+
'froude': np.mean(self.froudes[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1]),
|
11451
|
+
'wetted section':np.mean(self.wetted_sections[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1]),
|
11452
|
+
'top width': np.mean(self.widths[spatial_index_1: spatial_index_2 + 1, temporal_index_1: temporal_index_2 + 1]),
|
11453
|
+
}
|
11454
|
+
|
11455
|
+
return results
|
11456
|
+
|
11457
|
+
def ___get_buoy_trajectories(self,
|
11458
|
+
time:float,
|
11459
|
+
x:float,
|
11460
|
+
y:float,
|
11461
|
+
distance: float = None,
|
11462
|
+
number_cells = None) -> pd.DataFrame:
|
11463
|
+
"""Deprecated: Use get_buoy_trajectories instead."""
|
11464
|
+
assert time>=0 and time <= self.simulated_times[-1],\
|
11465
|
+
f'The time must be within the simulation time range. {self.simulated_times[0]} <= time <= {self.simulated_times[-1]}'
|
11466
|
+
|
11467
|
+
buoy_data = pd.DataFrame(columns = ['time',
|
11468
|
+
'distance',
|
11469
|
+
'water level',
|
11470
|
+
'water depth',
|
11471
|
+
'discharge',
|
11472
|
+
'velocity',
|
11473
|
+
'froude',
|
11474
|
+
'wetted section',
|
11475
|
+
'top width'])
|
11476
|
+
|
11477
|
+
|
11478
|
+
|
11479
|
+
# 1. Definition of spatial and temporal indices
|
11480
|
+
if distance is None and number_cells is None:
|
11481
|
+
# all_cells = len(self.s_coords)
|
11482
|
+
# index, s = self.project_coordinates_on_bed(x, y)
|
11483
|
+
spatial_index, distance = self.project_coordinates_on_bed(x, y)
|
11484
|
+
time_0 = time
|
11485
|
+
number_of_cells = len(self.s_coords)
|
11486
|
+
|
11487
|
+
# 2. Extraction of results
|
11488
|
+
|
11489
|
+
# while tqdm(spatial_index >= 0 and spatial_index < number_of_cells, desc='Computing buoy trajectories', unit='cell', disable=True):
|
11490
|
+
while tqdm(spatial_index < number_of_cells-1, desc='Computing buoy trajectories', unit='cell', disable=True):
|
11491
|
+
|
11492
|
+
results = self.get_closest_simulated_results(time, spatial_index)
|
11493
|
+
|
11494
|
+
# 2. 1. updating the spatial index (forard or backward movement)
|
11495
|
+
velocity = results['velocity']
|
11496
|
+
new_spatial_index = self.update_spatial_index_from_velocity(spatial_index, velocity)
|
11497
|
+
|
11498
|
+
# 2.2. Computing the distance between cells
|
11499
|
+
distance_between_cells = self.s_coords[new_spatial_index] - self.s_coords[spatial_index]
|
11500
|
+
|
11501
|
+
# 2.3. Updating the time
|
11502
|
+
time_delta = abs(distance_between_cells) / abs(velocity)
|
11503
|
+
time_1 = time_0 + time_delta
|
11504
|
+
|
11505
|
+
# 2.4. Correction of the results
|
11506
|
+
temporal_index_0 = self.get_closest_simulated_time_index(time_0)
|
11507
|
+
temporal_index_1 = self.get_closest_simulated_time_index(time_1)
|
11508
|
+
|
11509
|
+
if time_1 > self.simulated_times [-1] or time_1 < 0:
|
11510
|
+
# # temporal_index_1 = temporal_index_0
|
11511
|
+
time = self.simulated_times[-1]
|
11512
|
+
time_1 = self.simulated_times[-1]
|
11513
|
+
spatial_index = len(self.s_coords) - 1
|
11514
|
+
|
11515
|
+
|
11516
|
+
# while time_1 > self.simulated_times[-1]:
|
11517
|
+
# temporal_index_1 += 1
|
11518
|
+
# results = self.get_closest_simulated_results(self.simulated_times[temporal_index_1], spatial_index)
|
11519
|
+
# velocity = results['velocity']
|
11520
|
+
# time_delta = abs(distance_between_cells) / abs(velocity)
|
11521
|
+
# time_1 = time_0 + time_delta
|
11522
|
+
|
11523
|
+
else:
|
11524
|
+
if temporal_index_1 - temporal_index_0 > 1:
|
11525
|
+
potential_temporal_indices = np.arange(temporal_index_0, temporal_index_1+1, 1)
|
11526
|
+
potential_velocities = self.get_simulated_velocity(spatial_index, potential_temporal_indices) # FIXME check if it works for arrays.
|
11527
|
+
# 2.5. Checking whether the extracted velocities are rigoursly the same.
|
11528
|
+
# FIXME assert velocity in potential_velocities, \
|
11529
|
+
# f'The velocity extracted - {velocity} is not in the potential velocities - {potential_velocities}.'
|
11530
|
+
# assert potential_velocities[0]- velocity < 1e-5, 'The velocities extracted are not the same.'
|
11531
|
+
# 2.6. Getting the maximum velocity in the time range.
|
11532
|
+
maximum_velocity = np.max(potential_velocities) # FIXME find a better way to do this.
|
11533
|
+
# 2.7. If the maximum velocity is not the previously extracted velocity which means the buoy moved faster,
|
11534
|
+
# the temporal index is updated to the index of the maximum velocity.
|
11535
|
+
if maximum_velocity != velocity:
|
11536
|
+
updated_temporal_index = potential_temporal_indices[np.argmax(potential_velocities)]
|
11537
|
+
# 2.8. The time and the results are updated.
|
11538
|
+
updated_time = self.simulated_times[updated_temporal_index]
|
11539
|
+
results = self.get_closest_simulated_results(updated_time, spatial_index)
|
11540
|
+
velocity = results['velocity']
|
11541
|
+
# The time increment is updated.
|
11542
|
+
new_time_delta = abs(distance_between_cells) / abs(velocity)
|
11543
|
+
# The spatial index is updated (mainly the sign)
|
11544
|
+
new_spatial_index = self.update_spatial_index_from_velocity(spatial_index, velocity)
|
11545
|
+
time_1 = time_0 + new_time_delta
|
11546
|
+
|
11547
|
+
# 2.9. The results are added to the dataframe.
|
11548
|
+
buoy_data.loc[len(buoy_data.index)] ={'time': time,
|
11549
|
+
'distance': distance,
|
11550
|
+
'water level': results['water level'],
|
11551
|
+
'water depth': results['water depth'],
|
11552
|
+
'discharge': results['discharge'],
|
11553
|
+
'velocity': results['velocity'],
|
11554
|
+
'froude': results['froude'],
|
11555
|
+
'wetted section': results['wetted section'],
|
11556
|
+
'top width': results['top width']}
|
11557
|
+
|
11558
|
+
# 2.10. Updating the time and spatial index for the next iteration.
|
11559
|
+
if new_spatial_index < number_of_cells:
|
11560
|
+
time = time_1
|
11561
|
+
spatial_index = new_spatial_index
|
11562
|
+
|
11563
|
+
# (print(f'New spatial index: {spatial_index} out of {number_of_cells}'))
|
11564
|
+
|
11565
|
+
return buoy_data
|
11566
|
+
|
11567
|
+
|
9802
11568
|
# --- Multiple Wolf 1D results ---
|
9803
11569
|
#_________________________________
|
9804
11570
|
class MultipleWolfresults_1D:
|
@@ -9875,6 +11641,10 @@ class MultipleWolfresults_1D:
|
|
9875
11641
|
self.max_froude= max([model.froudes_max for model in self.models])
|
9876
11642
|
self.max_water_level = max([model.water_level_ymax for model in self.models])
|
9877
11643
|
|
11644
|
+
# grid
|
11645
|
+
self.subdivisions_x = 1000
|
11646
|
+
self.subdivisions_y = 2
|
11647
|
+
|
9878
11648
|
def create_graph(self, show_landmarks = False):
|
9879
11649
|
"""
|
9880
11650
|
Create the initial figure on which other simulated results will be added.
|
@@ -9891,6 +11661,8 @@ class MultipleWolfresults_1D:
|
|
9891
11661
|
save_as= self.save_as,
|
9892
11662
|
figsize= self.figsize,
|
9893
11663
|
alpha=self.alpha,
|
11664
|
+
grid_x_m=self.subdivisions_x,
|
11665
|
+
grid_y_m=self.subdivisions_y,
|
9894
11666
|
show=False)
|
9895
11667
|
else:
|
9896
11668
|
self.graph = self.models[self.model_index].plot_variables(figures=self.figures,
|
@@ -9899,6 +11671,8 @@ class MultipleWolfresults_1D:
|
|
9899
11671
|
save_as= self.save_as,
|
9900
11672
|
figsize= self.figsize,
|
9901
11673
|
alpha=self.alpha,
|
11674
|
+
grid_x_m=self.subdivisions_x,
|
11675
|
+
grid_y_m=self.subdivisions_y,
|
9902
11676
|
show=False)
|
9903
11677
|
# Dictionary containing each plot characteristics(plot for update, axis, figure)
|
9904
11678
|
self.characteristics = self.models[self.model_index].find_figures_characteristics(self.graph,self.figures)
|