floodmodeller-api 0.4.2__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 (178) 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 -838
  5. floodmodeller_api/diff.py +136 -119
  6. floodmodeller_api/ied.py +307 -311
  7. floodmodeller_api/ief.py +647 -646
  8. floodmodeller_api/ief_flags.py +253 -253
  9. floodmodeller_api/inp.py +266 -268
  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 -314
  21. floodmodeller_api/logs/lf_helpers.py +354 -346
  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 -154
  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 -330
  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 -94
  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 -72
  131. floodmodeller_api/units/__init__.py +10 -10
  132. floodmodeller_api/units/_base.py +214 -209
  133. floodmodeller_api/units/boundaries.py +467 -469
  134. floodmodeller_api/units/comment.py +52 -55
  135. floodmodeller_api/units/conduits.py +382 -403
  136. floodmodeller_api/units/helpers.py +123 -132
  137. floodmodeller_api/units/iic.py +107 -101
  138. floodmodeller_api/units/losses.py +305 -308
  139. floodmodeller_api/units/sections.py +444 -445
  140. floodmodeller_api/units/structures.py +1690 -1684
  141. floodmodeller_api/units/units.py +93 -102
  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 -177
  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 -107
  151. floodmodeller_api/urban1d/raingauges.py +111 -108
  152. floodmodeller_api/urban1d/subsections.py +92 -93
  153. floodmodeller_api/urban1d/xsections.py +147 -141
  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 -112
  158. floodmodeller_api/version.py +1 -1
  159. floodmodeller_api/xml2d.py +688 -684
  160. floodmodeller_api/xml2d_template.py +37 -37
  161. floodmodeller_api/zzn.py +387 -365
  162. {floodmodeller_api-0.4.2.dist-info → floodmodeller_api-0.4.3.dist-info}/LICENSE.txt +13 -13
  163. {floodmodeller_api-0.4.2.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.dist-info → floodmodeller_api-0.4.3.dist-info}/WHEEL +1 -1
  166. floodmodeller_api-0.4.3.dist-info/entry_points.txt +3 -0
  167. floodmodeller_api/libifcoremd.dll +0 -0
  168. floodmodeller_api/test/test_data/EX3.bmp +0 -0
  169. floodmodeller_api/test/test_data/test_output.csv +0 -87
  170. floodmodeller_api/zzn_read.dll +0 -0
  171. floodmodeller_api-0.4.2.data/scripts/fmapi-add_siltation.bat +0 -2
  172. floodmodeller_api-0.4.2.data/scripts/fmapi-add_siltation.py +0 -3
  173. floodmodeller_api-0.4.2.data/scripts/fmapi-structure_log.bat +0 -2
  174. floodmodeller_api-0.4.2.data/scripts/fmapi-structure_log.py +0 -3
  175. floodmodeller_api-0.4.2.data/scripts/fmapi-toolbox.bat +0 -2
  176. floodmodeller_api-0.4.2.data/scripts/fmapi-toolbox.py +0 -41
  177. floodmodeller_api-0.4.2.dist-info/RECORD +0 -169
  178. {floodmodeller_api-0.4.2.dist-info → floodmodeller_api-0.4.3.dist-info}/top_level.txt +0 -0
