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
floodmodeller_api/inp.py CHANGED
@@ -1,268 +1,266 @@
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
- self._filepath = inp_filepath
47
- if self._filepath is not None:
48
- FMFile.__init__(self)
49
- self._read()
50
-
51
- else:
52
- self._create_from_blank()
53
-
54
- self._get_section_definitions()
55
- except Exception as e:
56
- self._handle_exception(e, when="read")
57
-
58
- def _read(self):
59
- # Read INP file
60
- with open(self._filepath, "r") as inp_file:
61
- self._raw_data = [line.rstrip("\n") for line in inp_file.readlines()]
62
-
63
- # Generate INP file structure
64
- self._update_inp_struct()
65
-
66
- def _write(self) -> str:
67
- """Returns string representation of the current INP data
68
-
69
- Returns:
70
- str: Full string representation of INP in its most recent state (including changes not yet saved to disk)
71
- """
72
- try:
73
- _validate_unit(self, urban=True)
74
-
75
- block_shift = 0 # Used to allow changes in the length of subsections.
76
-
77
- for block in self._inp_struct:
78
- if block["Subsection_Type"] in subsections.SUPPORTED_SUBSECTIONS:
79
- subsection_data = self._raw_data[
80
- block["start"] + block_shift : block["end"] + 1 + block_shift
81
- ]
82
- prev_block_len = len(subsection_data)
83
-
84
- if (
85
- subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]]["group"]
86
- == "general"
87
- ):
88
- # General parameters
89
-
90
- if block["Subsection_Type"] == "[OPTIONS]":
91
- # Options subsection
92
-
93
- new_subsection_data = [
94
- "[OPTIONS]",
95
- ";;Option Value",
96
- ]
97
-
98
- for param, value in self.options.items():
99
- if value is not None:
100
- option_line = join_n_char_ljust(21, param.upper(), value)
101
- new_subsection_data.append(option_line)
102
-
103
- new_subsection_data.append("") # blank line before next section
104
-
105
- else: # Of unit type
106
- subsection = getattr(
107
- self,
108
- subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]][
109
- "attribute"
110
- ],
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[
119
- block["start"] + block_shift : block["end"] + 1 + block_shift
120
- ] = new_subsection_data # Replace existing subsection with new subsection string
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
- inp_string = ""
130
- for line in self._raw_data:
131
- inp_string += line + "\n"
132
-
133
- "\n".join(self._raw_data)
134
-
135
- return inp_string
136
-
137
- except Exception as e:
138
- self._handle_exception(e, when="write")
139
-
140
- def _create_from_blank(self):
141
- raise NotImplementedError(
142
- "Creating new 1D urban models (INP files) is not yet supported by floodmodeller_api, only existing models can be read"
143
- )
144
- pass
145
-
146
- def _get_section_definitions(self):
147
- """Internal method used to get section definitions for each supported unit type and general parameters."""
148
-
149
- # Loop through all blocks (subsections) within INP and process if of a supported type.
150
- for block in self._inp_struct:
151
- if block["Subsection_Type"] in subsections.SUPPORTED_SUBSECTIONS:
152
- raw_subsection_data = self._raw_data[
153
- block["start"] : block["end"] + 1
154
- ] # Raw data for subsection block of INP file
155
-
156
- # Check if subsection type is 'general'
157
- if (
158
- subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]]["group"]
159
- == "general"
160
- ):
161
- if block["Subsection_Type"] == "[OPTIONS]":
162
- self.options = DEFAULT_OPTIONS.copy()
163
- for line in raw_subsection_data:
164
- if (
165
- line.upper() not in subsections.SUPPORTED_SUBSECTIONS
166
- and line.strip() != ""
167
- and not line.startswith(";")
168
- ):
169
- data = units.helpers.split_n_char(line, 21)
170
-
171
- # Set type to Float or Stirng, as appropirate.
172
- self.options[data[0].lower()] = _to_str(
173
- data[1], None, check_float=True
174
- )
175
-
176
- # Create appropriate sub-class instences for supported units
177
- elif (
178
- subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]]["group"] == "units"
179
- ):
180
- subsection_class = subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]][
181
- "class"
182
- ]
183
-
184
- subsection_attribute = subsections.SUPPORTED_SUBSECTIONS[
185
- block["Subsection_Type"]
186
- ]["attribute"]
187
-
188
- setattr(
189
- self,
190
- subsection_attribute,
191
- subsection_class(raw_subsection_data),
192
- )
193
- subsection = getattr(self, subsection_attribute)
194
- subsection_units = getattr(subsection, subsection._attribute)
195
- setattr(self, subsection._attribute, subsection_units)
196
-
197
- # No action if subsection not supported. Leave block as raw data
198
-
199
- def _update_inp_struct(self):
200
- """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
201
- are a dictionary containing the 'start', 'end' and 'type' of the block.
202
-
203
- """
204
- # Generate INP file structure
205
- inp_struct = []
206
- in_block = False
207
- unit_block = {}
208
- for idx, line in enumerate(self._raw_data):
209
- # TODO: Add functionality to compare first four characters only (alphanumeric) - need to consider names shorter than 4 characters, and those with _ within name
210
-
211
- # Check if subsection is known
212
- if line.upper() in subsections.ALL_SUBSECTIONS:
213
- if in_block is True:
214
- unit_block["end"] = idx - 1 # add ending index
215
- inp_struct.append(unit_block) # append existing block bdy to the inp_struct
216
- unit_block = {} # reset bdy block
217
-
218
- unit_block["Subsection_Type"] = line.upper()
219
- unit_block["start"] = idx
220
- in_block = True
221
-
222
- if len(unit_block) != 0:
223
- # Only adds end block if there is a block present (i.e. an empty inp stays empty)
224
- # add ending index for final block
225
- unit_block["end"] = len(self._raw_data) - 1
226
- inp_struct.append(unit_block) # add final block
227
-
228
- self._inp_struct = inp_struct
229
-
230
- def diff(self, other: "INP", force_print: bool = False) -> None:
231
- """Compares the INP class against another INP class to check whether they are
232
- equivalent, or if not, what the differences are. Two instances of an INP class are
233
- deemed equivalent if all of their attributes are equal except for the filepath and
234
- raw data. For example, two INP files from different filepaths that had the same
235
- data except maybe some differences in decimal places and some default parameters
236
- ommitted, would be classed as equaivalent as they would produce the same INP instance
237
- and write the exact same data.
238
-
239
- The result is printed to the console. If you need to access the returned data, use
240
- the method ``INP._get_diff()``
241
-
242
- Args:
243
- other (floodmodeller_api.INP): Other instance of an INP class
244
- force_print (bool): Forces the API to print every difference found, rather than
245
- just the first 25 differences. Defaults to False.
246
- """
247
- self._diff(other, force_print=force_print)
248
-
249
- def update(self) -> None:
250
- """Updates the existing INP based on any altered attributes"""
251
-
252
- self._update()
253
-
254
- def save(self, filepath: Union[str, Path]) -> None:
255
- """Saves the INP to the given location, if pointing to an existing file it will be overwritten.
256
- Once saved, the INP() class will continue working from the saved location, therefore any further calls to INP.update() will
257
- update in the latest saved location rather than the original source INP used to construct the class
258
-
259
- Args:
260
- filepath (str): Filepath to new save location including the name and '.inp' extension
261
-
262
- Raises:
263
- TypeError: Raised if given filepath doesn't point to a file suffixed '.inp'
264
-
265
- """
266
-
267
- filepath = Path(filepath).absolute()
268
- 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 .validation import _validate_unit
27
+
28
+
29
+ class INP(FMFile):
30
+ """Reads and writes Flood Modeller 1DUrban file format '.inp'
31
+
32
+ Args:
33
+ inp_filepath (str, optional): Full filepath to inp file. If not specified, a new INP class will be created. Defaults to None.
34
+
35
+ Output:
36
+ Initiates 'INP' class object
37
+
38
+ Raises:
39
+ TypeError: Raised if inp_filepath does not point to a .inp file
40
+ FileNotFoundError: Raised if inp_filepath points to a file which does not exist
41
+ """
42
+
43
+ _filetype: str = "INP"
44
+ _suffix: str = ".inp"
45
+
46
+ def __init__(self, inp_filepath: str | Path | None = None, from_json: bool = False):
47
+ try:
48
+ if from_json:
49
+ return
50
+ if inp_filepath is not None:
51
+ FMFile.__init__(self, inp_filepath)
52
+ self._read()
53
+
54
+ else:
55
+ self._create_from_blank()
56
+
57
+ self._get_section_definitions()
58
+ except Exception as e:
59
+ self._handle_exception(e, when="read")
60
+
61
+ def _read(self):
62
+ # Read INP file
63
+ with open(self._filepath) as inp_file:
64
+ self._raw_data = [line.rstrip("\n") for line in inp_file.readlines()]
65
+
66
+ # Generate INP file structure
67
+ self._update_inp_struct()
68
+
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
+ try:
76
+ _validate_unit(self, urban=True)
77
+
78
+ block_shift = 0 # Used to allow changes in the length of subsections.
79
+
80
+ for block in self._inp_struct:
81
+ if block["Subsection_Type"] in subsections.SUPPORTED_SUBSECTIONS:
82
+ subsection_data = self._raw_data[
83
+ block["start"] + block_shift : block["end"] + 1 + block_shift
84
+ ]
85
+ prev_block_len = len(subsection_data)
86
+
87
+ if (
88
+ subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]]["group"]
89
+ == "general"
90
+ ):
91
+ # General parameters
92
+
93
+ if block["Subsection_Type"] == "[OPTIONS]":
94
+ # Options subsection
95
+
96
+ new_subsection_data = [
97
+ "[OPTIONS]",
98
+ ";;Option Value",
99
+ ]
100
+
101
+ for param, value in self.options.items():
102
+ if value is not None:
103
+ option_line = join_n_char_ljust(21, param.upper(), value)
104
+ new_subsection_data.append(option_line)
105
+
106
+ new_subsection_data.append("") # blank line before next section
107
+
108
+ else: # Of unit type
109
+ subsection = getattr(
110
+ self,
111
+ subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]][
112
+ "attribute"
113
+ ],
114
+ ) # Get unit object
115
+ new_subsection_data = (
116
+ subsection._write()
117
+ ) # String representation of unit object
118
+
119
+ new_block_len = len(new_subsection_data)
120
+
121
+ self._raw_data[
122
+ block["start"] + block_shift : block["end"] + 1 + block_shift
123
+ ] = new_subsection_data # Replace existing subsection with new subsection string
124
+ block_shift += (
125
+ new_block_len - prev_block_len
126
+ ) # adjust block shift for change in number of lines in block
127
+
128
+ # Regenerate INP file structure
129
+ self._update_inp_struct()
130
+
131
+ # Write _raw_data out to INP file.
132
+ return "\n".join(self._raw_data) + "\n"
133
+
134
+ except Exception as e:
135
+ self._handle_exception(e, when="write")
136
+
137
+ def _create_from_blank(self):
138
+ raise NotImplementedError(
139
+ "Creating new 1D urban models (INP files) is not yet supported by floodmodeller_api, only existing models can be read",
140
+ )
141
+
142
+ def _get_section_definitions(self):
143
+ """Internal method used to get section definitions for each supported unit type and general parameters."""
144
+
145
+ # Loop through all blocks (subsections) within INP and process if of a supported type.
146
+ for block in self._inp_struct:
147
+ if block["Subsection_Type"] in subsections.SUPPORTED_SUBSECTIONS:
148
+ raw_subsection_data = self._raw_data[
149
+ block["start"] : block["end"] + 1
150
+ ] # Raw data for subsection block of INP file
151
+
152
+ # Check if subsection type is 'general'
153
+ if (
154
+ subsections.SUPPORTED_SUBSECTIONS[block["Subsection_Type"]]["group"]
155
+ == "general"
156
+ ):
157
+ if block["Subsection_Type"] == "[OPTIONS]":
158
+ self.options = DEFAULT_OPTIONS.copy()
159
+ for line in raw_subsection_data:
160
+ if (
161
+ line.upper() not in subsections.SUPPORTED_SUBSECTIONS
162
+ and line.strip() != ""
163
+ and not line.startswith(";")
164
+ ):
165
+ data = units.helpers.split_n_char(line, 21)
166
+
167
+ # Set type to Float or Stirng, as appropirate.
168
+ self.options[data[0].lower()] = _to_str(
169
+ data[1],
170
+ None,
171
+ 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: 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)
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, lf_factory
2
+ from .lf_params import error_2d_dict