floodmodeller-api 0.4.2.post1__py3-none-any.whl → 0.4.4__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.
Files changed (178) hide show
  1. floodmodeller_api/__init__.py +8 -9
  2. floodmodeller_api/_base.py +169 -176
  3. floodmodeller_api/backup.py +273 -273
  4. floodmodeller_api/dat.py +889 -831
  5. floodmodeller_api/diff.py +136 -119
  6. floodmodeller_api/ied.py +302 -306
  7. floodmodeller_api/ief.py +553 -637
  8. floodmodeller_api/ief_flags.py +253 -253
  9. floodmodeller_api/inp.py +260 -266
  10. floodmodeller_api/libs/libifcoremd.dll +0 -0
  11. floodmodeller_api/libs/libifcoremt.so.5 +0 -0
  12. floodmodeller_api/libs/libifport.so.5 +0 -0
  13. floodmodeller_api/{libmmd.dll → libs/libimf.so} +0 -0
  14. floodmodeller_api/libs/libintlc.so.5 +0 -0
  15. floodmodeller_api/libs/libmmd.dll +0 -0
  16. floodmodeller_api/libs/libsvml.so +0 -0
  17. floodmodeller_api/libs/libzzn_read.so +0 -0
  18. floodmodeller_api/libs/zzn_read.dll +0 -0
  19. floodmodeller_api/logs/__init__.py +2 -2
  20. floodmodeller_api/logs/lf.py +364 -312
  21. floodmodeller_api/logs/lf_helpers.py +354 -352
  22. floodmodeller_api/logs/lf_params.py +643 -529
  23. floodmodeller_api/mapping.py +84 -0
  24. floodmodeller_api/test/__init__.py +4 -4
  25. floodmodeller_api/test/conftest.py +16 -8
  26. floodmodeller_api/test/test_backup.py +117 -117
  27. floodmodeller_api/test/test_conveyance.py +107 -0
  28. floodmodeller_api/test/test_dat.py +222 -92
  29. floodmodeller_api/test/test_data/All Units 4_6.DAT +1081 -1081
  30. floodmodeller_api/test/test_data/All Units 4_6.feb +1081 -1081
  31. floodmodeller_api/test/test_data/BRIDGE.DAT +926 -926
  32. floodmodeller_api/test/test_data/Culvert_Inlet_Outlet.dat +36 -36
  33. floodmodeller_api/test/test_data/Culvert_Inlet_Outlet.feb +36 -36
  34. floodmodeller_api/test/test_data/DamBreakADI.xml +52 -52
  35. floodmodeller_api/test/test_data/DamBreakFAST.xml +58 -58
  36. floodmodeller_api/test/test_data/DamBreakFAST_dy.xml +53 -53
  37. floodmodeller_api/test/test_data/DamBreakTVD.xml +55 -55
  38. floodmodeller_api/test/test_data/DefenceBreach.xml +53 -53
  39. floodmodeller_api/test/test_data/DefenceBreachFAST.xml +60 -60
  40. floodmodeller_api/test/test_data/DefenceBreachFAST_dy.xml +55 -55
  41. floodmodeller_api/test/test_data/Domain1+2_QH.xml +76 -76
  42. floodmodeller_api/test/test_data/Domain1_H.xml +41 -41
  43. floodmodeller_api/test/test_data/Domain1_Q.xml +41 -41
  44. floodmodeller_api/test/test_data/Domain1_Q_FAST.xml +48 -48
  45. floodmodeller_api/test/test_data/Domain1_Q_FAST_dy.xml +48 -48
  46. floodmodeller_api/test/test_data/Domain1_Q_xml_expected.json +263 -0
  47. floodmodeller_api/test/test_data/Domain1_W.xml +41 -41
  48. floodmodeller_api/test/test_data/EX1.DAT +321 -321
  49. floodmodeller_api/test/test_data/EX1.ext +107 -107
  50. floodmodeller_api/test/test_data/EX1.feb +320 -320
  51. floodmodeller_api/test/test_data/EX1.gxy +107 -107
  52. floodmodeller_api/test/test_data/EX17.DAT +421 -422
  53. floodmodeller_api/test/test_data/EX17.ext +213 -213
  54. floodmodeller_api/test/test_data/EX17.feb +422 -422
  55. floodmodeller_api/test/test_data/EX18.DAT +375 -375
  56. floodmodeller_api/test/test_data/EX18_DAT_expected.json +3876 -0
  57. floodmodeller_api/test/test_data/EX2.DAT +302 -302
  58. floodmodeller_api/test/test_data/EX3.DAT +926 -926
  59. floodmodeller_api/test/test_data/EX3_DAT_expected.json +16235 -0
  60. floodmodeller_api/test/test_data/EX3_IEF_expected.json +61 -0
  61. floodmodeller_api/test/test_data/EX6.DAT +2084 -2084
  62. floodmodeller_api/test/test_data/EX6.ext +532 -532
  63. floodmodeller_api/test/test_data/EX6.feb +2084 -2084
  64. floodmodeller_api/test/test_data/EX6_DAT_expected.json +31647 -0
  65. floodmodeller_api/test/test_data/Event Data Example.DAT +336 -336
  66. floodmodeller_api/test/test_data/Event Data Example.ext +107 -107
  67. floodmodeller_api/test/test_data/Event Data Example.feb +336 -336
  68. floodmodeller_api/test/test_data/Linked1D2D.xml +52 -52
  69. floodmodeller_api/test/test_data/Linked1D2DFAST.xml +53 -53
  70. floodmodeller_api/test/test_data/Linked1D2DFAST_dy.xml +48 -48
  71. floodmodeller_api/test/test_data/Linked1D2D_xml_expected.json +313 -0
  72. floodmodeller_api/test/test_data/blockage.dat +50 -50
  73. floodmodeller_api/test/test_data/blockage.ext +45 -45
  74. floodmodeller_api/test/test_data/blockage.feb +9 -9
  75. floodmodeller_api/test/test_data/blockage.gxy +71 -71
  76. floodmodeller_api/test/test_data/conveyance_test.dat +165 -0
  77. floodmodeller_api/test/test_data/conveyance_test.feb +116 -0
  78. floodmodeller_api/test/test_data/conveyance_test.gxy +85 -0
  79. floodmodeller_api/test/test_data/defaultUnits.dat +127 -127
  80. floodmodeller_api/test/test_data/defaultUnits.ext +45 -45
  81. floodmodeller_api/test/test_data/defaultUnits.feb +9 -9
  82. floodmodeller_api/test/test_data/defaultUnits.fmpx +58 -58
  83. floodmodeller_api/test/test_data/defaultUnits.gxy +85 -85
  84. floodmodeller_api/test/test_data/ex3.ief +20 -20
  85. floodmodeller_api/test/test_data/ex3.lf1 +2800 -2800
  86. floodmodeller_api/test/test_data/ex4.DAT +1374 -1374
  87. floodmodeller_api/test/test_data/ex4_changed.DAT +1374 -1374
  88. floodmodeller_api/test/test_data/example1.inp +329 -329
  89. floodmodeller_api/test/test_data/example2.inp +158 -158
  90. floodmodeller_api/test/test_data/example3.inp +297 -297
  91. floodmodeller_api/test/test_data/example4.inp +388 -388
  92. floodmodeller_api/test/test_data/example5.inp +147 -147
  93. floodmodeller_api/test/test_data/example6.inp +154 -154
  94. floodmodeller_api/test/test_data/expected_conveyance.csv +60 -0
  95. floodmodeller_api/test/test_data/jump.dat +176 -176
  96. floodmodeller_api/test/test_data/network.dat +1374 -1374
  97. floodmodeller_api/test/test_data/network.ext +45 -45
  98. floodmodeller_api/test/test_data/network.exy +1 -1
  99. floodmodeller_api/test/test_data/network.feb +45 -45
  100. floodmodeller_api/test/test_data/network.ied +45 -45
  101. floodmodeller_api/test/test_data/network.ief +20 -20
  102. floodmodeller_api/test/test_data/network.inp +147 -147
  103. floodmodeller_api/test/test_data/network.pxy +57 -57
  104. floodmodeller_api/test/test_data/network.zzd +122 -122
  105. floodmodeller_api/test/test_data/network_dat_expected.json +21837 -0
  106. floodmodeller_api/test/test_data/network_from_tabularCSV.csv +87 -87
  107. floodmodeller_api/test/test_data/network_ied_expected.json +287 -0
  108. floodmodeller_api/test/test_data/rnweir.dat +9 -9
  109. floodmodeller_api/test/test_data/rnweir.ext +45 -45
  110. floodmodeller_api/test/test_data/rnweir.feb +9 -9
  111. floodmodeller_api/test/test_data/rnweir.gxy +45 -45
  112. floodmodeller_api/test/test_data/rnweir_default.dat +74 -74
  113. floodmodeller_api/test/test_data/rnweir_default.ext +45 -45
  114. floodmodeller_api/test/test_data/rnweir_default.feb +9 -9
  115. floodmodeller_api/test/test_data/rnweir_default.fmpx +58 -58
  116. floodmodeller_api/test/test_data/rnweir_default.gxy +53 -53
  117. floodmodeller_api/test/test_data/unit checks.dat +16 -16
  118. floodmodeller_api/test/test_ied.py +29 -29
  119. floodmodeller_api/test/test_ief.py +136 -24
  120. floodmodeller_api/test/test_inp.py +47 -48
  121. floodmodeller_api/test/test_json.py +114 -0
  122. floodmodeller_api/test/test_logs_lf.py +102 -51
  123. floodmodeller_api/test/test_tool.py +165 -152
  124. floodmodeller_api/test/test_toolbox_structure_log.py +234 -239
  125. floodmodeller_api/test/test_xml2d.py +151 -156
  126. floodmodeller_api/test/test_zzn.py +36 -34
  127. floodmodeller_api/to_from_json.py +230 -0
  128. floodmodeller_api/tool.py +332 -329
  129. floodmodeller_api/toolbox/__init__.py +5 -5
  130. floodmodeller_api/toolbox/example_tool.py +45 -45
  131. floodmodeller_api/toolbox/model_build/__init__.py +2 -2
  132. floodmodeller_api/toolbox/model_build/add_siltation_definition.py +100 -98
  133. floodmodeller_api/toolbox/model_build/structure_log/__init__.py +1 -1
  134. floodmodeller_api/toolbox/model_build/structure_log/structure_log.py +287 -289
  135. floodmodeller_api/toolbox/model_build/structure_log_definition.py +76 -76
  136. floodmodeller_api/units/__init__.py +10 -10
  137. floodmodeller_api/units/_base.py +214 -212
  138. floodmodeller_api/units/boundaries.py +467 -467
  139. floodmodeller_api/units/comment.py +52 -55
  140. floodmodeller_api/units/conduits.py +382 -402
  141. floodmodeller_api/units/conveyance.py +301 -0
  142. floodmodeller_api/units/helpers.py +123 -131
  143. floodmodeller_api/units/iic.py +107 -101
  144. floodmodeller_api/units/losses.py +305 -306
  145. floodmodeller_api/units/sections.py +465 -446
  146. floodmodeller_api/units/structures.py +1690 -1683
  147. floodmodeller_api/units/units.py +93 -104
  148. floodmodeller_api/units/unsupported.py +44 -44
  149. floodmodeller_api/units/variables.py +87 -89
  150. floodmodeller_api/urban1d/__init__.py +11 -11
  151. floodmodeller_api/urban1d/_base.py +188 -179
  152. floodmodeller_api/urban1d/conduits.py +93 -85
  153. floodmodeller_api/urban1d/general_parameters.py +58 -58
  154. floodmodeller_api/urban1d/junctions.py +81 -79
  155. floodmodeller_api/urban1d/losses.py +81 -74
  156. floodmodeller_api/urban1d/outfalls.py +114 -110
  157. floodmodeller_api/urban1d/raingauges.py +111 -111
  158. floodmodeller_api/urban1d/subsections.py +92 -98
  159. floodmodeller_api/urban1d/xsections.py +147 -144
  160. floodmodeller_api/util.py +119 -21
  161. floodmodeller_api/validation/parameters.py +660 -660
  162. floodmodeller_api/validation/urban_parameters.py +388 -404
  163. floodmodeller_api/validation/validation.py +110 -108
  164. floodmodeller_api/version.py +1 -1
  165. floodmodeller_api/xml2d.py +632 -673
  166. floodmodeller_api/xml2d_template.py +37 -37
  167. floodmodeller_api/zzn.py +414 -363
  168. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/LICENSE.txt +13 -13
  169. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/METADATA +85 -82
  170. floodmodeller_api-0.4.4.dist-info/RECORD +185 -0
  171. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/WHEEL +1 -1
  172. floodmodeller_api/libifcoremd.dll +0 -0
  173. floodmodeller_api/test/test_data/EX3.bmp +0 -0
  174. floodmodeller_api/test/test_data/test_output.csv +0 -87
  175. floodmodeller_api/zzn_read.dll +0 -0
  176. floodmodeller_api-0.4.2.post1.dist-info/RECORD +0 -164
  177. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/entry_points.txt +0 -0
  178. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/top_level.txt +0 -0
