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,76 +1,76 @@
1
- from floodmodeller_api.tool import FMTool, Parameter
2
-
3
- from .structure_log import StructureLogBuilder
4
-
5
-
6
- class StructureLog(FMTool):
7
- """
8
- This tool creates an output log (.csv file) of all the conduits and structures within a DAT file.
9
- It lists:
10
-
11
- - Unit Name
12
- - Unit Type
13
- - Unit Subtype
14
- - Comment
15
- - Friction
16
- - Distance (m)
17
- - Weir Coefficient
18
- - Culvert Inlet/Outlet Loss
19
-
20
- Args:
21
- input_path (str): Path to the input DAT file
22
- output_path (str): Path to the output csv file
23
-
24
-
25
- **Usage**
26
-
27
- To use this tool, you can either run it from the command line:
28
-
29
- .. code::
30
-
31
- fmapi-structure_log --input_path "<dat file path>" --output_path "<give a file path to a csv>"
32
-
33
- Or as a gui:
34
-
35
- .. code::
36
-
37
- fmapi-structure_log gui
38
-
39
- Or you can use it from your code:
40
-
41
- .. code:: python
42
-
43
- from floodmodeller_api.toolbox import StructureLog
44
-
45
- StructureLog.run(
46
- input_path="path/to/dat.dat",
47
- output_path="path/to/csv.csv",
48
- )
49
- """
50
-
51
- name = "Structure Log"
52
- description = "Creates a structure log"
53
- parameters = [
54
- Parameter(
55
- name="input_path",
56
- dtype=str,
57
- description="Path to input DAT file",
58
- help_text="Where the DAT file will be read from",
59
- required=True,
60
- ),
61
- Parameter(
62
- name="output_path",
63
- dtype=str,
64
- description="Path to output csv",
65
- help_text="Where the new model will be saved (csv must be closed)",
66
- required=True,
67
- ),
68
- ]
69
-
70
- @classmethod
71
- def tool_function(cls, **kwargs):
72
- return StructureLogBuilder(**kwargs).create()
73
-
74
-
75
- def main():
76
- StructureLog().run_from_command_line()
1
+ from floodmodeller_api.tool import FMTool, Parameter
2
+
3
+ from .structure_log import StructureLogBuilder
4
+
5
+
6
+ class StructureLog(FMTool):
7
+ """
8
+ This tool creates an output log (.csv file) of all the conduits and structures within a DAT file.
9
+ It lists:
10
+
11
+ - Unit Name
12
+ - Unit Type
13
+ - Unit Subtype
14
+ - Comment
15
+ - Friction
16
+ - Distance (m)
17
+ - Weir Coefficient
18
+ - Culvert Inlet/Outlet Loss
19
+
20
+ Args:
21
+ input_path (str): Path to the input DAT file
22
+ output_path (str): Path to the output csv file
23
+
24
+
25
+ **Usage**
26
+
27
+ To use this tool, you can either run it from the command line:
28
+
29
+ .. code::
30
+
31
+ fmapi-structure_log --input_path "<dat file path>" --output_path "<give a file path to a csv>"
32
+
33
+ Or as a gui:
34
+
35
+ .. code::
36
+
37
+ fmapi-structure_log gui
38
+
39
+ Or you can use it from your code:
40
+
41
+ .. code:: python
42
+
43
+ from floodmodeller_api.toolbox import StructureLog
44
+
45
+ StructureLog.run(
46
+ input_path="path/to/dat.dat",
47
+ output_path="path/to/csv.csv",
48
+ )
49
+ """
50
+
51
+ name = "Structure Log"
52
+ description = "Creates a structure log"
53
+ parameters = [
54
+ Parameter(
55
+ name="input_path",
56
+ dtype=str,
57
+ description="Path to input DAT file",
58
+ help_text="Where the DAT file will be read from",
59
+ required=True,
60
+ ),
61
+ Parameter(
62
+ name="output_path",
63
+ dtype=str,
64
+ description="Path to output csv",
65
+ help_text="Where the new model will be saved (csv must be closed)",
66
+ required=True,
67
+ ),
68
+ ]
69
+
70
+ @classmethod
71
+ def tool_function(cls, **kwargs):
72
+ return StructureLogBuilder(**kwargs).create()
73
+
74
+
75
+ def main():
76
+ StructureLog().run_from_command_line()
@@ -1,10 +1,10 @@
1
- from .boundaries import HTBDY, QHBDY, QTBDY, REFHBDY
2
- from .comment import COMMENT
3
- from .conduits import CONDUIT
4
- from .iic import IIC
5
- from .losses import BLOCKAGE, CULVERT
6
- from .sections import INTERPOLATE, REPLICATE, RIVER
7
- from .structures import BRIDGE, CRUMP, FLAT_V_WEIR, ORIFICE, OUTFALL, RNWEIR, SLUICE, SPILL, WEIR
8
- from .units import ALL_UNIT_TYPES, SUPPORTED_UNIT_TYPES, UNSUPPORTED_UNIT_TYPES
9
- from .unsupported import UNSUPPORTED
10
- from .variables import Variables
1
+ from .boundaries import HTBDY, QHBDY, QTBDY, REFHBDY
2
+ from .comment import COMMENT
3
+ from .conduits import CONDUIT
4
+ from .iic import IIC
5
+ from .losses import BLOCKAGE, CULVERT
6
+ from .sections import INTERPOLATE, REPLICATE, RIVER
7
+ from .structures import BRIDGE, CRUMP, FLAT_V_WEIR, ORIFICE, OUTFALL, RNWEIR, SLUICE, SPILL, WEIR
8
+ from .units import ALL_UNIT_TYPES, SUPPORTED_UNIT_TYPES, UNSUPPORTED_UNIT_TYPES
9
+ from .unsupported import UNSUPPORTED
10
+ from .variables import Variables
@@ -1,212 +1,214 @@
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
- """ Holds the base unit class for all FM Units """
18
-
19
- from typing import List, Optional
20
-
21
- import pandas as pd
22
-
23
- from ..diff import check_item_with_dataframe_equal
24
- from .helpers import _to_float, _to_str, join_10_char, join_n_char_ljust, split_10_char
25
-
26
-
27
- class Unit:
28
- _unit: Optional[str] = None
29
- _subtype: Optional[str] = None
30
- _name: Optional[str] = None
31
-
32
- def __init__(self, unit_block=None, n=12, **kwargs):
33
- self._label_len = n
34
- if unit_block is not None:
35
- self._read(unit_block, **kwargs)
36
- else:
37
- self._create_from_blank(**kwargs)
38
-
39
- @property
40
- def name(self):
41
- return self._name
42
-
43
- @name.setter
44
- def name(self, new_name):
45
- try:
46
- new_name = str(new_name)
47
- if " " in new_name:
48
- raise Exception(
49
- f'Cannot set unit name to "{new_name}" as it contains one or more spaces'
50
- )
51
- self._name = new_name
52
- except Exception as e:
53
- raise Exception(f'Failed to set unit name to "{new_name}" due to error: {e}') from e
54
-
55
- @property
56
- def subtype(self):
57
- return self._subtype
58
-
59
- @subtype.setter
60
- def subtype(self, new_value):
61
- raise ValueError("You cannot change the subtype of a unit once it has been instantiated")
62
-
63
- def __repr__(self):
64
- if self._subtype is None:
65
- return f"<floodmodeller_api Unit Class: {self._unit}(name={self._name})>"
66
- return (
67
- f"<floodmodeller_api Unit Class: {self._unit}(name={self._name}, type={self._subtype})>"
68
- )
69
-
70
- def _create_from_blank(self):
71
- raise NotImplementedError(
72
- f"Creating new {self._unit} units is not yet supported by floodmodeller_api, only existing units can be read"
73
- )
74
-
75
- def __str__(self):
76
- return "\n".join(self._write())
77
-
78
- def _read(self, block: List[str]):
79
- raise NotImplementedError
80
-
81
- def _write(self):
82
- raise NotImplementedError
83
-
84
- def _diff(self, other):
85
- diff = self._get_diff(other)
86
- if diff[0]:
87
- print("No difference, units are equivalent")
88
- else:
89
- print("\n".join([f"{name}: {reason}" for name, reason in diff[1]]))
90
-
91
- def _get_diff(self, other):
92
- return self.__eq__(other, return_diff=True) # pylint: disable=unnecessary-dunder-call
93
-
94
- def __eq__(self, other, return_diff=False):
95
- result = True
96
- diff = []
97
- result, diff = check_item_with_dataframe_equal(
98
- self.__dict__,
99
- other.__dict__,
100
- name=f"{self._unit}.{self._subtype or ''}.{self._name}",
101
- diff=diff,
102
- )
103
- return (result, diff) if return_diff else result
104
-
105
- # rules & varrules
106
-
107
- def _read_rules(self, block):
108
- rule_params = split_10_char(block[self._last_gate_row + 1])
109
- self.nrules = int(rule_params[0])
110
- self.rule_sample_time = _to_float(rule_params[1])
111
- self.timeunit = _to_str(rule_params[2], "SECONDS", check_float=False)
112
- self.extendmethod = _to_str(rule_params[3], "EXTEND")
113
- self.rules = self._get_logical_rules(self.nrules, block, self._last_gate_row + 2)
114
- # Get time rule data set
115
- nrows = int(split_10_char(block[self._last_rule_row + 1])[0])
116
- data_list = []
117
- for row in block[self._last_rule_row + 2 : self._last_rule_row + 2 + nrows]:
118
- row_split = split_10_char(f"{row:<20}")
119
- x = _to_float(row_split[0]) # time
120
- y = row[10:].strip() # operating rules
121
- data_list.append([x, y])
122
- self._last_time_row = self._last_rule_row + nrows + 1
123
- rule_data = pd.DataFrame(data_list, columns=["Time", "Operating Rules"])
124
- rule_data = rule_data.set_index("Time")
125
- rule_data = rule_data["Operating Rules"]
126
- self.time_rule_data = rule_data
127
- # VARRULES (not always necessary)
128
- self.has_varrules = False
129
- if self._last_time_row + 1 < len(block):
130
- if block[self._last_time_row + 1].strip() == "VARRULES":
131
- self.has_varrules = True
132
- varrule_params = split_10_char(block[self._last_time_row + 2])
133
- self.nvarrules = int(varrule_params[0])
134
- self.varrule_sample_time = _to_float(rule_params[1])
135
- self.varrules = self._get_logical_rules(
136
- self.nvarrules, block, self._last_time_row + 3
137
- )
138
- # Get time rule data set
139
- var_nrows = int(split_10_char(block[self._last_rule_row + 1])[0])
140
- data_list = []
141
- for row in block[self._last_rule_row + 2 : self._last_rule_row + 2 + var_nrows]:
142
- row_split = split_10_char(f"{row:<20}")
143
- x = _to_float(row_split[0]) # time
144
- y = row[10:].strip() # operating rules
145
- data_list.append([x, y])
146
-
147
- varrule_data = pd.DataFrame(data_list, columns=["Time", "Operating Rules"])
148
- varrule_data = varrule_data.set_index("Time")
149
- varrule_data = varrule_data["Operating Rules"]
150
- self.time_varrule_data = varrule_data
151
-
152
- def _write_rules(self, block):
153
- # ADD RULES
154
- block.append("RULES")
155
- self.nrules = len(self.rules)
156
- block.append(
157
- f"{join_n_char_ljust(10, self.nrules)}{join_10_char(self.rule_sample_time)}{join_n_char_ljust(10, self.timeunit, self.extendmethod)}"
158
- )
159
- for rule in self.rules:
160
- block.append(rule["name"])
161
- block.extend(rule["logic"].split("\n"))
162
-
163
- # ADD TIME RULE DATA SET
164
- block.append("TIME RULE DATA SET")
165
- block.append(join_10_char(len(self.time_rule_data)))
166
- time_rule_data = [f"{join_10_char(t)}{o_r:<10}" for t, o_r in self.time_rule_data.items()]
167
- block.extend(time_rule_data)
168
-
169
- # ADD VARRULES (IF THEY ARE THERE)
170
- if self.has_varrules:
171
- block.append("VARRULES")
172
- self.nvarrules = len(self.varrules)
173
- block.append(
174
- f"{join_n_char_ljust(10, self.nvarrules)}{join_10_char(self.varrule_sample_time)}{join_n_char_ljust(10, self.timeunit, self.extendmethod)}"
175
- )
176
- for varrule in self.varrules:
177
- block.append(varrule["name"])
178
- block.extend(varrule["logic"].split("\n"))
179
-
180
- # ADD TIME VARRULE DATA SET
181
- block.append("TIME RULE DATA SET")
182
- block.append(join_10_char(len(self.time_varrule_data)))
183
- time_varrule_data = [
184
- f"{join_10_char(t)}{o_r:<10}" for t, o_r in self.time_rule_data.items()
185
- ]
186
- block.extend(time_varrule_data)
187
-
188
- return block
189
-
190
- def _get_logical_rules(self, nrules, block, rule_row):
191
- rules = []
192
- rules_recorded = 0
193
- rule_logic = []
194
- rule_dict = {}
195
- nl = "\n"
196
- while rules_recorded < nrules:
197
- if block[rule_row].strip().upper().endswith(("END", "ENDIF")):
198
- rule_logic.append(block[rule_row])
199
- rule_dict["logic"] = f"{nl.join(rule_logic)}"
200
- rules.append(rule_dict)
201
- rule_logic = []
202
- rule_dict = {}
203
- rules_recorded += 1
204
- elif len(rule_dict) == 0:
205
- rule_dict = {"name": block[rule_row].strip()}
206
- else:
207
- rule_logic.append(block[rule_row])
208
- rule_row += 1
209
-
210
- self._last_rule_row = rule_row
211
-
212
- return rules
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
+ """ Holds the base unit class for all FM Units """
20
+
21
+ import pandas as pd
22
+
23
+ from ..diff import check_item_with_dataframe_equal
24
+ from ..to_from_json import Jsonable
25
+ from .helpers import _to_float, _to_str, join_10_char, join_n_char_ljust, split_10_char
26
+
27
+
28
+ class Unit(Jsonable):
29
+ _unit: str
30
+ _subtype: str | None = None
31
+ _name: str | None = None
32
+
33
+ def __init__(self, unit_block=None, n=12, from_json: bool = False, **kwargs):
34
+ if from_json:
35
+ return
36
+ self._label_len = n
37
+ if unit_block is not None:
38
+ self._read(unit_block, **kwargs)
39
+ else:
40
+ self._create_from_blank(**kwargs)
41
+
42
+ @property
43
+ def name(self):
44
+ return self._name
45
+
46
+ @name.setter
47
+ def name(self, new_name):
48
+ try:
49
+ new_name = str(new_name)
50
+ if " " in new_name:
51
+ raise Exception(
52
+ f'Cannot set unit name to "{new_name}" as it contains one or more spaces',
53
+ )
54
+ self._name = new_name
55
+ except Exception as e:
56
+ raise Exception(f'Failed to set unit name to "{new_name}" due to error: {e}') from e
57
+
58
+ @property
59
+ def subtype(self):
60
+ return self._subtype
61
+
62
+ @subtype.setter
63
+ def subtype(self, new_value):
64
+ raise ValueError("You cannot change the subtype of a unit once it has been instantiated")
65
+
66
+ def __repr__(self):
67
+ if self._subtype is None:
68
+ return f"<floodmodeller_api Unit Class: {self._unit}(name={self._name})>"
69
+ return (
70
+ f"<floodmodeller_api Unit Class: {self._unit}(name={self._name}, type={self._subtype})>"
71
+ )
72
+
73
+ def _create_from_blank(self):
74
+ raise NotImplementedError(
75
+ f"Creating new {self._unit} units is not yet supported by floodmodeller_api, only existing units can be read",
76
+ )
77
+
78
+ def __str__(self):
79
+ return "\n".join(self._write())
80
+
81
+ def _read(self, block: list[str]):
82
+ raise NotImplementedError
83
+
84
+ def _write(self):
85
+ raise NotImplementedError
86
+
87
+ def _diff(self, other):
88
+ diff = self._get_diff(other)
89
+ if diff[0]:
90
+ print("No difference, units are equivalent")
91
+ else:
92
+ print("\n".join([f"{name}: {reason}" for name, reason in diff[1]]))
93
+
94
+ def _get_diff(self, other):
95
+ return self.__eq__(other, return_diff=True) # pylint: disable=unnecessary-dunder-call
96
+
97
+ def __eq__(self, other, return_diff=False):
98
+ result = True
99
+ diff = []
100
+ result, diff = check_item_with_dataframe_equal(
101
+ self.__dict__,
102
+ other.__dict__,
103
+ name=f"{self._unit}.{self._subtype or ''}.{self._name}",
104
+ diff=diff,
105
+ )
106
+ return (result, diff) if return_diff else result
107
+
108
+ # rules & varrules
109
+
110
+ def _read_rules(self, block):
111
+ rule_params = split_10_char(block[self._last_gate_row + 1])
112
+ self.nrules = int(rule_params[0])
113
+ self.rule_sample_time = _to_float(rule_params[1])
114
+ self.timeunit = _to_str(rule_params[2], "SECONDS", check_float=False)
115
+ self.extendmethod = _to_str(rule_params[3], "EXTEND")
116
+ self.rules = self._get_logical_rules(self.nrules, block, self._last_gate_row + 2)
117
+ # Get time rule data set
118
+ nrows = int(split_10_char(block[self._last_rule_row + 1])[0])
119
+ data_list = []
120
+ for row in block[self._last_rule_row + 2 : self._last_rule_row + 2 + nrows]:
121
+ row_split = split_10_char(f"{row:<20}")
122
+ x = _to_float(row_split[0]) # time
123
+ y = row[10:].strip() # operating rules
124
+ data_list.append([x, y])
125
+ self._last_time_row = self._last_rule_row + nrows + 1
126
+ rule_data = pd.DataFrame(data_list, columns=["Time", "Operating Rules"])
127
+ rule_data = rule_data.set_index("Time")
128
+ rule_data = rule_data["Operating Rules"]
129
+ self.time_rule_data = rule_data
130
+ # VARRULES (not always necessary)
131
+ self.has_varrules = False
132
+ if (self._last_time_row + 1 < len(block)) and (
133
+ block[self._last_time_row + 1].strip() == "VARRULES"
134
+ ):
135
+ self.has_varrules = True
136
+ varrule_params = split_10_char(block[self._last_time_row + 2])
137
+ self.nvarrules = int(varrule_params[0])
138
+ self.varrule_sample_time = _to_float(rule_params[1])
139
+ self.varrules = self._get_logical_rules(self.nvarrules, block, self._last_time_row + 3)
140
+ # Get time rule data set
141
+ var_nrows = int(split_10_char(block[self._last_rule_row + 1])[0])
142
+ data_list = []
143
+ for row in block[self._last_rule_row + 2 : self._last_rule_row + 2 + var_nrows]:
144
+ row_split = split_10_char(f"{row:<20}")
145
+ x = _to_float(row_split[0]) # time
146
+ y = row[10:].strip() # operating rules
147
+ data_list.append([x, y])
148
+
149
+ varrule_data = pd.DataFrame(data_list, columns=["Time", "Operating Rules"])
150
+ varrule_data = varrule_data.set_index("Time")
151
+ varrule_data = varrule_data["Operating Rules"]
152
+ self.time_varrule_data = varrule_data
153
+
154
+ def _write_rules(self, block):
155
+ # ADD RULES
156
+ block.append("RULES")
157
+ self.nrules = len(self.rules)
158
+ block.append(
159
+ f"{join_n_char_ljust(10, self.nrules)}{join_10_char(self.rule_sample_time)}{join_n_char_ljust(10, self.timeunit, self.extendmethod)}",
160
+ )
161
+ for rule in self.rules:
162
+ block.append(rule["name"])
163
+ block.extend(rule["logic"].split("\n"))
164
+
165
+ # ADD TIME RULE DATA SET
166
+ block.append("TIME RULE DATA SET")
167
+ block.append(join_10_char(len(self.time_rule_data)))
168
+ time_rule_data = [f"{join_10_char(t)}{o_r:<10}" for t, o_r in self.time_rule_data.items()]
169
+ block.extend(time_rule_data)
170
+
171
+ # ADD VARRULES (IF THEY ARE THERE)
172
+ if self.has_varrules:
173
+ block.append("VARRULES")
174
+ self.nvarrules = len(self.varrules)
175
+ block.append(
176
+ f"{join_n_char_ljust(10, self.nvarrules)}{join_10_char(self.varrule_sample_time)}{join_n_char_ljust(10, self.timeunit, self.extendmethod)}",
177
+ )
178
+ for varrule in self.varrules:
179
+ block.append(varrule["name"])
180
+ block.extend(varrule["logic"].split("\n"))
181
+
182
+ # ADD TIME VARRULE DATA SET
183
+ block.append("TIME RULE DATA SET")
184
+ block.append(join_10_char(len(self.time_varrule_data)))
185
+ time_varrule_data = [
186
+ f"{join_10_char(t)}{o_r:<10}" for t, o_r in self.time_rule_data.items()
187
+ ]
188
+ block.extend(time_varrule_data)
189
+
190
+ return block
191
+
192
+ def _get_logical_rules(self, nrules, block, rule_row):
193
+ rules = []
194
+ rules_recorded = 0
195
+ rule_logic = []
196
+ rule_dict = {}
197
+ nl = "\n"
198
+ while rules_recorded < nrules:
199
+ if block[rule_row].strip().upper().endswith(("END", "ENDIF")):
200
+ rule_logic.append(block[rule_row])
201
+ rule_dict["logic"] = f"{nl.join(rule_logic)}"
202
+ rules.append(rule_dict)
203
+ rule_logic = []
204
+ rule_dict = {}
205
+ rules_recorded += 1
206
+ elif len(rule_dict) == 0:
207
+ rule_dict = {"name": block[rule_row].strip()}
208
+ else:
209
+ rule_logic.append(block[rule_row])
210
+ rule_row += 1
211
+
212
+ self._last_rule_row = rule_row
213
+
214
+ return rules