floodmodeller-api 0.4.2__py3-none-any.whl → 0.4.3__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 +184 -176
  3. floodmodeller_api/backup.py +273 -273
  4. floodmodeller_api/dat.py +909 -838
  5. floodmodeller_api/diff.py +136 -119
  6. floodmodeller_api/ied.py +307 -311
  7. floodmodeller_api/ief.py +647 -646
  8. floodmodeller_api/ief_flags.py +253 -253
  9. floodmodeller_api/inp.py +266 -268
  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 +320 -314
  21. floodmodeller_api/logs/lf_helpers.py +354 -346
  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 +9 -8
  26. floodmodeller_api/test/test_backup.py +117 -117
  27. floodmodeller_api/test/test_dat.py +221 -92
  28. floodmodeller_api/test/test_data/All Units 4_6.DAT +1081 -1081
  29. floodmodeller_api/test/test_data/All Units 4_6.feb +1081 -1081
  30. floodmodeller_api/test/test_data/BRIDGE.DAT +926 -926
  31. floodmodeller_api/test/test_data/Culvert_Inlet_Outlet.dat +36 -36
  32. floodmodeller_api/test/test_data/Culvert_Inlet_Outlet.feb +36 -36
  33. floodmodeller_api/test/test_data/DamBreakADI.xml +52 -52
  34. floodmodeller_api/test/test_data/DamBreakFAST.xml +58 -58
  35. floodmodeller_api/test/test_data/DamBreakFAST_dy.xml +53 -53
  36. floodmodeller_api/test/test_data/DamBreakTVD.xml +55 -55
  37. floodmodeller_api/test/test_data/DefenceBreach.xml +53 -53
  38. floodmodeller_api/test/test_data/DefenceBreachFAST.xml +60 -60
  39. floodmodeller_api/test/test_data/DefenceBreachFAST_dy.xml +55 -55
  40. floodmodeller_api/test/test_data/Domain1+2_QH.xml +76 -76
  41. floodmodeller_api/test/test_data/Domain1_H.xml +41 -41
  42. floodmodeller_api/test/test_data/Domain1_Q.xml +41 -41
  43. floodmodeller_api/test/test_data/Domain1_Q_FAST.xml +48 -48
  44. floodmodeller_api/test/test_data/Domain1_Q_FAST_dy.xml +48 -48
  45. floodmodeller_api/test/test_data/Domain1_Q_xml_expected.json +263 -0
  46. floodmodeller_api/test/test_data/Domain1_W.xml +41 -41
  47. floodmodeller_api/test/test_data/EX1.DAT +321 -321
  48. floodmodeller_api/test/test_data/EX1.ext +107 -107
  49. floodmodeller_api/test/test_data/EX1.feb +320 -320
  50. floodmodeller_api/test/test_data/EX1.gxy +107 -107
  51. floodmodeller_api/test/test_data/EX17.DAT +421 -422
  52. floodmodeller_api/test/test_data/EX17.ext +213 -213
  53. floodmodeller_api/test/test_data/EX17.feb +422 -422
  54. floodmodeller_api/test/test_data/EX18.DAT +375 -375
  55. floodmodeller_api/test/test_data/EX18_DAT_expected.json +3876 -0
  56. floodmodeller_api/test/test_data/EX2.DAT +302 -302
  57. floodmodeller_api/test/test_data/EX3.DAT +926 -926
  58. floodmodeller_api/test/test_data/EX3_DAT_expected.json +16235 -0
  59. floodmodeller_api/test/test_data/EX3_IEF_expected.json +61 -0
  60. floodmodeller_api/test/test_data/EX6.DAT +2084 -2084
  61. floodmodeller_api/test/test_data/EX6.ext +532 -532
  62. floodmodeller_api/test/test_data/EX6.feb +2084 -2084
  63. floodmodeller_api/test/test_data/EX6_DAT_expected.json +31647 -0
  64. floodmodeller_api/test/test_data/Event Data Example.DAT +336 -336
  65. floodmodeller_api/test/test_data/Event Data Example.ext +107 -107
  66. floodmodeller_api/test/test_data/Event Data Example.feb +336 -336
  67. floodmodeller_api/test/test_data/Linked1D2D.xml +52 -52
  68. floodmodeller_api/test/test_data/Linked1D2DFAST.xml +53 -53
  69. floodmodeller_api/test/test_data/Linked1D2DFAST_dy.xml +48 -48
  70. floodmodeller_api/test/test_data/Linked1D2D_xml_expected.json +313 -0
  71. floodmodeller_api/test/test_data/blockage.dat +50 -50
  72. floodmodeller_api/test/test_data/blockage.ext +45 -45
  73. floodmodeller_api/test/test_data/blockage.feb +9 -9
  74. floodmodeller_api/test/test_data/blockage.gxy +71 -71
  75. floodmodeller_api/test/test_data/defaultUnits.dat +127 -127
  76. floodmodeller_api/test/test_data/defaultUnits.ext +45 -45
  77. floodmodeller_api/test/test_data/defaultUnits.feb +9 -9
  78. floodmodeller_api/test/test_data/defaultUnits.fmpx +58 -58
  79. floodmodeller_api/test/test_data/defaultUnits.gxy +85 -85
  80. floodmodeller_api/test/test_data/ex3.ief +20 -20
  81. floodmodeller_api/test/test_data/ex3.lf1 +2800 -2800
  82. floodmodeller_api/test/test_data/ex4.DAT +1374 -1374
  83. floodmodeller_api/test/test_data/ex4_changed.DAT +1374 -1374
  84. floodmodeller_api/test/test_data/example1.inp +329 -329
  85. floodmodeller_api/test/test_data/example2.inp +158 -158
  86. floodmodeller_api/test/test_data/example3.inp +297 -297
  87. floodmodeller_api/test/test_data/example4.inp +388 -388
  88. floodmodeller_api/test/test_data/example5.inp +147 -147
  89. floodmodeller_api/test/test_data/example6.inp +154 -154
  90. floodmodeller_api/test/test_data/jump.dat +176 -176
  91. floodmodeller_api/test/test_data/network.dat +1374 -1374
  92. floodmodeller_api/test/test_data/network.ext +45 -45
  93. floodmodeller_api/test/test_data/network.exy +1 -1
  94. floodmodeller_api/test/test_data/network.feb +45 -45
  95. floodmodeller_api/test/test_data/network.ied +45 -45
  96. floodmodeller_api/test/test_data/network.ief +20 -20
  97. floodmodeller_api/test/test_data/network.inp +147 -147
  98. floodmodeller_api/test/test_data/network.pxy +57 -57
  99. floodmodeller_api/test/test_data/network.zzd +122 -122
  100. floodmodeller_api/test/test_data/network_dat_expected.json +21837 -0
  101. floodmodeller_api/test/test_data/network_from_tabularCSV.csv +87 -87
  102. floodmodeller_api/test/test_data/network_ied_expected.json +287 -0
  103. floodmodeller_api/test/test_data/rnweir.dat +9 -9
  104. floodmodeller_api/test/test_data/rnweir.ext +45 -45
  105. floodmodeller_api/test/test_data/rnweir.feb +9 -9
  106. floodmodeller_api/test/test_data/rnweir.gxy +45 -45
  107. floodmodeller_api/test/test_data/rnweir_default.dat +74 -74
  108. floodmodeller_api/test/test_data/rnweir_default.ext +45 -45
  109. floodmodeller_api/test/test_data/rnweir_default.feb +9 -9
  110. floodmodeller_api/test/test_data/rnweir_default.fmpx +58 -58
  111. floodmodeller_api/test/test_data/rnweir_default.gxy +53 -53
  112. floodmodeller_api/test/test_data/unit checks.dat +16 -16
  113. floodmodeller_api/test/test_ied.py +29 -29
  114. floodmodeller_api/test/test_ief.py +125 -24
  115. floodmodeller_api/test/test_inp.py +47 -48
  116. floodmodeller_api/test/test_json.py +114 -0
  117. floodmodeller_api/test/test_logs_lf.py +48 -51
  118. floodmodeller_api/test/test_tool.py +165 -154
  119. floodmodeller_api/test/test_toolbox_structure_log.py +234 -239
  120. floodmodeller_api/test/test_xml2d.py +151 -156
  121. floodmodeller_api/test/test_zzn.py +36 -34
  122. floodmodeller_api/to_from_json.py +218 -0
  123. floodmodeller_api/tool.py +332 -330
  124. floodmodeller_api/toolbox/__init__.py +5 -5
  125. floodmodeller_api/toolbox/example_tool.py +45 -45
  126. floodmodeller_api/toolbox/model_build/__init__.py +2 -2
  127. floodmodeller_api/toolbox/model_build/add_siltation_definition.py +100 -94
  128. floodmodeller_api/toolbox/model_build/structure_log/__init__.py +1 -1
  129. floodmodeller_api/toolbox/model_build/structure_log/structure_log.py +287 -289
  130. floodmodeller_api/toolbox/model_build/structure_log_definition.py +76 -72
  131. floodmodeller_api/units/__init__.py +10 -10
  132. floodmodeller_api/units/_base.py +214 -209
  133. floodmodeller_api/units/boundaries.py +467 -469
  134. floodmodeller_api/units/comment.py +52 -55
  135. floodmodeller_api/units/conduits.py +382 -403
  136. floodmodeller_api/units/helpers.py +123 -132
  137. floodmodeller_api/units/iic.py +107 -101
  138. floodmodeller_api/units/losses.py +305 -308
  139. floodmodeller_api/units/sections.py +444 -445
  140. floodmodeller_api/units/structures.py +1690 -1684
  141. floodmodeller_api/units/units.py +93 -102
  142. floodmodeller_api/units/unsupported.py +44 -44
  143. floodmodeller_api/units/variables.py +87 -89
  144. floodmodeller_api/urban1d/__init__.py +11 -11
  145. floodmodeller_api/urban1d/_base.py +188 -177
  146. floodmodeller_api/urban1d/conduits.py +93 -85
  147. floodmodeller_api/urban1d/general_parameters.py +58 -58
  148. floodmodeller_api/urban1d/junctions.py +81 -79
  149. floodmodeller_api/urban1d/losses.py +81 -74
  150. floodmodeller_api/urban1d/outfalls.py +114 -107
  151. floodmodeller_api/urban1d/raingauges.py +111 -108
  152. floodmodeller_api/urban1d/subsections.py +92 -93
  153. floodmodeller_api/urban1d/xsections.py +147 -141
  154. floodmodeller_api/util.py +77 -21
  155. floodmodeller_api/validation/parameters.py +660 -660
  156. floodmodeller_api/validation/urban_parameters.py +388 -404
  157. floodmodeller_api/validation/validation.py +110 -112
  158. floodmodeller_api/version.py +1 -1
  159. floodmodeller_api/xml2d.py +688 -684
  160. floodmodeller_api/xml2d_template.py +37 -37
  161. floodmodeller_api/zzn.py +387 -365
  162. {floodmodeller_api-0.4.2.dist-info → floodmodeller_api-0.4.3.dist-info}/LICENSE.txt +13 -13
  163. {floodmodeller_api-0.4.2.dist-info → floodmodeller_api-0.4.3.dist-info}/METADATA +82 -82
  164. floodmodeller_api-0.4.3.dist-info/RECORD +179 -0
  165. {floodmodeller_api-0.4.2.dist-info → floodmodeller_api-0.4.3.dist-info}/WHEEL +1 -1
  166. floodmodeller_api-0.4.3.dist-info/entry_points.txt +3 -0
  167. floodmodeller_api/libifcoremd.dll +0 -0
  168. floodmodeller_api/test/test_data/EX3.bmp +0 -0
  169. floodmodeller_api/test/test_data/test_output.csv +0 -87
  170. floodmodeller_api/zzn_read.dll +0 -0
  171. floodmodeller_api-0.4.2.data/scripts/fmapi-add_siltation.bat +0 -2
  172. floodmodeller_api-0.4.2.data/scripts/fmapi-add_siltation.py +0 -3
  173. floodmodeller_api-0.4.2.data/scripts/fmapi-structure_log.bat +0 -2
  174. floodmodeller_api-0.4.2.data/scripts/fmapi-structure_log.py +0 -3
  175. floodmodeller_api-0.4.2.data/scripts/fmapi-toolbox.bat +0 -2
  176. floodmodeller_api-0.4.2.data/scripts/fmapi-toolbox.py +0 -41
  177. floodmodeller_api-0.4.2.dist-info/RECORD +0 -169
  178. {floodmodeller_api-0.4.2.dist-info → floodmodeller_api-0.4.3.dist-info}/top_level.txt +0 -0
