wolfhece 2.2.17__py3-none-any.whl → 2.2.20__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/Results2DGPU.py CHANGED
@@ -186,6 +186,7 @@ class wolfres2DGPU(Wolfresults_2D):
186
186
  self._cache = None
187
187
 
188
188
  def __getstate__(self):
189
+ """ Get state for pickle """
189
190
  dct= super().__getstate__()
190
191
 
191
192
  to_pop = ['_result_store', '_cache']
@@ -193,17 +194,25 @@ class wolfres2DGPU(Wolfresults_2D):
193
194
  if key in dct:
194
195
  dct.pop(key)
195
196
 
196
- dct['isGPU'] = True
197
+ dct['isGPU'] = True # Indicate that this is a GPU result --> to avoid confusion with CPU results and loading phase "_loader", "_post_loader"
197
198
 
198
199
  return dct
199
200
 
200
201
  def __setstate__(self, dct):
201
-
202
+ """ Set state from a dictionary from pickle """
202
203
  super().__setstate__(dct)
203
204
 
204
205
  self._loader(self.filename)
205
206
  self._post_loader()
206
207
 
208
+ if len(dct['myblocks']) > 0:
209
+ self.myblocks = dct['myblocks']
210
+ self.head_blocks = dct['head_blocks']
211
+ self.loaded_rough = dct['loaded_rough']
212
+ self.current_result = dct['current_result']
213
+ self.loaded = dct['loaded']
214
+ self._nap = dct['_nap']
215
+
207
216
  self._result_store = None
208
217
  self._cache = None
209
218
  self.setup_store(self._result_store)
