Skip to content

Commit 0dc917f

Browse files
authored
Merge pull request #346 from nens/golnesa-variable-friction
Golnesa variable friction
2 parents 43f878c + 224a37d commit 0dc917f

15 files changed

+268
-53
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,4 @@ jobs:
9595
- name: Run integration tests
9696
shell: bash
9797
run: |
98-
pytest -c /dev/null integration_tests
98+
pytest integration_tests

CHANGES.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ Changelog of threedigrid-builder
44
1.12.3 (unreleased)
55
-------------------
66

7-
- Nothing changed yet.
7+
- Add single vegetation and variable friction/vegetation for 1D elements.
8+
9+
- Add a table for open YZ profile to support 1D variable friction and vegetation.
10+
11+
- Update unit tests and the integration test (bergemeer test).
12+
13+
- Remove the spatialie4 version of the integration test (bergemeer test).
814

915

1016
1.12.2 (2023-12-07)

integration_tests/test_bergermeer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ def count_unique(arr):
2020
return dict(zip(*np.unique(arr, return_counts=True)))
2121

2222

23-
@pytest.mark.parametrize(
24-
"filename", ["v2_bergermeer.sqlite", "v2_bergermeer_spatialite4.sqlite"]
25-
)
23+
@pytest.mark.parametrize("filename", ["v2_bergermeer.sqlite"])
2624
def test_integration(tmp_path, filename):
2725
shutil.copyfile(unittests_data_path / filename, tmp_path / filename)
2826
progress_callback = Mock()
@@ -108,6 +106,8 @@ def test_integration(tmp_path, filename):
108106
-9999: 1,
109107
CrossSectionShape.CIRCLE: 8,
110108
CrossSectionShape.RECTANGLE: 3,
109+
CrossSectionShape.TABULATED_TRAPEZIUM: 1,
110+
CrossSectionShape.TABULATED_YZ: 1,
111111
}
112112

113113
## EMBEDDED NODES

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def get_version():
5252

5353
install_requires = [
5454
"numpy>=1.15,<1.25.0",
55-
"threedi-schema>=0.217.0",
55+
"threedi-schema==0.219.*",
5656
"shapely>=2",
5757
"pyproj>=3",
5858
"condenser[geo]>=0.1.1",

threedigrid_builder/base/lines.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class Line:
3838
frict_type2: int
3939
frict_value1: float # For 1D-2Dgw: resistance factor-out (hydr. cond / thickness)
4040
frict_value2: float # For 1D-2Dgw: resistance factor-in (hydr. cond / thickness)
41+
veg_coef1: float # the product of vegetation properties
42+
veg_coef2: float # the product of vegetation properties
4143
cross_weight: float # For 1D-2Dgw: the exchange length (tables: cross_width)
4244
discharge_coefficient_positive: float
4345
discharge_coefficient_negative: float

threedigrid_builder/grid/cross_section_definitions.py

Lines changed: 80 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ class CrossSectionDefinition:
1717
shape: CrossSectionShape
1818
height: str # space-separated list of floats
1919
width: str # space-separated list of floats
20+
friction_values: str # space-separated list of floats
21+
vegetation_stem_densities: str # space-separated list of floats
22+
vegetation_stem_diameters: str # space-separated list of floats
23+
vegetation_heights: str # space-separated list of floats
24+
vegetation_drag_coefficients: str # space-separated list of floats
2025

2126

2227
class CrossSectionDefinitions(Array[CrossSectionDefinition]):
@@ -43,36 +48,54 @@ def convert(self, ids):
4348
content_pk=ids,
4449
code=self.code[idx],
4550
count=0,
51+
count_yz=0,
4652
)
4753
if len(result) == 0:
4854
return result
4955

50-
offset = 0
5156
tables = []
57+
tables_yz = []
5258

53-
# Numpy array views on width/height based on idx
54-
width_idx = self.width[idx]
55-
height_idx = self.height[idx]
56-
57-
for i, shape in enumerate(self.shape[idx]):
59+
for i, self_i in enumerate(idx):
60+
shape = self.shape[self_i]
5861
tabulator = tabulators[shape]
59-
result.shape[i], result.width_1d[i], result.height_1d[i], table = tabulator(
60-
shape, width_idx[i], height_idx[i]
61-
)
62+
(
63+
result.shape[i],
64+
result.width_1d[i],
65+
result.height_1d[i],
66+
table,
67+
yz,
68+
) = tabulator(shape, self.width[self_i], self.height[self_i])
6269
if table is not None:
6370
result.count[i] = len(table)
64-
result.offset[i] = offset
65-
offset += len(table)
6671
tables.append(table)
72+
if yz is not None:
73+
result.count_yz[i] = len(yz)
74+
yz = set_friction_vegetation_values(
75+
yz,
76+
self.friction_values[self_i],
77+
self.vegetation_stem_densities[self_i],
78+
self.vegetation_stem_diameters[self_i],
79+
self.vegetation_heights[self_i],
80+
self.vegetation_drag_coefficients[self_i],
81+
)
82+
tables_yz.append(yz)
6783

