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/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 ='zones',
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, '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 < 1:
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 times [$s$]', fontsize= 'x-large')
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)