@@ -412,6 +421,9 @@ class wolfres2DGPU(Wolfresults_2D):
412
421
  """
413
422
 
414
423
  which = self._sanitize_result_step(which)
424
+ if which is None:
425
+ self.loaded = False
426
+ return
415
427
 
416
428
  # stored result files are 1-based -> which+1
417
429
  if self._cache is not None:
wolfhece/__init__.py CHANGED
@@ -11,5 +11,18 @@ except ImportError as e:
11
11
  raise Exception(_('Error importing GDAL library\nPlease ensure GDAL is installed and the Python bindings are available\n\ngdal wheels can be found at https://github.com/cgohlke/geospatial-wheels'))
12
12
 
13
13
  from .apps.version import WolfVersion
14
+ from packaging.version import Version
14
15
 
15
- __version__ = WolfVersion().get_version()
16
+ __version__ = WolfVersion().get_version()
17
+
18
+ def is_enough(version: str) -> bool:
19
+ """
20
+ Compare the current version of WolfHece to a given version string.
21
+
22
+ Args:
23
+ version (str): The version string to compare against.
24
+
25
+ Returns:
26
+ bool: True if the current version is greater than or equal to the given version, False otherwise.
27
+ """
28
+ return Version(__version__) >= Version(version)
@@ -0,0 +1,335 @@
1
+ import logging
2
+
3
+ import numpy as np
4
+ from shapely.geometry import Point, LineString
5
+ from typing import Literal
6
+ import pandas as pd
7
+
8
+ from .PyTranslate import _
9
+ from .drawing_obj import Element_To_Draw
10
+ from .PyVertexvectors import Triangulation, vector,Zones, zone
11
+ from .wolf_array import WolfArray, header_wolf
12
+
13
+ class Array_analysis_onepolygon():
14
+ """ Class for values analysis of an array based on a polygon.
15
+
16
+ This class select values insides a polygon and plot statistics of the values.
17
+
18
+ The class is designed to be used with the WolfArray class and the vector class from the PyVertexvectors module.
19
+
20
+ Plots of the values distribution can be generated using seaborn or plotly.
21
+ """
22
+
23
+ def __init__(self, wa:WolfArray, polygon:vector):
24
+
25
+ self._wa = wa
26
+ self._polygon = polygon
27
+
28
+ self._selected_cells = None
29
+ self._values = None
30
+
31
+ def values(self, which:Literal['Mean', 'Std', 'Median', 'Sum', 'Volume', 'Values']) -> pd.DataFrame | float:
32
+ """ Get the values as a pandas DataFrame
33
+
34
+ :param which: Mean, Std, Median, Sum, Volume, Values
35
+ """
36
+
37
+ authrorized = ['Mean', 'Std', 'Median', 'Sum', 'Volume', 'Values']
38
+ if which not in authrorized:
39
+ raise ValueError(f"Invalid value for 'which'. Must be one of {authrorized}.")
40
+
41
+ if self._values is None:
42
+ self.compute_values()
43
+
44
+ if self._values is None:
45
+ raise ValueError("No values computed. Please call compute_values() first.")
46
+
47
+ if which == 'Values':
48
+ return pd.DataFrame(self._values[_(which)], columns=[which])
49
+ else:
50
+ return self._values[which]
51
+
52
+ def select_cells(self, mode:Literal['polygon', 'buffer'] = 'polygon', **kwargs):
53
+ """ Select the cells inside the polygon """
54
+
55
+ if mode == 'polygon':
56
+ if 'polygon' in kwargs:
57
+ self._polygon = kwargs['polygon']
58
+ self._select_cells_polygon(self._polygon)
59
+ else:
60
+ raise ValueError("No polygon provided. Please provide a polygon to select cells.")
61
+ elif mode == 'buffer':
62
+ if 'buffer' in kwargs:
63
+ self._select_cells_buffer(kwargs['buffer'])
64
+ else:
65
+ raise ValueError("No buffer size provided. Please provide a buffer size to select cells.")
66
+ else:
67
+ raise ValueError("Invalid mode. Please use 'polygon' or 'buffer'.")
68
+
69
+ def _select_cells_polygon(self, selection_poly:vector):
70
+ """ Select the cells inside the polygon """
71
+
72
+ self._polygon = selection_poly
73
+ self._selected_cells = self._wa.get_xy_inside_polygon(self._polygon)
74
+
75
+ def _select_cells_buffer(self, buffer_size:float = 0.0):
76
+ """ Select the cells inside the buffer of the polygon """
77
+
78
+ self._polygon = self._polygon.buffer(buffer_size, inplace=False)
79
+ self._selected_cells = self._wa.get_xy_inside_polygon(self._polygon)
80
+
81
+ def compute_values(self):
82
+ """ Get the values of the array inside the polygon """
83
+
84
+ if self._selected_cells is None:
85
+ if self._polygon is None:
86
+ raise ValueError("No polygon provided. Please provide a polygon to select cells.")
87
+
88
+ self._values = self._wa.statistics(self._polygon)
89
+
90
+ def plot_values(self, show:bool = True, bins:int = 100,
91
+ engine:Literal['seaborn', 'plotly'] = 'seaborn'):
92
+ """ Plot a histogram of the values """
93
+
94
+ if engine == 'seaborn':
95
+ return self.plot_values_seaborn(show=show, bins=bins)
96
+ elif engine == 'plotly':
97
+ return self.plot_values_plotly(show=show, bins=bins)
98
+
99
+ def plot_values_seaborn(self, bins:int = 100, show:bool = True):
100
+ """ Plot a histogram of the values """
101
+
102
+ import seaborn as sns
103
+ import matplotlib.pyplot as plt
104
+
105
+ fig, ax = plt.subplots()
106
+ sns.histplot(self.values('Values'), bins=bins,
107
+ kde=True, ax=ax,
108
+ stat="density")
109
+
110
+ # Add mean, std, median values on plot
111
+ mean = self.values('Mean')
112
+ # std = self.values('Std').values[0]
113
+ median = self.values('Median')
114
+
115
+ # test noen and masked value
116
+ if mean is not None and mean is not np.ma.masked:
117
+ ax.axvline(mean, color='r', linestyle='--', label=f'Mean: {mean:.2f}')
118
+ if median is not None and median is not np.ma.masked:
119
+ ax.axvline(median, color='b', linestyle='--', label=f'Median: {median:.2f}')
120
+
121
+ ax.legend()
122
+ ax.set_xlabel('Values')
123
+ ax.set_ylabel('Frequency')
124
+ ax.set_title('Values distribution')
125
+
126
+ if show:
127
+ plt.show()
128
+
129
+ return (fig, ax)
130
+
131
+ def plot_values_plotly(self, bins:int = 100, show:bool = True):
132
+ """ Plot a histogram of the values """
133
+
134
+ import plotly.express as px
135
+
136
+ fig = px.histogram(self.values('Values'), x='Values',
137
+ nbins=bins, title='Values distribution',
138
+ histnorm='probability density')
139
+
140
+ # Add mean, std, median values on plot
141
+ mean = self.values('Mean')
142
+ median = self.values('Median')
143
+
144
+ if mean is not None and mean is not np.ma.masked:
145
+ fig.add_vline(x=mean, line_color='red', line_dash='dash', annotation_text=f'Mean: {mean:.2f}')
146
+ if median is not None and median is not np.ma.masked:
147
+ fig.add_vline(x=median, line_color='blue', line_dash='dash', annotation_text=f'Median: {median:.2f}')
148
+
149
+ fig.update_layout(xaxis_title='Values', yaxis_title='Frequency')
150
+
151
+ if show:
152
+ fig.show(renderer='browser')
153
+
154
+ return fig
155
+
156
+
157
+ class Array_analysis_polygons():
158
+ """ Class for values analysis of an array based on a polygon.
159
+
160
+ This class select values insides a polygon and plot statistics of the values.
161
+
162
+ The class is designed to be used with the WolfArray class and the vector class from the PyVertexvectors module.
163
+
164
+ Plots of the values distribution can be generated using seaborn or plotly.
165
+ """
166
+
167
+ def __init__(self, wa:WolfArray, polygons:zone):
168
+ """ Initialize the class with a WolfArray and a zone of polygons """
169
+
170
+ self._wa = wa
171
+ self._polygons = polygons
172
+
173
+ self._zone = {polygon.myname: Array_analysis_onepolygon(self._wa, polygon) for polygon in self._polygons.myvectors}
174
+
175
+ def __getitem__(self, key):
176
+ """ Get the polygon by name """
177
+ if key in self._zone:
178
+ return self._zone[key]
179
+ else:
180
+ raise KeyError(f"Polygon {key} not found in zone.")
181
+
182
+ def plot_values(self, show:bool = True, bins:int = 100,
183
+ engine:Literal['seaborn', 'plotly'] = 'seaborn'):
184
+ """ Plot a histogram of the values """
185
+
186
+ if engine == 'seaborn':
187
+ return self.plot_values_seaborn(show=show, bins=bins)
188
+ elif engine == 'plotly':
189
+ return self.plot_values_plotly(show=show, bins=bins)
190
+
191
+ def plot_values_seaborn(self, bins:int = 100, show:bool = True):
192
+ """ Plot a histogram of the values """
193
+ return {key: pol.plot_values_seaborn(bins=bins, show=show) for key, pol in self._zone.items()}
194
+
195
+ def plot_values_plotly(self, bins:int = 100, show:bool = True):
196
+ """ Plot a histogram of the values """
197
+
198
+ return {key: pol.plot_values_plotly(bins=bins, show=show) for key, pol in self._zone.items()}
199
+
200
+ class Slope_analysis:
201
+ """ Class for slope analysis of in an array based on a trace vector.
202
+
203
+ This class allows to select cells inside a polygon or a buffer around a trace vector
204
+ and compute the slope of the dike. The slope is computed as the difference in elevation
205
+ between the trace and the cell divided by the distance to the trace.
206
+
207
+ The slope is computed for each cell inside the polygon or buffer and accessed in a Pandas Dataframe.
208
+
209
+ Plots of the slope distribution can be generated using seaborn or plotly.
210
+
211
+ The class is designed to be used with the WolfArray class and the vector class from the PyVertexvectors module.
212
+ """
213
+
214
+ def __init__(self, wa:WolfArray, trace:vector):
215
+
216
+ self._wa = wa
217
+ self._trace = trace
218
+
219
+ self._selection_poly = None
220
+ self._buffer_size = 0.0
221
+
222
+ self._selected_cells = None
223
+ self._slopes = None
224
+
225
+ @property
226
+ def slopes(self) -> pd.DataFrame:
227
+ """ Get the slopes as a pandas DataFrame """
228
+
229
+ if self._slopes is None:
230
+ self.compute_slopes()
231
+
232
+ if self._slopes is None:
233
+ raise ValueError("No slopes computed. Please call compute_slopes() first.")
234
+
235
+ return pd.DataFrame(self._slopes, columns=['Slope [m/m]'])
236
+
237
+ def select_cells(self, mode:Literal['polygon', 'buffer'] = 'polygon', **kwargs):
238
+ """ Select the cells inside the trace """
239
+
240
+ if mode == 'polygon':
241
+ if 'polygon' in kwargs:
242
+ self._selection_poly = kwargs['polygon']
243
+ self._select_cells_polygon(self._selection_poly)
244
+ else:
245
+ raise ValueError("No polygon provided. Please provide a polygon to select cells.")
246
+ elif mode == 'buffer':
247
+ if 'buffer' in kwargs:
248
+ self._buffer_size = kwargs['buffer']
249
+ self._select_cells_buffer(self._buffer_size)
250
+ else:
251
+ raise ValueError("No buffer size provided. Please provide a buffer size to select cells.")
252
+ else:
253
+ raise ValueError("Invalid mode. Please use 'polygon' or 'buffer'.")
254
+
255
+ def _select_cells_buffer(self, buffer_size:float = 0.0):
256
+ """ Select the cells inside the buffer of the trace """
257
+
258
+ self._buffer_size = buffer_size
259
+ self._selection_poly = self._trace.buffer(self._buffer_size, inplace=False)
260
+ self._select_cells_polygon(self._selection_poly)
261
+
262
+ def _select_cells_polygon(self, selection_poly:vector):
263
+ """ Select the cells inside the polygon """
264
+
265
+ self._selection_poly = selection_poly
266
+ self._selected_cells = self._wa.get_xy_inside_polygon(self._selection_poly)
267
+
268
+ def compute_slopes(self):
269
+ """ Get the slope of the dike """
270
+
271
+ if self._selected_cells is None:
272
+ self.select_cells()
273
+ if self._selected_cells is None:
274
+ raise ValueError("No cells selected. Please call select_cells() first.")
275
+
276
+ trace_ls = self._trace.linestring
277
+
278
+ def compute_cell_slope(curxy):
279
+ i, j = self._wa.get_ij_from_xy(curxy[0], curxy[1])
280
+ pt = Point(curxy[0], curxy[1])
281
+ distance_to_trace = trace_ls.distance(pt)
282
+ elevation_on_trace = trace_ls.interpolate(trace_ls.project(pt, normalized=True), normalized=True).z
283
+ if distance_to_trace == 0.0:
284
+ return 0.0
285
+ if elevation_on_trace == -99999.0:
286
+ return 0.0
287
+
288
+ return (elevation_on_trace - self._wa.array[i, j]) / distance_to_trace
289
+
290
+ self._slopes = [compute_cell_slope(curxy) for curxy in self._selected_cells]
291
+
292
+ def plot_slopes(self, show:bool = True, bins:int = 100,
293
+ engine:Literal['seaborn', 'plotly'] = 'seaborn'):
294
+ """ Plot a histogram of the slopes """
295
+
296
+ if engine == 'seaborn':
297
+ return self.plot_slopes_seaborn(show=show, bins=bins)
298
+ elif engine == 'plotly':
299
+ return self.plot_slopes_plotly(show=show, bins=bins)
300
+
301
+ def plot_slopes_seaborn(self, bins:int = 100, show:bool = True):
302
+ """ Plot a histogram of the slopes """
303
+
304
+ import seaborn as sns
305
+ import matplotlib.pyplot as plt
306
+
307
+ fig, ax = plt.subplots()
308
+ sns.histplot(self.slopes, bins=bins,
309
+ kde=True, ax=ax,
310
+ stat="density")
311
+
312
+ ax.set_xlabel('Slope [m/m]')
313
+ ax.set_ylabel('Frequency')
314
+ ax.set_title('Slope distribution')
315
+
316
+ if show:
317
+ plt.show()
318
+
319
+ return (fig, ax)
320
+
321
+ def plot_slopes_plotly(self, bins:int = 100, show:bool = True):
322
+ """ Plot a histogram of the slopes """
323
+
324
+ import plotly.express as px
325
+
326
+ fig = px.histogram(self.slopes, x='Slope [m/m]',
327
+ nbins=bins, title='Slope distribution',
328
+ histnorm='probability density')
329
+
330
+ fig.update_layout(xaxis_title='Slope [m/m]', yaxis_title='Frequency')
331
+
332
+ if show:
333
+ fig.show(renderer='browser')
334
+
335
+ return fig
wolfhece/apps/version.py CHANGED
@@ -5,7 +5,7 @@ class WolfVersion():
5
5
 
6
6
  self.major = 2
7
7
  self.minor = 2
8
- self.patch = 17
8
+ self.patch = 20
9
9
 
10
10
  def __str__(self):
11
11
 
File without changes
@@ -1,13 +1,22 @@
1
- from . import _add_path
2
- from .viewer.viewer import *
3
- from .points.points import *
4
- from .points.expr import *
5
- from scipy.spatial import kdtree
6
-
7
- try:
8
- from .processing.estimate_normals.estimate_normals import estimate_normals
9
- except Exception as e:
10
- print(e)
11
- print('Could not import estimate_normals')
12
- print('Please installed the VC++ redistributable from https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads')
1
+ import logging
2
+ # Check __onWindows__ variable from wolfhece/__init__.py
3
+ from ..os_check import isWindows
4
+
5
+ if not isWindows():
6
+ # If not on Windows, raise an exception
7
+ logging.info('Not on Windows, ignoring the lazviewer package')
8
+
9
+ else:
10
+ from . import _add_path
11
+ from .viewer.viewer import *
12
+ from .points.points import *
13
+ from .points.expr import *
14
+ from scipy.spatial import kdtree
15
+
16
+ try:
17
+ from .processing.estimate_normals.estimate_normals import estimate_normals
18
+ except Exception as e:
19
+ print(e)
20
+ print('Could not import estimate_normals')
21
+ print('Please installed the VC++ redistributable from https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads')
13
22
 
@@ -1050,7 +1050,7 @@ class BcManager(wx.Frame):
1050
1050
  """