6884
result.offset[:] = np.roll(np.cumsum(result.count), 1)
6985
result.offset[0] = 0
86+
result.offset_yz[:] = np.roll(np.cumsum(result.count_yz), 1)
87+
result.offset_yz[0] = 0
7088

7189
if len(tables) > 0:
7290
result.tables = np.concatenate(tables, axis=0)
7391
else:
7492
result.tables = np.empty((0, 2))
7593

94+
if len(tables_yz) > 0:
95+
result.tables_yz = np.concatenate(tables_yz, axis=0)
96+
else:
97+
result.tables_yz = np.empty((0, 4))
98+
7699
return result
77100

78101

@@ -85,11 +108,14 @@ class CrossSection:
85108
height_1d: float
86109
offset: int
87110
count: int
111+
offset_yz: int
112+
count_yz: int
88113
# tables: Tuple[float, float] has different length so is specified on CrossSections
89114

90115

91116
class CrossSections(Array[CrossSection]):
92117
tables = None
118+
tables_yz = None
93119

94120

95121
def tabulate_builtin(shape, width, height):
@@ -103,7 +129,7 @@ def tabulate_builtin(shape, width, height):
103129
height (str): ignored
104130
105131
Returns:
106-
tuple: shape, width_1d (float), None, None
132+
tuple: shape, width_1d (float), None, None, None
107133
"""
108134
try:
109135
width = float(width)
@@ -112,7 +138,7 @@ def tabulate_builtin(shape, width, height):
112138
f"Unable to parse cross section definition width (got: '{width}')."
113139
)
114140

115-
return shape, width, None, None
141+
return shape, width, None, None, None
116142

117143

118144
def tabulate_egg(shape, width, height):
@@ -125,7 +151,7 @@ def tabulate_egg(shape, width, height):
125151
126152
Returns:
127153
tuple: TABULATED_TRAPEZIUM, width_1d (float),
128-
height_1d (float), table (ndarray of shape (M, 2))
154+
height_1d (float), table (ndarray of shape (M, 2)), None
129155
"""
130156
NUM_INCREMENTS = 16
131157

@@ -151,17 +177,17 @@ def tabulate_egg(shape, width, height):
151177
widths = np.sqrt(p / q) * 2
152178

153179
table = np.array([heights, widths]).T
154-
return CrossSectionShape.TABULATED_TRAPEZIUM, width, height, table
180+
return CrossSectionShape.TABULATED_TRAPEZIUM, width, height, table, None
155181

156182

157183
def tabulate_inverted_egg(shape, width, height):
158184
"""Tabulate the egg shape, upside down.
159185
160186
See tabulate_egg.
161187
"""
162-
type_, width, height, table = tabulate_egg(shape, width, height)
188+
type_, width, height, table, _ = tabulate_egg(shape, width, height)
163189
table[:, 1] = table[::-1, 1]
164-
return type_, width, height, table
190+
return type_, width, height, table, None
165191

166192

167193
def tabulate_closed_rectangle(shape, width, height):
@@ -174,7 +200,7 @@ def tabulate_closed_rectangle(shape, width, height):
174200
175201
Returns:
176202
tuple: TABULATED_RECTANGLE, width_1d (float),
177-
height (float), table (ndarray of shape (M, 2))
203+
height (float), table (ndarray of shape (M, 2)), None
178204
"""
179205
try:
180206
width = float(width)
@@ -185,7 +211,7 @@ def tabulate_closed_rectangle(shape, width, height):
185211
f"(got: '{width}', '{height}')."
186212
)
187213
table = np.array([[0.0, width], [height, 0.0]], order="F")
188-
return CrossSectionShape.TABULATED_RECTANGLE, width, height, table
214+
return CrossSectionShape.TABULATED_RECTANGLE, width, height, table, None
189215

190216

191217
def _parse_tabulated(width, height):
@@ -220,7 +246,7 @@ def tabulate_tabulated(shape, width, height):
220246
221247
Returns:
222248
tuple: shape, width_1d (float),
223-
height_1d (float), table (ndarray of shape (M, 2))
249+
height_1d (float), table (ndarray of shape (M, 2)), None
224250
"""
225251
widths, heights = _parse_tabulated(width, height)
226252
if len(heights) > 1 and np.any(np.diff(heights) < 0.0):
@@ -229,7 +255,7 @@ def tabulate_tabulated(shape, width, height):
229255
f"(got: {height})."
230256
)
231257

