floodmodeller-api 0.4.3__py3-none-any.whl → 0.4.4.post1__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/_base.py +22 -37
- floodmodeller_api/dat.py +165 -185
- floodmodeller_api/ied.py +82 -87
- floodmodeller_api/ief.py +92 -186
- floodmodeller_api/inp.py +64 -70
- floodmodeller_api/logs/__init__.py +1 -1
- floodmodeller_api/logs/lf.py +61 -17
- floodmodeller_api/test/conftest.py +7 -0
- floodmodeller_api/test/test_conveyance.py +107 -0
- floodmodeller_api/test/test_dat.py +5 -4
- floodmodeller_api/test/test_data/conveyance_test.dat +165 -0
- floodmodeller_api/test/test_data/conveyance_test.feb +116 -0
- floodmodeller_api/test/test_data/conveyance_test.gxy +85 -0
- floodmodeller_api/test/test_data/expected_conveyance.csv +60 -0
- floodmodeller_api/test/test_ief.py +26 -15
- floodmodeller_api/test/test_logs_lf.py +54 -0
- floodmodeller_api/to_from_json.py +24 -12
- floodmodeller_api/units/boundaries.py +6 -0
- floodmodeller_api/units/conveyance.py +301 -0
- floodmodeller_api/units/sections.py +21 -0
- floodmodeller_api/util.py +42 -0
- floodmodeller_api/version.py +1 -1
- floodmodeller_api/xml2d.py +80 -136
- floodmodeller_api/zzn.py +166 -139
- {floodmodeller_api-0.4.3.dist-info → floodmodeller_api-0.4.4.post1.dist-info}/METADATA +4 -1
- {floodmodeller_api-0.4.3.dist-info → floodmodeller_api-0.4.4.post1.dist-info}/RECORD +30 -24
- {floodmodeller_api-0.4.3.dist-info → floodmodeller_api-0.4.4.post1.dist-info}/WHEEL +1 -1
- {floodmodeller_api-0.4.3.dist-info → floodmodeller_api-0.4.4.post1.dist-info}/LICENSE.txt +0 -0
- {floodmodeller_api-0.4.3.dist-info → floodmodeller_api-0.4.4.post1.dist-info}/entry_points.txt +0 -0
- {floodmodeller_api-0.4.3.dist-info → floodmodeller_api-0.4.4.post1.dist-info}/top_level.txt +0 -0
floodmodeller_api/ied.py
CHANGED
|
@@ -20,6 +20,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
20
20
|
|
|
21
21
|
from . import units
|
|
22
22
|
from ._base import FMFile
|
|
23
|
+
from .util import handle_exception
|
|
23
24
|
|
|
24
25
|
if TYPE_CHECKING:
|
|
25
26
|
from pathlib import Path
|
|
@@ -41,24 +42,21 @@ class IED(FMFile):
|
|
|
41
42
|
_filetype: str = "IED"
|
|
42
43
|
_suffix: str = ".ied"
|
|
43
44
|
|
|
45
|
+
@handle_exception(when="read")
|
|
44
46
|
def __init__(self, ied_filepath: str | Path | None = None, from_json: bool = False):
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
FMFile.__init__(self, ied_filepath)
|
|
47
|
+
if from_json:
|
|
48
|
+
return
|
|
49
|
+
if ied_filepath is not None:
|
|
50
|
+
FMFile.__init__(self, ied_filepath)
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
self._read()
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
self._get_unit_definitions()
|
|
54
|
+
else:
|
|
55
|
+
# No filepath specified, create new 'blank' IED in memory
|
|
56
|
+
self._ied_struct: list[dict[str, Any]] = []
|
|
57
|
+
self._raw_data: list[str] = []
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
self._handle_exception(e, when="read")
|
|
59
|
+
self._get_unit_definitions()
|
|
62
60
|
|
|
63
61
|
def _read(self):
|
|
64
62
|
# Read IED data
|
|
@@ -68,82 +66,79 @@ class IED(FMFile):
|
|
|
68
66
|
# Generate IED structure
|
|
69
67
|
self._update_ied_struct()
|
|
70
68
|
|
|
69
|
+
@handle_exception(when="write")
|
|
71
70
|
def _write(self) -> str: # noqa: C901, PLR0912
|
|
72
71
|
"""Returns string representation of the current IED data"""
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
for
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
72
|
+
block_shift = 0
|
|
73
|
+
existing_units: dict[str, list[str]] = {
|
|
74
|
+
"boundaries": [],
|
|
75
|
+
"structures": [],
|
|
76
|
+
"sections": [],
|
|
77
|
+
"conduits": [],
|
|
78
|
+
"losses": [],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for block in self._ied_struct:
|
|
82
|
+
# Check for all supported boundary types
|
|
83
|
+
if block["Type"] in units.SUPPORTED_UNIT_TYPES:
|
|
84
|
+
unit_data = self._raw_data[
|
|
85
|
+
block["start"] + block_shift : block["end"] + 1 + block_shift
|
|
86
|
+
]
|
|
87
|
+
prev_block_len = len(unit_data)
|
|
88
|
+
if units.SUPPORTED_UNIT_TYPES[block["Type"]]["has_subtype"]:
|
|
89
|
+
unit_name = unit_data[2][:12].strip()
|
|
90
|
+
else:
|
|
91
|
+
unit_name = unit_data[1][:12].strip()
|
|
92
|
+
|
|
93
|
+
# Get unit object
|
|
94
|
+
unit_group = getattr(self, units.SUPPORTED_UNIT_TYPES[block["Type"]]["group"])
|
|
95
|
+
if unit_name in unit_group:
|
|
96
|
+
# block still exists
|
|
97
|
+
new_unit_data = unit_group[unit_name]._write()
|
|
98
|
+
existing_units[units.SUPPORTED_UNIT_TYPES[block["Type"]]["group"]].append(
|
|
99
|
+
unit_name,
|
|
100
|
+
)
|
|
101
|
+
else:
|
|
102
|
+
# Bdy block has been deleted
|
|
103
|
+
new_unit_data = []
|
|
104
|
+
|
|
105
|
+
new_block_len = len(new_unit_data)
|
|
106
|
+
self._raw_data[block["start"] + block_shift : block["end"] + 1 + block_shift] = (
|
|
107
|
+
new_unit_data
|
|
108
|
+
)
|
|
109
|
+
# adjust block shift for change in number of lines in bdy block
|
|
110
|
+
block_shift += new_block_len - prev_block_len
|
|
111
|
+
|
|
112
|
+
# Add any new units
|
|
113
|
+
for group_name, _units in existing_units.items():
|
|
114
|
+
for name, unit in getattr(self, group_name).items():
|
|
115
|
+
if name not in _units:
|
|
116
|
+
# Newly added unit
|
|
117
|
+
# Ensure that the 'name' attribute matches name key in boundaries
|
|
118
|
+
self._raw_data.extend(unit._write())
|
|
119
|
+
|
|
120
|
+
# Update ied_struct
|
|
121
|
+
self._update_ied_struct()
|
|
122
|
+
|
|
123
|
+
# Update unit names
|
|
124
|
+
for unit_group, unit_group_name in [
|
|
125
|
+
(self.boundaries, "boundaries"),
|
|
126
|
+
(self.sections, "sections"),
|
|
127
|
+
(self.structures, "structures"),
|
|
128
|
+
(self.conduits, "conduits"),
|
|
129
|
+
(self.losses, "losses"),
|
|
130
|
+
]:
|
|
131
|
+
for name, unit in unit_group.copy().items():
|
|
132
|
+
if name != unit.name:
|
|
133
|
+
# Check if new name already exists as a label
|
|
134
|
+
if unit.name in unit_group:
|
|
135
|
+
raise Exception(
|
|
136
|
+
f'Error: Cannot update label "{name}" to "{unit.name}" because "{unit.name}" already exists in the Network {unit_group_name} group',
|
|
102
137
|
)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
new_block_len = len(new_unit_data)
|
|
108
|
-
self._raw_data[
|
|
109
|
-
block["start"] + block_shift : block["end"] + 1 + block_shift
|
|
110
|
-
] = new_unit_data
|
|
111
|
-
# adjust block shift for change in number of lines in bdy block
|
|
112
|
-
block_shift += new_block_len - prev_block_len
|
|
113
|
-
|
|
114
|
-
# Add any new units
|
|
115
|
-
for group_name, _units in existing_units.items():
|
|
116
|
-
for name, unit in getattr(self, group_name).items():
|
|
117
|
-
if name not in _units:
|
|
118
|
-
# Newly added unit
|
|
119
|
-
# Ensure that the 'name' attribute matches name key in boundaries
|
|
120
|
-
self._raw_data.extend(unit._write())
|
|
121
|
-
|
|
122
|
-
# Update ied_struct
|
|
123
|
-
self._update_ied_struct()
|
|
124
|
-
|
|
125
|
-
# Update unit names
|
|
126
|
-
for unit_group, unit_group_name in [
|
|
127
|
-
(self.boundaries, "boundaries"),
|
|
128
|
-
(self.sections, "sections"),
|
|
129
|
-
(self.structures, "structures"),
|
|
130
|
-
(self.conduits, "conduits"),
|
|
131
|
-
(self.losses, "losses"),
|
|
132
|
-
]:
|
|
133
|
-
for name, unit in unit_group.copy().items():
|
|
134
|
-
if name != unit.name:
|
|
135
|
-
# Check if new name already exists as a label
|
|
136
|
-
if unit.name in unit_group:
|
|
137
|
-
raise Exception(
|
|
138
|
-
f'Error: Cannot update label "{name}" to "{unit.name}" because "{unit.name}" already exists in the Network {unit_group_name} group',
|
|
139
|
-
)
|
|
140
|
-
unit_group[unit.name] = unit
|
|
141
|
-
del unit_group[name]
|
|
142
|
-
|
|
143
|
-
return "\n".join(self._raw_data) + "\n"
|
|
144
|
-
|
|
145
|
-
except Exception as e:
|
|
146
|
-
self._handle_exception(e, when="write")
|
|
138
|
+
unit_group[unit.name] = unit
|
|
139
|
+
del unit_group[name]
|
|
140
|
+
|
|
141
|
+
return "\n".join(self._raw_data) + "\n"
|
|
147
142
|
|
|
148
143
|
def _get_unit_definitions(self):
|
|
149
144
|
# Get unit definitions
|
floodmodeller_api/ief.py
CHANGED
|
@@ -16,7 +16,6 @@ address: Jacobs UK Limited, Flood Modeller, Cottons Centre, Cottons Lane, London
|
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
-
import datetime as dt
|
|
20
19
|
import os
|
|
21
20
|
import subprocess
|
|
22
21
|
import time
|
|
@@ -29,7 +28,8 @@ from tqdm import trange
|
|
|
29
28
|
|
|
30
29
|
from ._base import FMFile
|
|
31
30
|
from .ief_flags import flags
|
|
32
|
-
from .logs import
|
|
31
|
+
from .logs import LF1, create_lf
|
|
32
|
+
from .util import handle_exception
|
|
33
33
|
from .zzn import ZZN
|
|
34
34
|
|
|
35
35
|
|
|
@@ -49,24 +49,19 @@ class IEF(FMFile):
|
|
|
49
49
|
|
|
50
50
|
_filetype: str = "IEF"
|
|
51
51
|
_suffix: str = ".ief"
|
|
52
|
-
OLD_FILE = 5
|
|
53
52
|
ERROR_MAX = 2000
|
|
54
53
|
WARNING_MAX = 3000
|
|
55
|
-
LOG_TIMEOUT = 10
|
|
56
54
|
|
|
55
|
+
@handle_exception(when="read")
|
|
57
56
|
def __init__(self, ief_filepath: str | Path | None = None, from_json: bool = False):
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
else:
|
|
67
|
-
self._create_from_blank()
|
|
68
|
-
except Exception as e:
|
|
69
|
-
self._handle_exception(e, when="read")
|
|
57
|
+
if from_json:
|
|
58
|
+
return
|
|
59
|
+
if ief_filepath is not None:
|
|
60
|
+
FMFile.__init__(self, ief_filepath)
|
|
61
|
+
self._read()
|
|
62
|
+
self._log_path = self._get_result_filepath("lf1")
|
|
63
|
+
else:
|
|
64
|
+
self._create_from_blank()
|
|
70
65
|
|
|
71
66
|
def _read(self):
|
|
72
67
|
# Read IEF data
|
|
@@ -112,42 +107,39 @@ class IEF(FMFile):
|
|
|
112
107
|
prev_comment = None
|
|
113
108
|
del raw_data
|
|
114
109
|
|
|
110
|
+
@handle_exception(when="write")
|
|
115
111
|
def _write(self) -> str:
|
|
116
112
|
"""Returns string representation of the current IEF data
|
|
117
113
|
|
|
118
114
|
Returns:
|
|
119
115
|
str: Full string representation of IEF in its most recent state (including changes not yet saved to disk)
|
|
120
116
|
"""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
117
|
+
# update _ief_properties
|
|
118
|
+
self._update_ief_properties()
|
|
119
|
+
|
|
120
|
+
ief_string = ""
|
|
121
|
+
event = 0 # Used as a counter for multiple eventdata files
|
|
122
|
+
for idx, prop in enumerate(self._ief_properties):
|
|
123
|
+
if prop.startswith("["):
|
|
124
|
+
# writes the [] bound headers to ief string
|
|
125
|
+
ief_string += prop + "\n"
|
|
126
|
+
elif prop.lstrip().startswith(";"):
|
|
127
|
+
if self._ief_properties[idx + 1].lower() != "eventdata":
|
|
128
|
+
# Only write comment if not preceding event data
|
|
130
129
|
ief_string += prop + "\n"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if event_idx == event:
|
|
140
|
-
ief_string += f";{key}\n{prop}={str(event_data[key])}\n"
|
|
141
|
-
break
|
|
142
|
-
event += 1
|
|
143
|
-
|
|
144
|
-
else:
|
|
145
|
-
# writes property and value to ief string
|
|
146
|
-
ief_string += f"{prop}={str(getattr(self, prop))}\n"
|
|
147
|
-
return ief_string
|
|
130
|
+
elif prop.lower() == "eventdata":
|
|
131
|
+
event_data = getattr(self, prop)
|
|
132
|
+
# Add multiple EventData if present
|
|
133
|
+
for event_idx, key in enumerate(event_data):
|
|
134
|
+
if event_idx == event:
|
|
135
|
+
ief_string += f";{key}\n{prop}={str(event_data[key])}\n"
|
|
136
|
+
break
|
|
137
|
+
event += 1
|
|
148
138
|
|
|
149
|
-
|
|
150
|
-
|
|
139
|
+
else:
|
|
140
|
+
# writes property and value to ief string
|
|
141
|
+
ief_string += f"{prop}={str(getattr(self, prop))}\n"
|
|
142
|
+
return ief_string
|
|
151
143
|
|
|
152
144
|
def _create_from_blank(self):
|
|
153
145
|
# No filepath specified, create new 'blank' IEF in memory
|
|
@@ -360,6 +352,7 @@ class IEF(FMFile):
|
|
|
360
352
|
"""
|
|
361
353
|
self._save(filepath)
|
|
362
354
|
|
|
355
|
+
@handle_exception(when="simulate")
|
|
363
356
|
def simulate( # noqa: C901, PLR0912, PLR0913
|
|
364
357
|
self,
|
|
365
358
|
method: str = "WAIT",
|
|
@@ -392,73 +385,70 @@ class IEF(FMFile):
|
|
|
392
385
|
Returns:
|
|
393
386
|
subprocess.Popen(): If method == 'RETURN_PROCESS', the Popen() instance of the process is returned.
|
|
394
387
|
"""
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
break
|
|
411
|
-
|
|
412
|
-
if enginespath == "":
|
|
413
|
-
_enginespath = r"C:\Program Files\Flood Modeller\bin" # Default location
|
|
414
|
-
else:
|
|
415
|
-
_enginespath = enginespath
|
|
416
|
-
if not Path(_enginespath).exists():
|
|
417
|
-
raise Exception(
|
|
418
|
-
f"Flood Modeller non-default engine path not found! {str(_enginespath)}",
|
|
419
|
-
)
|
|
388
|
+
self._range_function = range_function
|
|
389
|
+
self._range_settings = range_settings if range_settings else {}
|
|
390
|
+
if self._filepath is None:
|
|
391
|
+
raise UserWarning(
|
|
392
|
+
"IEF must be saved to a specific filepath before simulate() can be called.",
|
|
393
|
+
)
|
|
394
|
+
if precision.upper() == "DEFAULT":
|
|
395
|
+
precision = "SINGLE" # Defaults to single...
|
|
396
|
+
for attr in dir(self):
|
|
397
|
+
if (
|
|
398
|
+
attr.upper() == "LAUNCHDOUBLEPRECISIONVERSION" # Unless DP specified
|
|
399
|
+
and int(getattr(self, attr)) == 1
|
|
400
|
+
):
|
|
401
|
+
precision = "DOUBLE"
|
|
402
|
+
break
|
|
420
403
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
404
|
+
if enginespath == "":
|
|
405
|
+
_enginespath = r"C:\Program Files\Flood Modeller\bin" # Default location
|
|
406
|
+
else:
|
|
407
|
+
_enginespath = enginespath
|
|
408
|
+
if not Path(_enginespath).exists():
|
|
409
|
+
raise Exception(
|
|
410
|
+
f"Flood Modeller non-default engine path not found! {str(_enginespath)}",
|
|
411
|
+
)
|
|
425
412
|
|
|
426
|
-
|
|
427
|
-
|
|
413
|
+
if precision.upper() == "SINGLE":
|
|
414
|
+
isis32_fp = str(Path(_enginespath, "ISISf32.exe"))
|
|
415
|
+
else:
|
|
416
|
+
isis32_fp = str(Path(_enginespath, "ISISf32_DoubleP.exe"))
|
|
428
417
|
|
|
429
|
-
|
|
418
|
+
if not Path(isis32_fp).exists():
|
|
419
|
+
raise Exception(f"Flood Modeller engine not found! Expected location: {isis32_fp}")
|
|
430
420
|
|
|
431
|
-
|
|
432
|
-
print("Executing simulation...")
|
|
433
|
-
# execute simulation
|
|
434
|
-
process = Popen(run_command, cwd=os.path.dirname(self._filepath))
|
|
421
|
+
run_command = f'"{isis32_fp}" -sd "{self._filepath}"'
|
|
435
422
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
423
|
+
if method.upper() == "WAIT":
|
|
424
|
+
print("Executing simulation...")
|
|
425
|
+
# execute simulation
|
|
426
|
+
process = Popen(run_command, cwd=os.path.dirname(self._filepath))
|
|
439
427
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
428
|
+
# progress bar based on log files
|
|
429
|
+
steady = self.RunType == "Steady"
|
|
430
|
+
self._lf = create_lf(self._log_path, "lf1") if not steady else None
|
|
431
|
+
self._update_progress_bar(process)
|
|
443
432
|
|
|
444
|
-
|
|
433
|
+
while process.poll() is None:
|
|
434
|
+
# Process still running
|
|
435
|
+
time.sleep(1)
|
|
445
436
|
|
|
446
|
-
|
|
447
|
-
raise RuntimeError(summary)
|
|
448
|
-
print(summary)
|
|
437
|
+
result, summary = self._summarise_exy()
|
|
449
438
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
return Popen(run_command, cwd=os.path.dirname(self._filepath))
|
|
439
|
+
if result == 1 and raise_on_failure:
|
|
440
|
+
raise RuntimeError(summary)
|
|
441
|
+
print(summary)
|
|
454
442
|
|
|
455
|
-
|
|
443
|
+
elif method.upper() == "RETURN_PROCESS":
|
|
444
|
+
print("Executing simulation...")
|
|
445
|
+
# execute simulation
|
|
446
|
+
return Popen(run_command, cwd=os.path.dirname(self._filepath))
|
|
456
447
|
|
|
457
|
-
|
|
458
|
-
self._handle_exception(e, when="simulate")
|
|
448
|
+
return None
|
|
459
449
|
|
|
460
450
|
def _get_result_filepath(self, suffix):
|
|
461
|
-
if hasattr(self, "Results"):
|
|
451
|
+
if hasattr(self, "Results") and self.Results != '""': # because blank IEF has 'Results=""'
|
|
462
452
|
path = Path(self.Results).with_suffix("." + suffix)
|
|
463
453
|
if not path.is_absolute():
|
|
464
454
|
# set cwd to ief location and resolve path
|
|
@@ -491,95 +481,11 @@ class IEF(FMFile):
|
|
|
491
481
|
floodmodeller_api.LF1 class object
|
|
492
482
|
"""
|
|
493
483
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
# Get lf location
|
|
497
|
-
lf_path = self._get_result_filepath(suffix)
|
|
498
|
-
|
|
499
|
-
if not lf_path.exists():
|
|
500
|
-
raise FileNotFoundError("Log file (" + suffix + ") not found")
|
|
501
|
-
|
|
502
|
-
return lf_factory(lf_path, suffix, steady)
|
|
503
|
-
|
|
504
|
-
def _determine_lf_type(self): # (str, bool) or (None, None):
|
|
505
|
-
"""Determine the log file type"""
|
|
506
|
-
|
|
507
|
-
if self.RunType == "Unsteady":
|
|
508
|
-
suffix = "lf1"
|
|
509
|
-
steady = False
|
|
510
|
-
|
|
511
|
-
elif self.RunType == "Steady":
|
|
512
|
-
suffix = "lf1"
|
|
513
|
-
steady = True
|
|
514
|
-
|
|
515
|
-
else:
|
|
516
|
-
raise ValueError(f'Unexpected run type "{self.RunType}"')
|
|
517
|
-
|
|
518
|
-
return suffix, steady
|
|
519
|
-
|
|
520
|
-
def _init_log_file(self):
|
|
521
|
-
"""Checks for a new log file, waiting for its creation if necessary"""
|
|
522
|
-
|
|
523
|
-
# determine log file type based on self.RunType
|
|
524
|
-
try:
|
|
525
|
-
suffix, steady = self._determine_lf_type()
|
|
526
|
-
except ValueError:
|
|
527
|
-
self._no_log_file(f'run type "{self.RunType}" not supported')
|
|
528
|
-
self._lf = None
|
|
529
|
-
return
|
|
530
|
-
|
|
531
|
-
# ensure progress bar is supported for that type
|
|
532
|
-
if not (suffix == "lf1" and steady is False):
|
|
533
|
-
self._no_log_file("only 1D unsteady runs are supported")
|
|
534
|
-
self._lf = None
|
|
535
|
-
return
|
|
536
|
-
|
|
537
|
-
# find what log filepath should be
|
|
538
|
-
lf_filepath = self._get_result_filepath(suffix)
|
|
539
|
-
|
|
540
|
-
# wait for log file to exist
|
|
541
|
-
log_file_exists = False
|
|
542
|
-
max_time = time.time() + self.LOG_TIMEOUT
|
|
543
|
-
|
|
544
|
-
while not log_file_exists:
|
|
545
|
-
time.sleep(0.1)
|
|
546
|
-
|
|
547
|
-
log_file_exists = lf_filepath.is_file()
|
|
548
|
-
|
|
549
|
-
# timeout
|
|
550
|
-
if time.time() > max_time:
|
|
551
|
-
self._no_log_file("log file is expected but not detected")
|
|
552
|
-
self._lf = None
|
|
553
|
-
return
|
|
554
|
-
|
|
555
|
-
# wait for new log file
|
|
556
|
-
old_log_file = True
|
|
557
|
-
max_time = time.time() + self.LOG_TIMEOUT
|
|
558
|
-
|
|
559
|
-
while old_log_file:
|
|
560
|
-
time.sleep(0.1)
|
|
561
|
-
|
|
562
|
-
# difference between now and when log file was last modified
|
|
563
|
-
last_modified_timestamp = lf_filepath.stat().st_mtime
|
|
564
|
-
last_modified = dt.datetime.fromtimestamp(last_modified_timestamp)
|
|
565
|
-
time_diff_sec = (dt.datetime.now() - last_modified).total_seconds()
|
|
566
|
-
|
|
567
|
-
# it's old if it's over self.OLD_FILE seconds old (TODO: is this robust?)
|
|
568
|
-
old_log_file = time_diff_sec > self.OLD_FILE
|
|
569
|
-
|
|
570
|
-
# timeout
|
|
571
|
-
if time.time() > max_time:
|
|
572
|
-
self._no_log_file("log file is from previous run")
|
|
573
|
-
self._lf = None
|
|
574
|
-
return
|
|
575
|
-
|
|
576
|
-
# create LF instance
|
|
577
|
-
self._lf = lf_factory(lf_filepath, suffix, steady)
|
|
578
|
-
|
|
579
|
-
def _no_log_file(self, reason):
|
|
580
|
-
"""Warning that there will be no progress bar"""
|
|
484
|
+
if not self._log_path.exists():
|
|
485
|
+
raise FileNotFoundError("Log file (LF1) not found")
|
|
581
486
|
|
|
582
|
-
|
|
487
|
+
steady = self.RunType == "Steady"
|
|
488
|
+
return LF1(self._log_path, steady)
|
|
583
489
|
|
|
584
490
|
def _update_progress_bar(self, process: Popen):
|
|
585
491
|
"""Updates progress bar based on log file"""
|