floodmodeller-api 0.5.0__py3-none-any.whl → 0.5.1__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.
- floodmodeller_api/__init__.py +1 -1
- floodmodeller_api/_base.py +26 -16
- floodmodeller_api/backup.py +3 -2
- floodmodeller_api/dat.py +29 -30
- floodmodeller_api/diff.py +3 -3
- floodmodeller_api/hydrology_plus/hydrology_plus_export.py +14 -13
- floodmodeller_api/ied.py +6 -6
- floodmodeller_api/ief.py +27 -25
- floodmodeller_api/inp.py +3 -4
- floodmodeller_api/logs/lf.py +9 -16
- floodmodeller_api/logs/lf_helpers.py +18 -18
- floodmodeller_api/mapping.py +2 -0
- floodmodeller_api/test/__init__.py +2 -2
- floodmodeller_api/test/conftest.py +2 -3
- floodmodeller_api/test/test_backup.py +2 -2
- floodmodeller_api/test/test_conveyance.py +4 -3
- floodmodeller_api/test/test_dat.py +2 -2
- floodmodeller_api/test/test_data/structure_logs/EX17_expected.csv +4 -0
- floodmodeller_api/test/test_data/structure_logs/EX17_expected.json +69 -0
- floodmodeller_api/test/test_data/structure_logs/EX18_expected.csv +20 -0
- floodmodeller_api/test/test_data/structure_logs/EX18_expected.json +292 -0
- floodmodeller_api/test/test_data/structure_logs/EX6_expected.csv +4 -0
- floodmodeller_api/test/test_data/structure_logs/EX6_expected.json +35 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_flow.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_fr.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_mode.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_stage.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_state.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_velocity.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_left_fp_h.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_left_fp_mode.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_link_inflow.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_max.csv +87 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_right_fp_h.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_right_fp_mode.csv +182 -0
- floodmodeller_api/test/test_flowtimeprofile.py +2 -2
- floodmodeller_api/test/test_hydrology_plus_export.py +4 -2
- floodmodeller_api/test/test_ied.py +2 -2
- floodmodeller_api/test/test_ief.py +2 -2
- floodmodeller_api/test/test_inp.py +2 -2
- floodmodeller_api/test/test_json.py +5 -10
- floodmodeller_api/test/test_logs_lf.py +6 -6
- floodmodeller_api/test/test_read_file.py +1 -0
- floodmodeller_api/test/test_river.py +79 -2
- floodmodeller_api/test/test_tool.py +8 -5
- floodmodeller_api/test/test_toolbox_structure_log.py +149 -158
- floodmodeller_api/test/test_xml2d.py +9 -11
- floodmodeller_api/test/test_zz.py +143 -0
- floodmodeller_api/to_from_json.py +8 -8
- floodmodeller_api/tool.py +12 -6
- floodmodeller_api/toolbox/example_tool.py +5 -1
- floodmodeller_api/toolbox/model_build/add_siltation_definition.py +12 -8
- floodmodeller_api/toolbox/model_build/structure_log/structure_log.py +498 -196
- floodmodeller_api/toolbox/model_build/structure_log_definition.py +5 -1
- floodmodeller_api/units/_base.py +14 -10
- floodmodeller_api/units/conveyance.py +10 -8
- floodmodeller_api/units/helpers.py +1 -3
- floodmodeller_api/units/losses.py +2 -3
- floodmodeller_api/units/sections.py +15 -11
- floodmodeller_api/units/structures.py +9 -9
- floodmodeller_api/units/units.py +2 -0
- floodmodeller_api/urban1d/_base.py +6 -9
- floodmodeller_api/urban1d/outfalls.py +2 -1
- floodmodeller_api/urban1d/raingauges.py +2 -1
- floodmodeller_api/urban1d/subsections.py +2 -0
- floodmodeller_api/urban1d/xsections.py +3 -2
- floodmodeller_api/util.py +16 -2
- floodmodeller_api/validation/validation.py +2 -1
- floodmodeller_api/version.py +1 -1
- floodmodeller_api/xml2d.py +18 -20
- floodmodeller_api/zz.py +538 -0
- {floodmodeller_api-0.5.0.dist-info → floodmodeller_api-0.5.1.dist-info}/METADATA +20 -14
- {floodmodeller_api-0.5.0.dist-info → floodmodeller_api-0.5.1.dist-info}/RECORD +78 -60
- {floodmodeller_api-0.5.0.dist-info → floodmodeller_api-0.5.1.dist-info}/WHEEL +1 -1
- floodmodeller_api/test/test_zzn.py +0 -36
- floodmodeller_api/zzn.py +0 -414
- /floodmodeller_api/test/test_data/{network_from_tabularCSV.csv → tabular_csv_outputs/network_zzn_max.csv} +0 -0
- {floodmodeller_api-0.5.0.dist-info → floodmodeller_api-0.5.1.dist-info}/LICENSE.txt +0 -0
- {floodmodeller_api-0.5.0.dist-info → floodmodeller_api-0.5.1.dist-info}/entry_points.txt +0 -0
- {floodmodeller_api-0.5.0.dist-info → floodmodeller_api-0.5.1.dist-info}/top_level.txt +0 -0
floodmodeller_api/__init__.py
CHANGED
floodmodeller_api/_base.py
CHANGED
|
@@ -33,8 +33,8 @@ from .util import FloodModellerAPIError, handle_exception
|
|
|
33
33
|
class FMFile(Jsonable):
|
|
34
34
|
"""Base class for all Flood Modeller File types"""
|
|
35
35
|
|
|
36
|
-
_filetype: str
|
|
37
|
-
_suffix: str
|
|
36
|
+
_filetype: str
|
|
37
|
+
_suffix: str
|
|
38
38
|
MAX_DIFF = 25
|
|
39
39
|
|
|
40
40
|
def __init__(self, filepath: str | Path | None = None, **kwargs):
|
|
@@ -42,16 +42,16 @@ class FMFile(Jsonable):
|
|
|
42
42
|
self._filepath = Path(filepath)
|
|
43
43
|
# * Add check or fix for path lengths greater than DOS standard length of 260 characters
|
|
44
44
|
|
|
45
|
-
if
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
)
|
|
45
|
+
if self._filepath.suffix.lower() != self._suffix:
|
|
46
|
+
msg = f"Given filepath does not point to a {self._filetype} file. Please point to the full path for a {self._filetype} file"
|
|
47
|
+
raise TypeError(msg)
|
|
49
48
|
if not self._filepath.exists():
|
|
50
|
-
|
|
49
|
+
msg = (
|
|
51
50
|
f"{self._filetype} file does not exist! If you are wanting to create a new {self._filetype}, initiate the class without a given "
|
|
52
51
|
f"filepath to create a new blank {self._filetype} or point the filepath of an existing {self._filetype} to use as a template, "
|
|
53
|
-
f"then use the .save() method to save to a new filepath"
|
|
52
|
+
f"then use the .save() method to save to a new filepath"
|
|
54
53
|
)
|
|
54
|
+
raise FileNotFoundError(msg)
|
|
55
55
|
# If the file is not a ZZN file, then perform a backup
|
|
56
56
|
# This performs a conditional back up, only copying the file if an equivalent copy doesn't already exist
|
|
57
57
|
if self._filetype != "ZZN":
|
|
@@ -60,6 +60,17 @@ class FMFile(Jsonable):
|
|
|
60
60
|
# Add the file object as a property to expose the restore method
|
|
61
61
|
self.file = file
|
|
62
62
|
|
|
63
|
+
@property
|
|
64
|
+
def filepath(self) -> Path:
|
|
65
|
+
if not hasattr(self, "_filepath"):
|
|
66
|
+
msg = "Object has no filepath."
|
|
67
|
+
raise AttributeError(msg)
|
|
68
|
+
return self._filepath
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def filetype(self) -> str:
|
|
72
|
+
return self._filetype
|
|
73
|
+
|
|
63
74
|
def __repr__(self):
|
|
64
75
|
filepath = "<in_memory>" if not hasattr(self, "_filepath") else self._filepath
|
|
65
76
|
return f"<floodmodeller_api Class: {self._filetype}(filepath={filepath})>"
|
|
@@ -73,9 +84,8 @@ class FMFile(Jsonable):
|
|
|
73
84
|
def _update(self):
|
|
74
85
|
"""Updates the existing self._filetype based on any altered attributes"""
|
|
75
86
|
if self._filepath is None:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
87
|
+
msg = f"{self._filetype} must be saved to a specific filepath before update() can be called."
|
|
88
|
+
raise UserWarning(msg)
|
|
79
89
|
|
|
80
90
|
string = self._write()
|
|
81
91
|
with open(self._filepath, "w") as _file:
|
|
@@ -84,10 +94,9 @@ class FMFile(Jsonable):
|
|
|
84
94
|
|
|
85
95
|
def _save(self, filepath):
|
|
86
96
|
filepath = Path(filepath).absolute()
|
|
87
|
-
if
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
)
|
|
97
|
+
if filepath.suffix.lower() != self._suffix:
|
|
98
|
+
msg = f'Given filepath does not point to a filepath suffixed "{self._suffix}". Please point to the full path to save the {self._filetype} file'
|
|
99
|
+
raise TypeError(msg)
|
|
91
100
|
|
|
92
101
|
if not filepath.parent.exists():
|
|
93
102
|
Path.mkdir(filepath.parent)
|
|
@@ -102,7 +111,8 @@ class FMFile(Jsonable):
|
|
|
102
111
|
@handle_exception(when="compare")
|
|
103
112
|
def _diff(self, other, force_print=False):
|
|
104
113
|
if self._filetype != other._filetype:
|
|
105
|
-
|
|
114
|
+
msg = "Cannot compare objects of different filetypes"
|
|
115
|
+
raise TypeError(msg)
|
|
106
116
|
diff = self._get_diff(other)
|
|
107
117
|
if diff[0]:
|
|
108
118
|
print("No difference, files are equivalent")
|
floodmodeller_api/backup.py
CHANGED
|
@@ -195,7 +195,8 @@ class File(BackupControl):
|
|
|
195
195
|
self.path = Path(path)
|
|
196
196
|
# Check if the file exists
|
|
197
197
|
if not self.path.exists():
|
|
198
|
-
|
|
198
|
+
msg = "File not found!"
|
|
199
|
+
raise OSError(msg)
|
|
199
200
|
self.ext = self.path.suffix
|
|
200
201
|
self.dttm_str = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
|
|
201
202
|
self._generate_file_id()
|
|
@@ -232,7 +233,7 @@ class File(BackupControl):
|
|
|
232
233
|
copy(self.path, backup_filepath)
|
|
233
234
|
# Log an entry to the csv to make it easy to find the file
|
|
234
235
|
# TODO: Only log file_id and poath, don't log duplicate lines. Needs to be fast so it doesn't slow FMFile down
|
|
235
|
-
log_str = f"{
|
|
236
|
+
log_str = f"{self.path!s},{self.file_id},{self.dttm_str}\n"
|
|
236
237
|
with open(self.backup_csv_path, "a") as f:
|
|
237
238
|
f.write(log_str)
|
|
238
239
|
|
floodmodeller_api/dat.py
CHANGED
|
@@ -253,13 +253,11 @@ class DAT(FMFile):
|
|
|
253
253
|
Union[Unit, list[Unit], None]: Either a singular unit or list of units with ds_label matching, if none exist returns none.
|
|
254
254
|
"""
|
|
255
255
|
|
|
256
|
-
_ds_list = [
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
except AttributeError:
|
|
262
|
-
continue
|
|
256
|
+
_ds_list = [
|
|
257
|
+
item
|
|
258
|
+
for item in self._all_units
|
|
259
|
+
if hasattr(item, "ds_label") and item.ds_label == current_unit.name
|
|
260
|
+
]
|
|
263
261
|
|
|
264
262
|
if len(_ds_list) == 0:
|
|
265
263
|
return None
|
|
@@ -295,7 +293,7 @@ class DAT(FMFile):
|
|
|
295
293
|
def _read(self) -> None:
|
|
296
294
|
# Read DAT data
|
|
297
295
|
with open(self._filepath) as dat_file:
|
|
298
|
-
self._raw_data: list[str] = [line.rstrip("\n") for line in dat_file
|
|
296
|
+
self._raw_data: list[str] = [line.rstrip("\n") for line in dat_file]
|
|
299
297
|
|
|
300
298
|
# Generate DAT structure
|
|
301
299
|
self._update_dat_struct()
|
|
@@ -414,9 +412,8 @@ class DAT(FMFile):
|
|
|
414
412
|
if name != unit.name:
|
|
415
413
|
# Check if new name already exists as a label
|
|
416
414
|
if unit.name in unit_group:
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
)
|
|
415
|
+
msg = f'Error: Cannot update label "{name}" to "{unit.name}" because "{unit.name}" already exists in the Network {unit_group_name} group'
|
|
416
|
+
raise Exception(msg)
|
|
420
417
|
unit_group[unit.name] = unit
|
|
421
418
|
del unit_group[name]
|
|
422
419
|
# Update label in ICs
|
|
@@ -528,7 +525,8 @@ class DAT(FMFile):
|
|
|
528
525
|
elif unit_type in units.UNSUPPORTED_UNIT_TYPES:
|
|
529
526
|
self._process_unsupported_unit(unit_type, unit_data)
|
|
530
527
|
elif unit_type not in ("GENERAL", "GISINFO"):
|
|
531
|
-
|
|
528
|
+
msg = f"Unexpected unit type encountered: {unit_type}"
|
|
529
|
+
raise Exception(msg)
|
|
532
530
|
|
|
533
531
|
def _initialize_collections(self):
|
|
534
532
|
# Initialize unit collections
|
|
@@ -564,9 +562,8 @@ class DAT(FMFile):
|
|
|
564
562
|
def _add_unit_to_group(self, unit_group, unit_type, unit_name, unit_data):
|
|
565
563
|
# Raise exception if a duplicate label is encountered
|
|
566
564
|
if unit_name in unit_group:
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
)
|
|
565
|
+
msg = f'Duplicate label ({unit_name}) encountered within category: {units.SUPPORTED_UNIT_TYPES[unit_type]["group"]}'
|
|
566
|
+
raise Exception(msg)
|
|
570
567
|
# Changes done to account for unit types with spaces/dashes eg Flat-V Weir
|
|
571
568
|
unit_type_safe = unit_type.replace(" ", "_").replace("-", "_")
|
|
572
569
|
unit_group[unit_name] = eval(
|
|
@@ -720,7 +717,8 @@ class DAT(FMFile):
|
|
|
720
717
|
"""
|
|
721
718
|
# catch if not valid unit
|
|
722
719
|
if not isinstance(unit, Unit):
|
|
723
|
-
|
|
720
|
+
msg = "unit isn't a unit"
|
|
721
|
+
raise TypeError(msg)
|
|
724
722
|
|
|
725
723
|
# remove from all units
|
|
726
724
|
index = self._all_units.index(unit)
|
|
@@ -768,17 +766,17 @@ class DAT(FMFile):
|
|
|
768
766
|
# catch errors
|
|
769
767
|
provided_params = sum(arg is not None for arg in (add_before, add_after, add_at))
|
|
770
768
|
if provided_params == 0:
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
)
|
|
769
|
+
msg = "No positional argument given. Please provide either add_before, add_at or add_after"
|
|
770
|
+
raise SyntaxError(msg)
|
|
774
771
|
if provided_params > 1:
|
|
775
|
-
|
|
772
|
+
msg = "Only one of add_at, add_before, or add_after required"
|
|
773
|
+
raise SyntaxError(msg)
|
|
776
774
|
if not isinstance(unit, Unit):
|
|
777
|
-
|
|
775
|
+
msg = "unit isn't a unit"
|
|
776
|
+
raise TypeError(msg)
|
|
778
777
|
if add_at is None and not (isinstance(add_before, Unit) or isinstance(add_after, Unit)):
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
)
|
|
778
|
+
msg = "add_before or add_after argument must be a Flood Modeller Unit type"
|
|
779
|
+
raise TypeError(msg)
|
|
782
780
|
|
|
783
781
|
unit_class = unit._unit
|
|
784
782
|
if unit_class != "COMMENT":
|
|
@@ -786,9 +784,8 @@ class DAT(FMFile):
|
|
|
786
784
|
unit_group_name = units.SUPPORTED_UNIT_TYPES[unit._unit]["group"]
|
|
787
785
|
unit_group = getattr(self, unit_group_name)
|
|
788
786
|
if unit.name in unit_group:
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
)
|
|
787
|
+
msg = "Name already appears in unit group. Cannot have two units with same name in same group"
|
|
788
|
+
raise NameError(msg)
|
|
792
789
|
|
|
793
790
|
# positional argument
|
|
794
791
|
if add_at is not None:
|
|
@@ -796,7 +793,8 @@ class DAT(FMFile):
|
|
|
796
793
|
if insert_index < 0:
|
|
797
794
|
insert_index += len(self._all_units) + 1
|
|
798
795
|
if insert_index < 0:
|
|
799
|
-
|
|
796
|
+
msg = f"invalid add_at index: {add_at}"
|
|
797
|
+
raise Exception(msg)
|
|
800
798
|
else:
|
|
801
799
|
check_unit = add_before or add_after
|
|
802
800
|
for index, thing in enumerate(self._all_units):
|
|
@@ -805,9 +803,10 @@ class DAT(FMFile):
|
|
|
805
803
|
insert_index += 1 if add_after else 0
|
|
806
804
|
break
|
|
807
805
|
else:
|
|
808
|
-
|
|
809
|
-
f"{check_unit} not found in dat network, so cannot be used to add before/after"
|
|
806
|
+
msg = (
|
|
807
|
+
f"{check_unit} not found in dat network, so cannot be used to add before/after"
|
|
810
808
|
)
|
|
809
|
+
raise Exception(msg)
|
|
811
810
|
|
|
812
811
|
unit_data = unit._write()
|
|
813
812
|
self._all_units.insert(insert_index, unit)
|
floodmodeller_api/diff.py
CHANGED
|
@@ -59,8 +59,8 @@ def check_item_with_dataframe_equal( # noqa: C901, PLR0912
|
|
|
59
59
|
row_diffs = []
|
|
60
60
|
for row in rows:
|
|
61
61
|
for col in df_diff.columns:
|
|
62
|
-
if True not in df_diff.loc[row, col].duplicated().
|
|
63
|
-
vals = df_diff.loc[row, col].
|
|
62
|
+
if True not in df_diff.loc[row, col].duplicated().to_numpy():
|
|
63
|
+
vals = df_diff.loc[row, col].to_numpy()
|
|
64
64
|
row_diffs.append(
|
|
65
65
|
f" Row: {row}, Col: '{col}' - left: {vals[0]}, right: {vals[1]}",
|
|
66
66
|
)
|
|
@@ -96,7 +96,7 @@ def check_dict_with_dataframe_equal(dict_a, dict_b, name, diff, special_types):
|
|
|
96
96
|
)
|
|
97
97
|
if not _result:
|
|
98
98
|
result = False
|
|
99
|
-
except KeyError as ke:
|
|
99
|
+
except KeyError as ke: # noqa: PERF203
|
|
100
100
|
result = False
|
|
101
101
|
diff.append((name, f"Key: '{ke.args[0]}' missing in other"))
|
|
102
102
|
continue
|
|
@@ -51,7 +51,8 @@ class HydrologyPlusExport(FMFile):
|
|
|
51
51
|
with self._filepath.open("r") as file:
|
|
52
52
|
header = file.readline().strip(" ,\n\r")
|
|
53
53
|
if header != "Flood Modeller Hydrology+ hydrograph file":
|
|
54
|
-
|
|
54
|
+
msg = "Input file is not the correct format for Hydrology+ export data."
|
|
55
|
+
raise ValueError(msg)
|
|
55
56
|
|
|
56
57
|
self._data_file = pd.read_csv(self._filepath)
|
|
57
58
|
self._metadata = self._get_metadata()
|
|
@@ -85,20 +86,21 @@ class HydrologyPlusExport(FMFile):
|
|
|
85
86
|
return next(col for col in self.data.columns if col.lower().startswith(event.lower()))
|
|
86
87
|
|
|
87
88
|
if not (return_period and storm_duration and scenario):
|
|
88
|
-
|
|
89
|
+
msg = (
|
|
89
90
|
"Missing required inputs to find event, if no event string is passed then a "
|
|
90
91
|
"return_period, storm_duration and scenario are needed. You provided: "
|
|
91
|
-
f"{return_period=}, {storm_duration=}, {scenario=}"
|
|
92
|
+
f"{return_period=}, {storm_duration=}, {scenario=}"
|
|
92
93
|
)
|
|
94
|
+
raise ValueError(msg)
|
|
93
95
|
for column in self.data.columns:
|
|
94
96
|
s, sd, rp, *_ = column.split(" - ")
|
|
95
97
|
if s == scenario and float(sd) == storm_duration and float(rp) == return_period:
|
|
96
98
|
return column
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
msg = (
|
|
100
|
+
"No matching event was found based on "
|
|
101
|
+
f"{return_period=}, {storm_duration=}, {scenario=}"
|
|
102
|
+
)
|
|
103
|
+
raise ValueError(msg)
|
|
102
104
|
|
|
103
105
|
def get_event_flow(
|
|
104
106
|
self,
|
|
@@ -197,11 +199,10 @@ class HydrologyPlusExport(FMFile):
|
|
|
197
199
|
elif isinstance(template_ief, (Path, str)):
|
|
198
200
|
template_ief = IEF(template_ief)
|
|
199
201
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return generated_iefs
|
|
202
|
+
return [
|
|
203
|
+
self.generate_ief(node_label, template_ief, event=column)
|
|
204
|
+
for column in self.data.columns
|
|
205
|
+
]
|
|
205
206
|
|
|
206
207
|
def generate_ief( # noqa: PLR0913
|
|
207
208
|
self,
|
floodmodeller_api/ied.py
CHANGED
|
@@ -61,7 +61,7 @@ class IED(FMFile):
|
|
|
61
61
|
def _read(self):
|
|
62
62
|
# Read IED data
|
|
63
63
|
with open(self._filepath) as ied_file:
|
|
64
|
-
self._raw_data = [line.rstrip("\n") for line in ied_file
|
|
64
|
+
self._raw_data = [line.rstrip("\n") for line in ied_file]
|
|
65
65
|
|
|
66
66
|
# Generate IED structure
|
|
67
67
|
self._update_ied_struct()
|
|
@@ -87,10 +87,11 @@ class IED(FMFile):
|
|
|
87
87
|
if name != unit.name:
|
|
88
88
|
# Check if new name already exists as a label
|
|
89
89
|
if unit.name in unit_group:
|
|
90
|
-
|
|
90
|
+
msg = (
|
|
91
91
|
f'Error: Cannot update label "{name}" to "{unit.name}" because '
|
|
92
|
-
f'"{unit.name}" already exists in the Network {unit_group_name} group'
|
|
92
|
+
f'"{unit.name}" already exists in the Network {unit_group_name} group'
|
|
93
93
|
)
|
|
94
|
+
raise Exception(msg)
|
|
94
95
|
unit_group[unit.name] = unit
|
|
95
96
|
del unit_group[name]
|
|
96
97
|
|
|
@@ -180,9 +181,8 @@ class IED(FMFile):
|
|
|
180
181
|
# Create instance of unit and add to relevant group
|
|
181
182
|
unit_group = getattr(self, units.SUPPORTED_UNIT_TYPES[block["Type"]]["group"])
|
|
182
183
|
if unit_name in unit_group:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
)
|
|
184
|
+
msg = f'Duplicate label ({unit_name}) encountered within category: {units.SUPPORTED_UNIT_TYPES[block["Type"]]["group"]}'
|
|
185
|
+
raise Exception(msg)
|
|
186
186
|
unit_group[unit_name] = eval(f'units.{block["Type"]}({unit_data})')
|
|
187
187
|
|
|
188
188
|
self._all_units.append(unit_group[unit_name])
|
floodmodeller_api/ief.py
CHANGED
|
@@ -17,7 +17,6 @@ address: Jacobs UK Limited, Flood Modeller, Cottons Centre, Cottons Lane, London
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
import csv
|
|
20
|
-
import os
|
|
21
20
|
import subprocess
|
|
22
21
|
import time
|
|
23
22
|
from io import StringIO
|
|
@@ -34,7 +33,7 @@ from .ief_flags import flags
|
|
|
34
33
|
from .logs import LF1, create_lf
|
|
35
34
|
from .to_from_json import Jsonable
|
|
36
35
|
from .util import handle_exception
|
|
37
|
-
from .
|
|
36
|
+
from .zz import ZZN
|
|
38
37
|
|
|
39
38
|
|
|
40
39
|
def try_numeric(value: str) -> str | int | float:
|
|
@@ -82,7 +81,7 @@ class IEF(FMFile):
|
|
|
82
81
|
def _read(self):
|
|
83
82
|
# Read IEF data
|
|
84
83
|
with open(self._filepath) as ief_file:
|
|
85
|
-
raw_data = [line.rstrip("\n") for line in ief_file
|
|
84
|
+
raw_data = [line.rstrip("\n") for line in ief_file]
|
|
86
85
|
# Clean data and add as class properties
|
|
87
86
|
# Create a list to store the properties which are to be saved in IEF, so as to ignore any temp properties.
|
|
88
87
|
prev_comment = None
|
|
@@ -170,7 +169,7 @@ class IEF(FMFile):
|
|
|
170
169
|
# Add multiple EventData if present
|
|
171
170
|
for idx, key in enumerate(event_data):
|
|
172
171
|
if idx == event_index:
|
|
173
|
-
ief_string += f";{key}\nEventData{eq}{
|
|
172
|
+
ief_string += f";{key}\nEventData{eq}{event_data[key]!s}\n"
|
|
174
173
|
break
|
|
175
174
|
event_index += 1
|
|
176
175
|
|
|
@@ -181,7 +180,7 @@ class IEF(FMFile):
|
|
|
181
180
|
|
|
182
181
|
else:
|
|
183
182
|
# writes property and value to ief string
|
|
184
|
-
ief_string += f"{prop}{eq}{
|
|
183
|
+
ief_string += f"{prop}{eq}{getattr(self, prop)!s}\n"
|
|
185
184
|
|
|
186
185
|
return ief_string
|
|
187
186
|
|
|
@@ -279,10 +278,11 @@ class IEF(FMFile):
|
|
|
279
278
|
def _update_eventdata_info(self): # noqa: C901
|
|
280
279
|
if not isinstance(self.eventdata, dict):
|
|
281
280
|
# If attribute not a dict, adds the value as a single entry in list
|
|
282
|
-
|
|
281
|
+
msg = (
|
|
283
282
|
"The 'EventData' attribute should be a dictionary with keys defining the event"
|
|
284
|
-
" names and values referencing the IED files"
|
|
283
|
+
" names and values referencing the IED files"
|
|
285
284
|
)
|
|
285
|
+
raise AttributeError(msg)
|
|
286
286
|
|
|
287
287
|
# Number of 'EventData' flags in ief
|
|
288
288
|
event_properties = self._ief_properties.count("EventData")
|
|
@@ -328,11 +328,12 @@ class IEF(FMFile):
|
|
|
328
328
|
try:
|
|
329
329
|
self.NoOfFlowTimeSeries = sum([ftp.count_series() for ftp in self.flowtimeprofiles])
|
|
330
330
|
except FileNotFoundError as err:
|
|
331
|
-
|
|
331
|
+
msg = (
|
|
332
332
|
"Failed to read csv referenced in flowtimeprofile, file either does not exist or is"
|
|
333
333
|
"unable to be found due to relative path from IEF file. NoOfFlowTimeSeries has not"
|
|
334
|
-
"been updated."
|
|
335
|
-
)
|
|
334
|
+
"been updated."
|
|
335
|
+
)
|
|
336
|
+
raise UserWarning(msg) from err
|
|
336
337
|
|
|
337
338
|
end_index = None
|
|
338
339
|
start_index = (
|
|
@@ -471,9 +472,8 @@ class IEF(FMFile):
|
|
|
471
472
|
self._range_function = range_function
|
|
472
473
|
self._range_settings = range_settings if range_settings else {}
|
|
473
474
|
if self._filepath is None:
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
)
|
|
475
|
+
msg = "IEF must be saved to a specific filepath before simulate() can be called."
|
|
476
|
+
raise UserWarning(msg)
|
|
477
477
|
if precision.upper() == "DEFAULT":
|
|
478
478
|
precision = "SINGLE" # Defaults to single...
|
|
479
479
|
for attr in dir(self):
|
|
@@ -489,9 +489,8 @@ class IEF(FMFile):
|
|
|
489
489
|
else:
|
|
490
490
|
_enginespath = enginespath
|
|
491
491
|
if not Path(_enginespath).exists():
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
)
|
|
492
|
+
msg = f"Flood Modeller non-default engine path not found! {_enginespath!s}"
|
|
493
|
+
raise Exception(msg)
|
|
495
494
|
|
|
496
495
|
if precision.upper() == "SINGLE":
|
|
497
496
|
isis32_fp = str(Path(_enginespath, "ISISf32.exe"))
|
|
@@ -499,14 +498,15 @@ class IEF(FMFile):
|
|
|
499
498
|
isis32_fp = str(Path(_enginespath, "ISISf32_DoubleP.exe"))
|
|
500
499
|
|
|
501
500
|
if not Path(isis32_fp).exists():
|
|
502
|
-
|
|
501
|
+
msg = f"Flood Modeller engine not found! Expected location: {isis32_fp}"
|
|
502
|
+
raise Exception(msg)
|
|
503
503
|
|
|
504
504
|
run_command = f'"{isis32_fp}" -sd "{self._filepath}"'
|
|
505
505
|
|
|
506
506
|
if method.upper() == "WAIT":
|
|
507
507
|
print("Executing simulation...")
|
|
508
508
|
# execute simulation
|
|
509
|
-
process = Popen(run_command, cwd=
|
|
509
|
+
process = Popen(run_command, cwd=Path(self._filepath).parent)
|
|
510
510
|
|
|
511
511
|
# progress bar based on log files
|
|
512
512
|
steady = self.RunType == "Steady"
|
|
@@ -526,7 +526,7 @@ class IEF(FMFile):
|
|
|
526
526
|
elif method.upper() == "RETURN_PROCESS":
|
|
527
527
|
print("Executing simulation...")
|
|
528
528
|
# execute simulation
|
|
529
|
-
return Popen(run_command, cwd=
|
|
529
|
+
return Popen(run_command, cwd=Path(self._filepath).parent)
|
|
530
530
|
|
|
531
531
|
return None
|
|
532
532
|
|
|
@@ -553,7 +553,8 @@ class IEF(FMFile):
|
|
|
553
553
|
result_path = self._get_result_filepath(suffix="zzn")
|
|
554
554
|
|
|
555
555
|
if not result_path.exists():
|
|
556
|
-
|
|
556
|
+
msg = "Simulation results file (zzn) not found"
|
|
557
|
+
raise FileNotFoundError(msg)
|
|
557
558
|
|
|
558
559
|
return ZZN(result_path)
|
|
559
560
|
|
|
@@ -565,7 +566,8 @@ class IEF(FMFile):
|
|
|
565
566
|
"""
|
|
566
567
|
|
|
567
568
|
if not self._log_path.exists():
|
|
568
|
-
|
|
569
|
+
msg = "Log file (LF1) not found"
|
|
570
|
+
raise FileNotFoundError(msg)
|
|
569
571
|
|
|
570
572
|
steady = self.RunType == "Steady"
|
|
571
573
|
return LF1(self._log_path, steady)
|
|
@@ -616,7 +618,8 @@ class IEF(FMFile):
|
|
|
616
618
|
exy_path = self._filepath.with_suffix(".exy")
|
|
617
619
|
|
|
618
620
|
if not exy_path.exists():
|
|
619
|
-
|
|
621
|
+
msg = "Simulation results error log (.exy) not found"
|
|
622
|
+
raise FileNotFoundError(msg)
|
|
620
623
|
|
|
621
624
|
exy_data = pd.read_csv(exy_path, names=["node", "timestep", "severity", "code", "summary"])
|
|
622
625
|
exy_data["type"] = exy_data["code"].apply(
|
|
@@ -678,9 +681,8 @@ class FlowTimeProfile(Jsonable):
|
|
|
678
681
|
self.profile = kwargs.get("profile", "")
|
|
679
682
|
self.comment = kwargs.get("comment", "")
|
|
680
683
|
else:
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
)
|
|
684
|
+
msg = "You must provide either a single raw string argument or keyword arguments."
|
|
685
|
+
raise ValueError(msg)
|
|
684
686
|
|
|
685
687
|
base_path = Path(kwargs.get("ief_filepath", ""))
|
|
686
688
|
self._csvfile = (base_path / self.csv_filepath.strip('"')).resolve()
|
floodmodeller_api/inp.py
CHANGED
|
@@ -60,7 +60,7 @@ class INP(FMFile):
|
|
|
60
60
|
def _read(self):
|
|
61
61
|
# Read INP file
|
|
62
62
|
with open(self._filepath) as inp_file:
|
|
63
|
-
self._raw_data = [line.rstrip("\n") for line in inp_file
|
|
63
|
+
self._raw_data = [line.rstrip("\n") for line in inp_file]
|
|
64
64
|
|
|
65
65
|
# Generate INP file structure
|
|
66
66
|
self._update_inp_struct()
|
|
@@ -129,9 +129,8 @@ class INP(FMFile):
|
|
|
129
129
|
return "\n".join(self._raw_data) + "\n"
|
|
130
130
|
|
|
131
131
|
def _create_from_blank(self):
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
)
|
|
132
|
+
msg = "Creating new 1D urban models (INP files) is not yet supported by floodmodeller_api, only existing models can be read"
|
|
133
|
+
raise NotImplementedError(msg)
|
|
135
134
|
|
|
136
135
|
def _get_section_definitions(self):
|
|
137
136
|
"""Internal method used to get section definitions for each supported unit type and general parameters."""
|
floodmodeller_api/logs/lf.py
CHANGED
|
@@ -70,7 +70,7 @@ class LF(FMFile):
|
|
|
70
70
|
def _read(self, force_reread: bool = False, suppress_final_step: bool = False):
|
|
71
71
|
# Read LF file
|
|
72
72
|
with open(self._filepath) as lf_file:
|
|
73
|
-
self._raw_data = [line.rstrip("\n") for line in lf_file
|
|
73
|
+
self._raw_data = [line.rstrip("\n") for line in lf_file]
|
|
74
74
|
|
|
75
75
|
# Force rereading from start of file
|
|
76
76
|
if force_reread is True:
|
|
@@ -140,17 +140,12 @@ class LF(FMFile):
|
|
|
140
140
|
def _get_index(self):
|
|
141
141
|
"""Finds key and dataframe for variable that is the index"""
|
|
142
142
|
|
|
143
|
-
for
|
|
144
|
-
|
|
145
|
-
self.
|
|
146
|
-
index_key = key
|
|
147
|
-
index_df = self._extracted_data[key].data.get_value()
|
|
148
|
-
return index_key, index_df
|
|
149
|
-
|
|
150
|
-
except KeyError:
|
|
151
|
-
pass
|
|
143
|
+
for k, v in self._data_to_extract.items():
|
|
144
|
+
if "is_index" in v:
|
|
145
|
+
return k, self._extracted_data[k].data.get_value()
|
|
152
146
|
|
|
153
|
-
|
|
147
|
+
msg = "No index variable found"
|
|
148
|
+
raise Exception(msg)
|
|
154
149
|
|
|
155
150
|
def _set_attributes(self):
|
|
156
151
|
"""Makes each Parser value an attribute; "last" values in dictionary"""
|
|
@@ -198,12 +193,10 @@ class LF(FMFile):
|
|
|
198
193
|
if v["data_type"] == "all" and (include_tuflow or "tuflow" not in k)
|
|
199
194
|
}
|
|
200
195
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
df.sort_index(inplace=True)
|
|
196
|
+
lf_df = pd.concat(data_type_all, axis=1)
|
|
197
|
+
lf_df.columns = lf_df.columns.droplevel()
|
|
205
198
|
|
|
206
|
-
return
|
|
199
|
+
return lf_df.sort_index()
|
|
207
200
|
|
|
208
201
|
def _sync_cols(self):
|
|
209
202
|
"""Ensures Parser values (of type "all") have an entry each iteration"""
|