@@ -1,1684 +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
- elif 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
- elif 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
- else:
522
- return self._raw_block
523
-
524
-
525
- class SLUICE(Unit):
526
- """The Sluice class supports two sluice sub-types in Flood Modeller: RADIAL and VERTICAL. Each of these sub-types forms
527
- a unique instance of the class which is differentiated by the `SLUICE.subtype` attribute. There are also several different
528
- attributes depending on the `.control_method` used. All sluice types have the same common attributes:
529
-
530
- **Common Attributes**
531
-
532
- Args:
533
- name (str): Sluice section name (upstream label)
534
- ds_label (str): Downstream label
535
- remote_label (str): Remote label
536
- comment (str): Comment included in unit
537
- subtype (str): Defines the type of sluice unit (*Should not be changed*)
538
- weir_flow_coefficient (float): Coefficient of approach velocity for weir flow (0.4 - 3.0)
539
- under_gate_flow (float): Coefficient of approach velocity for under gate flow
540
- over_gate_flow (float): Coefficient of approach velocity for over gate flow
541
- weir_breadth (float): breadth of weir (for single gate) perpendicular to flow direction
542
- crest_elevation (float): Elevation of weir crest
543
- gate_height_or_chord (float): Height of sluice gate (m) (if sluice subtype is VERTICAL or subtype is RADIAL and
544
- ``use_degrees == False``). OR the cord made by the arc of sluice gate (m) if subtype is RADIAL and
545
- ``use_degrees == True``.
546
- weir_length (float): Length of weir (m) in flow direction
547
- us_weir_height (float): Vertical distance from weir crest to upstream bed level
548
- ds_weir_height (float): Vertical distance from weir crest to downstream bed level
549
- bias_factor (float): Only used when control_method set to 'REMOTE WATER LEVEL'
550
- modular_limits (dict): Dictionary of modular limit values. Keys: {'weir_flow', 'under_gate_flow', 'over_gate_flow'}. If they
551
- are all set equal to zero, then a variable calculation method is used.
552
- ngates (int): number of gates
553
- timeunit (str): Unit of time, e.g. 'HOURS', 'MINUTES' or 'SECONDS'. See Flood Modeller documentation for all available options.
554
- extendmethod (str): Data extending method: 'EXTEND', 'NOEXTEND' or 'REPEAT'.
555
-
556
-
557
- **Attributes used when ``SLUICE.control_method == 'TIME'``**
558
-
559
- Args:
560
- gates (List[pandas.Series]): List of Data series representing the gate control with 'Time' index and 'Gate Opening' (m) as the data
561
-
562
-
563
- **Attributes used when ``SLUICE.control_method == 'LOGICAL'``**
564
-
565
- Args:
566
- gates (List[pandas.DataFrame]): List of Dataframes representing the gate control with 'Time' as index and 'Mode' and 'Gate Opening' (m) as the columns.
567
- The Mode is set to 'AUTO', 'MANUAL' or [blank] depending on the control mode at that time.
568
- max_movement_rate (float): Maximum movement rate of the structure
569
- max_setting (float): Maximum setting of the structure
570
- min_setting (float): Minimum setting of the structure
571
- rules (List[dict]): List of logical rules to use. Each rule is represented as a Dictionary with keys 'name' and 'logic'.
572
- time_rule_data (pandas.Series): Series containing data on which operating rules to apply, with index of 'Time' and
573
- dataseries for 'Operating Rules'
574
- varrules (List[dict]): List of logical variable rules to use. Each varrule is represented as a Dictionary with keys 'name' and 'logic'.
575
- time_varrule_data (pandas.Series): Series containing data on which operating rules to apply, with index of 'Time' and
576
- dataseries for 'Operating Rules'
577
-
578
-
579
- **Radial Type (``SLUICE.subtype == 'RADIAL'``)**
580
-
581
- Args:
582
- use_degrees (bool): Whether to measure gate movement in degrees
583
- allow_free_flow_under (bool): Whether to allow free flow under gate
584
- pivot_height (float): Height of gate pivot (m) above sill
585
- gate_radius (float): Distance from gate pivot to surface (m)
586
-
587
- Returns:
588
- SLUICE: Flood Modeller SLUICE Unit class object
589
- """
590
-
591
- _unit = "SLUICE"
592
-
593
- def _read(self, block):
594
- """Function to read a given SLUICE block and store data as class attributes"""
595
- self._subtype = block[1].split(" ")[0].strip()
596
-
597
- # Extends label line to be correct length before splitting to pick up blank labels
598
- labels = split_n_char(f"{block[2]:<{3*self._label_len}}", self._label_len)
599
- self.name = labels[0]
600
- self.ds_label = labels[1]
601
- self.remote_label = labels[2]
602
- self.comment = block[0].replace("SLUICE", "").strip()
603
-
604
- # First parameter line
605
- params1 = split_10_char(f"{block[3]:<80}")
606
- self.weir_flow_coefficient = _to_float(params1[0], 1.0)
607
- self.under_gate_flow = _to_float(params1[1], 1.0)
608
- self.weir_breadth = _to_float(params1[2])
609
- self.crest_elevation = _to_float(params1[3])
610
- self.gate_height_or_chord = _to_float(params1[4])
611
- self.weir_length = _to_float(params1[5])
612
- if self.subtype == "RADIAL":
613
- self.use_degrees = True if params1[6] == "DEGREES" else False
614
- self.allow_free_flow_under = True if params1[7] == "FREESLUICE" else False
615
-
616
- # Second parameter line
617
- params2 = split_10_char(f"{block[4]:<70}")
618
- self.us_weir_height = _to_float(params2[0])
619
- self.ds_weir_height = _to_float(params2[1])
620
- self.bias_factor = _to_float(params2[2], 1.0)
621
- self.over_gate_flow = _to_float(params2[3], 1.0)
622
- if self.subtype == "RADIAL":
623
- self.pivot_height = _to_float(params2[4], 0.7)
624
- self.gate_radius = _to_float(params2[5], 0.7)
625
- else:
626
- self.modular_limits = {
627
- "weir_flow": _to_float(params2[4]),
628
- "under_gate_flow": _to_float(params2[5], 1.0),
629
- "over_gate_flow": _to_float(params2[6], 1.0),
630
- }
631
-
632
- # Third parameter line
633
- params3 = split_10_char(f"{block[5]:<60}")
634
- self.ngates = int(params3[0]) # number of gates
635
- if self.subtype == "RADIAL":
636
- self.modular_limits = {
637
- "weir_flow": _to_float(params3[1]),
638
- "under_gate_flow": _to_float(params3[2], 1.0),
639
- "over_gate_flow": _to_float(params3[3], 1.0),
640
- }
641
- self.timeunit = _to_str(params3[4], "SECONDS", check_float=True)
642
- self.extendmethod = _to_str(params3[5], "EXTEND")
643
- else:
644
- self.timeunit = _to_str(params3[1], "SECONDS", check_float=True)
645
- self.extendmethod = _to_str(params3[2], "EXTEND")
646
-
647
- # Control lines
648
- self.control_method = block[6].split()[0].upper()
649
- if self.control_method == "TIME":
650
- self.gates = self._get_gates(self.ngates, block, gate_row=7)
651
-
652
- elif self.control_method == "LOGICAL":
653
- logical_params = split_10_char(block[6])
654
- self.max_movement_rate = logical_params[1]
655
- self.max_setting = logical_params[2]
656
- self.min_setting = logical_params[3]
657
- self.gates = self._get_gates(self.ngates, block, gate_row=7)
658
- self._read_rules(block)
659
-
660
- else:
661
- self._raw_extra_lines = block[6:]
662
- print(
663
- f"Note: Sluice control using method: {self.control_method} is not currently supported."
664
- )
665
-
666
- def _write(self):
667
- """Function to write a valid SLUICE block"""
668
- _validate_unit(self) # Function to check the params are valid for CONDUIT unit
669
- header = "SLUICE " + self.comment
670
- labels = join_n_char_ljust(self._label_len, self.name, self.ds_label, self.remote_label)
671
- block = [header, self.subtype, labels]
672
-
673
- # First parameter line
674
- params1 = join_10_char(
675
- self.weir_flow_coefficient,
676
- self.under_gate_flow,
677
- self.weir_breadth,
678
- self.crest_elevation,
679
- self.gate_height_or_chord,
680
- self.weir_length,
681
- )
682
- if self.subtype == "RADIAL":
683
- params1 += f'{"DEGREES":<10}' if self.use_degrees else f'{"":<10}'
684
- params1 += "FREESLUICE" if self.allow_free_flow_under else f'{"":<10}'
685
-
686
- # Second parameter line
687
- params2 = join_10_char(
688
- self.us_weir_height,
689
- self.ds_weir_height,
690
- self.bias_factor,
691
- self.over_gate_flow,
692
- )
693
-
694
- if self.subtype == "RADIAL":
695
- params2 += join_10_char(self.pivot_height, self.gate_radius)
696
- else:
697
- params2 += join_10_char(
698
- self.modular_limits["weir_flow"],
699
- self.modular_limits["under_gate_flow"],
700
- self.modular_limits["over_gate_flow"],
701
- )
702
-
703
- # Third parameter line
704
- self.ngates = len(self.gates)
705
- params3 = join_10_char(self.ngates)
706
-
707
- if self.subtype == "RADIAL":
708
- params3 += join_10_char(
709
- self.modular_limits["weir_flow"],
710
- self.modular_limits["under_gate_flow"],
711
- self.modular_limits["over_gate_flow"],
712
- )
713
-
714
- params3 += join_n_char_ljust(10, self.timeunit, self.extendmethod)
715
-
716
- block.extend([params1, params2, params3])
717
-
718
- # Control lines
719
- if self.control_method == "TIME":
720
- block.append("TIME")
721
- n = 1
722
- for gate in self.gates:
723
- block.append(f"GATE {n}")
724
- nrows = len(gate)
725
- block.append(f"{nrows:>10}")
726
- gate_data = [f"{join_10_char(t, o)}" for t, o in gate.items()]
727
- block.extend(gate_data)
728
- n += 1
729
-
730
- elif self.control_method == "LOGICAL":
731
- # ADD GATES
732
- block.append(
733
- join_10_char(
734
- f'{"LOGICAL":<10}',
735
- self.max_movement_rate,
736
- self.max_setting,
737
- self.min_setting,
738
- )
739
- )
740
- n = 1
741
- for gate in self.gates:
742
- block.append(f"GATE {n}")
743
- nrows = len(gate)
744
- block.append(f"{nrows:>10}")
745
- gate_data = [f"{join_10_char(t, m, o)}" for t, m, o in gate.itertuples()]
746
- block.extend(gate_data)
747
- n += 1
748
- block = self._write_rules(block)
749
-
750
- else:
751
- block.extend(self._raw_extra_lines)
752
-
753
- return block
754
-
755
- def _get_gates(self, ngates, block, gate_row):
756
- gates = []
757
-
758
- if self.control_method == "TIME":
759
- for gate in range(ngates):
760
- nrows = int(split_10_char(block[gate_row + 1])[0])
761
- data_list = []
762
- for row in block[gate_row + 2 : gate_row + 2 + nrows]:
763
- row_split = split_10_char(f"{row:<20}")
764
- x = _to_float(row_split[0]) # time
765
- y = _to_float(row_split[1]) # opening
766
- data_list.append([x, y])
767
-
768
- gate_data = pd.DataFrame(data_list, columns=["Time", "Opening"])
769
- gate_data = gate_data.set_index("Time")
770
- gate_data = gate_data["Opening"]
771
-
772
- gates.append(gate_data)
773
-
774
- gate_row += 2 + nrows
775
-
776
- self._last_gate_row = gate_row
777
-
778
- return gates
779
-
780
- elif self.control_method == "LOGICAL":
781
- for gate in range(ngates):
782
- nrows = int(split_10_char(block[gate_row + 1])[0])
783
- data_list = []
784
- for row in block[gate_row + 2 : gate_row + 2 + nrows]:
785
- row_split = split_10_char(f"{row:<30}")
786
- x = _to_float(row_split[0]) # time
787
- y = row_split[1] # mode
788
- z = _to_float(row_split[2]) # opening
789
- data_list.append([x, y, z])
790
-
791
- gate_data = pd.DataFrame(data_list, columns=["Time", "Mode", "Opening"])
792
- gate_data = gate_data.set_index("Time")
793
-
794
- gates.append(gate_data)
795
-
796
- gate_row += 2 + nrows
797
-
798
- self._last_gate_row = gate_row
799
-
800
- return gates
801
-
802
-
803
- class ORIFICE(Unit):
804
- """Class to hold and process ORIFICE unit type
805
-
806
- Args:
807
- name (str, optional): Unit name.
808
- comment (str, optional): Comment included in unit.
809
- flapped (bool, optional): ``True`` if orifice is flapped, ``False`` if orifice is open
810
- ds_label (str, optional): Downstream label
811
- invert (float, optional): Throat invert level
812
- soffit (float, optional): Throat soffit level
813
- bore_area (float, optional): Cross sectional area of throat opening
814
- upstream_sill (float, optional): Upstream sill level
815
- downstream_sill (float, optional): Downstream sill level
816
- shape (str, optional): Shape of orifice aperture ('RECTANGLE' or 'CIRCULAR')
817
- weir_flow (float, optional): Calibration factor for weir flow
818
- surcharged_flow (float, optional): Calibration factor for surcharged flow
819
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode
820
-
821
-
822
- Returns:
823
- ORIFICE: Flood Modeller ORIFICE Unit class object
824
- """
825
-
826
- _unit = "ORIFICE"
827
-
828
- def _read(self, block):
829
- """Function to read a given ORIFICE block and store data as class attributes"""
830
- self._subtype = block[1].split(" ")[0].strip()
831
- self.flapped = True if self.subtype == "FLAPPED" else False
832
-
833
- # Extends label line to be correct length before splitting to pick up blank labels
834
- labels = split_n_char(f"{block[2]:<{2*self._label_len}}", self._label_len)
835
- self.name = labels[0]
836
- self.ds_label = labels[1]
837
- self.comment = block[0].replace("ORIFICE", "").strip()
838
-
839
- # First parameter line
840
- params1 = split_10_char(f"{block[3]:<60}")
841
- self.invert = _to_float(params1[0])
842
- self.soffit = _to_float(params1[1])
843
- self.bore_area = _to_float(params1[2])
844
- self.upstream_sill = _to_float(params1[3])
845
- self.downstream_sill = _to_float(params1[4])
846
- self.shape = _to_str(params1[5], "RECTANGLE")
847
-
848
- # Second parameter line
849
- params2 = split_10_char(block[4])
850
- self.weir_flow = _to_float(params2[0], 1.0)
851
- self.surcharged_flow = _to_float(params2[1], 1.0)
852
- self.modular_limit = _to_float(params2[2], 0.7)
853
-
854
- def _write(self):
855
- """Function to write a valid ORIFICE block"""
856
- _validate_unit(self) # Function to check the params are valid for CONDUIT unit
857
- header = "ORIFICE " + self.comment
858
- labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
859
-
860
- self._subtype = "FLAPPED" if self.flapped else "OPEN"
861
- block = [header, self.subtype, labels]
862
-
863
- # First parameter line
864
- params1 = join_10_char(
865
- self.invert,
866
- self.soffit,
867
- self.bore_area,
868
- self.upstream_sill,
869
- self.downstream_sill,
870
- self.shape,
871
- )
872
-
873
- # Second parameter line
874
- params2 = join_10_char(self.weir_flow, self.surcharged_flow, self.modular_limit)
875
-
876
- block.extend([params1, params2])
877
-
878
- return block
879
-
880
- def _create_from_blank(
881
- self,
882
- name="new_orifice",
883
- flapped=False,
884
- ds_label="",
885
- comment="",
886
- invert=0.0,
887
- soffit=0.0,
888
- bore_area=1.0,
889
- upstream_sill=0.0,
890
- downstream_sill=0.0,
891
- shape="RECTANGLE",
892
- weir_flow=1.0,
893
- surcharged_flow=1.0,
894
- modular_limit=0.7,
895
- ):
896
- for param, val in {
897
- "name": name,
898
- "flapped": flapped,
899
- "ds_label": ds_label,
900
- "comment": comment,
901
- "invert": invert,
902
- "soffit": soffit,
903
- "bore_area": bore_area,
904
- "upstream_sill": upstream_sill,
905
- "downstream_sill": downstream_sill,
906
- "shape": shape,
907
- "weir_flow": weir_flow,
908
- "surcharged_flow": surcharged_flow,
909
- "modular_limit": modular_limit,
910
- }.items():
911
- setattr(self, param, val)
912
-
913
-
914
- class SPILL(Unit):
915
- """Class to hold and process SPILL unit type
916
-
917
- Args:
918
- name (str, optional): Unit name.
919
- comment (str, optional): Comment included in unit.
920
- ds_label (str, optional): Downstream label
921
- weir_coefficient (float, optional): Weir coefficient
922
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode
923
- data (pandas.DataFrame): Dataframe object containing all the spill section data.
924
- Columns are ``'X', 'Y', 'Easting', 'Northing'``
925
-
926
- Returns:
927
- SPILL: Flood Modeller SPILL Unit class object
928
- """
929
-
930
- _unit = "SPILL"
931
-
932
- def _read(self, block):
933
- """Function to read a given SPILL block and store data as class attributes"""
934
- # Extends label line to be correct length before splitting to pick up blank labels
935
- labels = split_n_char(f"{block[1]:<{2*self._label_len}}", self._label_len)
936
- self.name = labels[0]
937
- self.ds_label = labels[1]
938
- self.comment = block[0].replace("SPILL", "").strip()
939
-
940
- # First parameter line
941
- params = split_10_char(block[2])
942
- self.weir_coefficient = _to_float(params[0], 1.2)
943
- self.modular_limit = _to_float(params[1], 0.9)
944
-
945
- # Spill section data
946
- data_list = []
947
- for row in block[4:]:
948
- row_split = split_10_char(f"{row:<40}")
949
- x = _to_float(row_split[0]) # chainage
950
- y = _to_float(row_split[1]) # elevation
951
- e = _to_float(row_split[2]) # easting
952
- n = _to_float(row_split[3]) # northing
953
-
954
- data_list.append([x, y, e, n])
955
-
956
- spill_data = pd.DataFrame(data_list, columns=["X", "Y", "Easting", "Northing"])
957
- self.data = spill_data
958
-
959
- def _write(self):
960
- """Function to write a valid SPILL block"""
961
- _validate_unit(self) # Function to check the params are valid for CONDUIT unit
962
- header = "SPILL " + self.comment
963
- labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
964
- block = [header, labels]
965
-
966
- # First parameter line
967
- params = join_10_char(self.weir_coefficient, self.modular_limit)
968
- block.append(params)
969
-
970
- # Section data
971
- nrows = len(self.data)
972
- block.append(join_10_char(nrows))
973
- section_data = [join_10_char(x, y, e, n) for _, x, y, e, n in self.data.itertuples()]
974
- block.extend(section_data)
975
-
976
- return block
977
-
978
- def _create_from_blank(
979
- self,
980
- name="new_spill",
981
- ds_label="",
982
- comment="",
983
- weir_coefficient=1.2,
984
- modular_limit=0.9,
985
- data=None,
986
- ):
987
- for param, val in {
988
- "name": name,
989
- "ds_label": ds_label,
990
- "comment": comment,
991
- "weir_coefficient": weir_coefficient,
992
- "modular_limit": modular_limit,
993
- }.items():
994
- setattr(self, param, val)
995
-
996
- self.data = (
997
- data
998
- if isinstance(data, pd.DataFrame)
999
- else pd.DataFrame([[0.0, 0.0, 0.0, 0.0]], columns=["X", "Y", "Easting", "Northing"])
1000
- )
1001
-
1002
-
1003
- class RNWEIR(Unit):
1004
- """Class to hold and process RNWEIR unit type
1005
-
1006
- Args:
1007
- name (str, optional): Upstream label name.
1008
- comment (str, optional): Comment included in unit.
1009
- ds_label (str, optional): Downstream label.
1010
- velocity_coefficient (float, optional): Coefficient of approach velocity.
1011
- weir_length (float, optional): Length of weir crest in the direction of flow (m).
1012
- weir_breadth (float, optional): Breadth of weir at control section (normal to the flow direction)(m).
1013
- weir_elevation (float, optional): Elevation of weir crest (m AD).
1014
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode.
1015
- upstream_crest_height (float, optional): Height of crest above bed of upstream channnel (m).
1016
- downstream_crest_height (float, optional): Height of crest above downstream channel (m).
1017
-
1018
- Returns:
1019
- RNWEIR: Flood Modeller RNWEIR Unit class object"""
1020
-
1021
- _unit = "RNWEIR"
1022
-
1023
- def _read(self, block):
1024
- """Function to read a given RNWEIR block and store data as class attributes"""
1025
- # Extends label line to be correct length before splitting to pick up blank labels
1026
- labels = split_n_char(f"{block[1]:<{2*self._label_len}}", self._label_len)
1027
- self.name = labels[0]
1028
- self.ds_label = labels[1]
1029
- self.comment = block[0].replace("RNWEIR", "").strip()
1030
-
1031
- # First parameter line
1032
- params1 = split_10_char(f"{block[2]:<50}")
1033
- self.velocity_coefficient = _to_float(params1[0])
1034
- self.weir_length = _to_float(params1[1])
1035
- self.weir_breadth = _to_float(params1[2])
1036
- self.weir_elevation = _to_float(params1[3])
1037
- self.modular_limit = _to_float(params1[4])
1038
-
1039
- # Second parameter line
1040
- params2 = split_10_char(f"{block[3]:<20}")
1041
- self.upstream_crest_height = _to_float(params2[0])
1042
- self.downstream_crest_height = _to_float(params2[1])
1043
-
1044
- def _write(self):
1045
- """Function to write a valid RNWEIR block"""
1046
- _validate_unit(self)
1047
- header = "RNWEIR " + self.comment
1048
- labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
1049
- block = [header, labels]
1050
-
1051
- # First parameter line
1052
- if self.modular_limit == 0.0:
1053
- params1 = join_10_char(
1054
- self.velocity_coefficient,
1055
- self.weir_length,
1056
- self.weir_breadth,
1057
- self.weir_elevation,
1058
- )
1059
- else:
1060
- params1 = join_10_char(
1061
- self.velocity_coefficient,
1062
- self.weir_length,
1063
- self.weir_breadth,
1064
- self.weir_elevation,
1065
- self.modular_limit,
1066
- )
1067
-
1068
- block.append(params1)
1069
-
1070
- # Second parameter line
1071
- params2 = join_10_char(self.upstream_crest_height, self.downstream_crest_height)
1072
- block.append(params2)
1073
-
1074
- return block
1075
-
1076
- def _create_from_blank(
1077
- self,
1078
- name="new_rnweir",
1079
- comment="",
1080
- ds_label="",
1081
- velocity_coefficient=1.0,
1082
- modular_limit=0.7,
1083
- upstream_crest_height=0.0,
1084
- downstream_crest_height=0.0,
1085
- weir_length=0.0,
1086
- weir_breadth=0.0,
1087
- weir_elevation=0.0,
1088
- ):
1089
- for param, val in {
1090
- "name": name,
1091
- "comment": comment,
1092
- "ds_label": ds_label,
1093
- "velocity_coefficient": velocity_coefficient,
1094
- "modular_limit": modular_limit,
1095
- "upstream_crest_height": upstream_crest_height,
1096
- "downstream_crest_height": downstream_crest_height,
1097
- "weir_length": weir_length,
1098
- "wier_breadth": weir_breadth,
1099
- "weir_elevation": weir_elevation,
1100
- }.items():
1101
- setattr(self, param, val)
1102
-
1103
-
1104
- class WEIR(Unit):
1105
- """Class to hold and process WEIR unit type
1106
-
1107
- Args:
1108
- name (str, optional): Upstream label name.
1109
- comment (str, optional): Comment included in unit.
1110
- ds_label (str, optional): Downstream label.
1111
- exponent (float, optional): Coefficient of discharge for the weir,
1112
- discharge_coefficeient (float, optional): Exponent used in the weir flow equation,
1113
- velocity_coefficient (float, optional): Coefficient of approach velocity.
1114
- weir_length (float, optional): Length of weir crest in the direction of flow (m).
1115
- weir_breadth (float, optional): Breadth of weir at control section (normal to the flow direction)(m).
1116
- weir_elevation (float, optional): Elevation of weir crest (m AD).
1117
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode.
1118
-
1119
- Returns:
1120
- WEIR: Flood Modeller WEIR Unit class object"""
1121
-
1122
- _unit = "WEIR"
1123
-
1124
- def _read(self, block):
1125
- """Function to read a given WEIR block and store data as class attributes"""
1126
- # Extends label line to be correct length before splitting to pick up blank labels
1127
- labels = split_n_char(f"{block[1]:<{2*self._label_len}}", self._label_len)
1128
- self.name = labels[0]
1129
- self.ds_label = labels[1]
1130
- self.comment = block[0].replace("WEIR", "").strip()
1131
-
1132
- # Exponent
1133
- self.exponent = _to_float(block[2].strip())
1134
-
1135
- # Parameters line
1136
- params = split_10_char(f"{block[3]:<50}")
1137
- self.discharge_coefficient = _to_float(params[0])
1138
- self.velocity_coefficient = _to_float(params[1])
1139
- self.weir_breadth = _to_float(params[2])
1140
- self.weir_elevation = _to_float(params[3])
1141
- self.modular_limit = _to_float(params[4])
1142
-
1143
- def _write(self):
1144
- """Function to write a valid WEIR block"""
1145
- _validate_unit(self)
1146
- header = "WEIR " + self.comment
1147
- labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
1148
- block = [header, labels]
1149
-
1150
- # Exponent line
1151
- exp_line = join_10_char(self.exponent)
1152
- block.append(exp_line)
1153
-
1154
- # Parameter line
1155
- params = join_10_char(
1156
- self.discharge_coefficient,
1157
- self.velocity_coefficient,
1158
- self.weir_breadth,
1159
- self.weir_elevation,
1160
- self.modular_limit,
1161
- )
1162
- block.append(params)
1163
-
1164
- return block
1165
-
1166
- def _create_from_blank(
1167
- self,
1168
- name="new_weir",
1169
- comment="",
1170
- ds_label="",
1171
- exponent=1.5,
1172
- discharge_coefficeient=1.0,
1173
- velocity_coefficient=1.0,
1174
- modular_limit=0.7,
1175
- weir_length=0.0,
1176
- weir_breadth=0.0,
1177
- weir_elevation=0.0,
1178
- ):
1179
- for param, val in {
1180
- "name": name,
1181
- "comment": comment,
1182
- "ds_label": ds_label,
1183
- "exponent": exponent,
1184
- "discharge_coefficeient": discharge_coefficeient,
1185
- "velocity_coefficient": velocity_coefficient,
1186
- "modular_limit": modular_limit,
1187
- "weir_length": weir_length,
1188
- "wier_breadth": weir_breadth,
1189
- "weir_elevation": weir_elevation,
1190
- }.items():
1191
- setattr(self, param, val)
1192
-
1193
-
1194
- class CRUMP(Unit):
1195
- """Class to hold and process CRUMP unit type
1196
-
1197
- Args:
1198
-
1199
- name (str, optional): Upstream label name.
1200
- comment (str,optional): Comment included in unit.
1201
- calibration_coefficient (float, optional): Calibration coefficient (should be set to unity for most cases).
1202
- weir_breadth (float, optional): Breadth of weir at crest (m).
1203
- weir_elevation (float, optional): Eleveation of weir crest (m above datum).
1204
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode.
1205
- upstream_crest_height (float, optional): Height of crest above bed of upstream channel (m).
1206
- downstream_crest_height (float, optional): Height oof crest above bed of downstream channel (m).
1207
- ds_label (str, optional): Downstream node label.
1208
- 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.
1209
- 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.
1210
-
1211
- Returns:
1212
- CRUMP: Flood Modeller CRUMP Unit class object"""
1213
-
1214
- _unit = "CRUMP"
1215
-
1216
- def _read(self, block):
1217
- """Function to read a given CRUMP block and store data as class attributes"""
1218
- # Extends label line to be correct length before splitting to pick up blank labels
1219
- labels = split_n_char(f"{block[1]:<{4*self._label_len}}", self._label_len)
1220
- self.name = labels[0]
1221
- self.ds_label = labels[1]
1222
- self.us_remote_label = labels[2]
1223
- self.ds_remote_label = labels[3]
1224
- self.comment = block[0].replace("CRUMP", "").strip()
1225
-
1226
- # First parameter line
1227
- params1 = split_10_char(f"{block[2]:<40}")
1228
- self.calibration_coefficient = _to_float(params1[0])
1229
- self.weir_breadth = _to_float(params1[1])
1230
- self.weir_elevation = _to_float(params1[2])
1231
- self.modular_limit = _to_float(params1[3])
1232
-
1233
- # Second parameter line
1234
- params2 = split_10_char(f"{block[3]:<20}")
1235
- self.upstream_crest_height = _to_float(params2[0])
1236
- self.downstream_crest_height = _to_float(params2[1])
1237
-
1238
- def _write(self):
1239
- """Function to write a valid CRUMP block"""
1240
- _validate_unit(self)
1241
- header = "CRUMP " + self.comment
1242
- labels = join_n_char_ljust(
1243
- self._label_len,
1244
- self.name,
1245
- self.ds_label,
1246
- self.us_remote_label,
1247
- self.ds_remote_label,
1248
- )
1249
- block = [header, labels]
1250
-
1251
- # First parameter line
1252
- params1 = join_10_char(
1253
- self.calibration_coefficient,
1254
- self.weir_breadth,
1255
- self.weir_elevation,
1256
- self.modular_limit if self.modular_limit != 0.0 else "",
1257
- )
1258
-
1259
- block.append(params1)
1260
-
1261
- # Second parameter line
1262
- params2 = join_10_char(self.upstream_crest_height, self.downstream_crest_height)
1263
- block.append(params2)
1264
-
1265
- return block
1266
-
1267
- def _create_from_blank(
1268
- self,
1269
- name="new_crump",
1270
- comment="",
1271
- calibration_coefficient=1.0,
1272
- weir_breadth=0.0,
1273
- weir_elevation=0.0,
1274
- modular_limit=0.7,
1275
- upstream_crest_height=0.0,
1276
- downstream_crest_height=0.0,
1277
- ds_label="",
1278
- us_remote_label="",
1279
- ds_remote_label="",
1280
- ):
1281
- for param, val in {
1282
- "name": name,
1283
- "comment": comment,
1284
- "calibration_coefficient": calibration_coefficient,
1285
- "weir_breadth": weir_breadth,
1286
- "weir_elevation": weir_elevation,
1287
- "modular_limit": modular_limit,
1288
- "upstream_crest_height": upstream_crest_height,
1289
- "downstream_crest_height": downstream_crest_height,
1290
- "ds_label": ds_label,
1291
- "us_remote_label": us_remote_label,
1292
- "ds_remote_label": ds_remote_label,
1293
- }.items():
1294
- setattr(self, param, val)
1295
-
1296
-
1297
- class FLAT_V_WEIR(Unit): # noqa: N801
1298
- """Class to hold and process FLAT-V WEIR unit type
1299
-
1300
- Args:
1301
-
1302
- name (str, optional): Upstream label name.
1303
- comment (str,optional): Comment included in unit.
1304
- ds_label (str, optional): Downstream node label.
1305
- 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.
1306
- 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.
1307
- weir_elevation (float, optional): Eleveation of weir crest (m above datum).
1308
- weir_breadth (float, optional): Breadth of weir at crest (m).
1309
- v_slope (float, optional): 'V' slope (horizontal distance/vertical distance).
1310
- side_slope (float, optional): Channel side slope (horizontal distance/vertical distance).
1311
- upstream_crest_height (float, optional): Weir crest height above upstream bed (m).
1312
- downstream_crest_height (float, optional): Weir crest height above downstream bed (m).
1313
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode.
1314
- calibration_coefficient (float, optional): Calibration coefficient (should be set to unity for most cases).
1315
- 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.
1316
- coriolis_coefficient (float, optional): Coriolis energy coefficient.
1317
- bank_top_elevation (float, optional): Elevation of channel bank top/ limit of extent of sloping channel walls (m AD).
1318
-
1319
- Returns:
1320
- FLAT_V_WEIR: Flood Modeller FLAT-V WEIR Unit class object"""
1321
-
1322
- _unit = "FLAT-V WEIR"
1323
-
1324
- def _read(self, block):
1325
- """Function to read a given FLAT-V WEIR block and store data as class attributes"""
1326
- # Extends label line to be correct length before splitting to pick up blank labels
1327
- labels = split_n_char(f"{block[1]:<{4*self._label_len}}", self._label_len)
1328
- self.name = labels[0]
1329
- self.ds_label = labels[1]
1330
- self.us_remote_label = labels[2]
1331
- self.ds_remote_label = labels[3]
1332
- self.comment = block[0].replace("FLAT-V WEIR", "").strip()
1333
-
1334
- # First parameter line
1335
- params1 = split_10_char(f"{block[2]:<90}")
1336
- self.calibration_coefficient = _to_float(params1[0])
1337
- self.weir_breadth = _to_float(params1[1])
1338
- self.weir_elevation = _to_float(params1[2])
1339
- self.modular_limit = _to_float(params1[3])
1340
- self.v_slope = _to_float(params1[4])
1341
- self.side_slope = _to_float(params1[5])
1342
- self.ds_face_slope = _to_float(params1[6])
1343
- self.coriolis_coefficient = _to_float(params1[7])
1344
- self.bank_top_elevation = _to_float(params1[8])
1345
-
1346
- # Second parameter line
1347
- params2 = split_10_char(f"{block[3]:<20}")
1348
- self.upstream_crest_height = _to_float(params2[0])
1349
- self.downstream_crest_height = _to_float(params2[1])
1350
-
1351
- def _write(self):
1352
- """Function to write a valid FLAT-V WEIR block"""
1353
-
1354
- _validate_unit(self)
1355
- header = "FLAT-V WEIR " + self.comment
1356
- labels = join_n_char_ljust(
1357
- self._label_len,
1358
- self.name,
1359
- self.ds_label,
1360
- self.us_remote_label,
1361
- self.ds_remote_label,
1362
- )
1363
- block = [header, labels]
1364
-
1365
- params1 = join_10_char(
1366
- self.calibration_coefficient,
1367
- self.weir_breadth,
1368
- self.weir_elevation,
1369
- self.modular_limit if self.modular_limit != 0.0 else "",
1370
- self.v_slope,
1371
- self.side_slope,
1372
- self.ds_face_slope,
1373
- self.coriolis_coefficient,
1374
- self.bank_top_elevation,
1375
- )
1376
-
1377
- block.append(params1)
1378
-
1379
- # Second parameter line
1380
- params2 = join_10_char(self.upstream_crest_height, self.downstream_crest_height)
1381
- block.append(params2)
1382
-
1383
- return block
1384
-
1385
- def _create_from_blank(
1386
- self,
1387
- name="new_flat_v",
1388
- comment="",
1389
- ds_label="",
1390
- us_remote_label="",
1391
- ds_remote_label="",
1392
- weir_elevation=0.0,
1393
- weir_breadth=0.0,
1394
- v_slope=0.0,
1395
- side_slope=0.0,
1396
- upstream_crest_height=0.0,
1397
- downstream_crest_height=0.0,
1398
- modular_limit=0.0,
1399
- calibration_coefficient=1.0,
1400
- ds_face_slope=5,
1401
- coriolis_coefficient=1.2,
1402
- bank_top_elevation=0.0,
1403
- ):
1404
- for param, val in {
1405
- "name": name,
1406
- "comment": comment,
1407
- "ds_label": ds_label,
1408
- "us_remote_label": us_remote_label,
1409
- "ds_remote_label": ds_remote_label,
1410
- "weir_elevation": weir_elevation,
1411
- "weir_breadth": weir_breadth,
1412
- "v_slope": v_slope,
1413
- "side_slope": side_slope,
1414
- "upstream_crest_height": upstream_crest_height,
1415
- "downstream_crest_height": downstream_crest_height,
1416
- "modular_limit": modular_limit,
1417
- "calibration_coefficient": calibration_coefficient,
1418
- "ds_face_slope": ds_face_slope,
1419
- "coriolis_coefficient": coriolis_coefficient,
1420
- "bank_top_elevation": bank_top_elevation,
1421
- }.items():
1422
- setattr(self, param, val)
1423
-
1424
-
1425
- class RESERVOIR(Unit): # NOT CURRENTLY IN USE
1426
- """Class to hold and process RESERVOIR unit type
1427
-
1428
- Args:
1429
- name (str, optional): Unit name.
1430
- comment (str, optional): Comment included in unit.
1431
- all_labels (str, optional): Unlimited number of labels - not including first label (name).
1432
- easting (float, optional): Easting coordinate of reservoir reference point (not used in computations).
1433
- northing (float, optional): Northing coordinate of reservoir reference point (not used in computations).
1434
- runoff_factor (float, optional): Rainfall runoff factor.
1435
- num_pairs (float, optional): Number of elevation/area pairs.
1436
- lat1 (str, optional): First lateral inflow label.
1437
- lat2 (str, optional): Second lateral inflow label.
1438
- lat3 (str, optional): Third lateral inflow label.
1439
- lat4 (str, optional): Fourth lateral inflow label.
1440
- data (pandas.DataFrame): Dataframe object containing all the reservoir section data.
1441
- Columns are ``'Elevation','Plan Area'``
1442
-
1443
- Returns:
1444
- RESERVOIR: Flood Modeller RESERVOIR Unit class object"""
1445
-
1446
- _unit = "RESERVOIR"
1447
-
1448
- def _read(self, block):
1449
- """Function to read a given RESERVOIR WEIR block and store data as class attributes"""
1450
-
1451
- # Extends label line to be correct length before splitting to pick up blank labels
1452
- num_labels = len(block[1]) // self._label_len
1453
- labels = split_n_char(f"{block[1]:<{num_labels*self._label_len}}", self._label_len)
1454
- self.name = labels[0]
1455
- self.all_labels = labels[0 : len(labels)]
1456
- self.comment = block[0].replace("RESERVOIR", "").strip()
1457
-
1458
- # Option 1 (runs if comment == "#revision#1")
1459
- if self.comment == "#revision#1":
1460
- # Lateral inflow labels
1461
- lateral_labels = split_n_char(f"{block[2]:<{4*self._label_len}}", self._label_len)
1462
- self.lat1 = lateral_labels[0]
1463
- self.lat2 = lateral_labels[1]
1464
- self.lat3 = lateral_labels[2]
1465
- self.lat4 = lateral_labels[3]
1466
-
1467
- # Number of pairs of data
1468
- self.num_pairs = _to_int(block[3])
1469
-
1470
- # Reservoir section data
1471
- data_list = []
1472
- for row in block[4 : len(block) - 1]:
1473
- row_split = split_10_char(f"{row:<20}")
1474
- elevation = _to_float(row_split[0]) # elevation
1475
- plan_area = _to_float(row_split[1]) # plan area
1476
- data_list.append([elevation, plan_area])
1477
- reservoir_data = pd.DataFrame(data_list, columns=["Elevation", "Plan Area"])
1478
- self.data = reservoir_data
1479
-
1480
- # Coordinate data
1481
- coordinate_data = split_n_char(
1482
- f"{block[len(block)-1]:<{3*self._label_len}}", self._label_len
1483
- )
1484
- self.easting = _to_float(coordinate_data[0])
1485
- self.northing = _to_float(coordinate_data[1])
1486
- self.runoff_factor = _to_float(coordinate_data[2])
1487
- else: # Option 2 (runs if comment != "#revision#1")
1488
- # Number of pairs of data
1489
- self.num_pairs = _to_int(block[2])
1490
-
1491
- # Reservoir section data
1492
- data_list = []
1493
- for row in block[3:]:
1494
- row_split = split_10_char(f"{row:<20}")
1495
- elevation = _to_float(row_split[0]) # elevation
1496
- plan_area = _to_float(row_split[1]) # plan area
1497
- data_list.append([elevation, plan_area])
1498
- reservoir_data = pd.DataFrame(data_list, columns=["Elevation", "Plan Area"])
1499
- self.data = reservoir_data
1500
-
1501
- def _write(self):
1502
- """Function to write a valid RESERVOIR block"""
1503
-
1504
- _validate_unit(self)
1505
- header = "RESERVOIR " + self.comment
1506
- self.labels = " ".join(self.all_labels)
1507
- block = [header, self.labels]
1508
-
1509
- # Option 1 (runs if comment == "#revision#1")
1510
- if self.comment == "#revision#1":
1511
- # Lateral inflow labels
1512
- lat_labels = join_12_char_ljust(self.lat1, self.lat2, self.lat3, self.lat4)
1513
- block.append(lat_labels)
1514
-
1515
- # Number of pairs of data
1516
- block.append(join_12_char_ljust(self.num_pairs))
1517
-
1518
- # Reservoir section data
1519
- section_data = [
1520
- join_12_char_ljust(elevation, plan_area)
1521
- for _, elevation, plan_area, in self.data.itertuples()
1522
- ]
1523
- block.extend(section_data)
1524
-
1525
- # Coordinate data
1526
- coords = join_12_char_ljust(self.easting, self.northing, self.runoff_factor)
1527
- block.append(coords)
1528
- else: # Option 2 (runs if comment != "#revision#1")
1529
- # Number of pairs of data
1530
- block.append(join_12_char_ljust(self.num_pairs))
1531
-
1532
- # Reservoir section data
1533
- section_data = [
1534
- join_12_char_ljust(elevation, plan_area)
1535
- for _, elevation, plan_area, in self.data.itertuples()
1536
- ]
1537
- block.extend(section_data)
1538
-
1539
- return block
1540
-
1541
- def _create_from_blank(
1542
- self,
1543
- name="new_reservoir",
1544
- comment="",
1545
- easting=0.0,
1546
- northing=0.0,
1547
- runoff_factor=0.0,
1548
- num_pairs=1,
1549
- data=None,
1550
- lat1="",
1551
- lat2="",
1552
- lat3="",
1553
- lat4="",
1554
- ):
1555
- for param, val in {
1556
- "name": name,
1557
- "comment": comment,
1558
- "easting": easting,
1559
- "northing": northing,
1560
- "runoff_factor": runoff_factor,
1561
- "num_pairs": num_pairs,
1562
- "lat1": lat1,
1563
- "lat2": lat2,
1564
- "lat3": lat3,
1565
- "lat4": lat4,
1566
- }.items():
1567
- setattr(self, param, val)
1568
-
1569
- self.data = (
1570
- data
1571
- if isinstance(data, pd.DataFrame)
1572
- else pd.DataFrame([[0.0, 0.0]], columns=["Elevation", "Plan Area"])
1573
- )
1574
-
1575
-
1576
- class OUTFALL(Unit):
1577
- """Class to hold and process OUTFALL unit type
1578
-
1579
- Args:
1580
- name (str, optional): Unit name.
1581
- comment (str, optional): Comment included in unit.
1582
- flapped (bool, optional): ``True`` if outfall is flapped, ``False`` if outfall is open
1583
- ds_label (str, optional): Downstream label
1584
- invert (float, optional): Throat invert level
1585
- soffit (float, optional): Throat soffit level
1586
- bore_area (float, optional): Cross sectional area of throat opening
1587
- upstream_sill (float, optional): Upstream sill level
1588
- downstream_sill (float, optional): Downstream sill level
1589
- shape (str, optional): Shape of outfall aperture ('RECTANGLE' or 'CIRCULAR')
1590
- weir_flow (float, optional): Calibration factor for weir flow
1591
- surcharged_flow (float, optional): Calibration factor for surcharged flow
1592
- modular_limit (float, optional): Ratio of upstream and downstream heads when switching between free and drowned mode
1593
-
1594
-
1595
- Returns:
1596
- OUTFALL: Flood Modeller OUTFALL Unit class object
1597
- """
1598
-
1599
- _unit = "OUTFALL"
1600
-
1601
- def _read(self, block):
1602
- """Function to read a given OUTFALL block and store data as class attributes"""
1603
- self._subtype = block[1].split(" ")[0].strip()
1604
- self.flapped = True if self.subtype == "FLAPPED" else False
1605
-
1606
- # Extends label line to be correct length before splitting to pick up blank labels
1607
- labels = split_n_char(f"{block[2]:<{2*self._label_len}}", self._label_len)
1608
- self.name = labels[0]
1609
- self.ds_label = labels[1]
1610
- self.comment = block[0].replace("OUTFALL", "").strip()
1611
-
1612
- # First parameter line
1613
- params1 = split_10_char(f"{block[3]:<60}")
1614
- self.invert = _to_float(params1[0])
1615
- self.soffit = _to_float(params1[1])
1616
- self.bore_area = _to_float(params1[2])
1617
- self.upstream_sill = _to_float(params1[3])
1618
- self.downstream_sill = _to_float(params1[4])
1619
- self.shape = _to_str(params1[5], "RECTANGLE")
1620
-
1621
- # Second parameter line
1622
- params2 = split_10_char(block[4])
1623
- self.weir_flow = _to_float(params2[0], 1.0)
1624
- self.surcharged_flow = _to_float(params2[1], 1.0)
1625
- self.modular_limit = _to_float(params2[2], 0.7)
1626
-
1627
- def _write(self):
1628
- """Function to write a valid OUTFALL block"""
1629
- _validate_unit(self) # Function to check the params are valid for CONDUIT unit
1630
- header = "OUTFALL " + self.comment
1631
- labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
1632
-
1633
- self._subtype = "FLAPPED" if self.flapped else "OPEN"
1634
- block = [header, self.subtype, labels]
1635
-
1636
- # First parameter line
1637
- params1 = join_10_char(
1638
- self.invert,
1639
- self.soffit,
1640
- self.bore_area,
1641
- self.upstream_sill,
1642
- self.downstream_sill,
1643
- self.shape,
1644
- )
1645
-
1646
- # Second parameter line
1647
- params2 = join_10_char(self.weir_flow, self.surcharged_flow, self.modular_limit)
1648
-
1649
- block.extend([params1, params2])
1650
-
1651
- return block
1652
-
1653
- def _create_from_blank(
1654
- self,
1655
- name="new_outfall",
1656
- flapped=False,
1657
- ds_label="",
1658
- comment="",
1659
- invert=0.0,
1660
- soffit=0.0,
1661
- bore_area=1.0,
1662
- upstream_sill=0.0,
1663
- downstream_sill=0.0,
1664
- shape="RECTANGLE",
1665
- weir_flow=1.0,
1666
- surcharged_flow=1.0,
1667
- modular_limit=0.7,
1668
- ):
1669
- for param, val in {
1670
- "name": name,
1671
- "flapped": flapped,
1672
- "ds_label": ds_label,
1673
- "comment": comment,
1674
- "invert": invert,
1675
- "soffit": soffit,
1676
- "bore_area": bore_area,
1677
- "upstream_sill": upstream_sill,
1678
- "downstream_sill": downstream_sill,
1679
- "shape": shape,
1680
- "weir_flow": weir_flow,
1681
- "surcharged_flow": surcharged_flow,
1682
- "modular_limit": modular_limit,
1683
- }.items():
1684
- 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)