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
@@ -1,312 +1,320 @@
1
- """
2
- Flood Modeller Python API
3
- Copyright (C) 2023 Jacobs U.K. Limited
4
-
5
- This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
6
- as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
-
8
- This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9
- of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10
-
11
- You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
12
-
13
- If you have any query about this program or this License, please contact us at support@floodmodeller.com or write to the following
14
- address: Jacobs UK Limited, Flood Modeller, Cottons Centre, Cottons Lane, London, SE1 2QG, United Kingdom.
15
- """
16
-
17
- from pathlib import Path
18
- from typing import Optional, Union
19
-
20
- import pandas as pd
21
-
22
- from .._base import FMFile
23
- from .lf_helpers import state_factory
24
- from .lf_params import lf1_steady_data_to_extract, lf1_unsteady_data_to_extract, lf2_data_to_extract
25
-
26
-
27
- class LF(FMFile):
28
- """Reads and processes Flood Modeller log file
29
-
30
- Args:
31
- lf1_filepath (str): Full filepath to model log file
32
- data_to_extract (dict): Dictionary defining each line type to parse
33
- steady (bool): True if for a steady-state simulation
34
-
35
- Output:
36
- Initiates 'LF' class object
37
- """
38
-
39
- def __init__(
40
- self,
41
- lf_filepath: Optional[Union[str, Path]],
42
- data_to_extract: dict,
43
- steady: bool = False,
44
- ):
45
- try:
46
- FMFile.__init__(self, lf_filepath)
47
-
48
- self._data_to_extract = data_to_extract
49
- self._init_counters()
50
- self._init_parsers()
51
- self._state = state_factory(steady, self._extracted_data)
52
-
53
- self._read()
54
-
55
- except Exception as e:
56
- self._handle_exception(e, when="read")
57
-
58
- def _read(self, force_reread: bool = False, suppress_final_step: bool = False):
59
- # Read LF file
60
- with open(self._filepath, "r") as lf_file:
61
- self._raw_data = [line.rstrip("\n") for line in lf_file.readlines()]
62
-
63
- # Force rereading from start of file
64
- if force_reread is True:
65
- self._del_attributes()
66
- self._init_counters()
67
- self._init_parsers()
68
-
69
- # Process file
70
- self._update_data()
71
-
72
- if not suppress_final_step:
73
- self._set_attributes()
74
-
75
- def read(self, force_reread: bool = False, suppress_final_step: bool = False) -> None:
76
- """Reads log file
77
-
78
- Args:
79
- force_reread (bool): If False, starts reading from where it stopped last time. If True, starts reading from the start of the file.
80
- suppress_final_step (bool): If False, dataframes and dictionary are not created as attributes.
81
-
82
- """
83
-
84
- self._read(force_reread, suppress_final_step)
85
-
86
- def _init_counters(self):
87
- """Initialises counters that keep track of file during simulation"""
88
-
89
- self._no_lines = 0 # number of lines that have been read so far
90
- self._no_iters = 0 # number of iterations so far
91
-
92
- def _init_parsers(self):
93
- """Creates dictionary of Parser objects for each entry in data_to_extract"""
94
-
95
- self._extracted_data = {}
96
-
97
- for key in self._data_to_extract:
98
- subdictionary = self._data_to_extract[key]
99
- subdictionary_class = subdictionary["class"]
100
- subdictionary_kwargs = {k: v for k, v in subdictionary.items() if k != "class"}
101
- subdictionary_kwargs["name"] = key
102
- self._extracted_data[key] = subdictionary_class(**subdictionary_kwargs)
103
-
104
- def _update_data(self):
105
- """Updates value of each Parser object based on raw data"""
106
-
107
- # self._print_no_lines()
108
-
109
- # loop through lines that haven't already been read
110
- raw_lines = self._raw_data[self._no_lines :]
111
- for raw_line in raw_lines:
112
- # loop through parser types
113
- for key in self._data_to_extract:
114
- parser = self._extracted_data[key]
115
-
116
- # lines which start with prefix
117
- if raw_line.startswith(parser.prefix):
118
- # store everything after prefix
119
- end_of_line = raw_line.split(parser.prefix)[1].lstrip()
120
- parser.process_line(end_of_line)
121
-
122
- # index marks the end of an iteration
123
- if parser.is_index is True:
124
- self._sync_cols()
125
- self._no_iters += 1
126
-
127
- # update counter
128
- self._no_lines += 1
129
-
130
- # self._print_no_lines()
131
-
132
- def _get_index(self):
133
- """Finds key and dataframe for variable that is the index"""
134
-
135
- for key in self._data_to_extract:
136
- try:
137
- self._data_to_extract[key]["is_index"]
138
- index_key = key
139
- index_df = self._extracted_data[key].data.get_value()
140
- return index_key, index_df
141
-
142
- except KeyError:
143
- pass
144
-
145
- raise Exception("No index variable found")
146
-
147
- def _set_attributes(self):
148
- """Makes each Parser value an attribute; "last" values in dictionary"""
149
-
150
- index_key, index_df = self._get_index()
151
-
152
- info = {}
153
-
154
- for key in self._data_to_extract:
155
- data_type = self._data_to_extract[key]["data_type"]
156
- value = self._extracted_data[key].data.get_value(index_key, index_df)
157
-
158
- if data_type == "all":
159
- setattr(self, key, value)
160
- elif data_type == "last" and value is not None:
161
- info[key] = value
162
-
163
- self.info = info
164
-
165
- def _del_attributes(self):
166
- """Deletes each Parser value direct attribute of LF"""
167
-
168
- for key in self._data_to_extract:
169
- data_type = self._data_to_extract[key]["data_type"]
170
- if data_type == "all":
171
- delattr(self, key)
172
-
173
- delattr(self, "info")
174
-
175
- def to_dataframe(self) -> pd.DataFrame:
176
- """Collects parameter values that change throughout simulation into a dataframe
177
-
178
- Returns:
179
- pd.DataFrame: DataFrame of log file parameters indexed by simulation time (unsteady) or network iterations (steady)
180
- """
181
-
182
- # TODO: make more like ZZN.to_dataframe
183
-
184
- data_type_all = {
185
- k: getattr(self, k) for k, v in self._data_to_extract.items() if v["data_type"] == "all"
186
- }
187
-
188
- df = pd.concat(data_type_all, axis=1)
189
- df.columns = df.columns.droplevel()
190
-
191
- df.sort_index(inplace=True)
192
-
193
- return df
194
-
195
- def _sync_cols(self):
196
- """Ensures Parser values (of type "all") have an entry each iteration"""
197
-
198
- # loop through parser types
199
- for key in self._data_to_extract:
200
- parser = self._extracted_data[key]
201
-
202
- # sync parser types that are not the index
203
- if parser.is_index is False:
204
- # if their number of values is not in sync
205
- if parser.data_type == "all" and parser.data.no_values < (
206
- self._no_iters + int(parser.before_index)
207
- ):
208
- # append nan to the list
209
- parser.data.update(parser._nan)
210
-
211
- def _print_no_lines(self):
212
- """Prints number of lines that have been read so far"""
213
-
214
- print("Last line read: " + str(self._no_lines))
215
-
216
- def report_progress(self) -> float:
217
- """Returns progress for unsteady simulations
218
-
219
- Returns:
220
- float: Last progress percentage recorded in log file
221
- """
222
-
223
- return self._state.report_progress()
224
-
225
-
226
- class LF1(LF):
227
- """Reads and processes Flood Modeller 1D log file '.lf1'
228
-
229
- Args:
230
- lf1_filepath (str): Full filepath to model lf1 file
231
- steady (bool): True for steady-state simulations
232
-
233
- **Attributes (unsteady)**
234
-
235
- Args:
236
- info (dict): Parameters with one value per simulation
237
- mass_error (pandas.DataFrame): Mass error
238
- timestep (pandas.DataFrame): Timestep
239
- elapsed (pandas.DataFrame): Elapsed
240
- simulated (pandas.DataFrame): Simulated
241
- iterations (pandas.DataFrame): PlotI1
242
- convergence (pandas.DataFrame): PlotC1
243
- flow (pandas.DataFrame): PlotF1
244
-
245
- **Attributes (steady)**
246
-
247
- Args:
248
- info (dict): Parameters with one value per simulation
249
- network_iteration (pandas.DataFrame): Network iteration
250
- largest_change_in_split_from_last_iteration (pandas.DataFrame): Largest change in split from last iteration
251
-
252
- Output:
253
- Initiates 'LF1' class object
254
- """
255
-
256
- _filetype: str = "LF1"
257
- _suffix: str = ".lf1"
258
-
259
- def __init__(self, lf_filepath: Optional[Union[str, Path]], steady: bool = False):
260
- if steady is False:
261
- data_to_extract = lf1_unsteady_data_to_extract
262
- else:
263
- data_to_extract = lf1_steady_data_to_extract
264
-
265
- super().__init__(lf_filepath, data_to_extract, steady)
266
-
267
-
268
- class LF2(LF):
269
- """Reads and processes Flood Modeller 1D log file '.lf2'
270
-
271
- Args:
272
- lf2_filepath (str): Full filepath to model lf2 file
273
-
274
- **Attributes**
275
-
276
- Args:
277
- info (dict): Parameters with one value per simulation
278
- simulated (pandas.DataFrame): Simulated
279
- wet_cells (pandas.DataFrame): Wet cells
280
- 2D_boundary_inflow (pandas.DataFrame): 2D boundary inflow
281
- 2D_boundary_outflow (pandas.DataFrame): 2D boundary outflow
282
- 1D_link_flow (pandas.DataFrame): 1D link flow
283
- change_in_volume (pandas.DataFrame): Change in volume
284
- volume (pandas.DataFrame): Volume
285
- inst_mass_err (pandas.DataFrame): Inst mass error
286
- mass_error (pandas.DataFrame): Mass error
287
- largest_cr (pandas.DataFrame): Largest Cr
288
- elapsed (pandas.DataFrame): Elapsed
289
-
290
- Output:
291
- Initiates 'LF2' class object
292
- """
293
-
294
- _filetype: str = "LF2"
295
- _suffix: str = ".lf2"
296
-
297
- def __init__(self, lf_filepath: Optional[Union[str, Path]]):
298
- data_to_extract = {
299
- **lf1_unsteady_data_to_extract,
300
- **lf2_data_to_extract,
301
- }
302
-
303
- super().__init__(lf_filepath, data_to_extract, steady=False)
304
-
305
-
306
- def lf_factory(filepath: str, suffix: str, steady: bool) -> LF:
307
- if suffix == "lf1":
308
- return LF1(filepath, steady)
309
- if suffix == "lf2":
310
- return LF2(filepath)
311
- flow_type = "steady" if steady else "unsteady"
312
- raise ValueError(f"Unexpected log file type {suffix} for {flow_type} flow")
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 typing import TYPE_CHECKING
20
+
21
+ import pandas as pd
22
+
23
+ from .._base import FMFile
24
+ from .lf_helpers import state_factory
25
+ from .lf_params import lf1_steady_data_to_extract, lf1_unsteady_data_to_extract, lf2_data_to_extract
26
+
27
+ if TYPE_CHECKING:
28
+ from pathlib import Path
29
+
30
+
31
+ class LF(FMFile):
32
+ """Reads and processes Flood Modeller log file
33
+
34
+ Args:
35
+ lf1_filepath (str): Full filepath to model log file
36
+ data_to_extract (dict): Dictionary defining each line type to parse
37
+ steady (bool): True if for a steady-state simulation
38
+
39
+ Output:
40
+ Initiates 'LF' class object
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ lf_filepath: str | Path | None,
46
+ data_to_extract: dict,
47
+ steady: bool = False,
48
+ ):
49
+ try:
50
+ FMFile.__init__(self, lf_filepath)
51
+
52
+ self._data_to_extract = data_to_extract
53
+ self._init_counters()
54
+ self._init_parsers()
55
+ self._state = state_factory(steady, self._extracted_data)
56
+
57
+ self._read()
58
+
59
+ except Exception as e:
60
+ self._handle_exception(e, when="read")
61
+
62
+ def _read(self, force_reread: bool = False, suppress_final_step: bool = False):
63
+ # Read LF file
64
+ with open(self._filepath) as lf_file:
65
+ self._raw_data = [line.rstrip("\n") for line in lf_file.readlines()]
66
+
67
+ # Force rereading from start of file
68
+ if force_reread is True:
69
+ self._del_attributes()
70
+ self._init_counters()
71
+ self._init_parsers()
72
+
73
+ # Process file
74
+ self._update_data()
75
+
76
+ if not suppress_final_step:
77
+ self._set_attributes()
78
+
79
+ def read(self, force_reread: bool = False, suppress_final_step: bool = False) -> None:
80
+ """Reads log file
81
+
82
+ Args:
83
+ force_reread (bool): If False, starts reading from where it stopped last time. If True, starts reading from the start of the file.
84
+ suppress_final_step (bool): If False, dataframes and dictionary are not created as attributes.
85
+
86
+ """
87
+
88
+ self._read(force_reread, suppress_final_step)
89
+
90
+ def _init_counters(self):
91
+ """Initialises counters that keep track of file during simulation"""
92
+
93
+ self._no_lines = 0 # number of lines that have been read so far
94
+ self._no_iters = 0 # number of iterations so far
95
+
96
+ def _init_parsers(self):
97
+ """Creates dictionary of Parser objects for each entry in data_to_extract"""
98
+
99
+ self._extracted_data = {}
100
+
101
+ for key in self._data_to_extract:
102
+ subdictionary = self._data_to_extract[key]
103
+ subdictionary_class = subdictionary["class"]
104
+ subdictionary_kwargs = {k: v for k, v in subdictionary.items() if k != "class"}
105
+ subdictionary_kwargs["name"] = key
106
+ self._extracted_data[key] = subdictionary_class(**subdictionary_kwargs)
107
+
108
+ def _update_data(self):
109
+ """Updates value of each Parser object based on raw data"""
110
+
111
+ # loop through lines that haven't already been read
112
+ raw_lines = self._raw_data[self._no_lines :]
113
+ for raw_line in raw_lines:
114
+ # loop through parser types
115
+ for key in self._data_to_extract:
116
+ parser = self._extracted_data[key]
117
+
118
+ # lines which start with prefix
119
+ if raw_line.startswith(parser.prefix):
120
+ # store everything after prefix
121
+ end_of_line = raw_line.split(parser.prefix)[1].lstrip()
122
+ parser.process_line(end_of_line)
123
+
124
+ # index marks the end of an iteration
125
+ if parser.is_index is True:
126
+ self._sync_cols()
127
+ self._no_iters += 1
128
+
129
+ # update counter
130
+ self._no_lines += 1
131
+
132
+ def _get_index(self):
133
+ """Finds key and dataframe for variable that is the index"""
134
+
135
+ for key in self._data_to_extract:
136
+ try:
137
+ self._data_to_extract[key]["is_index"]
138
+ index_key = key
139
+ index_df = self._extracted_data[key].data.get_value()
140
+ return index_key, index_df
141
+
142
+ except KeyError:
143
+ pass
144
+
145
+ raise Exception("No index variable found")
146
+
147
+ def _set_attributes(self):
148
+ """Makes each Parser value an attribute; "last" values in dictionary"""
149
+
150
+ index_key, index_df = self._get_index()
151
+
152
+ info = {}
153
+
154
+ for key in self._data_to_extract:
155
+ data_type = self._data_to_extract[key]["data_type"]
156
+ value = self._extracted_data[key].data.get_value(index_key, index_df)
157
+
158
+ if data_type == "all":
159
+ setattr(self, key, value)
160
+ elif data_type == "last" and value is not None:
161
+ info[key] = value
162
+
163
+ self.info = info
164
+
165
+ def _del_attributes(self):
166
+ """Deletes each Parser value direct attribute of LF"""
167
+
168
+ for key in self._data_to_extract:
169
+ data_type = self._data_to_extract[key]["data_type"]
170
+ if data_type == "all":
171
+ delattr(self, key)
172
+
173
+ delattr(self, "info")
174
+
175
+ def to_dataframe(self, *, include_tuflow: bool = False) -> pd.DataFrame:
176
+ """Collects parameter values that change throughout simulation into a dataframe
177
+
178
+ Args:
179
+ include_tuflow (bool): Include diagnostics for linked TUFLOW models
180
+
181
+ Returns:
182
+ pd.DataFrame: DataFrame of log file parameters indexed by simulation time (unsteady) or network iterations (steady)
183
+ """
184
+
185
+ # TODO: make more like ZZN.to_dataframe
186
+
187
+ data_type_all = {
188
+ k: getattr(self, k)
189
+ for k, v in self._data_to_extract.items()
190
+ if v["data_type"] == "all" and (include_tuflow or "tuflow" not in k)
191
+ }
192
+
193
+ df = pd.concat(data_type_all, axis=1)
194
+ df.columns = df.columns.droplevel()
195
+
196
+ df.sort_index(inplace=True)
197
+
198
+ return df
199
+
200
+ def _sync_cols(self):
201
+ """Ensures Parser values (of type "all") have an entry each iteration"""
202
+
203
+ # loop through parser types
204
+ for key in self._data_to_extract:
205
+ parser = self._extracted_data[key]
206
+
207
+ # sync parser types that are not the index
208
+ if (
209
+ parser.is_index is False # if their number of values is not in sync
210
+ and parser.data_type == "all"
211
+ and parser.data.no_values < (self._no_iters + int(parser.before_index))
212
+ ):
213
+ # append nan to the list
214
+ parser.data.update(parser._nan)
215
+
216
+ def _print_no_lines(self):
217
+ """Prints number of lines that have been read so far"""
218
+
219
+ print("Last line read: " + str(self._no_lines))
220
+
221
+ def report_progress(self) -> float:
222
+ """Returns progress for unsteady simulations
223
+
224
+ Returns:
225
+ float: Last progress percentage recorded in log file
226
+ """
227
+
228
+ return self._state.report_progress()
229
+
230
+
231
+ class LF1(LF):
232
+ """Reads and processes Flood Modeller 1D log file '.lf1'
233
+
234
+ Args:
235
+ lf1_filepath (str): Full filepath to model lf1 file
236
+ steady (bool): True for steady-state simulations
237
+
238
+ **Attributes (unsteady)**
239
+
240
+ Args:
241
+ info (dict): Parameters with one value per simulation
242
+ mass_error (pandas.DataFrame): Mass error
243
+ timestep (pandas.DataFrame): Timestep
244
+ elapsed (pandas.DataFrame): Elapsed
245
+ tuflow_vol (pandas.DataFrame): TUFLOW HPC Vol
246
+ tuflow_n_wet (pandas.DataFrame): TUFLOW HPC nWet
247
+ tuflow_dt (pandas.DataFrame): TUFLOW HPC dt
248
+ simulated (pandas.DataFrame): Simulated
249
+ iterations (pandas.DataFrame): PlotI1
250
+ convergence (pandas.DataFrame): PlotC1
251
+ flow (pandas.DataFrame): PlotF1
252
+
253
+ **Attributes (steady)**
254
+
255
+ Args:
256
+ info (dict): Parameters with one value per simulation
257
+ network_iteration (pandas.DataFrame): Network iteration
258
+ largest_change_in_split_from_last_iteration (pandas.DataFrame): Largest change in split from last iteration
259
+
260
+ Output:
261
+ Initiates 'LF1' class object
262
+ """
263
+
264
+ _filetype: str = "LF1"
265
+ _suffix: str = ".lf1"
266
+
267
+ def __init__(self, lf_filepath: str | Path | None, steady: bool = False):
268
+ if steady is False:
269
+ data_to_extract = lf1_unsteady_data_to_extract
270
+ else:
271
+ data_to_extract = lf1_steady_data_to_extract
272
+
273
+ super().__init__(lf_filepath, data_to_extract, steady)
274
+
275
+
276
+ class LF2(LF):
277
+ """Reads and processes Flood Modeller 1D log file '.lf2'
278
+
279
+ Args:
280
+ lf2_filepath (str): Full filepath to model lf2 file
281
+
282
+ **Attributes**
283
+
284
+ Args:
285
+ info (dict): Parameters with one value per simulation
286
+ simulated (pandas.DataFrame): Simulated
287
+ wet_cells (pandas.DataFrame): Wet cells
288
+ 2D_boundary_inflow (pandas.DataFrame): 2D boundary inflow
289
+ 2D_boundary_outflow (pandas.DataFrame): 2D boundary outflow
290
+ 1D_link_flow (pandas.DataFrame): 1D link flow
291
+ change_in_volume (pandas.DataFrame): Change in volume
292
+ volume (pandas.DataFrame): Volume
293
+ inst_mass_err (pandas.DataFrame): Inst mass error
294
+ mass_error (pandas.DataFrame): Mass error
295
+ largest_cr (pandas.DataFrame): Largest Cr
296
+ elapsed (pandas.DataFrame): Elapsed
297
+
298
+ Output:
299
+ Initiates 'LF2' class object
300
+ """
301
+
302
+ _filetype: str = "LF2"
303
+ _suffix: str = ".lf2"
304
+
305
+ def __init__(self, lf_filepath: str | Path | None):
306
+ data_to_extract = {
307
+ **lf1_unsteady_data_to_extract,
308
+ **lf2_data_to_extract,
309
+ }
310
+
311
+ super().__init__(lf_filepath, data_to_extract, steady=False)
312
+
313
+
314
+ def lf_factory(filepath: str, suffix: str, steady: bool) -> LF:
315
+ if suffix == "lf1":
316
+ return LF1(filepath, steady)
317
+ if suffix == "lf2":
318
+ return LF2(filepath)
319
+ flow_type = "steady" if steady else "unsteady"
320
+ raise ValueError(f"Unexpected log file type {suffix} for {flow_type} flow")