@@ -1,156 +1,151 @@
1
- import os
2
- from pathlib import Path
3
-
4
- import pytest
5
-
6
- from floodmodeller_api import XML2D
7
-
8
-
9
- @pytest.fixture
10
- def xml_fp(test_workspace):
11
- return os.path.join(test_workspace, "Domain1_Q.xml")
12
-
13
-
14
- @pytest.fixture
15
- def data_before(xml_fp):
16
- return XML2D(xml_fp)._write()
17
-
18
-
19
- def test_xml2d_str_representation(xml_fp, data_before):
20
- """XML2D: Test str representation equal to xml file with no changes"""
21
- x2d = XML2D(xml_fp)
22
- assert x2d._write() == data_before
23
-
24
-
25
- def test_xml2d_link_dtm_changes(xml_fp, data_before):
26
- """XML2D: Test changing and reverting link1d file and dtm makes no changes"""
27
- x2d = XML2D(xml_fp)
28
- prev_link = x2d.link1d[0]["link"]
29
- domain = list(x2d.domains)[0]
30
- prev_dtm = x2d.domains[domain]["topography"]
31
-
32
- x2d.link1d[0]["link"] = ["new_link"]
33
- x2d.domains[domain]["topography"] = ["new_dtm"]
34
- assert x2d._write() != data_before
35
-
36
- x2d.link1d[0]["link"] = prev_link
37
- x2d.domains[domain]["topography"] = prev_dtm
38
- assert x2d._write() == data_before
39
-
40
-
41
- def test_xml2d_all_files(test_workspace):
42
- """XML2D: Check all '.xml' files in folder by reading the _write() output into a
43
- new XML2D instance and checking it stays the same."""
44
- for xmlfile in Path(test_workspace).glob("*.xml"):
45
- x2d = XML2D(xmlfile)
46
- first_output = x2d._write()
47
- x2d.save("__temp.xml")
48
- second_x2d = XML2D("__temp.xml")
49
- assert x2d == second_x2d
50
- second_output = second_x2d._write()
51
- assert first_output == second_output
52
- os.remove("__temp.xml")
53
-
54
-
55
- # New tests being added for the add/remove functionalility
56
- def test_xml2d_change_revert_elem_topography():
57
- """XML2D: Check that when we change an existing element
58
- that it is actually adding it and that it is being reverted."""
59
- x2d = XML2D()
60
- domain = list(x2d.domains)[0]
61
- orig_topography = []
62
- for item in x2d.domains[domain]["topography"]:
63
- orig_topography.append(str(item))
64
- orig_xml = x2d._write()
65
- x2d.domains[domain]["topography"][0] = "my/new/topography"
66
-
67
- assert x2d._write() != orig_xml
68
- assert "my/new/topography" in x2d._write()
69
- x2d.domains[domain]["topography"] = orig_topography
70
- assert x2d._write() == orig_xml
71
- assert "my/new/topography" not in x2d._write()
72
-
73
-
74
- def test_xml2d_add_remove_branch_roughness():
75
- """XML2D: Check that we can actually add a branch and that
76
- it is being added and passes validation (i.e write)"""
77
- x2d = XML2D()
78
- domain = list(x2d.domains)[0]
79
- orig_xml = x2d._write()
80
- x2d.domains[domain]["roughness"] = []
81
- x2d.domains[domain]["roughness"].append(
82
- {"type": "file", "law": "manning", "value": "my/roughness/file.shp"}
83
- )
84
- assert x2d._write() != orig_xml
85
- assert "my/roughness/file.shp" in x2d._write()
86
- del x2d.domains[domain]["roughness"]
87
- assert x2d._write() == orig_xml
88
- assert "my/roughness/file.shp" not in x2d._write()
89
-
90
-
91
- def test_xml2d_append_remove_branch_roughness():
92
- """XML2D: Check that we can append an extra branch to preexisting branch
93
- so that it passes validation"""
94
- x2d = XML2D()
95
- domain = list(x2d.domains)[0]
96
- x2d.domains[domain]["roughness"] = []
97
- x2d.domains[domain]["roughness"].append(
98
- {"type": "file", "law": "manning", "value": "my/roughness/file.shp"}
99
- )
100
- xml_1_roughness = x2d._write()
101
- x2d.domains[domain]["roughness"].append(
102
- {"type": "file", "law": "manning", "value": "new/roughness/file.shp"}
103
- )
104
-
105
- assert x2d._write() != xml_1_roughness
106
- assert "new/roughness/file.shp" in x2d._write()
107
-
108
- del x2d.domains[domain]["roughness"][1]
109
-
110
- assert "new/roughness/file.shp" not in x2d._write()
111
-
112
-
113
- # validation/reordering tests
114
-
115
-
116
- def test_xml2d_reorder_elem_computational_area_wrong_position():
117
- """XML2D: Check that if we add ??? in the wrong position does it reorder"""
118
- x2d = XML2D()
119
- domain = list(x2d.domains)[0]
120
- x2d.domains[domain]["computational_area"] = {
121
- "yll": ...,
122
- "xll": ...,
123
- "dx": ...,
124
- "active_area": ...,
125
- "ncols": ...,
126
- "nrows": ...,
127
- "rotation": ...,
128
- }
129
- x2d.domains[domain]["computational_area"]["yll"] = float(1.1)
130
- x2d.domains[domain]["computational_area"]["xll"] = float(2.6)
131
- x2d.domains[domain]["computational_area"]["dx"] = float(2)
132
- x2d.domains[domain]["computational_area"]["active_area"] = "path/to/asc/file.asc"
133
- x2d.domains[domain]["computational_area"]["ncols"] = int(12)
134
- x2d.domains[domain]["computational_area"]["nrows"] = int(42)
135
- x2d.domains[domain]["computational_area"]["rotation"] = float(3.14159)
136
-
137
- x2d.domains[domain]["run_data"]["upwind"] = "upwind value"
138
- x2d.domains[domain]["run_data"]["wall"] = "Humpty Dumpty"
139
-
140
- # TODO: Add check that this should fail validation if in the wrong order
141
- # # how do I check that something fails?
142
- # with pytest.raises(Exception) as e_info:
143
- # x2d._validate()
144
-
145
- # assert "XML Validation Error for" in e_info
146
-
147
- assert x2d._write()
148
-
149
-
150
- def test_xml2d_update_value(xml_fp, data_before):
151
- """XML2D: Test changing and reverting link1d file and dtm makes no changes"""
152
- x2d = XML2D(xml_fp)
153
- domain = list(x2d.domains)[0]
154
- x2d.domains[domain]["run_data"]["scheme"] = "TVD"
155
-
156
- assert x2d._write()
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from floodmodeller_api import XML2D
6
+
7
+
8
+ @pytest.fixture
9
+ def xml_fp(test_workspace):
10
+ return Path(test_workspace, "Domain1_Q.xml")
11
+
12
+
13
+ @pytest.fixture
14
+ def data_before(xml_fp):
15
+ return XML2D(xml_fp)._write()
16
+
17
+
18
+ def test_xml2d_str_representation(xml_fp, data_before):
19
+ """XML2D: Test str representation equal to xml file with no changes"""
20
+ x2d = XML2D(xml_fp)
21
+ assert x2d._write() == data_before
22
+
23
+
24
+ def test_xml2d_link_dtm_changes(xml_fp, data_before):
25
+ """XML2D: Test changing and reverting link1d file and dtm makes no changes"""
26
+ x2d = XML2D(xml_fp)
27
+ prev_link = x2d.link1d[0]["link"]
28
+ domain = list(x2d.domains)[0]
29
+ prev_dtm = x2d.domains[domain]["topography"]
30
+
31
+ x2d.link1d[0]["link"] = ["new_link"]
32
+ x2d.domains[domain]["topography"] = ["new_dtm"]
33
+ assert x2d._write() != data_before
34
+
35
+ x2d.link1d[0]["link"] = prev_link
36
+ x2d.domains[domain]["topography"] = prev_dtm
37
+ assert x2d._write() == data_before
38
+
39
+
40
+ def test_xml2d_all_files(test_workspace, tmpdir):
41
+ """XML2D: Check all '.xml' files in folder by reading the _write() output into a
42
+ new XML2D instance and checking it stays the same."""
43
+ for xmlfile in Path(test_workspace).glob("*.xml"):
44
+ x2d = XML2D(xmlfile)
45
+ first_output = x2d._write()
46
+ new_path = Path(tmpdir) / "tmp.xml"
47
+ x2d.save(new_path)
48
+ second_x2d = XML2D(new_path)
49
+ assert x2d == second_x2d
50
+ second_output = second_x2d._write()
51
+ assert first_output == second_output
52
+
53
+
54
+ # New tests being added for the add/remove functionalility
55
+ def test_xml2d_change_revert_elem_topography():
56
+ """XML2D: Check that when we change an existing element
57
+ that it is actually adding it and that it is being reverted."""
58
+ x2d = XML2D()
59
+ domain = list(x2d.domains)[0]
60
+ orig_topography = []
61
+ for item in x2d.domains[domain]["topography"]:
62
+ orig_topography.append(str(item))
63
+ orig_xml = x2d._write()
64
+ x2d.domains[domain]["topography"][0] = "my/new/topography"
65
+
66
+ assert x2d._write() != orig_xml
67
+ assert "my/new/topography" in x2d._write()
68
+ x2d.domains[domain]["topography"] = orig_topography
69
+ assert x2d._write() == orig_xml
70
+ assert "my/new/topography" not in x2d._write()
71
+
72
+
73
+ def test_xml2d_add_remove_branch_roughness():
74
+ """XML2D: Check that we can actually add a branch and that
75
+ it is being added and passes validation (i.e write)"""
76
+ x2d = XML2D()
77
+ domain = list(x2d.domains)[0]
78
+ orig_xml = x2d._write()
79
+ x2d.domains[domain]["roughness"] = []
80
+ x2d.domains[domain]["roughness"].append(
81
+ {"type": "file", "law": "manning", "value": "my/roughness/file.shp"},
82
+ )
83
+ assert x2d._write() != orig_xml
84
+ assert "my/roughness/file.shp" in x2d._write()
85
+ del x2d.domains[domain]["roughness"]
86
+ assert x2d._write() == orig_xml
87
+ assert "my/roughness/file.shp" not in x2d._write()
88
+
89
+
90
+ def test_xml2d_append_remove_branch_roughness():
91
+ """XML2D: Check that we can append an extra branch to preexisting branch
92
+ so that it passes validation"""
93
+ x2d = XML2D()
94
+ domain = list(x2d.domains)[0]
95
+ x2d.domains[domain]["roughness"] = []
96
+ x2d.domains[domain]["roughness"].append(
97
+ {"type": "file", "law": "manning", "value": "my/roughness/file.shp"},
98
+ )
99
+ xml_1_roughness = x2d._write()
100
+ x2d.domains[domain]["roughness"].append(
101
+ {"type": "file", "law": "manning", "value": "new/roughness/file.shp"},
102
+ )
103
+
104
+ assert x2d._write() != xml_1_roughness
105
+ assert "new/roughness/file.shp" in x2d._write()
106
+
107
+ del x2d.domains[domain]["roughness"][1]
108
+
109
+ assert "new/roughness/file.shp" not in x2d._write()
110
+
111
+
112
+ # validation/reordering tests
113
+
114
+
115
+ def test_xml2d_reorder_elem_computational_area_wrong_position():
116
+ """XML2D: Check that if we add ??? in the wrong position does it reorder"""
117
+ x2d = XML2D()
118
+ domain = list(x2d.domains)[0]
119
+ x2d.domains[domain]["computational_area"] = {
120
+ "yll": ...,
121
+ "xll": ...,
122
+ "dx": ...,
123
+ "active_area": ...,
124
+ "ncols": ...,
125
+ "nrows": ...,
126
+ "rotation": ...,
127
+ }
128
+ x2d.domains[domain]["computational_area"]["yll"] = 1.1
129
+ x2d.domains[domain]["computational_area"]["xll"] = 2.6
130
+ x2d.domains[domain]["computational_area"]["dx"] = float(2)
131
+ x2d.domains[domain]["computational_area"]["active_area"] = "path/to/asc/file.asc"
132
+ x2d.domains[domain]["computational_area"]["ncols"] = 12
133
+ x2d.domains[domain]["computational_area"]["nrows"] = 42
134
+ x2d.domains[domain]["computational_area"]["rotation"] = 3.14159
135
+
136
+ x2d.domains[domain]["run_data"]["upwind"] = "upwind value"
137
+ x2d.domains[domain]["run_data"]["wall"] = "Humpty Dumpty"
138
+
139
+ # TODO: Add check that this should fail validation if in the wrong order
140
+ # # how do I check that something fails?
141
+
142
+ assert x2d._write()
143
+
144
+
145
+ def test_xml2d_update_value(xml_fp, data_before):
146
+ """XML2D: Test changing and reverting link1d file and dtm makes no changes"""
147
+ x2d = XML2D(xml_fp)
148
+ domain = list(x2d.domains)[0]
149
+ x2d.domains[domain]["run_data"]["scheme"] = "TVD"
150
+
151
+ assert x2d._write()
@@ -1,34 +1,36 @@
1
- import os
2
-
3
- import pandas as pd
4
- import pytest
5
-
6
- from floodmodeller_api import ZZN
7
-
8
-
9
- @pytest.fixture
10
- def zzn_fp(test_workspace):
11
- return os.path.join(test_workspace, "network.zzn")
12
-
13
-
14
- @pytest.fixture
15
- def tab_csv_output(test_workspace):
16
- tab_csv_output = pd.read_csv(os.path.join(test_workspace, "network_from_tabularCSV.csv"))
17
- tab_csv_output["Max State"] = tab_csv_output["Max State"].astype("float64")
18
- return tab_csv_output
19
-
20
-
21
- def test_load_zzn_using_dll(zzn_fp, tab_csv_output, test_workspace):
22
- """ZZN: Check loading zzn okay using dll"""
23
- zzn = ZZN(zzn_fp)
24
- zzn.export_to_csv(
25
- result_type="max",
26
- save_location=os.path.join(test_workspace, "test_output.csv"),
27
- )
28
- output = pd.read_csv(os.path.join(test_workspace, "test_output.csv"))
29
- output = round(output, 3) # Round to 3dp
30
- # https://stackoverflow.com/questions/20274987/how-to-use-pytest-to-check-that-error-is-not-raised
31
- try:
32
- pd.testing.assert_frame_equal(output, tab_csv_output, rtol=0.0001)
33
- except Exception:
34
- pytest.fail("data frames not equal")
1
+ from pathlib import Path
2
+
3
+ import pandas as pd
4
+ import pytest
5
+
6
+ from floodmodeller_api import IEF, ZZN
7
+
8
+
9
+ @pytest.fixture
10
+ def zzn_fp(test_workspace):
11
+ return Path(test_workspace, "network.zzn")
12
+
13
+
14
+ @pytest.fixture
15
+ def tab_csv_output(test_workspace):
16
+ tab_csv_output = pd.read_csv(Path(test_workspace, "network_from_tabularCSV.csv"))
17
+ tab_csv_output["Max State"] = tab_csv_output["Max State"].astype("float64")
18
+ return tab_csv_output
19
+
20
+
21
+ def test_load_zzn_using_dll(zzn_fp, tab_csv_output, tmpdir):
22
+ """ZZN: Check loading zzn okay using dll"""
23
+ zzn = ZZN(zzn_fp)
24
+ zzn.export_to_csv(
25
+ result_type="max",
26
+ save_location=Path(tmpdir, "test_output.csv"),
27
+ )
28
+ output = pd.read_csv(Path(tmpdir, "test_output.csv"))
29
+ output = round(output, 3)
30
+ pd.testing.assert_frame_equal(output, tab_csv_output, rtol=0.0001)
31
+
32
+
33
+ def test_load_zzn_using_ief(zzn_fp):
34
+ zzn = ZZN(zzn_fp).to_dataframe()
35
+ zzn_from_ief = IEF(zzn_fp.with_suffix(".ief")).get_results().to_dataframe()
36
+ pd.testing.assert_frame_equal(zzn, zzn_from_ief)
@@ -0,0 +1,218 @@
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
+ import json
18
+ from pathlib import Path
19
+ from typing import Any, Union
20
+
21
+ import pandas as pd
22
+ from pandas import Index
23
+
24
+ from .version import __version__
25
+
26
+
27
+ def to_json(obj: Any) -> str:
28
+ """
29
+ Function to convert any flood modeller object into a JSON object
30
+
31
+ Args:
32
+ obj (object): Any flood modeller object (dat, ied, ief, cross sections...)
33
+
34
+ Returns:
35
+ A JSON formatted string.
36
+ """
37
+ return json.dumps(recursive_to_json(obj), indent=2)
38
+
39
+
40
+ def is_jsonable(obj: Any) -> bool:
41
+ try:
42
+ json.dumps(obj)
43
+ return True
44
+ except Exception:
45
+ return False
46
+
47
+
48
+ def recursive_to_json(obj: Any, is_top_level: bool = True) -> Any: # noqa: PLR0911
49
+ """
50
+ Function to undertake a recursion through the different elements of the python object
51
+
52
+ Args:
53
+ Obj (object): Any flood modeller object (dat, ied, ief, cross sections...)
54
+
55
+ Returns:
56
+ if the object is serializable, it creates the object to go to the function to_json and to create the JSON file,
57
+ otherwise, it will move back through this function recursively until the object is finally serializable.
58
+ """
59
+ from ._base import FMFile
60
+ from .backup import File
61
+ from .units import IIC
62
+ from .units._base import Unit
63
+ from .urban1d._base import UrbanSubsection, UrbanUnit
64
+
65
+ if is_jsonable(obj):
66
+ return obj
67
+
68
+ if isinstance(obj, pd.DataFrame):
69
+ return {"class": "pandas.DataFrame", "object": obj.to_dict()}
70
+ if isinstance(obj, pd.Series):
71
+ return {
72
+ "class": "pandas.Series",
73
+ "variable_name": obj.name,
74
+ "index_name": obj.index.name,
75
+ "object": obj.to_dict(),
76
+ }
77
+
78
+ # To convert WindowsPath, no serializable, objects to string, serializable.
79
+ if isinstance(obj, Path):
80
+ return str(obj)
81
+
82
+ if isinstance(obj, set):
83
+ # create list and append
84
+ return {"python_set": [recursive_to_json(item, is_top_level=False) for item in sorted(obj)]}
85
+
86
+ # Case list or dict of non-jsonable stuff
87
+ if isinstance(obj, list):
88
+ # create list and append
89
+ return [recursive_to_json(item, is_top_level=False) for item in obj]
90
+
91
+ # Dictionary to the all the serializable objects
92
+ if isinstance(obj, dict):
93
+ return {key: recursive_to_json(value, is_top_level=False) for key, value in obj.items()}
94
+
95
+ # Either a type of FM API Class
96
+ if isinstance(obj, (FMFile, Unit, IIC, File, UrbanSubsection, UrbanUnit)): # noqa: RET503
97
+ # Information from the flood modeller object will be included in the JSON output
98
+ # slicing undertaken to remove quotation marks
99
+ return_dict: dict[str, Any] = {"API Class": str(obj.__class__)[8:-2]}
100
+ if is_top_level:
101
+ return_dict["API Version"] = __version__
102
+
103
+ return_dict["Object Attributes"] = {
104
+ key: recursive_to_json(value, is_top_level=False) for key, value in obj.__dict__.items()
105
+ }
106
+
107
+ return return_dict
108
+
109
+
110
+ def from_json(obj: Union[str, dict]) -> dict:
111
+ """
112
+ Function to convert a JSON string back into Python objects
113
+
114
+ Args:
115
+ json_str (str): A JSON string
116
+
117
+ Returns:
118
+ A FMP object
119
+ """
120
+ # To convert a JSON string into a python dictionary
121
+ if isinstance(obj, str):
122
+ obj_dict = json.loads(obj)["Object Attributes"]
123
+ else:
124
+ obj_dict = obj["Object Attributes"]
125
+
126
+ return recursive_from_json(obj_dict)
127
+
128
+
129
+ def recursive_from_json(obj: Union[dict, Any]) -> Any:
130
+ """
131
+ Function to undertake a recursion through the different elements of the JSON object
132
+
133
+ Args:
134
+ obj (dict): A JSON dict. IT was converted from str to dict in from_json
135
+
136
+ Returns:
137
+ A FMP object
138
+ """
139
+
140
+ if "API Class" in obj:
141
+ class_type = obj["API Class"]
142
+ from .mapping import api_class_mapping
143
+
144
+ return api_class_mapping[class_type].from_json(obj)
145
+
146
+ if "class" in obj and obj["class"] == "pandas.DataFrame":
147
+ df = pd.DataFrame.from_dict(obj["object"])
148
+ df.index = convert_dataframe_index(df.index)
149
+ return df
150
+ if "class" in obj and obj["class"] == "pandas.Series":
151
+ sr = pd.Series(obj["object"])
152
+ sr.index = convert_dataframe_index(sr.index)
153
+ sr.index.name = obj["index_name"]
154
+ sr.name = obj["variable_name"]
155
+ return sr
156
+
157
+ if "python_set" in obj:
158
+ return set(obj["python_set"])
159
+
160
+ for key, value in obj.items():
161
+ if isinstance(value, dict):
162
+ obj[key] = recursive_from_json(value)
163
+ elif isinstance(value, list):
164
+ new_list = []
165
+ for item in value:
166
+ if isinstance(item, dict):
167
+ new_list.append(recursive_from_json(item))
168
+ else:
169
+ new_list.append(item)
170
+
171
+ obj[key] = new_list
172
+
173
+ return obj
174
+
175
+
176
+ def convert_dataframe_index(index: Index) -> Index:
177
+ try:
178
+ return index.astype("int")
179
+ except ValueError:
180
+ pass
181
+ try:
182
+ return index.astype("float")
183
+ except ValueError:
184
+ return index
185
+
186
+
187
+ class Jsonable:
188
+ """Base class used to provide underlying to_json and from_json methods"""
189
+
190
+ def __init__(self, **kwargs):
191
+ pass
192
+
193
+ def to_json(self) -> str:
194
+ """Converts the object instance into a JSON string representation.
195
+
196
+ Returns:
197
+ str: A JSON string representing the object instance.
198
+ """
199
+ return to_json(self)
200
+
201
+ @classmethod
202
+ def from_json(cls, json_string: str):
203
+ """Creates an object instance from a JSON string.
204
+
205
+ Args:
206
+ json_string (str): A JSON string representation of the object.
207
+
208
+ Returns:
209
+ An object instance of the class.
210
+ """
211
+ object_dict = from_json(json_string)
212
+ api_object = cls(from_json=True)
213
+
214
+ # Loop through the dictionary and update the object
215
+ for key, value in object_dict.items():
216
+ setattr(api_object, key, value)
217
+
218
+ return api_object