@@ -1,1683 +1,1690 @@
1
- """
2
- Flood Modeller Python API
3
- Copyright (C) 2023 Jacobs U.K. Limited
4
-
5
- This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
6
- as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
-
8
- This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9
- of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10
-
11
- You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
12
-
13
- If you have any query about this program or this License, please contact us at support@floodmodeller.com or write to the following
14
- address: Jacobs UK Limited, Flood Modeller, Cottons Centre, Cottons Lane, London, SE1 2QG, United Kingdom.
15
- """
16
-
17
-
18
- import pandas as pd
19
-
20
- from floodmodeller_api.validation import _validate_unit
21
-
22
- from ._base import Unit
23
- from .helpers import (
24
- _to_float,
25
- _to_int,
26
- _to_str,
27
- join_10_char,
28
- join_12_char_ljust,
29
- join_n_char_ljust,
30
- split_10_char,
31
- split_n_char,
32
- )
33
-
34
-
35
- class BRIDGE(Unit):
36
- """Class to hold and process BRIDGE unit type. The Bridge class supports the three main bridge sub-types in
37
- Flood Modeller: Arch, USBPR1978 and Pierloss. Each of these sub-types forms a unique instance of the class
38
- which is differentiated by the `BRIDGE.subtype` attribute. All bridge types have the same common attributes:
39
-
40
- **Common Attributes**
41
-
42
- Args:
43
- name (str): Bridge section name
44
- comment (str): Comment included in unit
45
- ds_label (str): Downstream label
46
- us_remote_label, ds_remote_label (str): Remote labels
47
- subtype (str): Defines the type of bridge unit (*Should not be changed*)
48
-
49
- **ARCH Type (``BRIDGE.subtype == 'ARCH'``)**
50
-
51
- Args:
52
- calibration_coefficient (float): Calibration coefficient
53
- skew (float): Bridge skew
54
- bridge_width_dual (float): If modelled as dual bridge, the distance between upstream and downstream face of
55
- bridge (0.00 if not modelling as dual bridge):
56
- bridge_dist_dual (float): If modelled as dual bridge, the total distance between upstream and downstream
57
- faces of both bridges (0.00 if not modelling as dual bridge):
58
- orifice_flow (bool): Whether or not to model surcharged bridge as orifice flow
59
- orifice_lower_transition_dist, orifice_upper_transition_dist (float): Upper and lower transition distances
60
- for when using orifice flow
61
- orifice_discharge_coefficient (float): Orifice discharge coefficient for when using orifice flow
62
- section_data (pandas.DataFrame): Dataframe object representing the cross section. Columns are ``'X'``, ``'Y'``,
63
- ``'Mannings n'`` and ``'Embankments'``
64
- opening_data (pandas.DataFrame): Dataframe object representing the openings in the bridge. Columns are
65
- ``'Start'``, ``'Finish'``, ``'Springing Level'`` and ``'Soffit Level'``
66
-
67
- **USBPR Type (``BRIDGE.subtype == 'USBPR1978'``)**
68
-
69
- Args:
70
- calibration_coefficient (float): Calibration coefficient
71
- skew (float): Bridge skew
72
- bridge_width_dual (float): If modelled as dual bridge, the distance between upstream and downstream face of bridge
73
- (0.00 if not modelling as dual bridge)
74
- bridge_dist_dual (float): If modelled as dual bridge, the total distance between upstream and downstream faces of
75
- both bridges (0.00 if not modelling as dual bridge)
76
- orifice_flow (bool): Whether or not to model surcharged bridge as orifice flow
77
- orifice_lower_transition_dist, orifice_upper_transition_dist (float): Transition distances for when using orifice flow
78
- orifice_discharge_coefficient (float): Orifice discharge coefficient for when using orifice flow
79
- abutment_type (str): Type of abutment: ``'3'`` (default), ``'2'`` (30-degree wing wall abutment), ``'1'`` (span between
80
- abutments < 60 metres and either 90-degree wing or vertical wall abutment)
81
- abutment_alignment (str): Abutment alignment to normal direction of flow: ``'ALIGNED'`` or ``'SKEW'``
82
- specify_piers (bool): Whether or not to explicity model piers
83
- total_pier_width (float): Total width of all piers at right ange to flow direction, only used if ``specify_piers == True``
84
- npiers (int): Total number of piers in direction of flow, only used if ``specify_piers == True``
85
- pier_use_calibration_coeff (bool): Whether to use a calibration coefficient to model piers. If set to False it would use
86
- the pier shape.
87
- pier_calibration_coeff (float): Calibration coefficient for modelling piers, only used if
88
- ``specify_piers == True AND pier_use_calibration_coeff == True``
89
- pier_shape (str): Cross-sectional pier shape with options: ``'RECTANGLE'``, ``'CYLINDER'``, ``'SQUARE'``, ``'I-BEAM'``.
90
- Only used if ``specify_piers == True AND pier_use_calibration_coeff == False``
91
- pier_faces (str): Shape of pier faces with options: ``'STREAMLINE'``, ``'SEMICIRCLE'``, ``'TRIANGLE'``, ``'DIAPHRAGM'``.
92
- Only used if ``specify_piers == True AND pier_use_calibration_coeff == False``
93
- soffit_shape (str): Shape of soffit (``'FLAT'`` or ``'ARCH'``), only used if ``specify_piers == False``
94
- section_data (pandas.Dataframe): Dataframe object representing the cross section. Columns are ``'X'``, ``'Y'``, ``'Mannings n'``
95
- and ``'Embankments'``
96
- opening_data (pandas.Dataframe): Dataframe object representing the openings in the bridge. Columns are ``'Start'``, ``'Finish'``,
97
- ``'Springing Level'`` and ``'Soffit Level'``
98
- culvert_data (pandas.Dataframe): Dataframe object representing any flood relief culverts in the bridge. Columns are ``'Invert'``,
99
- ``'Soffit'``, ``'Section Area'``, ``'Cd Part Full'``, ``'Cd Full'`` and ``'Drowning Coefficient'``
100
-
101
- **PIERLOSS Type (``BRIDGE.subtype == 'PIERLOSS'``)**
102
-
103
- Args:
104
- calibration_coefficient (float): Calibration coefficient
105
- orifice_flow (bool): Whether or not to model surcharged bridge as orifice flow
106
- orifice_lower_transition_dist, orifice_upper_transition_dist (float): Transition distances for when using orifice flow
107
- orifice_discharge_coefficient (float): Orifice discharge coefficient for when using orifice flow
108
- pier_coefficient (float): Pier coefficient
109
- bridge_width (float): Distance in direction of flow from U/S to D/S cross section of bridge (for reference only)
110
- us_section_data (pandas.Dataframe): Dataframe object representing the upstream cross section. Columns are ``'X'``, ``'Y'``,
111
- ``'Mannings n'``, ``'Embankments'`` and ``'Top Level'``
112
- ds_section_data (pandas.Dataframe): Dataframe object representing the downstream cross section, if no downstream section is
113
- specified this will be an empty dataframe. Columns are ``'X'``, ``'Y'``, ``'Mannings n'``, ``'Embankments'`` and ``'Top Level'``
114
- pier_locs_data (pandas.Dataframe): Dataframe object representing the pier locations. Columns are ``'Left X'``, ``'Left Top Level'``,
115
- ``'Right X'``, ``'Right Top Level'``
116
-
117
-
118
- Raises:
119
- NotImplementedError: Raised if class is initialised without existing Bridge block (i.e. if attempting to create new Bridge unit).
120
- This will be an option for future releases
121
-
122
- Returns:
123
- BRIDGE: Flood Modeller BRIDGE Unit class object
124
- """
125
-
126
- _unit = "BRIDGE"
127
-
128
- def _read(self, br_block): # noqa: C901
129
- """Function to read a given BRIDGE block and store data as class attributes"""
130
- self._subtype = br_block[1].split(" ")[0].strip()
131
- # Extends label line to be correct length before splitting to pick up blank labels
132
- labels = split_n_char(f"{br_block[2]:<{4*self._label_len}}", self._label_len)
133
- self.name = labels[0]
134
- self.ds_label = labels[1]
135
- self.us_remote_label = labels[2]
136
- self.ds_remote_label = labels[3]
137
- self.comment = br_block[0].replace("BRIDGE", "").strip()
138
-
139
- # Read ARCH type unit
140
- if self.subtype == "ARCH":
141
- # Read Params
142
- params = split_10_char(f"{br_block[4]:<90}")
143
- self.calibration_coefficient = _to_float(params[0], 1.0)
144
- self.skew = _to_float(params[1])
145
- self.bridge_width_dual = _to_float(params[2])
146
- self.bridge_dist_dual = _to_float(params[3])
147
- if params[5] == "ORIFICE":
148
- self.orifice_flow = True
149
- else:
150
- self.orifice_flow = False
151
- self.orifice_lower_transition_dist = _to_float(params[6])
152
- self.orifice_upper_transition_dist = _to_float(params[7])
153
- self.orifice_discharge_coefficient = _to_float(params[8], 1.0)
154
-
155
- # Read cross section data
156
- self.section_nrows = int(split_10_char(br_block[5])[0])
157
- data_list = []
158
- for row in br_block[6 : 6 + self.section_nrows]:
159
- row_split = split_10_char(f"{row:<50}")
160
- x = _to_float(row_split[0]) # chainage
161
- y = _to_float(row_split[1]) # elevation
162
- n = _to_float(row_split[2]) # Mannings
163
- embankment = row_split[4] # Embankment flag
164
- data_list.append([x, y, n, embankment])
165
- self.section_data = pd.DataFrame(
166
- data_list, columns=["X", "Y", "Mannings n", "Embankments"]
167
- )
168
-
169
- # Read bridge opening data
170
- self.opening_nrows = int(split_10_char(br_block[6 + self.section_nrows])[0])
171
- data_list = []
172
- for row in br_block[6 + self.section_nrows + 1 :]:
173
- row_split = split_10_char(f"{row:<40}")
174
- start = _to_float(row_split[0]) # Start (m)
175
- finish = _to_float(row_split[1]) # Finish (m)
176
- spring = _to_float(row_split[2]) # Springing Level
177
- soffit = _to_float(row_split[3]) # Soffit Level
178
- data_list.append([start, finish, spring, soffit])
179
- self.opening_data = pd.DataFrame(
180
- data_list,
181
- columns=["Start", "Finish", "Springing Level", "Soffit Level"],
182
- )
183
-
184
- # Read USBPR type unit
185
- elif self.subtype == "USBPR1978":
186
- # Read Params
187
- params = split_10_char(f"{br_block[4]:<90}")
188
- self.calibration_coefficient = _to_float(params[0], 1.0)
189
- self.skew = _to_float(params[1])
190
- self.bridge_width_dual = _to_float(params[2])
191
- self.bridge_dist_dual = _to_float(params[3])
192
- self.total_pier_width = _to_float(params[4])
193
- if params[5] == "ORIFICE":
194
- self.orifice_flow = True
195
- else:
196
- self.orifice_flow = False
197
- self.orifice_lower_transition_dist = _to_float(params[6])
198
- self.orifice_upper_transition_dist = _to_float(params[7])
199
- self.orifice_discharge_coefficient = _to_float(params[8], 1.0)
200
-
201
- self.abutment_type = split_10_char(br_block[5])[0]
202
- self.abutment_alignment = split_10_char(br_block[7])[0]
203
-
204
- pier_info = split_10_char(f"{br_block[6]:<40}")
205
- if int(pier_info[0]) > 0:
206
- self.specify_piers = True
207
- self.npiers = int(pier_info[0])
208
- if pier_info[1] == "COEF":
209
- self.pier_use_calibration_coeff = True
210
- self.pier_calibration_coeff = _to_float(pier_info[3])
211
- else:
212
- self.pier_use_calibration_coeff = False
213
- self.pier_shape = pier_info[1]
214
- self.pier_faces = pier_info[2]
215
- else:
216
- self.specify_piers = False
217
- self.soffit_shape = pier_info[1]
218
-
219
- # Read cross section data
220
- self.section_nrows = int(split_10_char(br_block[8])[0])
221
- data_list = []
222
- for row in br_block[9 : 9 + self.section_nrows]:
223
- row_split = split_10_char(f"{row:<50}")
224
- x = _to_float(row_split[0]) # chainage
225
- y = _to_float(row_split[1]) # elevation
226
- n = _to_float(row_split[2]) # Mannings
227
- embankment = row_split[4] # Embankment flag
228
- data_list.append([x, y, n, embankment])
229
- self.section_data = pd.DataFrame(
230
- data_list, columns=["X", "Y", "Mannings n", "Embankments"]
231
- )
232
-
233
- # Read bridge opening data
234
- self.opening_nrows = int(split_10_char(br_block[9 + self.section_nrows])[0])
235
- data_list = []
236
- start_row = 9 + self.section_nrows + 1
237
- end_row = start_row + self.opening_nrows
238
- for row in br_block[start_row:end_row]:
239
- row_split = split_10_char(f"{row:<40}")
240
- start = _to_float(row_split[0]) # Start (m)
241
- finish = _to_float(row_split[1]) # Finish (m)
242
- spring = _to_float(row_split[2]) # Springing Level
243
- soffit = _to_float(row_split[3]) # Soffit Level
244
- data_list.append([start, finish, spring, soffit])
245
- self.opening_data = pd.DataFrame(
246
- data_list,
247
- columns=["Start", "Finish", "Springing Level", "Soffit Level"],
248
- )
249
-
250
- # Read flood relief culvert data
251
- self.culvert_nrows = int(
252
- split_10_char(br_block[9 + self.section_nrows + self.opening_nrows + 1])[0]
253
- )
254
- data_list = []
255
- start_row = 9 + self.section_nrows + self.opening_nrows + 2
256
- end_row = start_row + self.culvert_nrows
257
- for row in br_block[start_row:end_row]:
258
- row_split = split_10_char(f"{row:<60}")
259
- invert = _to_float(row_split[0]) # Invert
260
- soffit = _to_float(row_split[1]) # Soffit
261
- area = _to_float(row_split[2]) # Section Area
262
- cd_part = _to_float(row_split[3]) # Cd Part Full
263
- cd_full = _to_float(row_split[4]) # Cd Full
264
- drown = _to_float(row_split[5]) # Drowning Coefficient
265
- data_list.append([invert, soffit, area, cd_part, cd_full, drown])
266
- self.culvert_data = pd.DataFrame(
267
- data_list,
268
- columns=[
269
- "Invert",
270
- "Soffit",
271
- "Section Area",
272
- "Cd Part Full",
273
- "Cd Full",
274
- "Drowning Coefficient",
275
- ],
276
- )
277
-
278
- # Read Pierloss type bridge
279
- elif self.subtype == "PIERLOSS":
280
- # Read Params
281
- params = split_10_char(f"{br_block[4]:<50}")
282
- self.calibration_coefficient = _to_float(params[0], 1.0)
283
- if params[1] == "ORIFICE":
284
- self.orifice_flow = True
285
- else:
286
- self.orifice_flow = False
287
- self.orifice_lower_transition_dist = _to_float(params[3])
288
- self.orifice_upper_transition_dist = _to_float(params[4])
289
- self.orifice_discharge_coefficient = _to_float(params[2], 1.0)
290
- additional_params = split_10_char(f"{br_block[5]:<20}")
291
- self.pier_coefficient = _to_float(additional_params[0], 0.9)
292
- self.bridge_width = _to_float(additional_params[1])
293
-
294
- # Read US cross section data
295
- self.us_section_nrows = int(split_10_char(br_block[6])[0])
296
- data_list = []
297
- for row in br_block[7 : 7 + self.us_section_nrows]:
298
- row_split = split_10_char(f"{row:<60}")
299
- x = _to_float(row_split[0]) # chainage
300
- y = _to_float(row_split[1]) # elevation
301
- n = _to_float(row_split[2]) # Mannings
302
- embankment = row_split[4] # Embankment flag
303
- top_level = row_split[5] # Top Level (m)
304
- data_list.append([x, y, n, embankment, top_level])
305
- self.us_section_data = pd.DataFrame(
306
- data_list, columns=["X", "Y", "Mannings n", "Embankments", "Top Level"]
307
- )
308
-
309
- # Read DS cross section data
310
- new_idx = 6 + 1 + self.us_section_nrows
311
- self.ds_section_nrows = int(split_10_char(br_block[new_idx])[0])
312
- data_list = []
313
- for row in br_block[new_idx + 1 : new_idx + 1 + self.ds_section_nrows]:
314
- row_split = split_10_char(f"{row:<60}")
315
- x = _to_float(row_split[0]) # chainage
316
- y = _to_float(row_split[1]) # elevation
317
- n = _to_float(row_split[2]) # Mannings
318
- embankment = row_split[4] # Embankment flag
319
- top_level = row_split[5] # Top Level (m)
320
- data_list.append([x, y, n, embankment, top_level])
321
- self.ds_section_data = pd.DataFrame(
322
- data_list, columns=["X", "Y", "Mannings n", "Embankments", "Top Level"]
323
- )
324
-
325
- # Read pier locations
326
- new_idx += 1 + self.ds_section_nrows
327
- self.pier_locs_nrows = int(split_10_char(br_block[new_idx])[0])
328
- data_list = []
329
- for row in br_block[new_idx + 1 : new_idx + 1 + self.pier_locs_nrows]:
330
- row_split = split_10_char(f"{row:<40}")
331
- l_x = _to_float(row_split[0]) # chainage
332
- l_top_level = _to_float(row_split[1]) # Top Level (m)
333
- r_x = _to_float(row_split[2]) # chainage
334
- r_top_level = _to_float(row_split[3]) # Top Level (m)
335
- data_list.append([l_x, l_top_level, r_x, r_top_level])
336
- self.pier_locs_data = pd.DataFrame(
337
- data_list,
338
- columns=["Left X", "Left Top Level", "Right X", "Right Top Level"],
339
- )
340
-
341
- else:
342
- # This else block is triggered for bridge subtypes which aren't yet supported, and just keeps the 'br_block' in it's raw state to write back.
343
- print(
344
- f'This Bridge sub-type: "{self.subtype}" is currently unsupported for reading/editing'
345
- )
346
- self._raw_block = br_block
347
- self.name = br_block[2][:12].strip()
348
-
349
- def _write(self): # noqa: C901
350
- """Function to write a valid BRIDGE block"""
351
- _validate_unit(self) # Function to check the params are valid for BRIDGE unit
352
- header = "BRIDGE " + self.comment
353
- labels = join_n_char_ljust(
354
- self._label_len,
355
- self.name,
356
- self.ds_label,
357
- self.us_remote_label,
358
- self.ds_remote_label,
359
- )
360
- br_block = [header, self.subtype, labels]
361
-
362
- if self.subtype == "ARCH":
363
- orifice = "ORIFICE" if self.orifice_flow else ""
364
- params = join_10_char(
365
- self.calibration_coefficient,
366
- self.skew,
367
- self.bridge_width_dual,
368
- self.bridge_dist_dual,
369
- "",
370
- orifice,
371
- self.orifice_lower_transition_dist,
372
- self.orifice_upper_transition_dist,
373
- self.orifice_discharge_coefficient,
374
- )
375
- self.section_nrows = len(self.section_data)
376
- br_block.extend(["MANNING", params, f"{str(self.section_nrows):>10}"])
377
-
378
- section_data = []
379
- for _, x, y, n, embankments in self.section_data.itertuples():
380
- # Adding extra 10 spaces before embankment flag
381
- row = join_10_char(x, y, n, "")
382
- row += embankments
383
- section_data.append(row)
384
-
385
- br_block.extend(section_data)
386
-
387
- self.opening_nrows = len(self.opening_data)
388
- br_block.append(f"{str(self.opening_nrows):>10}")
389
- opening_data = []
390
- for _, start, finish, spring, soffit in self.opening_data.itertuples():
391
- row = join_10_char(start, finish, spring, soffit)
392
- opening_data.append(row)
393
-
394
- br_block.extend(opening_data)
395
-
396
- return br_block
397
-
398
- if self.subtype == "USBPR1978":
399
- orifice = "ORIFICE" if self.orifice_flow else ""
400
- params = join_10_char(
401
- self.calibration_coefficient,
402
- self.skew,
403
- self.bridge_width_dual,
404
- self.bridge_dist_dual,
405
- self.total_pier_width,
406
- orifice,
407
- self.orifice_lower_transition_dist,
408
- self.orifice_upper_transition_dist,
409
- self.orifice_discharge_coefficient,
410
- )
411
- if self.specify_piers:
412
- if self.pier_use_calibration_coeff:
413
- pier_params = f'{self.npiers:>10}{"COEF":<10}{"":>10}{self.calibration_coefficient:>10.3f}'
414
- else:
415
- pier_params = f"{self.npiers:>10}{self.pier_shape:<10}{self.pier_faces:<10}"
416
- else:
417
- pier_params = f"{0:>10}{self.soffit_shape}"
418
-
419
- self.section_nrows = len(self.section_data)
420
- br_block.extend(
421
- [
422
- "MANNING",
423
- params,
424
- f"{str(self.abutment_type):>10}",
425
- pier_params,
426
- self.abutment_alignment,
427
- f"{str(self.section_nrows):>10}",
428
- ]
429
- )
430
-
431
- section_data = []
432
- for _, x, y, n, embankments in self.section_data.itertuples():
433
- # Adding extra 10 spaces before embankment flag
434
- row = join_10_char(x, y, n, "")
435
- row += embankments
436
- section_data.append(row)
437
- br_block.extend(section_data)
438
-
439
- self.opening_nrows = len(self.opening_data)
440
- br_block.append(f"{str(self.opening_nrows):>10}")
441
- opening_data = []
442
- for _, start, finish, spring, soffit in self.opening_data.itertuples():
443
- row = join_10_char(start, finish, spring, soffit)
444
- opening_data.append(row)
445
- br_block.extend(opening_data)
446
-
447
- self.culvert_nrows = len(self.culvert_data)
448
- br_block.append(f"{str(self.culvert_nrows):>10}")
449
- culvert_data = []
450
- for (
451
- _,
452
- invert,
453
- soffit,
454
- area,
455
- cd_part,
456
- cd_full,
457
- drown,
458
- ) in self.culvert_data.itertuples():
459
- row = join_10_char(invert, soffit, area, cd_part, cd_full, drown)
460
- culvert_data.append(row)
461
- br_block.extend(culvert_data)
462
-
463
- return br_block
464
-
465
- if self.subtype == "PIERLOSS":
466
- orifice = "ORIFICE" if self.orifice_flow else ""
467
- params = join_10_char(
468
- self.calibration_coefficient,
469
- orifice,
470
- self.orifice_discharge_coefficient,
471
- self.orifice_lower_transition_dist,
472
- self.orifice_upper_transition_dist,
473
- )
474
- additional_params = join_10_char(self.pier_coefficient, self.bridge_width)
475
- self.us_section_nrows = len(self.us_section_data)
476
- br_block.extend(
477
- [
478
- "YARNELL",
479
- params,
480
- additional_params,
481
- f"{str(self.us_section_nrows):>10}",
482
- ]
483
- )
484
-
485
- us_section_data = []
486
- for _, x, y, n, embankments, top_level in self.us_section_data.itertuples():
487
- # Adding extra 10 spaces before embankment flag
488
- row = join_10_char(x, y, n, "")
489
- row += f"{embankments:<10}"
490
- row += join_10_char(top_level)
491
- us_section_data.append(row)
492
- br_block.extend(us_section_data)
493
-
494
- self.ds_section_nrows = len(self.ds_section_data)
495
- br_block.append(f"{str(self.ds_section_nrows):>10}")
496
- ds_section_data = []
497
- for _, x, y, n, embankments, top_level in self.ds_section_data.itertuples():
498
- # Adding extra 10 spaces before embankment flag
499
- row = join_10_char(x, y, n, "")
500
- row += f"{embankments:<10}"
501
- row += join_10_char(top_level)
502
- ds_section_data.append(row)
503
- br_block.extend(ds_section_data)
504
-
505
- self.pier_locs_nrows = len(self.pier_locs_data)
506
- br_block.append(f"{str(self.pier_locs_nrows):>10}")
507
- pier_locs_data = []
508
- for (
509
- _,
510
- l_x,
511
- l_top_level,
512
- r_x,
513
- r_top_level,
514
- ) in self.pier_locs_data.itertuples():
515
- row = join_10_char(l_x, l_top_level, r_x, r_top_level)
516
- pier_locs_data.append(row)
517
- br_block.extend(pier_locs_data)
518
-
519
- return br_block
520
-
521
- return self._raw_block
522
-
523
-
524
- class SLUICE(Unit):
525
- """The Sluice class supports two sluice sub-types in Flood Modeller: RADIAL and VERTICAL. Each of these sub-types forms
526
- a unique instance of the class which is differentiated by the `SLUICE.subtype` attribute. There are also several different
527
- attributes depending on the `.control_method` used. All sluice types have the same common attributes:
528
-
529
- **Common Attributes**
530
-
531
- Args:
532
- name (str): Sluice section name (upstream label)
533
- ds_label (str): Downstream label
534
- remote_label (str): Remote label
535
- comment (str): Comment included in unit
536
- subtype (str): Defines the type of sluice unit (*Should not be changed*)
537
- weir_flow_coefficient (float): Coefficient of approach velocity for weir flow (0.4 - 3.0)
538
- under_gate_flow (float): Coefficient of approach velocity for under gate flow
539
- over_gate_flow (float): Coefficient of approach velocity for over gate flow
540
- weir_breadth (float): breadth of weir (for single gate) perpendicular to flow direction
541
- crest_elevation (float): Elevation of weir crest
542
- gate_height_or_chord (float): Height of sluice gate (m) (if sluice subtype is VERTICAL or subtype is RADIAL and
543
- ``use_degrees == False``). OR the cord made by the arc of sluice gate (m) if subtype is RADIAL and
544
- ``use_degrees == True``.
545
- weir_length (float): Length of weir (m) in flow direction
546
- us_weir_height (float): Vertical distance from weir crest to upstream bed level
547
- ds_weir_height (float): Vertical distance from weir crest to downstream bed level
548
- bias_factor (float): Only used when control_method set to 'REMOTE WATER LEVEL'
549
- modular_limits (dict): Dictionary of modular limit values. Keys: {'weir_flow', 'under_gate_flow', 'over_gate_flow'}. If they
550
- are all set equal to zero, then a variable calculation method is used.
551
- ngates (int): number of gates
552
- timeunit (str): Unit of time, e.g. 'HOURS', 'MINUTES' or 'SECONDS'. See Flood Modeller documentation for all available options.
553
- extendmethod (str): Data extending method: 'EXTEND', 'NOEXTEND' or 'REPEAT'.
554
-
555
-
556
- **Attributes used when ``SLUICE.control_method == 'TIME'``**
557
-
558
- Args:
559
- gates (List[pandas.Series]): List of Data series representing the gate control with 'Time' index and 'Gate Opening' (m) as the data
560
-
561
-
562
- **Attributes used when ``SLUICE.control_method == 'LOGICAL'``**
563
-
564
- Args:
565
- gates (List[pandas.DataFrame]): List of Dataframes representing the gate control with 'Time' as index and 'Mode' and 'Gate Opening' (m) as the columns.
566
- The Mode is set to 'AUTO', 'MANUAL' or [blank] depending on the control mode at that time.
567
- max_movement_rate (float): Maximum movement rate of the structure
568
- max_setting (float): Maximum setting of the structure
569
- min_setting (float): Minimum setting of the structure
570
- rules (List[dict]): List of logical rules to use. Each rule is represented as a Dictionary with keys 'name' and 'logic'.
571
- time_rule_data (pandas.Series): Series containing data on which operating rules to apply, with index of 'Time' and
572
- dataseries for 'Operating Rules'
573
- varrules (List[dict]): List of logical variable rules to use. Each varrule is represented as a Dictionary with keys 'name' and 'logic'.
574
- time_varrule_data (pandas.Series): Series containing data on which operating rules to apply, with index of 'Time' and
575
- dataseries for 'Operating Rules'
576
-
577
-
578
- **Radial Type (``SLUICE.subtype == 'RADIAL'``)**
579
-
580
- Args:
581
- use_degrees (bool): Whether to measure gate movement in degrees
582
- allow_free_flow_under (bool): Whether to allow free flow under gate
583
- pivot_height (float): Height of gate pivot (m) above sill
584
- gate_radius (float): Distance from gate pivot to surface (m)
585
-
586
- Returns:
587
- SLUICE: Flood Modeller SLUICE Unit class object
588
- """
589
-
590
- _unit = "SLUICE"
591
-
592
- def _read(self, block):
593
- """Function to read a given SLUICE block and store data as class attributes"""
594
- self._subtype = block[1].split(" ")[0].strip()
595
-
596
- # Extends label line to be correct length before splitting to pick up blank labels
597
- labels = split_n_char(f"{block[2]:<{3*self._label_len}}", self._label_len)
598
- self.name = labels[0]
599
- self.ds_label = labels[1]
600
- self.remote_label = labels[2]
601
- self.comment = block[0].replace("SLUICE", "").strip()
602
-
603
- # First parameter line
604
- params1 = split_10_char(f"{block[3]:<80}")
605
- self.weir_flow_coefficient = _to_float(params1[0], 1.0)
606
- self.under_gate_flow = _to_float(params1[1], 1.0)
607
- self.weir_breadth = _to_float(params1[2])
608
- self.crest_elevation = _to_float(params1[3])
609
- self.gate_height_or_chord = _to_float(params1[4])
610
- self.weir_length = _to_float(params1[5])
611
- if self.subtype == "RADIAL":
612
- self.use_degrees = params1[6] == "DEGREES"
613
- self.allow_free_flow_under = params1[7] == "FREESLUICE"
614
-
615
- # Second parameter line
616
- params2 = split_10_char(f"{block[4]:<70}")
617
- self.us_weir_height = _to_float(params2[0])
618
- self.ds_weir_height = _to_float(params2[1])
619
- self.bias_factor = _to_float(params2[2], 1.0)
620
- self.over_gate_flow = _to_float(params2[3], 1.0)
621
- if self.subtype == "RADIAL":
622
- self.pivot_height = _to_float(params2[4], 0.7)
623
- self.gate_radius = _to_float(params2[5], 0.7)
624
- else:
625
- self.modular_limits = {
626
- "weir_flow": _to_float(params2[4]),
627
- "under_gate_flow": _to_float(params2[5], 1.0),
628
- "over_gate_flow": _to_float(params2[6], 1.0),
629
- }
630
-
631
- # Third parameter line
632
- params3 = split_10_char(f"{block[5]:<60}")
633
- self.ngates = int(params3[0]) # number of gates
634
- if self.subtype == "RADIAL":
635
- self.modular_limits = {
636
- "weir_flow": _to_float(params3[1]),
637
- "under_gate_flow": _to_float(params3[2], 1.0),
638
- "over_gate_flow": _to_float(params3[3], 1.0),
639
- }
640
- self.timeunit = _to_str(params3[4], "SECONDS", check_float=True)
641
- self.extendmethod = _to_str(params3[5], "EXTEND")
642
- else:
643
- self.timeunit = _to_str(params3[1], "SECONDS", check_float=True)
644
- self.extendmethod = _to_str(params3[2], "EXTEND")
645
-
646
- # Control lines
647
- self.control_method = block[6].split()[0].upper()
648
- if self.control_method == "TIME":
649
- self.gates = self._get_gates(self.ngates, block, gate_row=7)
650
-
651
- elif self.control_method == "LOGICAL":
652
- logical_params = split_10_char(block[6])
653
- self.max_movement_rate = logical_params[1]
654
- self.max_setting = logical_params[2]
655
- self.min_setting = logical_params[3]
656
- self.gates = self._get_gates(self.ngates, block, gate_row=7)
657
- self._read_rules(block)
658
-
659
- else:
660
- self._raw_extra_lines = block[6:]
661
- print(
662
- f"Note: Sluice control using method: {self.control_method} is not currently supported."
663
- )
664
-
665
- def _write(self):
666
- """Function to write a valid SLUICE block"""
667
- _validate_unit(self) # Function to check the params are valid for CONDUIT unit
668
- header = "SLUICE " + self.comment
669
- labels = join_n_char_ljust(self._label_len, self.name, self.ds_label, self.remote_label)
670
- block = [header, self.subtype, labels]
671
-
672
- # First parameter line
673
- params1 = join_10_char(
674
- self.weir_flow_coefficient,
675
- self.under_gate_flow,
676
- self.weir_breadth,
677
- self.crest_elevation,
678
- self.gate_height_or_chord,
679
- self.weir_length,
680
- )
681
- if self.subtype == "RADIAL":
682
- params1 += f'{"DEGREES":<10}' if self.use_degrees else f'{"":<10}'
683
- params1 += "FREESLUICE" if self.allow_free_flow_under else f'{"":<10}'
684
-
685
- # Second parameter line
686
- params2 = join_10_char(
687
- self.us_weir_height,
688
- self.ds_weir_height,
689
- self.bias_factor,
690
- self.over_gate_flow,
691
- )
692
-
693
- if self.subtype == "RADIAL":
694
- params2 += join_10_char(self.pivot_height, self.gate_radius)
695
- else:
696
- params2 += join_10_char(
697
- self.modular_limits["weir_flow"],
698
- self.modular_limits["under_gate_flow"],
699
- self.modular_limits["over_gate_flow"],
700
- )
701
-
702
- # Third parameter line
703
- self.ngates = len(self.gates)
704
- params3 = join_10_char(self.ngates)
705
-
706
- if self.subtype == "RADIAL":
707
- params3 += join_10_char(
708
- self.modular_limits["weir_flow"],
709
- self.modular_limits["under_gate_flow"],
710
- self.modular_limits["over_gate_flow"],
711
- )
712
-
713
- params3 += join_n_char_ljust(10, self.timeunit, self.extendmethod)
714
-
715
- block.extend([params1, params2, params3])
716
-
717
- # Control lines
718
- if self.control_method == "TIME":
719
- block.append("TIME")
720
- n = 1
721
- for gate in self.gates:
722
- block.append(f"GATE {n}")
723
- nrows = len(gate)
724
- block.append(f"{nrows:>10}")
725
- gate_data = [f"{join_10_char(t, o)}" for t, o in gate.items()]
726
- block.extend(gate_data)
727
- n += 1
728
-
729
- elif self.control_method == "LOGICAL":
730
- # ADD GATES
731
- block.append(
732
- join_10_char(
733
- f'{"LOGICAL":<10}',
734
- self.max_movement_rate,
735
- self.max_setting,
736
- self.min_setting,
737
- )
738
- )
739
- n = 1
740
- for gate in self.gates:
741
- block.append(f"GATE {n}")
742
- nrows = len(gate)
743
- block.append(f"{nrows:>10}")
744
- gate_data = [f"{join_10_char(t, m, o)}" for t, m, o in gate.itertuples()]
745
- block.extend(gate_data)
746
- n += 1
747
- block = self._write_rules(block)
748
-
749
- else:
750
- block.extend(self._raw_extra_lines)
751
-
752
- return block
753
-
754
- def _get_gates(self, ngates, block, gate_row):
755
- gates = []
756
-
757
- if self.control_method == "TIME":
758
- for _ in range(ngates):
759
- nrows = int(split_10_char(block[gate_row + 1])[0])
760
- data_list = []
761
- for row in block[gate_row + 2 : gate_row + 2 + nrows]:
762
- row_split = split_10_char(f"{row:<20}")
763
- x = _to_float(row_split[0]) # time
764
- y = _to_float(row_split[1]) # opening
765
- data_list.append([x, y])
766
-
767
- gate_data = pd.DataFrame(data_list, columns=["Time", "Opening"])
768
- gate_data = gate_data.set_index("Time")
769
- gate_data = gate_data["Opening"]
770
-
771
- gates.append(gate_data)
772
-
773
- gate_row += 2 + nrows
774
-
775
- self._last_gate_row = gate_row
776
-
777
- return gates
778
-
779
- if self.control_method == "LOGICAL":
780
- for _ in range(ngates):
781
- nrows = int(split_10_char(block[gate_row + 1])[0])
782
- data_list = []
783
- for row in block[gate_row + 2 : gate_row + 2 + nrows]:
784
- row_split = split_10_char(f"{row:<30}")
785
- x = _to_float(row_split[0]) # time
786
- y = row_split[1] # mode
787
- z = _to_float(row_split[2]) # opening
788
- data_list.append([x, y, z])
789
-
790
- gate_data = pd.DataFrame(data_list, columns=["Time", "Mode", "Opening"])
791
- gate_data = gate_data.set_index("Time")
792
-
793
- gates.append(gate_data)
794
-
795
- gate_row += 2 + nrows
796
-
797
- self._last_gate_row = gate_row
798
-
799
- return gates
800
-
801
-
802
- class ORIFICE(Unit):
803
- """Class to hold and process ORIFICE unit type
804
-
805
- Args:
806
- name (str, optional): Unit name.
807
- comment (str, optional): Comment included in unit.
808
- flapped (bool, optional): ``True`` if orifice is flapped, ``False`` if orifice is open
809
- ds_label (str, optional): Downstream label
810
- invert (float, optional): Throat invert level
811
- soffit (float, optional): Throat soffit level
812
- bore_area (float, optional): Cross sectional area of throat opening
813
- upstream_sill (float, optional): Upstream sill level
814
- downstream_sill (float, optional): Downstream sill level
815
- shape (str, optional): Shape of orifice aperture ('RECTANGLE' or 'CIRCULAR')
816
- weir_flow (float, optional): Calibration factor for weir flow
817
- surcharged_flow (float, optional): Calibration factor for surcharged flow
818
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode
819
-
820
-
821
- Returns:
822
- ORIFICE: Flood Modeller ORIFICE Unit class object
823
- """
824
-
825
- _unit = "ORIFICE"
826
-
827
- def _read(self, block):
828
- """Function to read a given ORIFICE block and store data as class attributes"""
829
- self._subtype = block[1].split(" ")[0].strip()
830
- self.flapped = self.subtype == "FLAPPED"
831
-
832
- # Extends label line to be correct length before splitting to pick up blank labels
833
- labels = split_n_char(f"{block[2]:<{2*self._label_len}}", self._label_len)
834
- self.name = labels[0]
835
- self.ds_label = labels[1]
836
- self.comment = block[0].replace("ORIFICE", "").strip()
837
-
838
- # First parameter line
839
- params1 = split_10_char(f"{block[3]:<60}")
840
- self.invert = _to_float(params1[0])
841
- self.soffit = _to_float(params1[1])
842
- self.bore_area = _to_float(params1[2])
843
- self.upstream_sill = _to_float(params1[3])
844
- self.downstream_sill = _to_float(params1[4])
845
- self.shape = _to_str(params1[5], "RECTANGLE")
846
-
847
- # Second parameter line
848
- params2 = split_10_char(block[4])
849
- self.weir_flow = _to_float(params2[0], 1.0)
850
- self.surcharged_flow = _to_float(params2[1], 1.0)
851
- self.modular_limit = _to_float(params2[2], 0.7)
852
-
853
- def _write(self):
854
- """Function to write a valid ORIFICE block"""
855
- _validate_unit(self) # Function to check the params are valid for CONDUIT unit
856
- header = "ORIFICE " + self.comment
857
- labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
858
-
859
- self._subtype = "FLAPPED" if self.flapped else "OPEN"
860
- block = [header, self.subtype, labels]
861
-
862
- # First parameter line
863
- params1 = join_10_char(
864
- self.invert,
865
- self.soffit,
866
- self.bore_area,
867
- self.upstream_sill,
868
- self.downstream_sill,
869
- self.shape,
870
- )
871
-
872
- # Second parameter line
873
- params2 = join_10_char(self.weir_flow, self.surcharged_flow, self.modular_limit)
874
-
875
- block.extend([params1, params2])
876
-
877
- return block
878
-
879
- def _create_from_blank(
880
- self,
881
- name="new_orifice",
882
- flapped=False,
883
- ds_label="",
884
- comment="",
885
- invert=0.0,
886
- soffit=0.0,
887
- bore_area=1.0,
888
- upstream_sill=0.0,
889
- downstream_sill=0.0,
890
- shape="RECTANGLE",
891
- weir_flow=1.0,
892
- surcharged_flow=1.0,
893
- modular_limit=0.7,
894
- ):
895
- for param, val in {
896
- "name": name,
897
- "flapped": flapped,
898
- "ds_label": ds_label,
899
- "comment": comment,
900
- "invert": invert,
901
- "soffit": soffit,
902
- "bore_area": bore_area,
903
- "upstream_sill": upstream_sill,
904
- "downstream_sill": downstream_sill,
905
- "shape": shape,
906
- "weir_flow": weir_flow,
907
- "surcharged_flow": surcharged_flow,
908
- "modular_limit": modular_limit,
909
- }.items():
910
- setattr(self, param, val)
911
-
912
-
913
- class SPILL(Unit):
914
- """Class to hold and process SPILL unit type
915
-
916
- Args:
917
- name (str, optional): Unit name.
918
- comment (str, optional): Comment included in unit.
919
- ds_label (str, optional): Downstream label
920
- weir_coefficient (float, optional): Weir coefficient
921
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode
922
- data (pandas.DataFrame): Dataframe object containing all the spill section data.
923
- Columns are ``'X', 'Y', 'Easting', 'Northing'``
924
-
925
- Returns:
926
- SPILL: Flood Modeller SPILL Unit class object
927
- """
928
-
929
- _unit = "SPILL"
930
-
931
- def _read(self, block):
932
- """Function to read a given SPILL block and store data as class attributes"""
933
- # Extends label line to be correct length before splitting to pick up blank labels
934
- labels = split_n_char(f"{block[1]:<{2*self._label_len}}", self._label_len)
935
- self.name = labels[0]
936
- self.ds_label = labels[1]
937
- self.comment = block[0].replace("SPILL", "").strip()
938
-
939
- # First parameter line
940
- params = split_10_char(block[2])
941
- self.weir_coefficient = _to_float(params[0], 1.2)
942
- self.modular_limit = _to_float(params[1], 0.9)
943
-
944
- # Spill section data
945
- data_list = []
946
- for row in block[4:]:
947
- row_split = split_10_char(f"{row:<40}")
948
- x = _to_float(row_split[0]) # chainage
949
- y = _to_float(row_split[1]) # elevation
950
- e = _to_float(row_split[2]) # easting
951
- n = _to_float(row_split[3]) # northing
952
-
953
- data_list.append([x, y, e, n])
954
-
955
- spill_data = pd.DataFrame(data_list, columns=["X", "Y", "Easting", "Northing"])
956
- self.data = spill_data
957
-
958
- def _write(self):
959
- """Function to write a valid SPILL block"""
960
- _validate_unit(self) # Function to check the params are valid for CONDUIT unit
961
- header = "SPILL " + self.comment
962
- labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
963
- block = [header, labels]
964
-
965
- # First parameter line
966
- params = join_10_char(self.weir_coefficient, self.modular_limit)
967
- block.append(params)
968
-
969
- # Section data
970
- nrows = len(self.data)
971
- block.append(join_10_char(nrows))
972
- section_data = [join_10_char(x, y, e, n) for _, x, y, e, n in self.data.itertuples()]
973
- block.extend(section_data)
974
-
975
- return block
976
-
977
- def _create_from_blank(
978
- self,
979
- name="new_spill",
980
- ds_label="",
981
- comment="",
982
- weir_coefficient=1.2,
983
- modular_limit=0.9,
984
- data=None,
985
- ):
986
- for param, val in {
987
- "name": name,
988
- "ds_label": ds_label,
989
- "comment": comment,
990
- "weir_coefficient": weir_coefficient,
991
- "modular_limit": modular_limit,
992
- }.items():
993
- setattr(self, param, val)
994
-
995
- self.data = (
996
- data
997
- if isinstance(data, pd.DataFrame)
998
- else pd.DataFrame([[0.0, 0.0, 0.0, 0.0]], columns=["X", "Y", "Easting", "Northing"])
999
- )
1000
-
1001
-
1002
- class RNWEIR(Unit):
1003
- """Class to hold and process RNWEIR unit type
1004
-
1005
- Args:
1006
- name (str, optional): Upstream label name.
1007
- comment (str, optional): Comment included in unit.
1008
- ds_label (str, optional): Downstream label.
1009
- velocity_coefficient (float, optional): Coefficient of approach velocity.
1010
- weir_length (float, optional): Length of weir crest in the direction of flow (m).
1011
- weir_breadth (float, optional): Breadth of weir at control section (normal to the flow direction)(m).
1012
- weir_elevation (float, optional): Elevation of weir crest (m AD).
1013
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode.
1014
- upstream_crest_height (float, optional): Height of crest above bed of upstream channnel (m).
1015
- downstream_crest_height (float, optional): Height of crest above downstream channel (m).
1016
-
1017
- Returns:
1018
- RNWEIR: Flood Modeller RNWEIR Unit class object"""
1019
-
1020
- _unit = "RNWEIR"
1021
-
1022
- def _read(self, block):
1023
- """Function to read a given RNWEIR block and store data as class attributes"""
1024
- # Extends label line to be correct length before splitting to pick up blank labels
1025
- labels = split_n_char(f"{block[1]:<{2*self._label_len}}", self._label_len)
1026
- self.name = labels[0]
1027
- self.ds_label = labels[1]
1028
- self.comment = block[0].replace("RNWEIR", "").strip()
1029
-
1030
- # First parameter line
1031
- params1 = split_10_char(f"{block[2]:<50}")
1032
- self.velocity_coefficient = _to_float(params1[0])
1033
- self.weir_length = _to_float(params1[1])
1034
- self.weir_breadth = _to_float(params1[2])
1035
- self.weir_elevation = _to_float(params1[3])
1036
- self.modular_limit = _to_float(params1[4])
1037
-
1038
- # Second parameter line
1039
- params2 = split_10_char(f"{block[3]:<20}")
1040
- self.upstream_crest_height = _to_float(params2[0])
1041
- self.downstream_crest_height = _to_float(params2[1])
1042
-
1043
- def _write(self):
1044
- """Function to write a valid RNWEIR block"""
1045
- _validate_unit(self)
1046
- header = "RNWEIR " + self.comment
1047
- labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
1048
- block = [header, labels]
1049
-
1050
- # First parameter line
1051
- if self.modular_limit == 0.0:
1052
- params1 = join_10_char(
1053
- self.velocity_coefficient,
1054
- self.weir_length,
1055
- self.weir_breadth,
1056
- self.weir_elevation,
1057
- )
1058
- else:
1059
- params1 = join_10_char(
1060
- self.velocity_coefficient,
1061
- self.weir_length,
1062
- self.weir_breadth,
1063
- self.weir_elevation,
1064
- self.modular_limit,
1065
- )
1066
-
1067
- block.append(params1)
1068
-
1069
- # Second parameter line
1070
- params2 = join_10_char(self.upstream_crest_height, self.downstream_crest_height)
1071
- block.append(params2)
1072
-
1073
- return block
1074
-
1075
- def _create_from_blank(
1076
- self,
1077
- name="new_rnweir",
1078
- comment="",
1079
- ds_label="",
1080
- velocity_coefficient=1.0,
1081
- modular_limit=0.7,
1082
- upstream_crest_height=0.0,
1083
- downstream_crest_height=0.0,
1084
- weir_length=0.0,
1085
- weir_breadth=0.0,
1086
- weir_elevation=0.0,
1087
- ):
1088
- for param, val in {
1089
- "name": name,
1090
- "comment": comment,
1091
- "ds_label": ds_label,
1092
- "velocity_coefficient": velocity_coefficient,
1093
- "modular_limit": modular_limit,
1094
- "upstream_crest_height": upstream_crest_height,
1095
- "downstream_crest_height": downstream_crest_height,
1096
- "weir_length": weir_length,
1097
- "wier_breadth": weir_breadth,
1098
- "weir_elevation": weir_elevation,
1099
- }.items():
1100
- setattr(self, param, val)
1101
-
1102
-
1103
- class WEIR(Unit):
1104
- """Class to hold and process WEIR unit type
1105
-
1106
- Args:
1107
- name (str, optional): Upstream label name.
1108
- comment (str, optional): Comment included in unit.
1109
- ds_label (str, optional): Downstream label.
1110
- exponent (float, optional): Coefficient of discharge for the weir,
1111
- discharge_coefficeient (float, optional): Exponent used in the weir flow equation,
1112
- velocity_coefficient (float, optional): Coefficient of approach velocity.
1113
- weir_length (float, optional): Length of weir crest in the direction of flow (m).
1114
- weir_breadth (float, optional): Breadth of weir at control section (normal to the flow direction)(m).
1115
- weir_elevation (float, optional): Elevation of weir crest (m AD).
1116
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode.
1117
-
1118
- Returns:
1119
- WEIR: Flood Modeller WEIR Unit class object"""
1120
-
1121
- _unit = "WEIR"
1122
-
1123
- def _read(self, block):
1124
- """Function to read a given WEIR block and store data as class attributes"""
1125
- # Extends label line to be correct length before splitting to pick up blank labels
1126
- labels = split_n_char(f"{block[1]:<{2*self._label_len}}", self._label_len)
1127
- self.name = labels[0]
1128
- self.ds_label = labels[1]
1129
- self.comment = block[0].replace("WEIR", "").strip()
1130
-
1131
- # Exponent
1132
- self.exponent = _to_float(block[2].strip())
1133
-
1134
- # Parameters line
1135
- params = split_10_char(f"{block[3]:<50}")
1136
- self.discharge_coefficient = _to_float(params[0])
1137
- self.velocity_coefficient = _to_float(params[1])
1138
- self.weir_breadth = _to_float(params[2])
1139
- self.weir_elevation = _to_float(params[3])
1140
- self.modular_limit = _to_float(params[4])
1141
-
1142
- def _write(self):
1143
- """Function to write a valid WEIR block"""
1144
- _validate_unit(self)
1145
- header = "WEIR " + self.comment
1146
- labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
1147
- block = [header, labels]
1148
-
1149
- # Exponent line
1150
- exp_line = join_10_char(self.exponent)
1151
- block.append(exp_line)
1152
-
1153
- # Parameter line
1154
- params = join_10_char(
1155
- self.discharge_coefficient,
1156
- self.velocity_coefficient,
1157
- self.weir_breadth,
1158
- self.weir_elevation,
1159
- self.modular_limit,
1160
- )
1161
- block.append(params)
1162
-
1163
- return block
1164
-
1165
- def _create_from_blank(
1166
- self,
1167
- name="new_weir",
1168
- comment="",
1169
- ds_label="",
1170
- exponent=1.5,
1171
- discharge_coefficeient=1.0,
1172
- velocity_coefficient=1.0,
1173
- modular_limit=0.7,
1174
- weir_length=0.0,
1175
- weir_breadth=0.0,
1176
- weir_elevation=0.0,
1177
- ):
1178
- for param, val in {
1179
- "name": name,
1180
- "comment": comment,
1181
- "ds_label": ds_label,
1182
- "exponent": exponent,
1183
- "discharge_coefficeient": discharge_coefficeient,
1184
- "velocity_coefficient": velocity_coefficient,
1185
- "modular_limit": modular_limit,
1186
- "weir_length": weir_length,
1187
- "wier_breadth": weir_breadth,
1188
- "weir_elevation": weir_elevation,
1189
- }.items():
1190
- setattr(self, param, val)
1191
-
1192
-
1193
- class CRUMP(Unit):
1194
- """Class to hold and process CRUMP unit type
1195
-
1196
- Args:
1197
-
1198
- name (str, optional): Upstream label name.
1199
- comment (str,optional): Comment included in unit.
1200
- calibration_coefficient (float, optional): Calibration coefficient (should be set to unity for most cases).
1201
- weir_breadth (float, optional): Breadth of weir at crest (m).
1202
- weir_elevation (float, optional): Eleveation of weir crest (m above datum).
1203
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode.
1204
- upstream_crest_height (float, optional): Height of crest above bed of upstream channel (m).
1205
- downstream_crest_height (float, optional): Height oof crest above bed of downstream channel (m).
1206
- ds_label (str, optional): Downstream node label.
1207
- us_remote_label (str, optional): Upstream remote node label (must be a river or conduit section) - use if name is not a river or conduit section.
1208
- ds_remote_label (str, optional): Downstream remote node label (must be a river or conduit section) - use if ds_label is not a river or conduit section.
1209
-
1210
- Returns:
1211
- CRUMP: Flood Modeller CRUMP Unit class object"""
1212
-
1213
- _unit = "CRUMP"
1214
-
1215
- def _read(self, block):
1216
- """Function to read a given CRUMP block and store data as class attributes"""
1217
- # Extends label line to be correct length before splitting to pick up blank labels
1218
- labels = split_n_char(f"{block[1]:<{4*self._label_len}}", self._label_len)
1219
- self.name = labels[0]
1220
- self.ds_label = labels[1]
1221
- self.us_remote_label = labels[2]
1222
- self.ds_remote_label = labels[3]
1223
- self.comment = block[0].replace("CRUMP", "").strip()
1224
-
1225
- # First parameter line
1226
- params1 = split_10_char(f"{block[2]:<40}")
1227
- self.calibration_coefficient = _to_float(params1[0])
1228
- self.weir_breadth = _to_float(params1[1])
1229
- self.weir_elevation = _to_float(params1[2])
1230
- self.modular_limit = _to_float(params1[3])
1231
-
1232
- # Second parameter line
1233
- params2 = split_10_char(f"{block[3]:<20}")
1234
- self.upstream_crest_height = _to_float(params2[0])
1235
- self.downstream_crest_height = _to_float(params2[1])
1236
-
1237
- def _write(self):
1238
- """Function to write a valid CRUMP block"""
1239
- _validate_unit(self)
1240
- header = "CRUMP " + self.comment
1241
- labels = join_n_char_ljust(
1242
- self._label_len,
1243
- self.name,
1244
- self.ds_label,
1245
- self.us_remote_label,
1246
- self.ds_remote_label,
1247
- )
1248
- block = [header, labels]
1249
-
1250
- # First parameter line
1251
- params1 = join_10_char(
1252
- self.calibration_coefficient,
1253
- self.weir_breadth,
1254
- self.weir_elevation,
1255
- self.modular_limit if self.modular_limit != 0.0 else "",
1256
- )
1257
-
1258
- block.append(params1)
1259
-
1260
- # Second parameter line
1261
- params2 = join_10_char(self.upstream_crest_height, self.downstream_crest_height)
1262
- block.append(params2)
1263
-
1264
- return block
1265
-
1266
- def _create_from_blank(
1267
- self,
1268
- name="new_crump",
1269
- comment="",
1270
- calibration_coefficient=1.0,
1271
- weir_breadth=0.0,
1272
- weir_elevation=0.0,
1273
- modular_limit=0.7,
1274
- upstream_crest_height=0.0,
1275
- downstream_crest_height=0.0,
1276
- ds_label="",
1277
- us_remote_label="",
1278
- ds_remote_label="",
1279
- ):
1280
- for param, val in {
1281
- "name": name,
1282
- "comment": comment,
1283
- "calibration_coefficient": calibration_coefficient,
1284
- "weir_breadth": weir_breadth,
1285
- "weir_elevation": weir_elevation,
1286
- "modular_limit": modular_limit,
1287
- "upstream_crest_height": upstream_crest_height,
1288
- "downstream_crest_height": downstream_crest_height,
1289
- "ds_label": ds_label,
1290
- "us_remote_label": us_remote_label,
1291
- "ds_remote_label": ds_remote_label,
1292
- }.items():
1293
- setattr(self, param, val)
1294
-
1295
-
1296
- class FLAT_V_WEIR(Unit): # noqa: N801
1297
- """Class to hold and process FLAT-V WEIR unit type
1298
-
1299
- Args:
1300
-
1301
- name (str, optional): Upstream label name.
1302
- comment (str,optional): Comment included in unit.
1303
- ds_label (str, optional): Downstream node label.
1304
- us_remote_label (str, optional): Upstream remote node label (must be a river or conduit section) - use if name is not a river or conduit section.
1305
- ds_remote_label (str, optional): Downstream remote node label (must be a river or conduit section) - use if ds_label is not a river or conduit section.
1306
- weir_elevation (float, optional): Eleveation of weir crest (m above datum).
1307
- weir_breadth (float, optional): Breadth of weir at crest (m).
1308
- v_slope (float, optional): 'V' slope (horizontal distance/vertical distance).
1309
- side_slope (float, optional): Channel side slope (horizontal distance/vertical distance).
1310
- upstream_crest_height (float, optional): Weir crest height above upstream bed (m).
1311
- downstream_crest_height (float, optional): Weir crest height above downstream bed (m).
1312
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode.
1313
- calibration_coefficient (float, optional): Calibration coefficient (should be set to unity for most cases).
1314
- ds_face_slope (int, optional): Flag to switch between 1:5 or 1:2 for d/s face. Can be set to 2 or 5 ONLY.
1315
- coriolis_coefficient (float, optional): Coriolis energy coefficient.
1316
- bank_top_elevation (float, optional): Elevation of channel bank top/ limit of extent of sloping channel walls (m AD).
1317
-
1318
- Returns:
1319
- FLAT_V_WEIR: Flood Modeller FLAT-V WEIR Unit class object"""
1320
-
1321
- _unit = "FLAT-V WEIR"
1322
-
1323
- def _read(self, block):
1324
- """Function to read a given FLAT-V WEIR block and store data as class attributes"""
1325
- # Extends label line to be correct length before splitting to pick up blank labels
1326
- labels = split_n_char(f"{block[1]:<{4*self._label_len}}", self._label_len)
1327
- self.name = labels[0]
1328
- self.ds_label = labels[1]
1329
- self.us_remote_label = labels[2]
1330
- self.ds_remote_label = labels[3]
1331
- self.comment = block[0].replace("FLAT-V WEIR", "").strip()
1332
-
1333
- # First parameter line
1334
- params1 = split_10_char(f"{block[2]:<90}")
1335
- self.calibration_coefficient = _to_float(params1[0])
1336
- self.weir_breadth = _to_float(params1[1])
1337
- self.weir_elevation = _to_float(params1[2])
1338
- self.modular_limit = _to_float(params1[3])
1339
- self.v_slope = _to_float(params1[4])
1340
- self.side_slope = _to_float(params1[5])
1341
- self.ds_face_slope = _to_float(params1[6])
1342
- self.coriolis_coefficient = _to_float(params1[7])
1343
- self.bank_top_elevation = _to_float(params1[8])
1344
-
1345
- # Second parameter line
1346
- params2 = split_10_char(f"{block[3]:<20}")
1347
- self.upstream_crest_height = _to_float(params2[0])
1348
- self.downstream_crest_height = _to_float(params2[1])
1349
-
1350
- def _write(self):
1351
- """Function to write a valid FLAT-V WEIR block"""
1352
-
1353
- _validate_unit(self)
1354
- header = "FLAT-V WEIR " + self.comment
1355
- labels = join_n_char_ljust(
1356
- self._label_len,
1357
- self.name,
1358
- self.ds_label,
1359
- self.us_remote_label,
1360
- self.ds_remote_label,
1361
- )
1362
- block = [header, labels]
1363
-
1364
- params1 = join_10_char(
1365
- self.calibration_coefficient,
1366
- self.weir_breadth,
1367
- self.weir_elevation,
1368
- self.modular_limit if self.modular_limit != 0.0 else "",
1369
- self.v_slope,
1370
- self.side_slope,
1371
- self.ds_face_slope,
1372
- self.coriolis_coefficient,
1373
- self.bank_top_elevation,
1374
- )
1375
-
1376
- block.append(params1)
1377
-
1378
- # Second parameter line
1379
- params2 = join_10_char(self.upstream_crest_height, self.downstream_crest_height)
1380
- block.append(params2)
1381
-
1382
- return block
1383
-
1384
- def _create_from_blank(
1385
- self,
1386
- name="new_flat_v",
1387
- comment="",
1388
- ds_label="",
1389
- us_remote_label="",
1390
- ds_remote_label="",
1391
- weir_elevation=0.0,
1392
- weir_breadth=0.0,
1393
- v_slope=0.0,
1394
- side_slope=0.0,
1395
- upstream_crest_height=0.0,
1396
- downstream_crest_height=0.0,
1397
- modular_limit=0.0,
1398
- calibration_coefficient=1.0,
1399
- ds_face_slope=5,
1400
- coriolis_coefficient=1.2,
1401
- bank_top_elevation=0.0,
1402
- ):
1403
- for param, val in {
1404
- "name": name,
1405
- "comment": comment,
1406
- "ds_label": ds_label,
1407
- "us_remote_label": us_remote_label,
1408
- "ds_remote_label": ds_remote_label,
1409
- "weir_elevation": weir_elevation,
1410
- "weir_breadth": weir_breadth,
1411
- "v_slope": v_slope,
1412
- "side_slope": side_slope,
1413
- "upstream_crest_height": upstream_crest_height,
1414
- "downstream_crest_height": downstream_crest_height,
1415
- "modular_limit": modular_limit,
1416
- "calibration_coefficient": calibration_coefficient,
1417
- "ds_face_slope": ds_face_slope,
1418
- "coriolis_coefficient": coriolis_coefficient,
1419
- "bank_top_elevation": bank_top_elevation,
1420
- }.items():
1421
- setattr(self, param, val)
1422
-
1423
-
1424
- class RESERVOIR(Unit): # NOT CURRENTLY IN USE
1425
- """Class to hold and process RESERVOIR unit type
1426
-
1427
- Args:
1428
- name (str, optional): Unit name.
1429
- comment (str, optional): Comment included in unit.
1430
- all_labels (str, optional): Unlimited number of labels - not including first label (name).
1431
- easting (float, optional): Easting coordinate of reservoir reference point (not used in computations).
1432
- northing (float, optional): Northing coordinate of reservoir reference point (not used in computations).
1433
- runoff_factor (float, optional): Rainfall runoff factor.
1434
- num_pairs (float, optional): Number of elevation/area pairs.
1435
- lat1 (str, optional): First lateral inflow label.
1436
- lat2 (str, optional): Second lateral inflow label.
1437
- lat3 (str, optional): Third lateral inflow label.
1438
- lat4 (str, optional): Fourth lateral inflow label.
1439
- data (pandas.DataFrame): Dataframe object containing all the reservoir section data.
1440
- Columns are ``'Elevation','Plan Area'``
1441
-
1442
- Returns:
1443
- RESERVOIR: Flood Modeller RESERVOIR Unit class object"""
1444
-
1445
- _unit = "RESERVOIR"
1446
-
1447
- def _read(self, block):
1448
- """Function to read a given RESERVOIR WEIR block and store data as class attributes"""
1449
-
1450
- # Extends label line to be correct length before splitting to pick up blank labels
1451
- num_labels = len(block[1]) // self._label_len
1452
- labels = split_n_char(f"{block[1]:<{num_labels*self._label_len}}", self._label_len)
1453
- self.name = labels[0]
1454
- self.all_labels = labels[0 : len(labels)]
1455
- self.comment = block[0].replace("RESERVOIR", "").strip()
1456
-
1457
- # Option 1 (runs if comment == "#revision#1")
1458
- if self.comment == "#revision#1":
1459
- # Lateral inflow labels
1460
- lateral_labels = split_n_char(f"{block[2]:<{4*self._label_len}}", self._label_len)
1461
- self.lat1 = lateral_labels[0]
1462
- self.lat2 = lateral_labels[1]
1463
- self.lat3 = lateral_labels[2]
1464
- self.lat4 = lateral_labels[3]
1465
-
1466
- # Number of pairs of data
1467
- self.num_pairs = _to_int(block[3])
1468
-
1469
- # Reservoir section data
1470
- data_list = []
1471
- for row in block[4 : len(block) - 1]:
1472
- row_split = split_10_char(f"{row:<20}")
1473
- elevation = _to_float(row_split[0]) # elevation
1474
- plan_area = _to_float(row_split[1]) # plan area
1475
- data_list.append([elevation, plan_area])
1476
- reservoir_data = pd.DataFrame(data_list, columns=["Elevation", "Plan Area"])
1477
- self.data = reservoir_data
1478
-
1479
- # Coordinate data
1480
- coordinate_data = split_n_char(
1481
- f"{block[len(block)-1]:<{3*self._label_len}}", self._label_len
1482
- )
1483
- self.easting = _to_float(coordinate_data[0])
1484
- self.northing = _to_float(coordinate_data[1])
1485
- self.runoff_factor = _to_float(coordinate_data[2])
1486
- else: # Option 2 (runs if comment != "#revision#1")
1487
- # Number of pairs of data
1488
- self.num_pairs = _to_int(block[2])
1489
-
1490
- # Reservoir section data
1491
- data_list = []
1492
- for row in block[3:]:
1493
- row_split = split_10_char(f"{row:<20}")
1494
- elevation = _to_float(row_split[0]) # elevation
1495
- plan_area = _to_float(row_split[1]) # plan area
1496
- data_list.append([elevation, plan_area])
1497
- reservoir_data = pd.DataFrame(data_list, columns=["Elevation", "Plan Area"])
1498
- self.data = reservoir_data
1499
-
1500
- def _write(self):
1501
- """Function to write a valid RESERVOIR block"""
1502
-
1503
- _validate_unit(self)
1504
- header = "RESERVOIR " + self.comment
1505
- self.labels = " ".join(self.all_labels)
1506
- block = [header, self.labels]
1507
-
1508
- # Option 1 (runs if comment == "#revision#1")
1509
- if self.comment == "#revision#1":
1510
- # Lateral inflow labels
1511
- lat_labels = join_12_char_ljust(self.lat1, self.lat2, self.lat3, self.lat4)
1512
- block.append(lat_labels)
1513
-
1514
- # Number of pairs of data
1515
- block.append(join_12_char_ljust(self.num_pairs))
1516
-
1517
- # Reservoir section data
1518
- section_data = [
1519
- join_12_char_ljust(elevation, plan_area)
1520
- for _, elevation, plan_area, in self.data.itertuples()
1521
- ]
1522
- block.extend(section_data)
1523
-
1524
- # Coordinate data
1525
- coords = join_12_char_ljust(self.easting, self.northing, self.runoff_factor)
1526
- block.append(coords)
1527
- else: # Option 2 (runs if comment != "#revision#1")
1528
- # Number of pairs of data
1529
- block.append(join_12_char_ljust(self.num_pairs))
1530
-
1531
- # Reservoir section data
1532
- section_data = [
1533
- join_12_char_ljust(elevation, plan_area)
1534
- for _, elevation, plan_area, in self.data.itertuples()
1535
- ]
1536
- block.extend(section_data)
1537
-
1538
- return block
1539
-
1540
- def _create_from_blank(
1541
- self,
1542
- name="new_reservoir",
1543
- comment="",
1544
- easting=0.0,
1545
- northing=0.0,
1546
- runoff_factor=0.0,
1547
- num_pairs=1,
1548
- data=None,
1549
- lat1="",
1550
- lat2="",
1551
- lat3="",
1552
- lat4="",
1553
- ):
1554
- for param, val in {
1555
- "name": name,
1556
- "comment": comment,
1557
- "easting": easting,
1558
- "northing": northing,
1559
- "runoff_factor": runoff_factor,
1560
- "num_pairs": num_pairs,
1561
- "lat1": lat1,
1562
- "lat2": lat2,
1563
- "lat3": lat3,
1564
- "lat4": lat4,
1565
- }.items():
1566
- setattr(self, param, val)
1567
-
1568
- self.data = (
1569
- data
1570
- if isinstance(data, pd.DataFrame)
1571
- else pd.DataFrame([[0.0, 0.0]], columns=["Elevation", "Plan Area"])
1572
- )
1573
-
1574
-
1575
- class OUTFALL(Unit):
1576
- """Class to hold and process OUTFALL unit type
1577
-
1578
- Args:
1579
- name (str, optional): Unit name.
1580
- comment (str, optional): Comment included in unit.
1581
- flapped (bool, optional): ``True`` if outfall is flapped, ``False`` if outfall is open
1582
- ds_label (str, optional): Downstream label
1583
- invert (float, optional): Throat invert level
1584
- soffit (float, optional): Throat soffit level
1585
- bore_area (float, optional): Cross sectional area of throat opening
1586
- upstream_sill (float, optional): Upstream sill level
1587
- downstream_sill (float, optional): Downstream sill level
1588
- shape (str, optional): Shape of outfall aperture ('RECTANGLE' or 'CIRCULAR')
1589
- weir_flow (float, optional): Calibration factor for weir flow
1590
- surcharged_flow (float, optional): Calibration factor for surcharged flow
1591
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode
1592
-
1593
-
1594
- Returns:
1595
- OUTFALL: Flood Modeller OUTFALL Unit class object
1596
- """
1597
-
1598
- _unit = "OUTFALL"
1599
-
1600
- def _read(self, block):
1601
- """Function to read a given OUTFALL block and store data as class attributes"""
1602
- self._subtype = block[1].split(" ")[0].strip()
1603
- self.flapped = self.subtype == "FLAPPED"
1604
-
1605
- # Extends label line to be correct length before splitting to pick up blank labels
1606
- labels = split_n_char(f"{block[2]:<{2*self._label_len}}", self._label_len)
1607
- self.name = labels[0]
1608
- self.ds_label = labels[1]
1609
- self.comment = block[0].replace("OUTFALL", "").strip()
1610
-
1611
- # First parameter line
1612
- params1 = split_10_char(f"{block[3]:<60}")
1613
- self.invert = _to_float(params1[0])
1614
- self.soffit = _to_float(params1[1])
1615
- self.bore_area = _to_float(params1[2])
1616
- self.upstream_sill = _to_float(params1[3])
1617
- self.downstream_sill = _to_float(params1[4])
1618
- self.shape = _to_str(params1[5], "RECTANGLE")
1619
-
1620
- # Second parameter line
1621
- params2 = split_10_char(block[4])
1622
- self.weir_flow = _to_float(params2[0], 1.0)
1623
- self.surcharged_flow = _to_float(params2[1], 1.0)
1624
- self.modular_limit = _to_float(params2[2], 0.7)
1625
-
1626
- def _write(self):
1627
- """Function to write a valid OUTFALL block"""
1628
- _validate_unit(self) # Function to check the params are valid for CONDUIT unit
1629
- header = "OUTFALL " + self.comment
1630
- labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
1631
-
1632
- self._subtype = "FLAPPED" if self.flapped else "OPEN"
1633
- block = [header, self.subtype, labels]
1634
-
1635
- # First parameter line
1636
- params1 = join_10_char(
1637
- self.invert,
1638
- self.soffit,
1639
- self.bore_area,
1640
- self.upstream_sill,
1641
- self.downstream_sill,
1642
- self.shape,
1643
- )
1644
-
1645
- # Second parameter line
1646
- params2 = join_10_char(self.weir_flow, self.surcharged_flow, self.modular_limit)
1647
-
1648
- block.extend([params1, params2])
1649
-
1650
- return block
1651
-
1652
- def _create_from_blank(
1653
- self,
1654
- name="new_outfall",
1655
- flapped=False,
1656
- ds_label="",
1657
- comment="",
1658
- invert=0.0,
1659
- soffit=0.0,
1660
- bore_area=1.0,
1661
- upstream_sill=0.0,
1662
- downstream_sill=0.0,
1663
- shape="RECTANGLE",
1664
- weir_flow=1.0,
1665
- surcharged_flow=1.0,
1666
- modular_limit=0.7,
1667
- ):
1668
- for param, val in {
1669
- "name": name,
1670
- "flapped": flapped,
1671
- "ds_label": ds_label,
1672
- "comment": comment,
1673
- "invert": invert,
1674
- "soffit": soffit,
1675
- "bore_area": bore_area,
1676
- "upstream_sill": upstream_sill,
1677
- "downstream_sill": downstream_sill,
1678
- "shape": shape,
1679
- "weir_flow": weir_flow,
1680
- "surcharged_flow": surcharged_flow,
1681
- "modular_limit": modular_limit,
1682
- }.items():
1683
- setattr(self, param, val)
1
+ """
2
+ Flood Modeller Python API
3
+ Copyright (C) 2024 Jacobs U.K. Limited
4
+
5
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
6
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10
+
11
+ You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
12
+
13
+ If you have any query about this program or this License, please contact us at support@floodmodeller.com or write to the following
14
+ address: Jacobs UK Limited, Flood Modeller, Cottons Centre, Cottons Lane, London, SE1 2QG, United Kingdom.
15
+ """
16
+
17
+ import pandas as pd
18
+
19
+ from floodmodeller_api.validation import _validate_unit
20
+
21
+ from ._base import Unit
22
+ from .helpers import (
23
+ _to_float,
24
+ _to_int,
25
+ _to_str,
26
+ join_10_char,
27
+ join_12_char_ljust,
28
+ join_n_char_ljust,
29
+ split_10_char,
30
+ split_n_char,
31
+ )
32
+
33
+
34
+ class BRIDGE(Unit):
35
+ """Class to hold and process BRIDGE unit type. The Bridge class supports the three main bridge sub-types in
36
+ Flood Modeller: Arch, USBPR1978 and Pierloss. Each of these sub-types forms a unique instance of the class
37
+ which is differentiated by the `BRIDGE.subtype` attribute. All bridge types have the same common attributes:
38
+
39
+ **Common Attributes**
40
+
41
+ Args:
42
+ name (str): Bridge section name
43
+ comment (str): Comment included in unit
44
+ ds_label (str): Downstream label
45
+ us_remote_label, ds_remote_label (str): Remote labels
46
+ subtype (str): Defines the type of bridge unit (*Should not be changed*)
47
+
48
+ **ARCH Type (``BRIDGE.subtype == 'ARCH'``)**
49
+
50
+ Args:
51
+ calibration_coefficient (float): Calibration coefficient
52
+ skew (float): Bridge skew
53
+ bridge_width_dual (float): If modelled as dual bridge, the distance between upstream and downstream face of
54
+ bridge (0.00 if not modelling as dual bridge):
55
+ bridge_dist_dual (float): If modelled as dual bridge, the total distance between upstream and downstream
56
+ faces of both bridges (0.00 if not modelling as dual bridge):
57
+ orifice_flow (bool): Whether or not to model surcharged bridge as orifice flow
58
+ orifice_lower_transition_dist, orifice_upper_transition_dist (float): Upper and lower transition distances
59
+ for when using orifice flow
60
+ orifice_discharge_coefficient (float): Orifice discharge coefficient for when using orifice flow
61
+ section_data (pandas.DataFrame): Dataframe object representing the cross section. Columns are ``'X'``, ``'Y'``,
62
+ ``'Mannings n'`` and ``'Embankments'``
63
+ opening_data (pandas.DataFrame): Dataframe object representing the openings in the bridge. Columns are
64
+ ``'Start'``, ``'Finish'``, ``'Springing Level'`` and ``'Soffit Level'``
65
+
66
+ **USBPR Type (``BRIDGE.subtype == 'USBPR1978'``)**
67
+
68
+ Args:
69
+ calibration_coefficient (float): Calibration coefficient
70
+ skew (float): Bridge skew
71
+ bridge_width_dual (float): If modelled as dual bridge, the distance between upstream and downstream face of bridge
72
+ (0.00 if not modelling as dual bridge)
73
+ bridge_dist_dual (float): If modelled as dual bridge, the total distance between upstream and downstream faces of
74
+ both bridges (0.00 if not modelling as dual bridge)
75
+ orifice_flow (bool): Whether or not to model surcharged bridge as orifice flow
76
+ orifice_lower_transition_dist, orifice_upper_transition_dist (float): Transition distances for when using orifice flow
77
+ orifice_discharge_coefficient (float): Orifice discharge coefficient for when using orifice flow
78
+ abutment_type (str): Type of abutment: ``'3'`` (default), ``'2'`` (30-degree wing wall abutment), ``'1'`` (span between
79
+ abutments < 60 metres and either 90-degree wing or vertical wall abutment)
80
+ abutment_alignment (str): Abutment alignment to normal direction of flow: ``'ALIGNED'`` or ``'SKEW'``
81
+ specify_piers (bool): Whether or not to explicity model piers
82
+ total_pier_width (float): Total width of all piers at right ange to flow direction, only used if ``specify_piers == True``
83
+ npiers (int): Total number of piers in direction of flow, only used if ``specify_piers == True``
84
+ pier_use_calibration_coeff (bool): Whether to use a calibration coefficient to model piers. If set to False it would use
85
+ the pier shape.
86
+ pier_calibration_coeff (float): Calibration coefficient for modelling piers, only used if
87
+ ``specify_piers == True AND pier_use_calibration_coeff == True``
88
+ pier_shape (str): Cross-sectional pier shape with options: ``'RECTANGLE'``, ``'CYLINDER'``, ``'SQUARE'``, ``'I-BEAM'``.
89
+ Only used if ``specify_piers == True AND pier_use_calibration_coeff == False``
90
+ pier_faces (str): Shape of pier faces with options: ``'STREAMLINE'``, ``'SEMICIRCLE'``, ``'TRIANGLE'``, ``'DIAPHRAGM'``.
91
+ Only used if ``specify_piers == True AND pier_use_calibration_coeff == False``
92
+ soffit_shape (str): Shape of soffit (``'FLAT'`` or ``'ARCH'``), only used if ``specify_piers == False``
93
+ section_data (pandas.Dataframe): Dataframe object representing the cross section. Columns are ``'X'``, ``'Y'``, ``'Mannings n'``
94
+ and ``'Embankments'``
95
+ opening_data (pandas.Dataframe): Dataframe object representing the openings in the bridge. Columns are ``'Start'``, ``'Finish'``,
96
+ ``'Springing Level'`` and ``'Soffit Level'``
97
+ culvert_data (pandas.Dataframe): Dataframe object representing any flood relief culverts in the bridge. Columns are ``'Invert'``,
98
+ ``'Soffit'``, ``'Section Area'``, ``'Cd Part Full'``, ``'Cd Full'`` and ``'Drowning Coefficient'``
99
+
100
+ **PIERLOSS Type (``BRIDGE.subtype == 'PIERLOSS'``)**
101
+
102
+ Args:
103
+ calibration_coefficient (float): Calibration coefficient
104
+ orifice_flow (bool): Whether or not to model surcharged bridge as orifice flow
105
+ orifice_lower_transition_dist, orifice_upper_transition_dist (float): Transition distances for when using orifice flow
106
+ orifice_discharge_coefficient (float): Orifice discharge coefficient for when using orifice flow
107
+ pier_coefficient (float): Pier coefficient
108
+ bridge_width (float): Distance in direction of flow from U/S to D/S cross section of bridge (for reference only)
109
+ us_section_data (pandas.Dataframe): Dataframe object representing the upstream cross section. Columns are ``'X'``, ``'Y'``,
110
+ ``'Mannings n'``, ``'Embankments'`` and ``'Top Level'``
111
+ ds_section_data (pandas.Dataframe): Dataframe object representing the downstream cross section, if no downstream section is
112
+ specified this will be an empty dataframe. Columns are ``'X'``, ``'Y'``, ``'Mannings n'``, ``'Embankments'`` and ``'Top Level'``
113
+ pier_locs_data (pandas.Dataframe): Dataframe object representing the pier locations. Columns are ``'Left X'``, ``'Left Top Level'``,
114
+ ``'Right X'``, ``'Right Top Level'``
115
+
116
+
117
+ Raises:
118
+ NotImplementedError: Raised if class is initialised without existing Bridge block (i.e. if attempting to create new Bridge unit).
119
+ This will be an option for future releases
120
+
121
+ Returns:
122
+ BRIDGE: Flood Modeller BRIDGE Unit class object
123
+ """
124
+
125
+ _unit = "BRIDGE"
126
+
127
+ def _read(self, br_block): # noqa: C901, PLR0912, PLR0915
128
+ """Function to read a given BRIDGE block and store data as class attributes"""
129
+ self._subtype = br_block[1].split(" ")[0].strip()
130
+ # Extends label line to be correct length before splitting to pick up blank labels
131
+ labels = split_n_char(f"{br_block[2]:<{4*self._label_len}}", self._label_len)
132
+ self.name = labels[0]
133
+ self.ds_label = labels[1]
134
+ self.us_remote_label = labels[2]
135
+ self.ds_remote_label = labels[3]
136
+ self.comment = br_block[0].replace("BRIDGE", "").strip()
137
+
138
+ # Read ARCH type unit
139
+ if self.subtype == "ARCH":
140
+ # Read Params
141
+ params = split_10_char(f"{br_block[4]:<90}")
142
+ self.calibration_coefficient = _to_float(params[0], 1.0)
143
+ self.skew = _to_float(params[1])
144
+ self.bridge_width_dual = _to_float(params[2])
145
+ self.bridge_dist_dual = _to_float(params[3])
146
+ if params[5] == "ORIFICE":
147
+ self.orifice_flow = True
148
+ else:
149
+ self.orifice_flow = False
150
+ self.orifice_lower_transition_dist = _to_float(params[6])
151
+ self.orifice_upper_transition_dist = _to_float(params[7])
152
+ self.orifice_discharge_coefficient = _to_float(params[8], 1.0)
153
+
154
+ # Read cross section data
155
+ self.section_nrows = int(split_10_char(br_block[5])[0])
156
+ data_list = []
157
+ for row in br_block[6 : 6 + self.section_nrows]:
158
+ row_split = split_10_char(f"{row:<50}")
159
+ x = _to_float(row_split[0]) # chainage
160
+ y = _to_float(row_split[1]) # elevation
161
+ n = _to_float(row_split[2]) # Mannings
162
+ embankment = row_split[4] # Embankment flag
163
+ data_list.append([x, y, n, embankment])
164
+ self.section_data = pd.DataFrame(
165
+ data_list,
166
+ columns=["X", "Y", "Mannings n", "Embankments"],
167
+ )
168
+
169
+ # Read bridge opening data
170
+ self.opening_nrows = int(split_10_char(br_block[6 + self.section_nrows])[0])
171
+ data_list = []
172
+ for row in br_block[6 + self.section_nrows + 1 :]:
173
+ row_split = split_10_char(f"{row:<40}")
174
+ start = _to_float(row_split[0]) # Start (m)
175
+ finish = _to_float(row_split[1]) # Finish (m)
176
+ spring = _to_float(row_split[2]) # Springing Level
177
+ soffit = _to_float(row_split[3]) # Soffit Level
178
+ data_list.append([start, finish, spring, soffit])
179
+ self.opening_data = pd.DataFrame(
180
+ data_list,
181
+ columns=["Start", "Finish", "Springing Level", "Soffit Level"],
182
+ )
183
+
184
+ # Read USBPR type unit
185
+ elif self.subtype == "USBPR1978":
186
+ # Read Params
187
+ params = split_10_char(f"{br_block[4]:<90}")
188
+ self.calibration_coefficient = _to_float(params[0], 1.0)
189
+ self.skew = _to_float(params[1])
190
+ self.bridge_width_dual = _to_float(params[2])
191
+ self.bridge_dist_dual = _to_float(params[3])
192
+ self.total_pier_width = _to_float(params[4])
193
+ if params[5] == "ORIFICE":
194
+ self.orifice_flow = True
195
+ else:
196
+ self.orifice_flow = False
197
+ self.orifice_lower_transition_dist = _to_float(params[6])
198
+ self.orifice_upper_transition_dist = _to_float(params[7])
199
+ self.orifice_discharge_coefficient = _to_float(params[8], 1.0)
200
+
201
+ self.abutment_type = split_10_char(br_block[5])[0]
202
+ self.abutment_alignment = split_10_char(br_block[7])[0]
203
+
204
+ pier_info = split_10_char(f"{br_block[6]:<40}")
205
+ if int(pier_info[0]) > 0:
206
+ self.specify_piers = True
207
+ self.npiers = int(pier_info[0])
208
+ if pier_info[1] == "COEF":
209
+ self.pier_use_calibration_coeff = True
210
+ self.pier_calibration_coeff = _to_float(pier_info[3])
211
+ else:
212
+ self.pier_use_calibration_coeff = False
213
+ self.pier_shape = pier_info[1]
214
+ self.pier_faces = pier_info[2]
215
+ else:
216
+ self.specify_piers = False
217
+ self.soffit_shape = pier_info[1]
218
+
219
+ # Read cross section data
220
+ self.section_nrows = int(split_10_char(br_block[8])[0])
221
+ data_list = []
222
+ for row in br_block[9 : 9 + self.section_nrows]:
223
+ row_split = split_10_char(f"{row:<50}")
224
+ x = _to_float(row_split[0]) # chainage
225
+ y = _to_float(row_split[1]) # elevation
226
+ n = _to_float(row_split[2]) # Mannings
227
+ embankment = row_split[4] # Embankment flag
228
+ data_list.append([x, y, n, embankment])
229
+ self.section_data = pd.DataFrame(
230
+ data_list,
231
+ columns=["X", "Y", "Mannings n", "Embankments"],
232
+ )
233
+
234
+ # Read bridge opening data
235
+ self.opening_nrows = int(split_10_char(br_block[9 + self.section_nrows])[0])
236
+ data_list = []
237
+ start_row = 9 + self.section_nrows + 1
238
+ end_row = start_row + self.opening_nrows
239
+ for row in br_block[start_row:end_row]:
240
+ row_split = split_10_char(f"{row:<40}")
241
+ start = _to_float(row_split[0]) # Start (m)
242
+ finish = _to_float(row_split[1]) # Finish (m)
243
+ spring = _to_float(row_split[2]) # Springing Level
244
+ soffit = _to_float(row_split[3]) # Soffit Level
245
+ data_list.append([start, finish, spring, soffit])
246
+ self.opening_data = pd.DataFrame(
247
+ data_list,
248
+ columns=["Start", "Finish", "Springing Level", "Soffit Level"],
249
+ )
250
+
251
+ # Read flood relief culvert data
252
+ self.culvert_nrows = int(
253
+ split_10_char(br_block[9 + self.section_nrows + self.opening_nrows + 1])[0],
254
+ )
255
+ data_list = []
256
+ start_row = 9 + self.section_nrows + self.opening_nrows + 2
257
+ end_row = start_row + self.culvert_nrows
258
+ for row in br_block[start_row:end_row]:
259
+ row_split = split_10_char(f"{row:<60}")
260
+ invert = _to_float(row_split[0]) # Invert
261
+ soffit = _to_float(row_split[1]) # Soffit
262
+ area = _to_float(row_split[2]) # Section Area
263
+ cd_part = _to_float(row_split[3]) # Cd Part Full
264
+ cd_full = _to_float(row_split[4]) # Cd Full
265
+ drown = _to_float(row_split[5]) # Drowning Coefficient
266
+ data_list.append([invert, soffit, area, cd_part, cd_full, drown])
267
+ self.culvert_data = pd.DataFrame(
268
+ data_list,
269
+ columns=[
270
+ "Invert",
271
+ "Soffit",
272
+ "Section Area",
273
+ "Cd Part Full",
274
+ "Cd Full",
275
+ "Drowning Coefficient",
276
+ ],
277
+ )
278
+
279
+ # Read Pierloss type bridge
280
+ elif self.subtype == "PIERLOSS":
281
+ # Read Params
282
+ params = split_10_char(f"{br_block[4]:<50}")
283
+ self.calibration_coefficient = _to_float(params[0], 1.0)
284
+ if params[1] == "ORIFICE":
285
+ self.orifice_flow = True
286
+ else:
287
+ self.orifice_flow = False
288
+ self.orifice_lower_transition_dist = _to_float(params[3])
289
+ self.orifice_upper_transition_dist = _to_float(params[4])
290
+ self.orifice_discharge_coefficient = _to_float(params[2], 1.0)
291
+ additional_params = split_10_char(f"{br_block[5]:<20}")
292
+ self.pier_coefficient = _to_float(additional_params[0], 0.9)
293
+ self.bridge_width = _to_float(additional_params[1])
294
+
295
+ # Read US cross section data
296
+ self.us_section_nrows = int(split_10_char(br_block[6])[0])
297
+ data_list = []
298
+ for row in br_block[7 : 7 + self.us_section_nrows]:
299
+ row_split = split_10_char(f"{row:<60}")
300
+ x = _to_float(row_split[0]) # chainage
301
+ y = _to_float(row_split[1]) # elevation
302
+ n = _to_float(row_split[2]) # Mannings
303
+ embankment = row_split[4] # Embankment flag
304
+ top_level = row_split[5] # Top Level (m)
305
+ data_list.append([x, y, n, embankment, top_level])
306
+ self.us_section_data = pd.DataFrame(
307
+ data_list,
308
+ columns=["X", "Y", "Mannings n", "Embankments", "Top Level"],
309
+ )
310
+
311
+ # Read DS cross section data
312
+ new_idx = 6 + 1 + self.us_section_nrows
313
+ self.ds_section_nrows = int(split_10_char(br_block[new_idx])[0])
314
+ data_list = []
315
+ for row in br_block[new_idx + 1 : new_idx + 1 + self.ds_section_nrows]:
316
+ row_split = split_10_char(f"{row:<60}")
317
+ x = _to_float(row_split[0]) # chainage
318
+ y = _to_float(row_split[1]) # elevation
319
+ n = _to_float(row_split[2]) # Mannings
320
+ embankment = row_split[4] # Embankment flag
321
+ top_level = row_split[5] # Top Level (m)
322
+ data_list.append([x, y, n, embankment, top_level])
323
+ self.ds_section_data = pd.DataFrame(
324
+ data_list,
325
+ columns=["X", "Y", "Mannings n", "Embankments", "Top Level"],
326
+ )
327
+
328
+ # Read pier locations
329
+ new_idx += 1 + self.ds_section_nrows
330
+ self.pier_locs_nrows = int(split_10_char(br_block[new_idx])[0])
331
+ data_list = []
332
+ for row in br_block[new_idx + 1 : new_idx + 1 + self.pier_locs_nrows]:
333
+ row_split = split_10_char(f"{row:<40}")
334
+ l_x = _to_float(row_split[0]) # chainage
335
+ l_top_level = _to_float(row_split[1]) # Top Level (m)
336
+ r_x = _to_float(row_split[2]) # chainage
337
+ r_top_level = _to_float(row_split[3]) # Top Level (m)
338
+ data_list.append([l_x, l_top_level, r_x, r_top_level])
339
+ self.pier_locs_data = pd.DataFrame(
340
+ data_list,
341
+ columns=["Left X", "Left Top Level", "Right X", "Right Top Level"],
342
+ )
343
+
344
+ else:
345
+ # This else block is triggered for bridge subtypes which aren't yet supported, and just keeps the 'br_block' in it's raw state to write back.
346
+ print(
347
+ f'This Bridge sub-type: "{self.subtype}" is currently unsupported for reading/editing',
348
+ )
349
+ self._raw_block = br_block
350
+ self.name = br_block[2][:12].strip()
351
+
352
+ def _write(self): # noqa: C901, PLR0912, PLR0915
353
+ """Function to write a valid BRIDGE block"""
354
+ _validate_unit(self) # Function to check the params are valid for BRIDGE unit
355
+ header = "BRIDGE " + self.comment
356
+ labels = join_n_char_ljust(
357
+ self._label_len,
358
+ self.name,
359
+ self.ds_label,
360
+ self.us_remote_label,
361
+ self.ds_remote_label,
362
+ )
363
+ br_block = [header, self.subtype, labels]
364
+
365
+ if self.subtype == "ARCH":
366
+ orifice = "ORIFICE" if self.orifice_flow else ""
367
+ params = join_10_char(
368
+ self.calibration_coefficient,
369
+ self.skew,
370
+ self.bridge_width_dual,
371
+ self.bridge_dist_dual,
372
+ "",
373
+ orifice,
374
+ self.orifice_lower_transition_dist,
375
+ self.orifice_upper_transition_dist,
376
+ self.orifice_discharge_coefficient,
377
+ )
378
+ self.section_nrows = len(self.section_data)
379
+ br_block.extend(["MANNING", params, f"{str(self.section_nrows):>10}"])
380
+
381
+ section_data = []
382
+ for _, x, y, n, embankments in self.section_data.itertuples():
383
+ # Adding extra 10 spaces before embankment flag
384
+ row = join_10_char(x, y, n, "")
385
+ row += embankments
386
+ section_data.append(row)
387
+
388
+ br_block.extend(section_data)
389
+
390
+ self.opening_nrows = len(self.opening_data)
391
+ br_block.append(f"{str(self.opening_nrows):>10}")
392
+ opening_data = []
393
+ for _, start, finish, spring, soffit in self.opening_data.itertuples():
394
+ row = join_10_char(start, finish, spring, soffit)
395
+ opening_data.append(row)
396
+
397
+ br_block.extend(opening_data)
398
+
399
+ return br_block
400
+
401
+ if self.subtype == "USBPR1978":
402
+ orifice = "ORIFICE" if self.orifice_flow else ""
403
+ params = join_10_char(
404
+ self.calibration_coefficient,
405
+ self.skew,
406
+ self.bridge_width_dual,
407
+ self.bridge_dist_dual,
408
+ self.total_pier_width,
409
+ orifice,
410
+ self.orifice_lower_transition_dist,
411
+ self.orifice_upper_transition_dist,
412
+ self.orifice_discharge_coefficient,
413
+ )
414
+ if self.specify_piers:
415
+ if self.pier_use_calibration_coeff:
416
+ pier_params = f'{self.npiers:>10}{"COEF":<10}{"":>10}{self.calibration_coefficient:>10.3f}'
417
+ else:
418
+ pier_params = f"{self.npiers:>10}{self.pier_shape:<10}{self.pier_faces:<10}"
419
+ else:
420
+ pier_params = f"{0:>10}{self.soffit_shape}"
421
+
422
+ self.section_nrows = len(self.section_data)
423
+ br_block.extend(
424
+ [
425
+ "MANNING",
426
+ params,
427
+ f"{str(self.abutment_type):>10}",
428
+ pier_params,
429
+ self.abutment_alignment,
430
+ f"{str(self.section_nrows):>10}",
431
+ ],
432
+ )
433
+
434
+ section_data = []
435
+ for _, x, y, n, embankments in self.section_data.itertuples():
436
+ # Adding extra 10 spaces before embankment flag
437
+ row = join_10_char(x, y, n, "")
438
+ row += embankments
439
+ section_data.append(row)
440
+ br_block.extend(section_data)
441
+
442
+ self.opening_nrows = len(self.opening_data)
443
+ br_block.append(f"{str(self.opening_nrows):>10}")
444
+ opening_data = []
445
+ for _, start, finish, spring, soffit in self.opening_data.itertuples():
446
+ row = join_10_char(start, finish, spring, soffit)
447
+ opening_data.append(row)
448
+ br_block.extend(opening_data)
449
+
450
+ self.culvert_nrows = len(self.culvert_data)
451
+ br_block.append(f"{str(self.culvert_nrows):>10}")
452
+ culvert_data = []
453
+ for (
454
+ _,
455
+ invert,
456
+ soffit,
457
+ area,
458
+ cd_part,
459
+ cd_full,
460
+ drown,
461
+ ) in self.culvert_data.itertuples():
462
+ row = join_10_char(invert, soffit, area, cd_part, cd_full, drown)
463
+ culvert_data.append(row)
464
+ br_block.extend(culvert_data)
465
+
466
+ return br_block
467
+
468
+ if self.subtype == "PIERLOSS":
469
+ orifice = "ORIFICE" if self.orifice_flow else ""
470
+ params = join_10_char(
471
+ self.calibration_coefficient,
472
+ orifice,
473
+ self.orifice_discharge_coefficient,
474
+ self.orifice_lower_transition_dist,
475
+ self.orifice_upper_transition_dist,
476
+ )
477
+ additional_params = join_10_char(self.pier_coefficient, self.bridge_width)
478
+ self.us_section_nrows = len(self.us_section_data)
479
+ br_block.extend(
480
+ [
481
+ "YARNELL",
482
+ params,
483
+ additional_params,
484
+ f"{str(self.us_section_nrows):>10}",
485
+ ],
486
+ )
487
+
488
+ us_section_data = []
489
+ for _, x, y, n, embankments, top_level in self.us_section_data.itertuples():
490
+ # Adding extra 10 spaces before embankment flag
491
+ row = join_10_char(x, y, n, "")
492
+ row += f"{embankments:<10}"
493
+ row += join_10_char(top_level)
494
+ us_section_data.append(row)
495
+ br_block.extend(us_section_data)
496
+
497
+ self.ds_section_nrows = len(self.ds_section_data)
498
+ br_block.append(f"{str(self.ds_section_nrows):>10}")
499
+ ds_section_data = []
500
+ for _, x, y, n, embankments, top_level in self.ds_section_data.itertuples():
501
+ # Adding extra 10 spaces before embankment flag
502
+ row = join_10_char(x, y, n, "")
503
+ row += f"{embankments:<10}"
504
+ row += join_10_char(top_level)
505
+ ds_section_data.append(row)
506
+ br_block.extend(ds_section_data)
507
+
508
+ self.pier_locs_nrows = len(self.pier_locs_data)
509
+ br_block.append(f"{str(self.pier_locs_nrows):>10}")
510
+ pier_locs_data = []
511
+ for (
512
+ _,
513
+ l_x,
514
+ l_top_level,
515
+ r_x,
516
+ r_top_level,
517
+ ) in self.pier_locs_data.itertuples():
518
+ row = join_10_char(l_x, l_top_level, r_x, r_top_level)
519
+ pier_locs_data.append(row)
520
+ br_block.extend(pier_locs_data)
521
+
522
+ return br_block
523
+
524
+ return self._raw_block
525
+
526
+
527
+ class SLUICE(Unit):
528
+ """The Sluice class supports two sluice sub-types in Flood Modeller: RADIAL and VERTICAL. Each of these sub-types forms
529
+ a unique instance of the class which is differentiated by the `SLUICE.subtype` attribute. There are also several different
530
+ attributes depending on the `.control_method` used. All sluice types have the same common attributes:
531
+
532
+ **Common Attributes**
533
+
534
+ Args:
535
+ name (str): Sluice section name (upstream label)
536
+ ds_label (str): Downstream label
537
+ remote_label (str): Remote label
538
+ comment (str): Comment included in unit
539
+ subtype (str): Defines the type of sluice unit (*Should not be changed*)
540
+ weir_flow_coefficient (float): Coefficient of approach velocity for weir flow (0.4 - 3.0)
541
+ under_gate_flow (float): Coefficient of approach velocity for under gate flow
542
+ over_gate_flow (float): Coefficient of approach velocity for over gate flow
543
+ weir_breadth (float): breadth of weir (for single gate) perpendicular to flow direction
544
+ crest_elevation (float): Elevation of weir crest
545
+ gate_height_or_chord (float): Height of sluice gate (m) (if sluice subtype is VERTICAL or subtype is RADIAL and
546
+ ``use_degrees == False``). OR the cord made by the arc of sluice gate (m) if subtype is RADIAL and
547
+ ``use_degrees == True``.
548
+ weir_length (float): Length of weir (m) in flow direction
549
+ us_weir_height (float): Vertical distance from weir crest to upstream bed level
550
+ ds_weir_height (float): Vertical distance from weir crest to downstream bed level
551
+ bias_factor (float): Only used when control_method set to 'REMOTE WATER LEVEL'
552
+ modular_limits (dict): Dictionary of modular limit values. Keys: {'weir_flow', 'under_gate_flow', 'over_gate_flow'}. If they
553
+ are all set equal to zero, then a variable calculation method is used.
554
+ ngates (int): number of gates
555
+ timeunit (str): Unit of time, e.g. 'HOURS', 'MINUTES' or 'SECONDS'. See Flood Modeller documentation for all available options.
556
+ extendmethod (str): Data extending method: 'EXTEND', 'NOEXTEND' or 'REPEAT'.
557
+
558
+
559
+ **Attributes used when ``SLUICE.control_method == 'TIME'``**
560
+
561
+ Args:
562
+ gates (List[pandas.Series]): List of Data series representing the gate control with 'Time' index and 'Gate Opening' (m) as the data
563
+
564
+
565
+ **Attributes used when ``SLUICE.control_method == 'LOGICAL'``**
566
+
567
+ Args:
568
+ gates (List[pandas.DataFrame]): List of Dataframes representing the gate control with 'Time' as index and 'Mode' and 'Gate Opening' (m) as the columns.
569
+ The Mode is set to 'AUTO', 'MANUAL' or [blank] depending on the control mode at that time.
570
+ max_movement_rate (float): Maximum movement rate of the structure
571
+ max_setting (float): Maximum setting of the structure
572
+ min_setting (float): Minimum setting of the structure
573
+ rules (List[dict]): List of logical rules to use. Each rule is represented as a Dictionary with keys 'name' and 'logic'.
574
+ time_rule_data (pandas.Series): Series containing data on which operating rules to apply, with index of 'Time' and
575
+ dataseries for 'Operating Rules'
576
+ varrules (List[dict]): List of logical variable rules to use. Each varrule is represented as a Dictionary with keys 'name' and 'logic'.
577
+ time_varrule_data (pandas.Series): Series containing data on which operating rules to apply, with index of 'Time' and
578
+ dataseries for 'Operating Rules'
579
+
580
+
581
+ **Radial Type (``SLUICE.subtype == 'RADIAL'``)**
582
+
583
+ Args:
584
+ use_degrees (bool): Whether to measure gate movement in degrees
585
+ allow_free_flow_under (bool): Whether to allow free flow under gate
586
+ pivot_height (float): Height of gate pivot (m) above sill
587
+ gate_radius (float): Distance from gate pivot to surface (m)
588
+
589
+ Returns:
590
+ SLUICE: Flood Modeller SLUICE Unit class object
591
+ """
592
+
593
+ _unit = "SLUICE"
594
+
595
+ def _read(self, block):
596
+ """Function to read a given SLUICE block and store data as class attributes"""
597
+ self._subtype = block[1].split(" ")[0].strip()
598
+
599
+ # Extends label line to be correct length before splitting to pick up blank labels
600
+ labels = split_n_char(f"{block[2]:<{3*self._label_len}}", self._label_len)
601
+ self.name = labels[0]
602
+ self.ds_label = labels[1]
603
+ self.remote_label = labels[2]
604
+ self.comment = block[0].replace("SLUICE", "").strip()
605
+
606
+ # First parameter line
607
+ params1 = split_10_char(f"{block[3]:<80}")
608
+ self.weir_flow_coefficient = _to_float(params1[0], 1.0)
609
+ self.under_gate_flow = _to_float(params1[1], 1.0)
610
+ self.weir_breadth = _to_float(params1[2])
611
+ self.crest_elevation = _to_float(params1[3])
612
+ self.gate_height_or_chord = _to_float(params1[4])
613
+ self.weir_length = _to_float(params1[5])
614
+ if self.subtype == "RADIAL":
615
+ self.use_degrees = params1[6] == "DEGREES"
616
+ self.allow_free_flow_under = params1[7] == "FREESLUICE"
617
+
618
+ # Second parameter line
619
+ params2 = split_10_char(f"{block[4]:<70}")
620
+ self.us_weir_height = _to_float(params2[0])
621
+ self.ds_weir_height = _to_float(params2[1])
622
+ self.bias_factor = _to_float(params2[2], 1.0)
623
+ self.over_gate_flow = _to_float(params2[3], 1.0)
624
+ if self.subtype == "RADIAL":
625
+ self.pivot_height = _to_float(params2[4], 0.7)
626
+ self.gate_radius = _to_float(params2[5], 0.7)
627
+ else:
628
+ self.modular_limits = {
629
+ "weir_flow": _to_float(params2[4]),
630
+ "under_gate_flow": _to_float(params2[5], 1.0),
631
+ "over_gate_flow": _to_float(params2[6], 1.0),
632
+ }
633
+
634
+ # Third parameter line
635
+ params3 = split_10_char(f"{block[5]:<60}")
636
+ self.ngates = int(params3[0]) # number of gates
637
+ if self.subtype == "RADIAL":
638
+ self.modular_limits = {
639
+ "weir_flow": _to_float(params3[1]),
640
+ "under_gate_flow": _to_float(params3[2], 1.0),
641
+ "over_gate_flow": _to_float(params3[3], 1.0),
642
+ }
643
+ self.timeunit = _to_str(params3[4], "SECONDS", check_float=True)
644
+ self.extendmethod = _to_str(params3[5], "EXTEND")
645
+ else:
646
+ self.timeunit = _to_str(params3[1], "SECONDS", check_float=True)
647
+ self.extendmethod = _to_str(params3[2], "EXTEND")
648
+
649
+ # Control lines
650
+ self.control_method = block[6].split()[0].upper()
651
+ if self.control_method == "TIME":
652
+ self.gates = self._get_gates(self.ngates, block, gate_row=7)
653
+
654
+ elif self.control_method == "LOGICAL":
655
+ logical_params = split_10_char(block[6])
656
+ self.max_movement_rate = logical_params[1]
657
+ self.max_setting = logical_params[2]
658
+ self.min_setting = logical_params[3]
659
+ self.gates = self._get_gates(self.ngates, block, gate_row=7)
660
+ self._read_rules(block)
661
+
662
+ else:
663
+ self._raw_extra_lines = block[6:]
664
+ print(
665
+ f"Note: Sluice control using method: {self.control_method} is not currently supported.",
666
+ )
667
+
668
+ def _write(self):
669
+ """Function to write a valid SLUICE block"""
670
+ _validate_unit(self) # Function to check the params are valid for CONDUIT unit
671
+ header = "SLUICE " + self.comment
672
+ labels = join_n_char_ljust(self._label_len, self.name, self.ds_label, self.remote_label)
673
+ block = [header, self.subtype, labels]
674
+
675
+ # First parameter line
676
+ params1 = join_10_char(
677
+ self.weir_flow_coefficient,
678
+ self.under_gate_flow,
679
+ self.weir_breadth,
680
+ self.crest_elevation,
681
+ self.gate_height_or_chord,
682
+ self.weir_length,
683
+ )
684
+ if self.subtype == "RADIAL":
685
+ params1 += f'{"DEGREES":<10}' if self.use_degrees else f'{"":<10}'
686
+ params1 += "FREESLUICE" if self.allow_free_flow_under else f'{"":<10}'
687
+
688
+ # Second parameter line
689
+ params2 = join_10_char(
690
+ self.us_weir_height,
691
+ self.ds_weir_height,
692
+ self.bias_factor,
693
+ self.over_gate_flow,
694
+ )
695
+
696
+ if self.subtype == "RADIAL":
697
+ params2 += join_10_char(self.pivot_height, self.gate_radius)
698
+ else:
699
+ params2 += join_10_char(
700
+ self.modular_limits["weir_flow"],
701
+ self.modular_limits["under_gate_flow"],
702
+ self.modular_limits["over_gate_flow"],
703
+ )
704
+
705
+ # Third parameter line
706
+ self.ngates = len(self.gates)
707
+ params3 = join_10_char(self.ngates)
708
+
709
+ if self.subtype == "RADIAL":
710
+ params3 += join_10_char(
711
+ self.modular_limits["weir_flow"],
712
+ self.modular_limits["under_gate_flow"],
713
+ self.modular_limits["over_gate_flow"],
714
+ )
715
+
716
+ params3 += join_n_char_ljust(10, self.timeunit, self.extendmethod)
717
+
718
+ block.extend([params1, params2, params3])
719
+
720
+ # Control lines
721
+ if self.control_method == "TIME":
722
+ block.append("TIME")
723
+ n = 1
724
+ for gate in self.gates:
725
+ block.append(f"GATE {n}")
726
+ nrows = len(gate)
727
+ block.append(f"{nrows:>10}")
728
+ gate_data = [f"{join_10_char(t, o)}" for t, o in gate.items()]
729
+ block.extend(gate_data)
730
+ n += 1
731
+
732
+ elif self.control_method == "LOGICAL":
733
+ # ADD GATES
734
+ block.append(
735
+ join_10_char(
736
+ f'{"LOGICAL":<10}',
737
+ self.max_movement_rate,
738
+ self.max_setting,
739
+ self.min_setting,
740
+ ),
741
+ )
742
+ n = 1
743
+ for gate in self.gates:
744
+ block.append(f"GATE {n}")
745
+ nrows = len(gate)
746
+ block.append(f"{nrows:>10}")
747
+ gate_data = [f"{join_10_char(t, m, o)}" for t, m, o in gate.itertuples()]
748
+ block.extend(gate_data)
749
+ n += 1
750
+ block = self._write_rules(block)
751
+
752
+ else:
753
+ block.extend(self._raw_extra_lines)
754
+
755
+ return block
756
+
757
+ def _get_gates(self, ngates, block, gate_row):
758
+ gates = []
759
+
760
+ if self.control_method == "TIME":
761
+ for _ in range(ngates):
762
+ nrows = int(split_10_char(block[gate_row + 1])[0])
763
+ data_list = []
764
+ for row in block[gate_row + 2 : gate_row + 2 + nrows]:
765
+ row_split = split_10_char(f"{row:<20}")
766
+ x = _to_float(row_split[0]) # time
767
+ y = _to_float(row_split[1]) # opening
768
+ data_list.append([x, y])
769
+
770
+ gate_data = pd.DataFrame(data_list, columns=["Time", "Opening"])
771
+ gate_data = gate_data.set_index("Time")
772
+ gate_data = gate_data["Opening"]
773
+
774
+ gates.append(gate_data)
775
+
776
+ gate_row += 2 + nrows
777
+
778
+ self._last_gate_row = gate_row
779
+
780
+ return gates
781
+
782
+ if self.control_method == "LOGICAL":
783
+ for _ in range(ngates):
784
+ nrows = int(split_10_char(block[gate_row + 1])[0])
785
+ data_list = []
786
+ for row in block[gate_row + 2 : gate_row + 2 + nrows]:
787
+ row_split = split_10_char(f"{row:<30}")
788
+ x = _to_float(row_split[0]) # time
789
+ y = row_split[1] # mode
790
+ z = _to_float(row_split[2]) # opening
791
+ data_list.append([x, y, z])
792
+
793
+ gate_data = pd.DataFrame(data_list, columns=["Time", "Mode", "Opening"])
794
+ gate_data = gate_data.set_index("Time")
795
+
796
+ gates.append(gate_data)
797
+
798
+ gate_row += 2 + nrows
799
+
800
+ self._last_gate_row = gate_row
801
+
802
+ return gates
803
+ return None
804
+
805
+
806
+ class ORIFICE(Unit):
807
+ """Class to hold and process ORIFICE unit type
808
+
809
+ Args:
810
+ name (str, optional): Unit name.
811
+ comment (str, optional): Comment included in unit.
812
+ flapped (bool, optional): ``True`` if orifice is flapped, ``False`` if orifice is open
813
+ ds_label (str, optional): Downstream label
814
+ invert (float, optional): Throat invert level
815
+ soffit (float, optional): Throat soffit level
816
+ bore_area (float, optional): Cross sectional area of throat opening
817
+ upstream_sill (float, optional): Upstream sill level
818
+ downstream_sill (float, optional): Downstream sill level
819
+ shape (str, optional): Shape of orifice aperture ('RECTANGLE' or 'CIRCULAR')
820
+ weir_flow (float, optional): Calibration factor for weir flow
821
+ surcharged_flow (float, optional): Calibration factor for surcharged flow
822
+ modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode
823
+
824
+
825
+ Returns:
826
+ ORIFICE: Flood Modeller ORIFICE Unit class object
827
+ """
828
+
829
+ _unit = "ORIFICE"
830
+
831
+ def _read(self, block):
832
+ """Function to read a given ORIFICE block and store data as class attributes"""
833
+ self._subtype = block[1].split(" ")[0].strip()
834
+ self.flapped = self.subtype == "FLAPPED"
835
+
836
+ # Extends label line to be correct length before splitting to pick up blank labels
837
+ labels = split_n_char(f"{block[2]:<{2*self._label_len}}", self._label_len)
838
+ self.name = labels[0]
839
+ self.ds_label = labels[1]
840
+ self.comment = block[0].replace("ORIFICE", "").strip()
841
+
842
+ # First parameter line
843
+ params1 = split_10_char(f"{block[3]:<60}")
844
+ self.invert = _to_float(params1[0])
845
+ self.soffit = _to_float(params1[1])
846
+ self.bore_area = _to_float(params1[2])
847
+ self.upstream_sill = _to_float(params1[3])
848
+ self.downstream_sill = _to_float(params1[4])
849
+ self.shape = _to_str(params1[5], "RECTANGLE")
850
+
851
+ # Second parameter line
852
+ params2 = split_10_char(block[4])
853
+ self.weir_flow = _to_float(params2[0], 1.0)
854
+ self.surcharged_flow = _to_float(params2[1], 1.0)
855
+ self.modular_limit = _to_float(params2[2], 0.7)
856
+
857
+ def _write(self):
858
+ """Function to write a valid ORIFICE block"""
859
+ _validate_unit(self) # Function to check the params are valid for CONDUIT unit
860
+ header = "ORIFICE " + self.comment
861
+ labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
862
+
863
+ self._subtype = "FLAPPED" if self.flapped else "OPEN"
864
+ block = [header, self.subtype, labels]
865
+
866
+ # First parameter line
867
+ params1 = join_10_char(
868
+ self.invert,
869
+ self.soffit,
870
+ self.bore_area,
871
+ self.upstream_sill,
872
+ self.downstream_sill,
873
+ self.shape,
874
+ )
875
+
876
+ # Second parameter line
877
+ params2 = join_10_char(self.weir_flow, self.surcharged_flow, self.modular_limit)
878
+
879
+ block.extend([params1, params2])
880
+
881
+ return block
882
+
883
+ def _create_from_blank( # noqa: PLR0913
884
+ self,
885
+ name="new_orifice",
886
+ flapped=False,
887
+ ds_label="",
888
+ comment="",
889
+ invert=0.0,
890
+ soffit=0.0,
891
+ bore_area=1.0,
892
+ upstream_sill=0.0,
893
+ downstream_sill=0.0,
894
+ shape="RECTANGLE",
895
+ weir_flow=1.0,
896
+ surcharged_flow=1.0,
897
+ modular_limit=0.7,
898
+ ):
899
+ for param, val in {
900
+ "name": name,
901
+ "flapped": flapped,
902
+ "ds_label": ds_label,
903
+ "comment": comment,
904
+ "invert": invert,
905
+ "soffit": soffit,
906
+ "bore_area": bore_area,
907
+ "upstream_sill": upstream_sill,
908
+ "downstream_sill": downstream_sill,
909
+ "shape": shape,
910
+ "weir_flow": weir_flow,
911
+ "surcharged_flow": surcharged_flow,
912
+ "modular_limit": modular_limit,
913
+ }.items():
914
+ setattr(self, param, val)
915
+
916
+
917
+ class SPILL(Unit):
918
+ """Class to hold and process SPILL unit type
919
+
920
+ Args:
921
+ name (str, optional): Unit name.
922
+ comment (str, optional): Comment included in unit.
923
+ ds_label (str, optional): Downstream label
924
+ weir_coefficient (float, optional): Weir coefficient
925
+ modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode
926
+ data (pandas.DataFrame): Dataframe object containing all the spill section data.
927
+ Columns are ``'X', 'Y', 'Easting', 'Northing'``
928
+
929
+ Returns:
930
+ SPILL: Flood Modeller SPILL Unit class object
931
+ """
932
+
933
+ _unit = "SPILL"
934
+
935
+ def _read(self, block):
936
+ """Function to read a given SPILL block and store data as class attributes"""
937
+ # Extends label line to be correct length before splitting to pick up blank labels
938
+ labels = split_n_char(f"{block[1]:<{2*self._label_len}}", self._label_len)
939
+ self.name = labels[0]
940
+ self.ds_label = labels[1]
941
+ self.comment = block[0].replace("SPILL", "").strip()
942
+
943
+ # First parameter line
944
+ params = split_10_char(block[2])
945
+ self.weir_coefficient = _to_float(params[0], 1.2)
946
+ self.modular_limit = _to_float(params[1], 0.9)
947
+
948
+ # Spill section data
949
+ data_list = []
950
+ for row in block[4:]:
951
+ row_split = split_10_char(f"{row:<40}")
952
+ x = _to_float(row_split[0]) # chainage
953
+ y = _to_float(row_split[1]) # elevation
954
+ e = _to_float(row_split[2]) # easting
955
+ n = _to_float(row_split[3]) # northing
956
+
957
+ data_list.append([x, y, e, n])
958
+
959
+ spill_data = pd.DataFrame(data_list, columns=["X", "Y", "Easting", "Northing"])
960
+ self.data = spill_data
961
+
962
+ def _write(self):
963
+ """Function to write a valid SPILL block"""
964
+ _validate_unit(self) # Function to check the params are valid for CONDUIT unit
965
+ header = "SPILL " + self.comment
966
+ labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
967
+ block = [header, labels]
968
+
969
+ # First parameter line
970
+ params = join_10_char(self.weir_coefficient, self.modular_limit)
971
+ block.append(params)
972
+
973
+ # Section data
974
+ nrows = len(self.data)
975
+ block.append(join_10_char(nrows))
976
+ section_data = [join_10_char(x, y, e, n) for _, x, y, e, n in self.data.itertuples()]
977
+ block.extend(section_data)
978
+
979
+ return block
980
+
981
+ def _create_from_blank( # noqa: PLR0913
982
+ self,
983
+ name="new_spill",
984
+ ds_label="",
985
+ comment="",
986
+ weir_coefficient=1.2,
987
+ modular_limit=0.9,
988
+ data=None,
989
+ ):
990
+ for param, val in {
991
+ "name": name,
992
+ "ds_label": ds_label,
993
+ "comment": comment,
994
+ "weir_coefficient": weir_coefficient,
995
+ "modular_limit": modular_limit,
996
+ }.items():
997
+ setattr(self, param, val)
998
+
999
+ self.data = (
1000
+ data
1001
+ if isinstance(data, pd.DataFrame)
1002
+ else pd.DataFrame([[0.0, 0.0, 0.0, 0.0]], columns=["X", "Y", "Easting", "Northing"])
1003
+ )
1004
+
1005
+
1006
+ class RNWEIR(Unit):
1007
+ """Class to hold and process RNWEIR unit type
1008
+
1009
+ Args:
1010
+ name (str, optional): Upstream label name.
1011
+ comment (str, optional): Comment included in unit.
1012
+ ds_label (str, optional): Downstream label.
1013
+ velocity_coefficient (float, optional): Coefficient of approach velocity.
1014
+ weir_length (float, optional): Length of weir crest in the direction of flow (m).
1015
+ weir_breadth (float, optional): Breadth of weir at control section (normal to the flow direction)(m).
1016
+ weir_elevation (float, optional): Elevation of weir crest (m AD).
1017
+ modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode.
1018
+ upstream_crest_height (float, optional): Height of crest above bed of upstream channnel (m).
1019
+ downstream_crest_height (float, optional): Height of crest above downstream channel (m).
1020
+
1021
+ Returns:
1022
+ RNWEIR: Flood Modeller RNWEIR Unit class object"""
1023
+
1024
+ _unit = "RNWEIR"
1025
+ ACCEPTABLE_MODULAR_LIMIT = 0.0
1026
+
1027
+ def _read(self, block):
1028
+ """Function to read a given RNWEIR block and store data as class attributes"""
1029
+ # Extends label line to be correct length before splitting to pick up blank labels
1030
+ labels = split_n_char(f"{block[1]:<{2*self._label_len}}", self._label_len)
1031
+ self.name = labels[0]
1032
+ self.ds_label = labels[1]
1033
+ self.comment = block[0].replace("RNWEIR", "").strip()
1034
+
1035
+ # First parameter line
1036
+ params1 = split_10_char(f"{block[2]:<50}")
1037
+ self.velocity_coefficient = _to_float(params1[0])
1038
+ self.weir_length = _to_float(params1[1])
1039
+ self.weir_breadth = _to_float(params1[2])
1040
+ self.weir_elevation = _to_float(params1[3])
1041
+ self.modular_limit = _to_float(params1[4])
1042
+
1043
+ # Second parameter line
1044
+ params2 = split_10_char(f"{block[3]:<20}")
1045
+ self.upstream_crest_height = _to_float(params2[0])
1046
+ self.downstream_crest_height = _to_float(params2[1])
1047
+
1048
+ def _write(self):
1049
+ """Function to write a valid RNWEIR block"""
1050
+ _validate_unit(self)
1051
+ header = "RNWEIR " + self.comment
1052
+ labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
1053
+ block = [header, labels]
1054
+
1055
+ # First parameter line
1056
+ if self.modular_limit == self.ACCEPTABLE_MODULAR_LIMIT:
1057
+ params1 = join_10_char(
1058
+ self.velocity_coefficient,
1059
+ self.weir_length,
1060
+ self.weir_breadth,
1061
+ self.weir_elevation,
1062
+ )
1063
+ else:
1064
+ params1 = join_10_char(
1065
+ self.velocity_coefficient,
1066
+ self.weir_length,
1067
+ self.weir_breadth,
1068
+ self.weir_elevation,
1069
+ self.modular_limit,
1070
+ )
1071
+
1072
+ block.append(params1)
1073
+
1074
+ # Second parameter line
1075
+ params2 = join_10_char(self.upstream_crest_height, self.downstream_crest_height)
1076
+ block.append(params2)
1077
+
1078
+ return block
1079
+
1080
+ def _create_from_blank( # noqa: PLR0913
1081
+ self,
1082
+ name="new_rnweir",
1083
+ comment="",
1084
+ ds_label="",
1085
+ velocity_coefficient=1.0,
1086
+ modular_limit=0.7,
1087
+ upstream_crest_height=0.0,
1088
+ downstream_crest_height=0.0,
1089
+ weir_length=0.0,
1090
+ weir_breadth=0.0,
1091
+ weir_elevation=0.0,
1092
+ ):
1093
+ for param, val in {
1094
+ "name": name,
1095
+ "comment": comment,
1096
+ "ds_label": ds_label,
1097
+ "velocity_coefficient": velocity_coefficient,
1098
+ "modular_limit": modular_limit,
1099
+ "upstream_crest_height": upstream_crest_height,
1100
+ "downstream_crest_height": downstream_crest_height,
1101
+ "weir_length": weir_length,
1102
+ "wier_breadth": weir_breadth,
1103
+ "weir_elevation": weir_elevation,
1104
+ }.items():
1105
+ setattr(self, param, val)
1106
+
1107
+
1108
+ class WEIR(Unit):
1109
+ """Class to hold and process WEIR unit type
1110
+
1111
+ Args:
1112
+ name (str, optional): Upstream label name.
1113
+ comment (str, optional): Comment included in unit.
1114
+ ds_label (str, optional): Downstream label.
1115
+ exponent (float, optional): Coefficient of discharge for the weir,
1116
+ discharge_coefficeient (float, optional): Exponent used in the weir flow equation,
1117
+ velocity_coefficient (float, optional): Coefficient of approach velocity.
1118
+ weir_length (float, optional): Length of weir crest in the direction of flow (m).
1119
+ weir_breadth (float, optional): Breadth of weir at control section (normal to the flow direction)(m).
1120
+ weir_elevation (float, optional): Elevation of weir crest (m AD).
1121
+ modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode.
1122
+
1123
+ Returns:
1124
+ WEIR: Flood Modeller WEIR Unit class object"""
1125
+
1126
+ _unit = "WEIR"
1127
+
1128
+ def _read(self, block):
1129
+ """Function to read a given WEIR block and store data as class attributes"""
1130
+ # Extends label line to be correct length before splitting to pick up blank labels
1131
+ labels = split_n_char(f"{block[1]:<{2*self._label_len}}", self._label_len)
1132
+ self.name = labels[0]
1133
+ self.ds_label = labels[1]
1134
+ self.comment = block[0].replace("WEIR", "").strip()
1135
+
1136
+ # Exponent
1137
+ self.exponent = _to_float(block[2].strip())
1138
+
1139
+ # Parameters line
1140
+ params = split_10_char(f"{block[3]:<50}")
1141
+ self.discharge_coefficient = _to_float(params[0])
1142
+ self.velocity_coefficient = _to_float(params[1])
1143
+ self.weir_breadth = _to_float(params[2])
1144
+ self.weir_elevation = _to_float(params[3])
1145
+ self.modular_limit = _to_float(params[4])
1146
+
1147
+ def _write(self):
1148
+ """Function to write a valid WEIR block"""
1149
+ _validate_unit(self)
1150
+ header = "WEIR " + self.comment
1151
+ labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
1152
+ block = [header, labels]
1153
+
1154
+ # Exponent line
1155
+ exp_line = join_10_char(self.exponent)
1156
+ block.append(exp_line)
1157
+
1158
+ # Parameter line
1159
+ params = join_10_char(
1160
+ self.discharge_coefficient,
1161
+ self.velocity_coefficient,
1162
+ self.weir_breadth,
1163
+ self.weir_elevation,
1164
+ self.modular_limit,
1165
+ )
1166
+ block.append(params)
1167
+
1168
+ return block
1169
+
1170
+ def _create_from_blank( # noqa: PLR0913
1171
+ self,
1172
+ name="new_weir",
1173
+ comment="",
1174
+ ds_label="",
1175
+ exponent=1.5,
1176
+ discharge_coefficeient=1.0,
1177
+ velocity_coefficient=1.0,
1178
+ modular_limit=0.7,
1179
+ weir_length=0.0,
1180
+ weir_breadth=0.0,
1181
+ weir_elevation=0.0,
1182
+ ):
1183
+ for param, val in {
1184
+ "name": name,
1185
+ "comment": comment,
1186
+ "ds_label": ds_label,
1187
+ "exponent": exponent,
1188
+ "discharge_coefficeient": discharge_coefficeient,
1189
+ "velocity_coefficient": velocity_coefficient,
1190
+ "modular_limit": modular_limit,
1191
+ "weir_length": weir_length,
1192
+ "wier_breadth": weir_breadth,
1193
+ "weir_elevation": weir_elevation,
1194
+ }.items():
1195
+ setattr(self, param, val)
1196
+
1197
+
1198
+ class CRUMP(Unit):
1199
+ """Class to hold and process CRUMP unit type
1200
+
1201
+ Args:
1202
+ name (str, optional): Upstream label name.
1203
+ comment (str,optional): Comment included in unit.
1204
+ calibration_coefficient (float, optional): Calibration coefficient (should be set to unity for most cases).
1205
+ weir_breadth (float, optional): Breadth of weir at crest (m).
1206
+ weir_elevation (float, optional): Eleveation of weir crest (m above datum).
1207
+ modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode.
1208
+ upstream_crest_height (float, optional): Height of crest above bed of upstream channel (m).
1209
+ downstream_crest_height (float, optional): Height oof crest above bed of downstream channel (m).
1210
+ ds_label (str, optional): Downstream node label.
1211
+ us_remote_label (str, optional): Upstream remote node label (must be a river or conduit section) - use if name is not a river or conduit section.
1212
+ ds_remote_label (str, optional): Downstream remote node label (must be a river or conduit section) - use if ds_label is not a river or conduit section.
1213
+
1214
+ Returns:
1215
+ CRUMP: Flood Modeller CRUMP Unit class object"""
1216
+
1217
+ _unit = "CRUMP"
1218
+ ACCEPTABLE_MODULAR_LIMIT = 0.0
1219
+
1220
+ def _read(self, block):
1221
+ """Function to read a given CRUMP block and store data as class attributes"""
1222
+ # Extends label line to be correct length before splitting to pick up blank labels
1223
+ labels = split_n_char(f"{block[1]:<{4*self._label_len}}", self._label_len)
1224
+ self.name = labels[0]
1225
+ self.ds_label = labels[1]
1226
+ self.us_remote_label = labels[2]
1227
+ self.ds_remote_label = labels[3]
1228
+ self.comment = block[0].replace("CRUMP", "").strip()
1229
+
1230
+ # First parameter line
1231
+ params1 = split_10_char(f"{block[2]:<40}")
1232
+ self.calibration_coefficient = _to_float(params1[0])
1233
+ self.weir_breadth = _to_float(params1[1])
1234
+ self.weir_elevation = _to_float(params1[2])
1235
+ self.modular_limit = _to_float(params1[3])
1236
+
1237
+ # Second parameter line
1238
+ params2 = split_10_char(f"{block[3]:<20}")
1239
+ self.upstream_crest_height = _to_float(params2[0])
1240
+ self.downstream_crest_height = _to_float(params2[1])
1241
+
1242
+ def _write(self):
1243
+ """Function to write a valid CRUMP block"""
1244
+ _validate_unit(self)
1245
+ header = "CRUMP " + self.comment
1246
+ labels = join_n_char_ljust(
1247
+ self._label_len,
1248
+ self.name,
1249
+ self.ds_label,
1250
+ self.us_remote_label,
1251
+ self.ds_remote_label,
1252
+ )
1253
+ block = [header, labels]
1254
+
1255
+ # First parameter line
1256
+ params1 = join_10_char(
1257
+ self.calibration_coefficient,
1258
+ self.weir_breadth,
1259
+ self.weir_elevation,
1260
+ self.modular_limit if self.modular_limit != self.ACCEPTABLE_MODULAR_LIMIT else "",
1261
+ )
1262
+
1263
+ block.append(params1)
1264
+
1265
+ # Second parameter line
1266
+ params2 = join_10_char(self.upstream_crest_height, self.downstream_crest_height)
1267
+ block.append(params2)
1268
+
1269
+ return block
1270
+
1271
+ def _create_from_blank( # noqa: PLR0913
1272
+ self,
1273
+ name="new_crump",
1274
+ comment="",
1275
+ calibration_coefficient=1.0,
1276
+ weir_breadth=0.0,
1277
+ weir_elevation=0.0,
1278
+ modular_limit=0.7,
1279
+ upstream_crest_height=0.0,
1280
+ downstream_crest_height=0.0,
1281
+ ds_label="",
1282
+ us_remote_label="",
1283
+ ds_remote_label="",
1284
+ ):
1285
+ for param, val in {
1286
+ "name": name,
1287
+ "comment": comment,
1288
+ "calibration_coefficient": calibration_coefficient,
1289
+ "weir_breadth": weir_breadth,
1290
+ "weir_elevation": weir_elevation,
1291
+ "modular_limit": modular_limit,
1292
+ "upstream_crest_height": upstream_crest_height,
1293
+ "downstream_crest_height": downstream_crest_height,
1294
+ "ds_label": ds_label,
1295
+ "us_remote_label": us_remote_label,
1296
+ "ds_remote_label": ds_remote_label,
1297
+ }.items():
1298
+ setattr(self, param, val)
1299
+
1300
+
1301
+ class FLAT_V_WEIR(Unit): # noqa: N801
1302
+ """Class to hold and process FLAT-V WEIR unit type
1303
+
1304
+ Args:
1305
+
1306
+ name (str, optional): Upstream label name.
1307
+ comment (str,optional): Comment included in unit.
1308
+ ds_label (str, optional): Downstream node label.
1309
+ us_remote_label (str, optional): Upstream remote node label (must be a river or conduit section) - use if name is not a river or conduit section.
1310
+ ds_remote_label (str, optional): Downstream remote node label (must be a river or conduit section) - use if ds_label is not a river or conduit section.
1311
+ weir_elevation (float, optional): Eleveation of weir crest (m above datum).
1312
+ weir_breadth (float, optional): Breadth of weir at crest (m).
1313
+ v_slope (float, optional): 'V' slope (horizontal distance/vertical distance).
1314
+ side_slope (float, optional): Channel side slope (horizontal distance/vertical distance).
1315
+ upstream_crest_height (float, optional): Weir crest height above upstream bed (m).
1316
+ downstream_crest_height (float, optional): Weir crest height above downstream bed (m).
1317
+ modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode.
1318
+ calibration_coefficient (float, optional): Calibration coefficient (should be set to unity for most cases).
1319
+ ds_face_slope (int, optional): Flag to switch between 1:5 or 1:2 for d/s face. Can be set to 2 or 5 ONLY.
1320
+ coriolis_coefficient (float, optional): Coriolis energy coefficient.
1321
+ bank_top_elevation (float, optional): Elevation of channel bank top/ limit of extent of sloping channel walls (m AD).
1322
+
1323
+ Returns:
1324
+ FLAT_V_WEIR: Flood Modeller FLAT-V WEIR Unit class object"""
1325
+
1326
+ _unit = "FLAT-V WEIR"
1327
+ ACCEPTABLE_MODULAR_LIMIT = 0.0
1328
+
1329
+ def _read(self, block):
1330
+ """Function to read a given FLAT-V WEIR block and store data as class attributes"""
1331
+ # Extends label line to be correct length before splitting to pick up blank labels
1332
+ labels = split_n_char(f"{block[1]:<{4*self._label_len}}", self._label_len)
1333
+ self.name = labels[0]
1334
+ self.ds_label = labels[1]
1335
+ self.us_remote_label = labels[2]
1336
+ self.ds_remote_label = labels[3]
1337
+ self.comment = block[0].replace("FLAT-V WEIR", "").strip()
1338
+
1339
+ # First parameter line
1340
+ params1 = split_10_char(f"{block[2]:<90}")
1341
+ self.calibration_coefficient = _to_float(params1[0])
1342
+ self.weir_breadth = _to_float(params1[1])
1343
+ self.weir_elevation = _to_float(params1[2])
1344
+ self.modular_limit = _to_float(params1[3])
1345
+ self.v_slope = _to_float(params1[4])
1346
+ self.side_slope = _to_float(params1[5])
1347
+ self.ds_face_slope = _to_float(params1[6])
1348
+ self.coriolis_coefficient = _to_float(params1[7])
1349
+ self.bank_top_elevation = _to_float(params1[8])
1350
+
1351
+ # Second parameter line
1352
+ params2 = split_10_char(f"{block[3]:<20}")
1353
+ self.upstream_crest_height = _to_float(params2[0])
1354
+ self.downstream_crest_height = _to_float(params2[1])
1355
+
1356
+ def _write(self):
1357
+ """Function to write a valid FLAT-V WEIR block"""
1358
+
1359
+ _validate_unit(self)
1360
+ header = "FLAT-V WEIR " + self.comment
1361
+ labels = join_n_char_ljust(
1362
+ self._label_len,
1363
+ self.name,
1364
+ self.ds_label,
1365
+ self.us_remote_label,
1366
+ self.ds_remote_label,
1367
+ )
1368
+ block = [header, labels]
1369
+
1370
+ params1 = join_10_char(
1371
+ self.calibration_coefficient,
1372
+ self.weir_breadth,
1373
+ self.weir_elevation,
1374
+ self.modular_limit if self.modular_limit != self.ACCEPTABLE_MODULAR_LIMIT else "",
1375
+ self.v_slope,
1376
+ self.side_slope,
1377
+ self.ds_face_slope,
1378
+ self.coriolis_coefficient,
1379
+ self.bank_top_elevation,
1380
+ )
1381
+
1382
+ block.append(params1)
1383
+
1384
+ # Second parameter line
1385
+ params2 = join_10_char(self.upstream_crest_height, self.downstream_crest_height)
1386
+ block.append(params2)
1387
+
1388
+ return block
1389
+
1390
+ def _create_from_blank( # noqa: PLR0913
1391
+ self,
1392
+ name="new_flat_v",
1393
+ comment="",
1394
+ ds_label="",
1395
+ us_remote_label="",
1396
+ ds_remote_label="",
1397
+ weir_elevation=0.0,
1398
+ weir_breadth=0.0,
1399
+ v_slope=0.0,
1400
+ side_slope=0.0,
1401
+ upstream_crest_height=0.0,
1402
+ downstream_crest_height=0.0,
1403
+ modular_limit=0.0,
1404
+ calibration_coefficient=1.0,
1405
+ ds_face_slope=5,
1406
+ coriolis_coefficient=1.2,
1407
+ bank_top_elevation=0.0,
1408
+ ):
1409
+ for param, val in {
1410
+ "name": name,
1411
+ "comment": comment,
1412
+ "ds_label": ds_label,
1413
+ "us_remote_label": us_remote_label,
1414
+ "ds_remote_label": ds_remote_label,
1415
+ "weir_elevation": weir_elevation,
1416
+ "weir_breadth": weir_breadth,
1417
+ "v_slope": v_slope,
1418
+ "side_slope": side_slope,
1419
+ "upstream_crest_height": upstream_crest_height,
1420
+ "downstream_crest_height": downstream_crest_height,
1421
+ "modular_limit": modular_limit,
1422
+ "calibration_coefficient": calibration_coefficient,
1423
+ "ds_face_slope": ds_face_slope,
1424
+ "coriolis_coefficient": coriolis_coefficient,
1425
+ "bank_top_elevation": bank_top_elevation,
1426
+ }.items():
1427
+ setattr(self, param, val)
1428
+
1429
+
1430
+ class RESERVOIR(Unit): # NOT CURRENTLY IN USE
1431
+ """Class to hold and process RESERVOIR unit type
1432
+
1433
+ Args:
1434
+ name (str, optional): Unit name.
1435
+ comment (str, optional): Comment included in unit.
1436
+ all_labels (str, optional): Unlimited number of labels - not including first label (name).
1437
+ easting (float, optional): Easting coordinate of reservoir reference point (not used in computations).
1438
+ northing (float, optional): Northing coordinate of reservoir reference point (not used in computations).
1439
+ runoff_factor (float, optional): Rainfall runoff factor.
1440
+ num_pairs (float, optional): Number of elevation/area pairs.
1441
+ lat1 (str, optional): First lateral inflow label.
1442
+ lat2 (str, optional): Second lateral inflow label.
1443
+ lat3 (str, optional): Third lateral inflow label.
1444
+ lat4 (str, optional): Fourth lateral inflow label.
1445
+ data (pandas.DataFrame): Dataframe object containing all the reservoir section data.
1446
+ Columns are ``'Elevation','Plan Area'``
1447
+
1448
+ Returns:
1449
+ RESERVOIR: Flood Modeller RESERVOIR Unit class object"""
1450
+
1451
+ _unit = "RESERVOIR"
1452
+
1453
+ def _read(self, block):
1454
+ """Function to read a given RESERVOIR WEIR block and store data as class attributes"""
1455
+
1456
+ # Extends label line to be correct length before splitting to pick up blank labels
1457
+ num_labels = len(block[1]) // self._label_len
1458
+ labels = split_n_char(f"{block[1]:<{num_labels*self._label_len}}", self._label_len)
1459
+ self.name = labels[0]
1460
+ self.all_labels = labels[0 : len(labels)]
1461
+ self.comment = block[0].replace("RESERVOIR", "").strip()
1462
+
1463
+ # Option 1 (runs if comment == "#revision#1")
1464
+ if self.comment == "#revision#1":
1465
+ # Lateral inflow labels
1466
+ lateral_labels = split_n_char(f"{block[2]:<{4*self._label_len}}", self._label_len)
1467
+ self.lat1 = lateral_labels[0]
1468
+ self.lat2 = lateral_labels[1]
1469
+ self.lat3 = lateral_labels[2]
1470
+ self.lat4 = lateral_labels[3]
1471
+
1472
+ # Number of pairs of data
1473
+ self.num_pairs = _to_int(block[3])
1474
+
1475
+ # Reservoir section data
1476
+ data_list = []
1477
+ for row in block[4 : len(block) - 1]:
1478
+ row_split = split_10_char(f"{row:<20}")
1479
+ elevation = _to_float(row_split[0]) # elevation
1480
+ plan_area = _to_float(row_split[1]) # plan area
1481
+ data_list.append([elevation, plan_area])
1482
+ reservoir_data = pd.DataFrame(data_list, columns=["Elevation", "Plan Area"])
1483
+ self.data = reservoir_data
1484
+
1485
+ # Coordinate data
1486
+ coordinate_data = split_n_char(
1487
+ f"{block[len(block)-1]:<{3*self._label_len}}",
1488
+ self._label_len,
1489
+ )
1490
+ self.easting = _to_float(coordinate_data[0])
1491
+ self.northing = _to_float(coordinate_data[1])
1492
+ self.runoff_factor = _to_float(coordinate_data[2])
1493
+ else: # Option 2 (runs if comment != "#revision#1")
1494
+ # Number of pairs of data
1495
+ self.num_pairs = _to_int(block[2])
1496
+
1497
+ # Reservoir section data
1498
+ data_list = []
1499
+ for row in block[3:]:
1500
+ row_split = split_10_char(f"{row:<20}")
1501
+ elevation = _to_float(row_split[0]) # elevation
1502
+ plan_area = _to_float(row_split[1]) # plan area
1503
+ data_list.append([elevation, plan_area])
1504
+ reservoir_data = pd.DataFrame(data_list, columns=["Elevation", "Plan Area"])
1505
+ self.data = reservoir_data
1506
+
1507
+ def _write(self):
1508
+ """Function to write a valid RESERVOIR block"""
1509
+
1510
+ _validate_unit(self)
1511
+ header = "RESERVOIR " + self.comment
1512
+ self.labels = " ".join(self.all_labels)
1513
+ block = [header, self.labels]
1514
+
1515
+ # Option 1 (runs if comment == "#revision#1")
1516
+ if self.comment == "#revision#1":
1517
+ # Lateral inflow labels
1518
+ lat_labels = join_12_char_ljust(self.lat1, self.lat2, self.lat3, self.lat4)
1519
+ block.append(lat_labels)
1520
+
1521
+ # Number of pairs of data
1522
+ block.append(join_12_char_ljust(self.num_pairs))
1523
+
1524
+ # Reservoir section data
1525
+ section_data = [
1526
+ join_12_char_ljust(elevation, plan_area)
1527
+ for _, elevation, plan_area, in self.data.itertuples()
1528
+ ]
1529
+ block.extend(section_data)
1530
+
1531
+ # Coordinate data
1532
+ coords = join_12_char_ljust(self.easting, self.northing, self.runoff_factor)
1533
+ block.append(coords)
1534
+ else: # Option 2 (runs if comment != "#revision#1")
1535
+ # Number of pairs of data
1536
+ block.append(join_12_char_ljust(self.num_pairs))
1537
+
1538
+ # Reservoir section data
1539
+ section_data = [
1540
+ join_12_char_ljust(elevation, plan_area)
1541
+ for _, elevation, plan_area, in self.data.itertuples()
1542
+ ]
1543
+ block.extend(section_data)
1544
+
1545
+ return block
1546
+
1547
+ def _create_from_blank( # noqa: PLR0913
1548
+ self,
1549
+ name="new_reservoir",
1550
+ comment="",
1551
+ easting=0.0,
1552
+ northing=0.0,
1553
+ runoff_factor=0.0,
1554
+ num_pairs=1,
1555
+ data=None,
1556
+ lat1="",
1557
+ lat2="",
1558
+ lat3="",
1559
+ lat4="",
1560
+ ):
1561
+ for param, val in {
1562
+ "name": name,
1563
+ "comment": comment,
1564
+ "easting": easting,
1565
+ "northing": northing,
1566
+ "runoff_factor": runoff_factor,
1567
+ "num_pairs": num_pairs,
1568
+ "lat1": lat1,
1569
+ "lat2": lat2,
1570
+ "lat3": lat3,
1571
+ "lat4": lat4,
1572
+ }.items():
1573
+ setattr(self, param, val)
1574
+
1575
+ self.data = (
1576
+ data
1577
+ if isinstance(data, pd.DataFrame)
1578
+ else pd.DataFrame([[0.0, 0.0]], columns=["Elevation", "Plan Area"])
1579
+ )
1580
+
1581
+
1582
+ class OUTFALL(Unit):
1583
+ """Class to hold and process OUTFALL unit type
1584
+
1585
+ Args:
1586
+ name (str, optional): Unit name.
1587
+ comment (str, optional): Comment included in unit.
1588
+ flapped (bool, optional): ``True`` if outfall is flapped, ``False`` if outfall is open
1589
+ ds_label (str, optional): Downstream label
1590
+ invert (float, optional): Throat invert level
1591
+ soffit (float, optional): Throat soffit level
1592
+ bore_area (float, optional): Cross sectional area of throat opening
1593
+ upstream_sill (float, optional): Upstream sill level
1594
+ downstream_sill (float, optional): Downstream sill level
1595
+ shape (str, optional): Shape of outfall aperture ('RECTANGLE' or 'CIRCULAR')
1596
+ weir_flow (float, optional): Calibration factor for weir flow
1597
+ surcharged_flow (float, optional): Calibration factor for surcharged flow
1598
+ modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode
1599
+
1600
+
1601
+ Returns:
1602
+ OUTFALL: Flood Modeller OUTFALL Unit class object
1603
+ """
1604
+
1605
+ _unit = "OUTFALL"
1606
+
1607
+ def _read(self, block):
1608
+ """Function to read a given OUTFALL block and store data as class attributes"""
1609
+ self._subtype = block[1].split(" ")[0].strip()
1610
+ self.flapped = self.subtype == "FLAPPED"
1611
+
1612
+ # Extends label line to be correct length before splitting to pick up blank labels
1613
+ labels = split_n_char(f"{block[2]:<{2*self._label_len}}", self._label_len)
1614
+ self.name = labels[0]
1615
+ self.ds_label = labels[1]
1616
+ self.comment = block[0].replace("OUTFALL", "").strip()
1617
+
1618
+ # First parameter line
1619
+ params1 = split_10_char(f"{block[3]:<60}")
1620
+ self.invert = _to_float(params1[0])
1621
+ self.soffit = _to_float(params1[1])
1622
+ self.bore_area = _to_float(params1[2])
1623
+ self.upstream_sill = _to_float(params1[3])
1624
+ self.downstream_sill = _to_float(params1[4])
1625
+ self.shape = _to_str(params1[5], "RECTANGLE")
1626
+
1627
+ # Second parameter line
1628
+ params2 = split_10_char(block[4])
1629
+ self.weir_flow = _to_float(params2[0], 1.0)
1630
+ self.surcharged_flow = _to_float(params2[1], 1.0)
1631
+ self.modular_limit = _to_float(params2[2], 0.7)
1632
+
1633
+ def _write(self):
1634
+ """Function to write a valid OUTFALL block"""
1635
+ _validate_unit(self) # Function to check the params are valid for CONDUIT unit
1636
+ header = "OUTFALL " + self.comment
1637
+ labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
1638
+
1639
+ self._subtype = "FLAPPED" if self.flapped else "OPEN"
1640
+ block = [header, self.subtype, labels]
1641
+
1642
+ # First parameter line
1643
+ params1 = join_10_char(
1644
+ self.invert,
1645
+ self.soffit,
1646
+ self.bore_area,
1647
+ self.upstream_sill,
1648
+ self.downstream_sill,
1649
+ self.shape,
1650
+ )
1651
+
1652
+ # Second parameter line
1653
+ params2 = join_10_char(self.weir_flow, self.surcharged_flow, self.modular_limit)
1654
+
1655
+ block.extend([params1, params2])
1656
+
1657
+ return block
1658
+
1659
+ def _create_from_blank( # noqa: PLR0913
1660
+ self,
1661
+ name="new_outfall",
1662
+ flapped=False,
1663
+ ds_label="",
1664
+ comment="",
1665
+ invert=0.0,
1666
+ soffit=0.0,
1667
+ bore_area=1.0,
1668
+ upstream_sill=0.0,
1669
+ downstream_sill=0.0,
1670
+ shape="RECTANGLE",
1671
+ weir_flow=1.0,
1672
+ surcharged_flow=1.0,
1673
+ modular_limit=0.7,
1674
+ ):
1675
+ for param, val in {
1676
+ "name": name,
1677
+ "flapped": flapped,
1678
+ "ds_label": ds_label,
1679
+ "comment": comment,
1680
+ "invert": invert,
1681
+ "soffit": soffit,
1682
+ "bore_area": bore_area,
1683
+ "upstream_sill": upstream_sill,
1684
+ "downstream_sill": downstream_sill,
1685
+ "shape": shape,
1686
+ "weir_flow": weir_flow,
1687
+ "surcharged_flow": surcharged_flow,
1688
+ "modular_limit": modular_limit,
1689
+ }.items():
1690
+ setattr(self, param, val)