floodmodeller-api 0.4.2.post1__py3-none-any.whl → 0.4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. floodmodeller_api/__init__.py +8 -9
  2. floodmodeller_api/_base.py +169 -176
  3. floodmodeller_api/backup.py +273 -273
  4. floodmodeller_api/dat.py +889 -831
  5. floodmodeller_api/diff.py +136 -119
  6. floodmodeller_api/ied.py +302 -306
  7. floodmodeller_api/ief.py +553 -637
  8. floodmodeller_api/ief_flags.py +253 -253
  9. floodmodeller_api/inp.py +260 -266
  10. floodmodeller_api/libs/libifcoremd.dll +0 -0
  11. floodmodeller_api/libs/libifcoremt.so.5 +0 -0
  12. floodmodeller_api/libs/libifport.so.5 +0 -0
  13. floodmodeller_api/{libmmd.dll → libs/libimf.so} +0 -0
  14. floodmodeller_api/libs/libintlc.so.5 +0 -0
  15. floodmodeller_api/libs/libmmd.dll +0 -0
  16. floodmodeller_api/libs/libsvml.so +0 -0
  17. floodmodeller_api/libs/libzzn_read.so +0 -0
  18. floodmodeller_api/libs/zzn_read.dll +0 -0
  19. floodmodeller_api/logs/__init__.py +2 -2
  20. floodmodeller_api/logs/lf.py +364 -312
  21. floodmodeller_api/logs/lf_helpers.py +354 -352
  22. floodmodeller_api/logs/lf_params.py +643 -529
  23. floodmodeller_api/mapping.py +84 -0
  24. floodmodeller_api/test/__init__.py +4 -4
  25. floodmodeller_api/test/conftest.py +16 -8
  26. floodmodeller_api/test/test_backup.py +117 -117
  27. floodmodeller_api/test/test_conveyance.py +107 -0
  28. floodmodeller_api/test/test_dat.py +222 -92
  29. floodmodeller_api/test/test_data/All Units 4_6.DAT +1081 -1081
  30. floodmodeller_api/test/test_data/All Units 4_6.feb +1081 -1081
  31. floodmodeller_api/test/test_data/BRIDGE.DAT +926 -926
  32. floodmodeller_api/test/test_data/Culvert_Inlet_Outlet.dat +36 -36
  33. floodmodeller_api/test/test_data/Culvert_Inlet_Outlet.feb +36 -36
  34. floodmodeller_api/test/test_data/DamBreakADI.xml +52 -52
  35. floodmodeller_api/test/test_data/DamBreakFAST.xml +58 -58
  36. floodmodeller_api/test/test_data/DamBreakFAST_dy.xml +53 -53
  37. floodmodeller_api/test/test_data/DamBreakTVD.xml +55 -55
  38. floodmodeller_api/test/test_data/DefenceBreach.xml +53 -53
  39. floodmodeller_api/test/test_data/DefenceBreachFAST.xml +60 -60
  40. floodmodeller_api/test/test_data/DefenceBreachFAST_dy.xml +55 -55
  41. floodmodeller_api/test/test_data/Domain1+2_QH.xml +76 -76
  42. floodmodeller_api/test/test_data/Domain1_H.xml +41 -41
  43. floodmodeller_api/test/test_data/Domain1_Q.xml +41 -41
  44. floodmodeller_api/test/test_data/Domain1_Q_FAST.xml +48 -48
  45. floodmodeller_api/test/test_data/Domain1_Q_FAST_dy.xml +48 -48
  46. floodmodeller_api/test/test_data/Domain1_Q_xml_expected.json +263 -0
  47. floodmodeller_api/test/test_data/Domain1_W.xml +41 -41
  48. floodmodeller_api/test/test_data/EX1.DAT +321 -321
  49. floodmodeller_api/test/test_data/EX1.ext +107 -107
  50. floodmodeller_api/test/test_data/EX1.feb +320 -320
  51. floodmodeller_api/test/test_data/EX1.gxy +107 -107
  52. floodmodeller_api/test/test_data/EX17.DAT +421 -422
  53. floodmodeller_api/test/test_data/EX17.ext +213 -213
  54. floodmodeller_api/test/test_data/EX17.feb +422 -422
  55. floodmodeller_api/test/test_data/EX18.DAT +375 -375
  56. floodmodeller_api/test/test_data/EX18_DAT_expected.json +3876 -0
  57. floodmodeller_api/test/test_data/EX2.DAT +302 -302
  58. floodmodeller_api/test/test_data/EX3.DAT +926 -926
  59. floodmodeller_api/test/test_data/EX3_DAT_expected.json +16235 -0
  60. floodmodeller_api/test/test_data/EX3_IEF_expected.json +61 -0
  61. floodmodeller_api/test/test_data/EX6.DAT +2084 -2084
  62. floodmodeller_api/test/test_data/EX6.ext +532 -532
  63. floodmodeller_api/test/test_data/EX6.feb +2084 -2084
  64. floodmodeller_api/test/test_data/EX6_DAT_expected.json +31647 -0
  65. floodmodeller_api/test/test_data/Event Data Example.DAT +336 -336
  66. floodmodeller_api/test/test_data/Event Data Example.ext +107 -107
  67. floodmodeller_api/test/test_data/Event Data Example.feb +336 -336
  68. floodmodeller_api/test/test_data/Linked1D2D.xml +52 -52
  69. floodmodeller_api/test/test_data/Linked1D2DFAST.xml +53 -53
  70. floodmodeller_api/test/test_data/Linked1D2DFAST_dy.xml +48 -48
  71. floodmodeller_api/test/test_data/Linked1D2D_xml_expected.json +313 -0
  72. floodmodeller_api/test/test_data/blockage.dat +50 -50
  73. floodmodeller_api/test/test_data/blockage.ext +45 -45
  74. floodmodeller_api/test/test_data/blockage.feb +9 -9
  75. floodmodeller_api/test/test_data/blockage.gxy +71 -71
  76. floodmodeller_api/test/test_data/conveyance_test.dat +165 -0
  77. floodmodeller_api/test/test_data/conveyance_test.feb +116 -0
  78. floodmodeller_api/test/test_data/conveyance_test.gxy +85 -0
  79. floodmodeller_api/test/test_data/defaultUnits.dat +127 -127
  80. floodmodeller_api/test/test_data/defaultUnits.ext +45 -45
  81. floodmodeller_api/test/test_data/defaultUnits.feb +9 -9
  82. floodmodeller_api/test/test_data/defaultUnits.fmpx +58 -58
  83. floodmodeller_api/test/test_data/defaultUnits.gxy +85 -85
  84. floodmodeller_api/test/test_data/ex3.ief +20 -20
  85. floodmodeller_api/test/test_data/ex3.lf1 +2800 -2800
  86. floodmodeller_api/test/test_data/ex4.DAT +1374 -1374
  87. floodmodeller_api/test/test_data/ex4_changed.DAT +1374 -1374
  88. floodmodeller_api/test/test_data/example1.inp +329 -329
  89. floodmodeller_api/test/test_data/example2.inp +158 -158
  90. floodmodeller_api/test/test_data/example3.inp +297 -297
  91. floodmodeller_api/test/test_data/example4.inp +388 -388
  92. floodmodeller_api/test/test_data/example5.inp +147 -147
  93. floodmodeller_api/test/test_data/example6.inp +154 -154
  94. floodmodeller_api/test/test_data/expected_conveyance.csv +60 -0
  95. floodmodeller_api/test/test_data/jump.dat +176 -176
  96. floodmodeller_api/test/test_data/network.dat +1374 -1374
  97. floodmodeller_api/test/test_data/network.ext +45 -45
  98. floodmodeller_api/test/test_data/network.exy +1 -1
  99. floodmodeller_api/test/test_data/network.feb +45 -45
  100. floodmodeller_api/test/test_data/network.ied +45 -45
  101. floodmodeller_api/test/test_data/network.ief +20 -20
  102. floodmodeller_api/test/test_data/network.inp +147 -147
  103. floodmodeller_api/test/test_data/network.pxy +57 -57
  104. floodmodeller_api/test/test_data/network.zzd +122 -122
  105. floodmodeller_api/test/test_data/network_dat_expected.json +21837 -0
  106. floodmodeller_api/test/test_data/network_from_tabularCSV.csv +87 -87
  107. floodmodeller_api/test/test_data/network_ied_expected.json +287 -0
  108. floodmodeller_api/test/test_data/rnweir.dat +9 -9
  109. floodmodeller_api/test/test_data/rnweir.ext +45 -45
  110. floodmodeller_api/test/test_data/rnweir.feb +9 -9
  111. floodmodeller_api/test/test_data/rnweir.gxy +45 -45
  112. floodmodeller_api/test/test_data/rnweir_default.dat +74 -74
  113. floodmodeller_api/test/test_data/rnweir_default.ext +45 -45
  114. floodmodeller_api/test/test_data/rnweir_default.feb +9 -9
  115. floodmodeller_api/test/test_data/rnweir_default.fmpx +58 -58
  116. floodmodeller_api/test/test_data/rnweir_default.gxy +53 -53
  117. floodmodeller_api/test/test_data/unit checks.dat +16 -16
  118. floodmodeller_api/test/test_ied.py +29 -29
  119. floodmodeller_api/test/test_ief.py +136 -24
  120. floodmodeller_api/test/test_inp.py +47 -48
  121. floodmodeller_api/test/test_json.py +114 -0
  122. floodmodeller_api/test/test_logs_lf.py +102 -51
  123. floodmodeller_api/test/test_tool.py +165 -152
  124. floodmodeller_api/test/test_toolbox_structure_log.py +234 -239
  125. floodmodeller_api/test/test_xml2d.py +151 -156
  126. floodmodeller_api/test/test_zzn.py +36 -34
  127. floodmodeller_api/to_from_json.py +230 -0
  128. floodmodeller_api/tool.py +332 -329
  129. floodmodeller_api/toolbox/__init__.py +5 -5
  130. floodmodeller_api/toolbox/example_tool.py +45 -45
  131. floodmodeller_api/toolbox/model_build/__init__.py +2 -2
  132. floodmodeller_api/toolbox/model_build/add_siltation_definition.py +100 -98
  133. floodmodeller_api/toolbox/model_build/structure_log/__init__.py +1 -1
  134. floodmodeller_api/toolbox/model_build/structure_log/structure_log.py +287 -289
  135. floodmodeller_api/toolbox/model_build/structure_log_definition.py +76 -76
  136. floodmodeller_api/units/__init__.py +10 -10
  137. floodmodeller_api/units/_base.py +214 -212
  138. floodmodeller_api/units/boundaries.py +467 -467
  139. floodmodeller_api/units/comment.py +52 -55
  140. floodmodeller_api/units/conduits.py +382 -402
  141. floodmodeller_api/units/conveyance.py +301 -0
  142. floodmodeller_api/units/helpers.py +123 -131
  143. floodmodeller_api/units/iic.py +107 -101
  144. floodmodeller_api/units/losses.py +305 -306
  145. floodmodeller_api/units/sections.py +465 -446
  146. floodmodeller_api/units/structures.py +1690 -1683
  147. floodmodeller_api/units/units.py +93 -104
  148. floodmodeller_api/units/unsupported.py +44 -44
  149. floodmodeller_api/units/variables.py +87 -89
  150. floodmodeller_api/urban1d/__init__.py +11 -11
  151. floodmodeller_api/urban1d/_base.py +188 -179
  152. floodmodeller_api/urban1d/conduits.py +93 -85
  153. floodmodeller_api/urban1d/general_parameters.py +58 -58
  154. floodmodeller_api/urban1d/junctions.py +81 -79
  155. floodmodeller_api/urban1d/losses.py +81 -74
  156. floodmodeller_api/urban1d/outfalls.py +114 -110
  157. floodmodeller_api/urban1d/raingauges.py +111 -111
  158. floodmodeller_api/urban1d/subsections.py +92 -98
  159. floodmodeller_api/urban1d/xsections.py +147 -144
  160. floodmodeller_api/util.py +119 -21
  161. floodmodeller_api/validation/parameters.py +660 -660
  162. floodmodeller_api/validation/urban_parameters.py +388 -404
  163. floodmodeller_api/validation/validation.py +110 -108
  164. floodmodeller_api/version.py +1 -1
  165. floodmodeller_api/xml2d.py +632 -673
  166. floodmodeller_api/xml2d_template.py +37 -37
  167. floodmodeller_api/zzn.py +414 -363
  168. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/LICENSE.txt +13 -13
  169. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/METADATA +85 -82
  170. floodmodeller_api-0.4.4.dist-info/RECORD +185 -0
  171. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/WHEEL +1 -1
  172. floodmodeller_api/libifcoremd.dll +0 -0
  173. floodmodeller_api/test/test_data/EX3.bmp +0 -0
  174. floodmodeller_api/test/test_data/test_output.csv +0 -87
  175. floodmodeller_api/zzn_read.dll +0 -0
  176. floodmodeller_api-0.4.2.post1.dist-info/RECORD +0 -164
  177. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/entry_points.txt +0 -0
  178. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/top_level.txt +0 -0