232-
return shape, np.max(widths), np.max(heights), np.array([heights, widths]).T
258+
return shape, np.max(widths), np.max(heights), np.array([heights, widths]).T, None
233259

234260

235261
def tabulate_yz(shape, width, height):
@@ -242,7 +268,7 @@ def tabulate_yz(shape, width, height):
242268
243269
Returns:
244270
tuple: shape, width_1d (float),
245-
height_1d (float), table (ndarray of shape (M, 2))
271+
height_1d (float), table (ndarray of shape (M, 2)), yz (ndarray of shape (M, 4))
246272
"""
247273
ys, zs = _parse_tabulated(width, height)
248274
is_closed = ys[0] == ys[-1] and zs[0] == zs[-1]
@@ -275,6 +301,13 @@ def tabulate_yz(shape, width, height):
275301
if is_closed:
276302
ys = ys[:-1]
277303
zs = zs[:-1]
304+
yz = None
305+
shape_return = CrossSectionShape.TABULATED_TRAPEZIUM
306+
else:
307+
yz = np.zeros((len(ys), 4), dtype=float)
308+
yz[:, 0] = ys
309+
yz[:, 1] = zs
310+
shape_return = CrossSectionShape.TABULATED_YZ
278311

279312
# Adapt non-unique height coordinates. Why?
280313
# Because if a segment of the profile is exactly horizontal, we need 2 widths
@@ -318,7 +351,7 @@ def tabulate_yz(shape, width, height):
318351
table = table[1:]
319352

320353
height_1d, width_1d = table.max(axis=0).tolist()
321-
return CrossSectionShape.TABULATED_TRAPEZIUM, width_1d, height_1d, table
354+
return shape_return, width_1d, height_1d, table, yz
322355

323356

324357
tabulators = {
@@ -331,3 +364,27 @@ def tabulate_yz(shape, width, height):
331364
CrossSectionShape.TABULATED_YZ: tabulate_yz,
332365
CrossSectionShape.INVERTED_EGG: tabulate_inverted_egg,
333366
}
367+
368+
369+
def set_friction_vegetation_values(
370+
yz,
371+
friction_values,
372+
vegetation_stem_densities,
373+
vegetation_stem_diameters,
374+
vegetation_heights,
375+
vegetation_drag_coefficients,
376+
):
377+
"""Convert friction and vegetation properties from list into arrays, if available,
378+
and add to yz"""
379+
if friction_values is not None:
380+
fric = np.array([float(x) for x in friction_values.split(" ")])
381+
yz[:-1, 2] = fric
382+
383+
if vegetation_drag_coefficients is not None:
384+
veg_stemden = np.array([float(x) for x in vegetation_stem_densities.split(" ")])
385+
veg_stemdia = np.array([float(x) for x in vegetation_stem_diameters.split(" ")])
386+
veg_hght = np.array([float(x) for x in vegetation_heights.split(" ")])
387+
veg_drag = np.array([float(x) for x in vegetation_drag_coefficients.split(" ")])
388+
yz[:-1, 3] = veg_stemden * veg_stemdia * veg_hght * veg_drag
389+
390+
return yz

threedigrid_builder/grid/cross_section_locations.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ class CrossSectionLocation:
1717
bank_level: float
1818
friction_type: FrictionType
1919
friction_value: float
20+
vegetation_stem_density: float
21+
vegetation_stem_diameter: float
22+
vegetation_height: float
23+
vegetation_drag_coefficient: float
2024

2125

2226
class CrossSectionLocations(Array[CrossSectionLocation]):
@@ -41,6 +45,8 @@ def apply_to_lines(self, lines, channels, extrapolate=False):
4145
- frict_type2: the friction type of the second cross section location
4246
- frict_value1: the friction value of the first cross section location
4347
- frict_value2: the friction value of the second cross section location
48+
- veg_coef1: the product of vegetation properties of the first cross section location
49+
- veg_coef2: the product of vegetation properties of the second cross section location
4450
- invert_level_start_point: 'reference_level' interpolated at the line end
4551
- invert_level_end_point: 'reference_level' interpolated at the line start
4652
- dpumax: the largest of the two invert levels
@@ -65,6 +71,22 @@ def apply_to_lines(self, lines, channels, extrapolate=False):
6571
lines.frict_value1 = self.friction_value[idx1]
6672
lines.frict_value2 = self.friction_value[idx2]
6773

74+
lines.veg_coef1 = (
75+
self.vegetation_stem_density[idx1]
76+
* self.vegetation_stem_diameter[idx1]
77+
* self.vegetation_height[idx1]
78+
* self.vegetation_drag_coefficient[idx1]
79+
)
80+
lines.veg_coef1[np.isnan(lines.veg_coef1)] = 0.0
81+
82+
lines.veg_coef2 = (
83+
self.vegetation_stem_density[idx2]
84+
* self.vegetation_stem_diameter[idx2]
85+
* self.vegetation_height[idx2]
86+
* self.vegetation_drag_coefficient[idx2]
87+
)
88+
lines.veg_coef2[np.isnan(lines.veg_coef2)] = 0.0
89+
6890
# Compute invert levels and start and end
6991
lines.invert_level_start_point = compute_bottom_level(
7092
lines.content_pk,

threedigrid_builder/interface/db.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,11 @@ def get_cross_section_definitions(self) -> CrossSectionDefinitions:
468468
models.CrossSectionDefinition.shape,
469469
models.CrossSectionDefinition.width,
470470
models.CrossSectionDefinition.height,
471+
models.CrossSectionDefinition.friction_values,
472+
models.CrossSectionDefinition.vegetation_stem_densities,
473+
models.CrossSectionDefinition.vegetation_stem_diameters,
474+
models.CrossSectionDefinition.vegetation_heights,
475+
models.CrossSectionDefinition.vegetation_drag_coefficients,
471476
)
472477
.order_by(models.CrossSectionDefinition.id)
473478
.as_structarray()
@@ -497,6 +502,10 @@ def get_cross_section_locations(self) -> CrossSectionLocations:
497502
models.CrossSectionLocation.bank_level,
498503
models.CrossSectionLocation.friction_type,
499504
models.CrossSectionLocation.friction_value,
505+
models.CrossSectionLocation.vegetation_stem_density,
506+
models.CrossSectionLocation.vegetation_stem_diameter,
507+
models.CrossSectionLocation.vegetation_height,
508+
models.CrossSectionLocation.vegetation_drag_coefficient,
500509
)
501510
.order_by(models.CrossSectionLocation.id)
502511
.as_structarray()

threedigrid_builder/interface/gridadmin.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,8 @@ def write_lines(self, lines: Lines, cross_sections):
442442
self.write_dataset(group, "frict_type2", lines.frict_type2)
443443
self.write_dataset(group, "frict_value1", lines.frict_value1)
444444
self.write_dataset(group, "frict_value2", lines.frict_value2)
445+
self.write_dataset(group, "veg_coef1", lines.veg_coef1)
446+
self.write_dataset(group, "veg_coef2", lines.veg_coef2)
445447
self.write_dataset(group, "cross_weight", lines.cross_weight)
446448
self.write_dataset(
447449
group, "hydraulic_resistance_in", lines.hydraulic_resistance_in
@@ -690,6 +692,12 @@ def write_cross_sections(self, cross_sections: CrossSections):
690692
self.write_dataset(group, "offset", cross_sections.offset)
691693
self.write_dataset(group, "count", cross_sections.count)
692694
self.write_dataset(group, "tables", cross_sections.tables.T, insert_dummy=False)
695+
if cross_sections.tables_yz is not None:
696+
self.write_dataset(group, "offset_yz", cross_sections.offset_yz)
697+
self.write_dataset(group, "count_yz", cross_sections.count_yz)
698+
self.write_dataset(
699+
group, "tables_yz", cross_sections.tables_yz.T, insert_dummy=False
700+
)
693701

694702
def write_obstacles(self, obstacles: Obstacles):
695703
"""For backwards compat, the group is named 'levees'"""

threedigrid_builder/tests/data/v2_bergermeer.sqlite

100755100644
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:656f80f442a7daf84a7e69e55f4737769bcb9734531747aec081bd133202c6b8
2+
oid sha256:cbf6018bef282854cdf591d4b568d67390b4a66a5bd8cead0951934865530ab0
33
size 6317056

threedigrid_builder/tests/data/v2_bergermeer_spatialite4.sqlite

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)