1051
1051
  nb=0
1052
1052
  for valbc in bc.values():
1053
- if valbc != '99999.0':
1053
+ if float(valbc) != 99999.0:
1054
1054
  nb+=1
1055
1055
  return nb
1056
1056
 
@@ -30,7 +30,7 @@ class BCType_2D_GPU(Enum):
30
30
  """ Boundary conditions for 2D simulations with wolfgpu """
31
31
 
32
32
  # The numbers match the numbers in Wolf's simulations parameters.
33
- # H = (1,_('Water level [m]'))
33
+ H = (1,_('Water level [m]'))
34
34
  QX = (2,_('Flow rate along X [m²/s]'))
35
35
  QY = (3,_('Flow rate along Y [m²/s]'))
36
36
  NONE = (4,_('None'))
wolfhece/mesh2d/gpu_2d.py CHANGED
@@ -101,9 +101,12 @@ class Sim_2D_GPU():
101
101
  ('qx','Initial discharge along X [m^2/s]',WOLF_ARRAY_FULL_SINGLE),
102
102
  ('qy','Initial discharge along Y [m^2/s]',WOLF_ARRAY_FULL_SINGLE),
103
103
  ('bridge_roof','Bridge/Culvert (roof el.) [m]',WOLF_ARRAY_FULL_SINGLE),