floodmodeller_api/inp.py CHANGED
@@ -1,266 +1,260 @@
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
- from pathlib import Path
17
- from typing import Optional, Union
18
-
19
- from . import units
20
- from ._base import FMFile
21
- from .units.helpers import _to_str, join_n_char_ljust
22
- from .urban1d import subsections
23
- from .urban1d.general_parameters import DEFAULT_OPTIONS
24
- from .validation import _validate_unit
25
-
26
-
27
- class INP(FMFile):
28
- """Reads and writes Flood Modeller 1DUrban file format '.inp'
29
-
30
- Args:
31
- inp_filepath (str, optional): Full filepath to inp file. If not specified, a new INP class will be created. Defaults to None.
32
-
33
- Output:
34
- Initiates 'INP' class object
35
-
36
- Raises:
37
- TypeError: Raised if inp_filepath does not point to a .inp file
38
- FileNotFoundError: Raised if inp_filepath points to a file which does not exist
39
- """
40
-
41
- _filetype: str = "INP"
42
- _suffix: str = ".inp"
43
-
44
- def __init__(self, inp_filepath: Optional[Union[str, Path]] = None):
45
- try:
46
- if inp_filepath is not None:
47
- FMFile.__init__(self, inp_filepath)
48
- self._read()
49
-
50
- else:
51
- self._create_from_blank()
52
-
53
- self._get_section_definitions()
54
- except Exception as e:
55
- self._handle_exception(e, when="read")
56
-
57
- def _read(self):
58
- # Read INP file
59
- with open(self._filepath, "r") as inp_file:
60
- self._raw_data = [line.rstrip("\n") for line in inp_file.readlines()]
61
-
62
- # Generate INP file structure
63
- self._update_inp_struct()
64
-
65
- def _write(self) -> str:
66
- """Returns string representation of the current INP data
67
-
68
- Returns:
69
- str: Full string representation of INP in its most recent state (including changes not yet saved to disk)
70
- """
71
- try:
72
- _validate_unit(self, urban=True)
73
-
74
- block_shift = 0 # Used to allow changes in the length of subsections.
75
-
76
- for block in self._inp_struct:
77
- if block["Subsection_Type"] in subsections.SUPPORTED_SUBSECTIONS:
78
- subsection_data = self._raw_data[
79
- block["start"] + block_shift : block["end"] + 1 + block_shift
80
- ]
81
- prev_block_len = len(subsection_data)
82
-
83
- if (
84
- subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]]["group"]
85
- == "general"
86
- ):
87
- # General parameters
88
-
89
- if block["Subsection_Type"] == "[OPTIONS]":
90
- # Options subsection
91
-
92
- new_subsection_data = [
93
- "[OPTIONS]",
94
- ";;Option Value",
95
- ]
96
-
97
- for param, value in self.options.items():
98
- if value is not None:
99
- option_line = join_n_char_ljust(21, param.upper(), value)
100
- new_subsection_data.append(option_line)
101
-
102
- new_subsection_data.append("") # blank line before next section
103
-
104
- else: # Of unit type
105
- subsection = getattr(
106
- self,
107
- subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]][
108
- "attribute"
109
- ],
110
- ) # Get unit object
111
- new_subsection_data = (
112
- subsection._write()
113
- ) # String representation of unit object
114
-
115
- new_block_len = len(new_subsection_data)
116
-
117
- self._raw_data[
118
- block["start"] + block_shift : block["end"] + 1 + block_shift
119
- ] = new_subsection_data # Replace existing subsection with new subsection string
120
- block_shift += (
121
- new_block_len - prev_block_len
122
- ) # adjust block shift for change in number of lines in block
123
-
124
- # Regenerate INP file structure
125
- self._update_inp_struct()
126
-
127
- # Write _raw_data out to INP file.
128
- inp_string = ""
129
- for line in self._raw_data:
130
- inp_string += line + "\n"
131
-
132
- "\n".join(self._raw_data)
133
-
134
- return inp_string
135
-
136
- except Exception as e:
137
- self._handle_exception(e, when="write")
138
-
139
- def _create_from_blank(self):
140
- raise NotImplementedError(
141
- "Creating new 1D urban models (INP files) is not yet supported by floodmodeller_api, only existing models can be read"
142
- )
143
-
144
- def _get_section_definitions(self):
145
- """Internal method used to get section definitions for each supported unit type and general parameters."""
146
-
147
- # Loop through all blocks (subsections) within INP and process if of a supported type.
148
- for block in self._inp_struct:
149
- if block["Subsection_Type"] in subsections.SUPPORTED_SUBSECTIONS:
150
- raw_subsection_data = self._raw_data[
151
- block["start"] : block["end"] + 1
152
- ] # Raw data for subsection block of INP file
153
-
154
- # Check if subsection type is 'general'
155
- if (
156
- subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]]["group"]
157
- == "general"
158
- ):
159
- if block["Subsection_Type"] == "[OPTIONS]":
160
- self.options = DEFAULT_OPTIONS.copy()
161
- for line in raw_subsection_data:
162
- if (
163
- line.upper() not in subsections.SUPPORTED_SUBSECTIONS
164
- and line.strip() != ""
165
- and not line.startswith(";")
166
- ):
167
- data = units.helpers.split_n_char(line, 21)
168
-
169
- # Set type to Float or Stirng, as appropirate.
170
- self.options[data[0].lower()] = _to_str(
171
- data[1], None, check_float=True
172
- )
173
-
174
- # Create appropriate sub-class instences for supported units
175
- elif (
176
- subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]]["group"] == "units"
177
- ):
178
- subsection_class = subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]][
179
- "class"
180
- ]
181
-
182
- subsection_attribute = subsections.SUPPORTED_SUBSECTIONS[
183
- block["Subsection_Type"]
184
- ]["attribute"]
185
-
186
- setattr(
187
- self,
188
- subsection_attribute,
189
- subsection_class(raw_subsection_data),
190
- )
191
- subsection = getattr(self, subsection_attribute)
192
- subsection_units = getattr(subsection, subsection._attribute)
193
- setattr(self, subsection._attribute, subsection_units)
194
-
195
- # No action if subsection not supported. Leave block as raw data
196
-
197
- def _update_inp_struct(self):
198
- """Internal method used to update self._inp_struct which details the overall structure of the inp file as a list of blocks, each of which
199
- are a dictionary containing the 'start', 'end' and 'type' of the block.
200
-
201
- """
202
- # Generate INP file structure
203
- inp_struct = []
204
- in_block = False
205
- unit_block = {}
206
- for idx, line in enumerate(self._raw_data):
207
- # TODO: Add functionality to compare first four characters only (alphanumeric) - need to consider names shorter than 4 characters, and those with _ within name
208
-
209
- # Check if subsection is known
210
- if line.upper() in subsections.ALL_SUBSECTIONS:
211
- if in_block is True:
212
- unit_block["end"] = idx - 1 # add ending index
213
- inp_struct.append(unit_block) # append existing block bdy to the inp_struct
214
- unit_block = {} # reset bdy block
215
-
216
- unit_block["Subsection_Type"] = line.upper()
217
- unit_block["start"] = idx
218
- in_block = True
219
-
220
- if len(unit_block) != 0:
221
- # Only adds end block if there is a block present (i.e. an empty inp stays empty)
222
- # add ending index for final block
223
- unit_block["end"] = len(self._raw_data) - 1
224
- inp_struct.append(unit_block) # add final block
225
-
226
- self._inp_struct = inp_struct
227
-
228
- def diff(self, other: "INP", force_print: bool = False) -> None:
229
- """Compares the INP class against another INP class to check whether they are
230
- equivalent, or if not, what the differences are. Two instances of an INP class are
231
- deemed equivalent if all of their attributes are equal except for the filepath and
232
- raw data. For example, two INP files from different filepaths that had the same
233
- data except maybe some differences in decimal places and some default parameters
234
- ommitted, would be classed as equaivalent as they would produce the same INP instance
235
- and write the exact same data.
236
-
237
- The result is printed to the console. If you need to access the returned data, use
238
- the method ``INP._get_diff()``
239
-
240
- Args:
241
- other (floodmodeller_api.INP): Other instance of an INP class
242
- force_print (bool): Forces the API to print every difference found, rather than
243
- just the first 25 differences. Defaults to False.
244
- """
245
- self._diff(other, force_print=force_print)
246
-
247
- def update(self) -> None:
248
- """Updates the existing INP based on any altered attributes"""
249
-
250
- self._update()
251
-
252
- def save(self, filepath: Union[str, Path]) -> None:
253
- """Saves the INP to the given location, if pointing to an existing file it will be overwritten.
254
- Once saved, the INP() class will continue working from the saved location, therefore any further calls to INP.update() will
255
- update in the latest saved location rather than the original source INP used to construct the class
256
-
257
- Args:
258
- filepath (str): Filepath to new save location including the name and '.inp' extension
259
-
260
- Raises:
261
- TypeError: Raised if given filepath doesn't point to a file suffixed '.inp'
262
-
263
- """
264
-
265
- filepath = Path(filepath).absolute()
266
- self._save(filepath)
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
+ from __future__ import annotations
18
+
19
+ from pathlib import Path
20
+
21
+ from . import units
22
+ from ._base import FMFile
23
+ from .units.helpers import _to_str, join_n_char_ljust
24
+ from .urban1d import subsections
25
+ from .urban1d.general_parameters import DEFAULT_OPTIONS
26
+ from .util import handle_exception
27
+ from .validation import _validate_unit
28
+
29
+
30
+ class INP(FMFile):
31
+ """Reads and writes Flood Modeller 1DUrban file format '.inp'
32
+
33
+ Args:
34
+ inp_filepath (str, optional): Full filepath to inp file. If not specified, a new INP class will be created. Defaults to None.
35
+
36
+ Output:
37
+ Initiates 'INP' class object
38
+
39
+ Raises:
40
+ TypeError: Raised if inp_filepath does not point to a .inp file
41
+ FileNotFoundError: Raised if inp_filepath points to a file which does not exist
42
+ """
43
+
44
+ _filetype: str = "INP"
45
+ _suffix: str = ".inp"
46
+
47
+ @handle_exception(when="read")
48
+ def __init__(self, inp_filepath: str | Path | None = None, from_json: bool = False):
49
+ if from_json:
50
+ return
51
+ if inp_filepath is not None:
52
+ FMFile.__init__(self, inp_filepath)
53
+ self._read()
54
+
55
+ else:
56
+ self._create_from_blank()
57
+
58
+ self._get_section_definitions()
59
+
60
+ def _read(self):
61
+ # Read INP file
62
+ with open(self._filepath) as inp_file:
63
+ self._raw_data = [line.rstrip("\n") for line in inp_file.readlines()]
64
+
65
+ # Generate INP file structure
66
+ self._update_inp_struct()
67
+
68
+ @handle_exception(when="write")
69
+ def _write(self) -> str:
70
+ """Returns string representation of the current INP data
71
+
72
+ Returns:
73
+ str: Full string representation of INP in its most recent state (including changes not yet saved to disk)
74
+ """
75
+ _validate_unit(self, urban=True)
76
+
77
+ block_shift = 0 # Used to allow changes in the length of subsections.
78
+
79
+ for block in self._inp_struct:
80
+ if block["Subsection_Type"] in subsections.SUPPORTED_SUBSECTIONS:
81
+ subsection_data = self._raw_data[
82
+ block["start"] + block_shift : block["end"] + 1 + block_shift
83
+ ]
84
+ prev_block_len = len(subsection_data)
85
+
86
+ if (
87
+ subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]]["group"]
88
+ == "general"
89
+ ):
90
+ # General parameters
91
+
92
+ if block["Subsection_Type"] == "[OPTIONS]":
93
+ # Options subsection
94
+
95
+ new_subsection_data = [
96
+ "[OPTIONS]",
97
+ ";;Option Value",
98
+ ]
99
+
100
+ for param, value in self.options.items():
101
+ if value is not None:
102
+ option_line = join_n_char_ljust(21, param.upper(), value)
103
+ new_subsection_data.append(option_line)
104
+
105
+ new_subsection_data.append("") # blank line before next section
106
+
107
+ else: # Of unit type
108
+ subsection = getattr(
109
+ self,
110
+ subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]]["attribute"],
111
+ ) # Get unit object
112
+ new_subsection_data = (
113
+ subsection._write()
114
+ ) # String representation of unit object
115
+
116
+ new_block_len = len(new_subsection_data)
117
+
118
+ self._raw_data[block["start"] + block_shift : block["end"] + 1 + block_shift] = (
119
+ new_subsection_data # Replace existing subsection with new subsection string
120
+ )
121
+ block_shift += (
122
+ new_block_len - prev_block_len
123
+ ) # adjust block shift for change in number of lines in block
124
+
125
+ # Regenerate INP file structure
126
+ self._update_inp_struct()
127
+
128
+ # Write _raw_data out to INP file.
129
+ return "\n".join(self._raw_data) + "\n"
130
+
131
+ def _create_from_blank(self):
132
+ raise NotImplementedError(
133
+ "Creating new 1D urban models (INP files) is not yet supported by floodmodeller_api, only existing models can be read",
134
+ )
135
+
136
+ def _get_section_definitions(self):
137
+ """Internal method used to get section definitions for each supported unit type and general parameters."""
138
+
139
+ # Loop through all blocks (subsections) within INP and process if of a supported type.
140
+ for block in self._inp_struct:
141
+ if block["Subsection_Type"] in subsections.SUPPORTED_SUBSECTIONS:
142
+ raw_subsection_data = self._raw_data[
143
+ block["start"] : block["end"] + 1
144
+ ] # Raw data for subsection block of INP file
145
+
146
+ # Check if subsection type is 'general'
147
+ if (
148
+ subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]]["group"]
149
+ == "general"
150
+ ):
151
+ if block["Subsection_Type"] == "[OPTIONS]":
152
+ self.options = DEFAULT_OPTIONS.copy()
153
+ for line in raw_subsection_data:
154
+ if (
155
+ line.upper() not in subsections.SUPPORTED_SUBSECTIONS
156
+ and line.strip() != ""
157
+ and not line.startswith(";")
158
+ ):
159
+ data = units.helpers.split_n_char(line, 21)
160
+
161
+ # Set type to Float or Stirng, as appropirate.
162
+ self.options[data[0].lower()] = _to_str(
163
+ data[1],
164
+ None,
165
+ check_float=True,
166
+ )
167
+
168
+ # Create appropriate sub-class instences for supported units
169
+ elif (
170
+ subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]]["group"] == "units"
171
+ ):
172
+ subsection_class = subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]][
173
+ "class"
174
+ ]
175
+
176
+ subsection_attribute = subsections.SUPPORTED_SUBSECTIONS[
177
+ block["Subsection_Type"]
178
+ ]["attribute"]
179
+
180
+ setattr(
181
+ self,
182
+ subsection_attribute,
183
+ subsection_class(raw_subsection_data),
184
+ )
185
+ subsection = getattr(self, subsection_attribute)
186
+ subsection_units = getattr(subsection, subsection._attribute)
187
+ setattr(self, subsection._attribute, subsection_units)
188
+
189
+ # No action if subsection not supported. Leave block as raw data
190
+
191
+ def _update_inp_struct(self):
192
+ """Internal method used to update self._inp_struct which details the overall structure of the inp file as a list of blocks, each of which
193
+ are a dictionary containing the 'start', 'end' and 'type' of the block.
194
+
195
+ """
196
+ # Generate INP file structure
197
+ inp_struct = []
198
+ in_block = False
199
+ unit_block = {}
200
+ for idx, line in enumerate(self._raw_data):
201
+ # TODO: Add functionality to compare first four characters only (alphanumeric) - need to consider names shorter than 4 characters, and those with _ within name
202
+
203
+ # Check if subsection is known
204
+ if line.upper() in subsections.ALL_SUBSECTIONS:
205
+ if in_block is True:
206
+ unit_block["end"] = idx - 1 # add ending index
207
+ inp_struct.append(unit_block) # append existing block bdy to the inp_struct
208
+ unit_block = {} # reset bdy block
209
+
210
+ unit_block["Subsection_Type"] = line.upper()
211
+ unit_block["start"] = idx
212
+ in_block = True
213
+
214
+ if len(unit_block) != 0:
215
+ # Only adds end block if there is a block present (i.e. an empty inp stays empty)
216
+ # add ending index for final block
217
+ unit_block["end"] = len(self._raw_data) - 1
218
+ inp_struct.append(unit_block) # add final block
219
+
220
+ self._inp_struct = inp_struct
221
+
222
+ def diff(self, other: INP, force_print: bool = False) -> None:
223
+ """Compares the INP class against another INP class to check whether they are
224
+ equivalent, or if not, what the differences are. Two instances of an INP class are
225
+ deemed equivalent if all of their attributes are equal except for the filepath and
226
+ raw data. For example, two INP files from different filepaths that had the same
227
+ data except maybe some differences in decimal places and some default parameters
228
+ ommitted, would be classed as equaivalent as they would produce the same INP instance
229
+ and write the exact same data.
230
+
231
+ The result is printed to the console. If you need to access the returned data, use
232
+ the method ``INP._get_diff()``
233
+
234
+ Args:
235
+ other (floodmodeller_api.INP): Other instance of an INP class
236
+ force_print (bool): Forces the API to print every difference found, rather than
237
+ just the first 25 differences. Defaults to False.
238
+ """
239
+ self._diff(other, force_print=force_print)
240
+
241
+ def update(self) -> None:
242
+ """Updates the existing INP based on any altered attributes"""
243
+
244
+ self._update()
245
+
246
+ def save(self, filepath: str | Path) -> None:
247
+ """Saves the INP to the given location, if pointing to an existing file it will be overwritten.
248
+ Once saved, the INP() class will continue working from the saved location, therefore any further calls to INP.update() will
249
+ update in the latest saved location rather than the original source INP used to construct the class
250
+
251
+ Args:
252
+ filepath (str): Filepath to new save location including the name and '.inp' extension
253
+
254
+ Raises:
255
+ TypeError: Raised if given filepath doesn't point to a file suffixed '.inp'
256
+
257
+ """
258
+
259
+ filepath = Path(filepath).absolute()
260
+ self._save(filepath)
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,2 +1,2 @@
1
- from .lf import LF1, LF2, lf_factory
2
- from .lf_params import error_2d_dict
1
+ from .lf import LF1, LF2, create_lf
2
+ from .lf_params import error_2d_dict