floodmodeller-api 0.4.2.post1__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 (172) 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 -831
  5. floodmodeller_api/diff.py +136 -119
  6. floodmodeller_api/ied.py +307 -306
  7. floodmodeller_api/ief.py +647 -637
  8. floodmodeller_api/ief_flags.py +253 -253
  9. floodmodeller_api/inp.py +266 -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 +320 -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 +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 -152
  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 -329
  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 -98
  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 -76
  131. floodmodeller_api/units/__init__.py +10 -10
  132. floodmodeller_api/units/_base.py +214 -212
  133. floodmodeller_api/units/boundaries.py +467 -467
  134. floodmodeller_api/units/comment.py +52 -55
  135. floodmodeller_api/units/conduits.py +382 -402
  136. floodmodeller_api/units/helpers.py +123 -131
  137. floodmodeller_api/units/iic.py +107 -101
  138. floodmodeller_api/units/losses.py +305 -306
  139. floodmodeller_api/units/sections.py +444 -446
  140. floodmodeller_api/units/structures.py +1690 -1683
  141. floodmodeller_api/units/units.py +93 -104
  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 -179
  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 -110
  151. floodmodeller_api/urban1d/raingauges.py +111 -111
  152. floodmodeller_api/urban1d/subsections.py +92 -98
  153. floodmodeller_api/urban1d/xsections.py +147 -144
  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 -108
  158. floodmodeller_api/version.py +1 -1
  159. floodmodeller_api/xml2d.py +688 -673
  160. floodmodeller_api/xml2d_template.py +37 -37
  161. floodmodeller_api/zzn.py +387 -363
  162. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.3.dist-info}/LICENSE.txt +13 -13
  163. {floodmodeller_api-0.4.2.post1.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.post1.dist-info → floodmodeller_api-0.4.3.dist-info}/WHEEL +1 -1
  166. floodmodeller_api/libifcoremd.dll +0 -0
  167. floodmodeller_api/test/test_data/EX3.bmp +0 -0
  168. floodmodeller_api/test/test_data/test_output.csv +0 -87
  169. floodmodeller_api/zzn_read.dll +0 -0
  170. floodmodeller_api-0.4.2.post1.dist-info/RECORD +0 -164
  171. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.3.dist-info}/entry_points.txt +0 -0
  172. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.3.dist-info}/top_level.txt +0 -0
floodmodeller_api/inp.py CHANGED
@@ -1,266 +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
- 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 .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