104
+ ('water_surface_elevation','Water surface elevation [m]',WOLF_ARRAY_FULL_SINGLE),
104
105
  ]}
105
106
 
106
- self.files_ic=['Initial water depth [m]','Initial discharge along X [m^2/s]','Initial discharge along Y [m^2/s]']
107
+ self.files_ic=['Initial water depth [m]',
108
+ 'Initial discharge along X [m^2/s]',
109
+ 'Initial discharge along Y [m^2/s]']
107
110
 
108
111
  # Files for the simulation
109
112
  self.files_others={'Generic file':[
@@ -166,7 +169,11 @@ class Sim_2D_GPU():
166
169
  else:
167
170
  return None
168
171
 
169
- def __getitem__(self, key:Literal['nap', 'bathymetry', 'manning', 'infiltration_zones', 'h', 'qx', 'qy', 'bridge_roof']) -> WolfArray:
172
+ def __getitem__(self, key:Literal['nap', 'bathymetry',
173
+ 'manning', 'infiltration_zones',
174
+ 'h', 'qx', 'qy',
175
+ 'bridge_roof',
176
+ 'water_surface_elevation']) -> WolfArray:
170
177
  """ Get an array from the simulation """
171
178
 
172
179
  if self.is_loaded:
@@ -174,12 +181,33 @@ class Sim_2D_GPU():
174
181
  descr = self._get_description_arrays()[self._get_name_arrays().index(key)]
175
182
 
176
183
  if key not in self._cached_arrays:
177
- locarray = WolfArray(srcheader=self.get_header(),
178
- np_source=self.sim.__getattribute__(key),
179
- idx= descr,
180
- nullvalue=self.nullvalues[key],
181
- whichtype=self.files_array['Characteristics'][self._get_name_arrays().index(key)][2],
182
- masknull=False)
184
+ if key in ['water_surface_elevation']:
185
+
186
+ top = self['bathymetry']
187
+ h = self['h']
188
+
189
+ locarray = top + h
190
+ locarray.idx = descr
191
+ locarray.nullvalue = self.nullvalues[key]
192
+
193
+ def wse_write_all():
194
+ top = self['bathymetry']
195
+ new_h = self['water_surface_elevation'] - top
196
+
197
+ # Filter negative values
198
+ new_h.array.data[new_h.array.data < 0.] = 0.
199
+
200
+ new_h.write_all(str(self.dir / "h.npy"))
201
+
202
+ locarray.write_all = wse_write_all
203
+
204
+ else:
205
+ locarray = WolfArray(srcheader=self.get_header(),
206
+ np_source=self.sim.__getattribute__(key),
207
+ idx= descr,
208
+ nullvalue=self.nullvalues[key],
209
+ whichtype=self.files_array['Characteristics'][self._get_name_arrays().index(key)][2],
210
+ masknull=False)
183
211
  locarray.loaded = True
184
212
  locarray.filename = str(self.dir / f"{key}.npy")
185
213
 
@@ -354,7 +382,15 @@ class Sim_2D_GPU():
354
382
  def nullvalues(self) -> dict[str,int]:
355
383
  """ Define null values for the arrays """
356
384
 
357
- return {'nap':0, 'bathymetry':99999., 'manning':0, 'infiltration_zones':0, 'h':0., 'qx':0., 'qy':0., 'bridge_roof':99999.}
385
+ return {'nap':0,
386
+ 'bathymetry':99999.,
387
+ 'manning':0,
388
+ 'infiltration_zones':0,
389
+ 'h':0.,
390
+ 'qx':0.,
391
+ 'qy':0.,
392
+ 'bridge_roof':99999.,
393
+ 'water_surface_elevation':0.}
358
394
 
359
395
  def verify_files(self):
360
396
  """ Verify the files """
@@ -649,4 +685,4 @@ class Sim_2D_GPU():
649
685
  self.sim._infiltration_zones[:,:]= tmp[:,:]
650
686
 
651
687
  tmp = np.load(self.dir / "NAP.npy")
652
- self.sim._nap[:,:]= tmp[:,:]
688
+ self.sim._nap[:,:]= tmp[:,:]