floodmodeller-api 0.4.2.post1__py3-none-any.whl → 0.4.4__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 +169 -176
  3. floodmodeller_api/backup.py +273 -273
  4. floodmodeller_api/dat.py +889 -831
  5. floodmodeller_api/diff.py +136 -119
  6. floodmodeller_api/ied.py +302 -306
  7. floodmodeller_api/ief.py +553 -637
  8. floodmodeller_api/ief_flags.py +253 -253
  9. floodmodeller_api/inp.py +260 -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 +364 -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 +16 -8
  26. floodmodeller_api/test/test_backup.py +117 -117
  27. floodmodeller_api/test/test_conveyance.py +107 -0
  28. floodmodeller_api/test/test_dat.py +222 -92
  29. floodmodeller_api/test/test_data/All Units 4_6.DAT +1081 -1081
  30. floodmodeller_api/test/test_data/All Units 4_6.feb +1081 -1081
  31. floodmodeller_api/test/test_data/BRIDGE.DAT +926 -926
  32. floodmodeller_api/test/test_data/Culvert_Inlet_Outlet.dat +36 -36
  33. floodmodeller_api/test/test_data/Culvert_Inlet_Outlet.feb +36 -36
  34. floodmodeller_api/test/test_data/DamBreakADI.xml +52 -52
  35. floodmodeller_api/test/test_data/DamBreakFAST.xml +58 -58
  36. floodmodeller_api/test/test_data/DamBreakFAST_dy.xml +53 -53
  37. floodmodeller_api/test/test_data/DamBreakTVD.xml +55 -55
  38. floodmodeller_api/test/test_data/DefenceBreach.xml +53 -53
  39. floodmodeller_api/test/test_data/DefenceBreachFAST.xml +60 -60
  40. floodmodeller_api/test/test_data/DefenceBreachFAST_dy.xml +55 -55
  41. floodmodeller_api/test/test_data/Domain1+2_QH.xml +76 -76
  42. floodmodeller_api/test/test_data/Domain1_H.xml +41 -41
  43. floodmodeller_api/test/test_data/Domain1_Q.xml +41 -41
  44. floodmodeller_api/test/test_data/Domain1_Q_FAST.xml +48 -48
  45. floodmodeller_api/test/test_data/Domain1_Q_FAST_dy.xml +48 -48
  46. floodmodeller_api/test/test_data/Domain1_Q_xml_expected.json +263 -0
  47. floodmodeller_api/test/test_data/Domain1_W.xml +41 -41
  48. floodmodeller_api/test/test_data/EX1.DAT +321 -321
  49. floodmodeller_api/test/test_data/EX1.ext +107 -107
  50. floodmodeller_api/test/test_data/EX1.feb +320 -320
  51. floodmodeller_api/test/test_data/EX1.gxy +107 -107
  52. floodmodeller_api/test/test_data/EX17.DAT +421 -422
  53. floodmodeller_api/test/test_data/EX17.ext +213 -213
  54. floodmodeller_api/test/test_data/EX17.feb +422 -422
  55. floodmodeller_api/test/test_data/EX18.DAT +375 -375
  56. floodmodeller_api/test/test_data/EX18_DAT_expected.json +3876 -0
  57. floodmodeller_api/test/test_data/EX2.DAT +302 -302
  58. floodmodeller_api/test/test_data/EX3.DAT +926 -926
  59. floodmodeller_api/test/test_data/EX3_DAT_expected.json +16235 -0
  60. floodmodeller_api/test/test_data/EX3_IEF_expected.json +61 -0
  61. floodmodeller_api/test/test_data/EX6.DAT +2084 -2084
  62. floodmodeller_api/test/test_data/EX6.ext +532 -532
  63. floodmodeller_api/test/test_data/EX6.feb +2084 -2084
  64. floodmodeller_api/test/test_data/EX6_DAT_expected.json +31647 -0
  65. floodmodeller_api/test/test_data/Event Data Example.DAT +336 -336
  66. floodmodeller_api/test/test_data/Event Data Example.ext +107 -107
  67. floodmodeller_api/test/test_data/Event Data Example.feb +336 -336
  68. floodmodeller_api/test/test_data/Linked1D2D.xml +52 -52
  69. floodmodeller_api/test/test_data/Linked1D2DFAST.xml +53 -53
  70. floodmodeller_api/test/test_data/Linked1D2DFAST_dy.xml +48 -48
  71. floodmodeller_api/test/test_data/Linked1D2D_xml_expected.json +313 -0
  72. floodmodeller_api/test/test_data/blockage.dat +50 -50
  73. floodmodeller_api/test/test_data/blockage.ext +45 -45
  74. floodmodeller_api/test/test_data/blockage.feb +9 -9
  75. floodmodeller_api/test/test_data/blockage.gxy +71 -71
  76. floodmodeller_api/test/test_data/conveyance_test.dat +165 -0
  77. floodmodeller_api/test/test_data/conveyance_test.feb +116 -0
  78. floodmodeller_api/test/test_data/conveyance_test.gxy +85 -0
  79. floodmodeller_api/test/test_data/defaultUnits.dat +127 -127
  80. floodmodeller_api/test/test_data/defaultUnits.ext +45 -45
  81. floodmodeller_api/test/test_data/defaultUnits.feb +9 -9
  82. floodmodeller_api/test/test_data/defaultUnits.fmpx +58 -58
  83. floodmodeller_api/test/test_data/defaultUnits.gxy +85 -85
  84. floodmodeller_api/test/test_data/ex3.ief +20 -20
  85. floodmodeller_api/test/test_data/ex3.lf1 +2800 -2800
  86. floodmodeller_api/test/test_data/ex4.DAT +1374 -1374
  87. floodmodeller_api/test/test_data/ex4_changed.DAT +1374 -1374
  88. floodmodeller_api/test/test_data/example1.inp +329 -329
  89. floodmodeller_api/test/test_data/example2.inp +158 -158
  90. floodmodeller_api/test/test_data/example3.inp +297 -297
  91. floodmodeller_api/test/test_data/example4.inp +388 -388
  92. floodmodeller_api/test/test_data/example5.inp +147 -147
  93. floodmodeller_api/test/test_data/example6.inp +154 -154
  94. floodmodeller_api/test/test_data/expected_conveyance.csv +60 -0
  95. floodmodeller_api/test/test_data/jump.dat +176 -176
  96. floodmodeller_api/test/test_data/network.dat +1374 -1374
  97. floodmodeller_api/test/test_data/network.ext +45 -45
  98. floodmodeller_api/test/test_data/network.exy +1 -1
  99. floodmodeller_api/test/test_data/network.feb +45 -45
  100. floodmodeller_api/test/test_data/network.ied +45 -45
  101. floodmodeller_api/test/test_data/network.ief +20 -20
  102. floodmodeller_api/test/test_data/network.inp +147 -147
  103. floodmodeller_api/test/test_data/network.pxy +57 -57
  104. floodmodeller_api/test/test_data/network.zzd +122 -122
  105. floodmodeller_api/test/test_data/network_dat_expected.json +21837 -0
  106. floodmodeller_api/test/test_data/network_from_tabularCSV.csv +87 -87
  107. floodmodeller_api/test/test_data/network_ied_expected.json +287 -0
  108. floodmodeller_api/test/test_data/rnweir.dat +9 -9
  109. floodmodeller_api/test/test_data/rnweir.ext +45 -45
  110. floodmodeller_api/test/test_data/rnweir.feb +9 -9
  111. floodmodeller_api/test/test_data/rnweir.gxy +45 -45
  112. floodmodeller_api/test/test_data/rnweir_default.dat +74 -74
  113. floodmodeller_api/test/test_data/rnweir_default.ext +45 -45
  114. floodmodeller_api/test/test_data/rnweir_default.feb +9 -9
  115. floodmodeller_api/test/test_data/rnweir_default.fmpx +58 -58
  116. floodmodeller_api/test/test_data/rnweir_default.gxy +53 -53
  117. floodmodeller_api/test/test_data/unit checks.dat +16 -16
  118. floodmodeller_api/test/test_ied.py +29 -29
  119. floodmodeller_api/test/test_ief.py +136 -24
  120. floodmodeller_api/test/test_inp.py +47 -48
  121. floodmodeller_api/test/test_json.py +114 -0
  122. floodmodeller_api/test/test_logs_lf.py +102 -51
  123. floodmodeller_api/test/test_tool.py +165 -152
  124. floodmodeller_api/test/test_toolbox_structure_log.py +234 -239
  125. floodmodeller_api/test/test_xml2d.py +151 -156
  126. floodmodeller_api/test/test_zzn.py +36 -34
  127. floodmodeller_api/to_from_json.py +230 -0
  128. floodmodeller_api/tool.py +332 -329
  129. floodmodeller_api/toolbox/__init__.py +5 -5
  130. floodmodeller_api/toolbox/example_tool.py +45 -45
  131. floodmodeller_api/toolbox/model_build/__init__.py +2 -2
  132. floodmodeller_api/toolbox/model_build/add_siltation_definition.py +100 -98
  133. floodmodeller_api/toolbox/model_build/structure_log/__init__.py +1 -1
  134. floodmodeller_api/toolbox/model_build/structure_log/structure_log.py +287 -289
  135. floodmodeller_api/toolbox/model_build/structure_log_definition.py +76 -76
  136. floodmodeller_api/units/__init__.py +10 -10
  137. floodmodeller_api/units/_base.py +214 -212
  138. floodmodeller_api/units/boundaries.py +467 -467
  139. floodmodeller_api/units/comment.py +52 -55
  140. floodmodeller_api/units/conduits.py +382 -402
  141. floodmodeller_api/units/conveyance.py +301 -0
  142. floodmodeller_api/units/helpers.py +123 -131
  143. floodmodeller_api/units/iic.py +107 -101
  144. floodmodeller_api/units/losses.py +305 -306
  145. floodmodeller_api/units/sections.py +465 -446
  146. floodmodeller_api/units/structures.py +1690 -1683
  147. floodmodeller_api/units/units.py +93 -104
  148. floodmodeller_api/units/unsupported.py +44 -44
  149. floodmodeller_api/units/variables.py +87 -89
  150. floodmodeller_api/urban1d/__init__.py +11 -11
  151. floodmodeller_api/urban1d/_base.py +188 -179
  152. floodmodeller_api/urban1d/conduits.py +93 -85
  153. floodmodeller_api/urban1d/general_parameters.py +58 -58
  154. floodmodeller_api/urban1d/junctions.py +81 -79
  155. floodmodeller_api/urban1d/losses.py +81 -74
  156. floodmodeller_api/urban1d/outfalls.py +114 -110
  157. floodmodeller_api/urban1d/raingauges.py +111 -111
  158. floodmodeller_api/urban1d/subsections.py +92 -98
  159. floodmodeller_api/urban1d/xsections.py +147 -144
  160. floodmodeller_api/util.py +119 -21
  161. floodmodeller_api/validation/parameters.py +660 -660
  162. floodmodeller_api/validation/urban_parameters.py +388 -404
  163. floodmodeller_api/validation/validation.py +110 -108
  164. floodmodeller_api/version.py +1 -1
  165. floodmodeller_api/xml2d.py +632 -673
  166. floodmodeller_api/xml2d_template.py +37 -37
  167. floodmodeller_api/zzn.py +414 -363
  168. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/LICENSE.txt +13 -13
  169. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/METADATA +85 -82
  170. floodmodeller_api-0.4.4.dist-info/RECORD +185 -0
  171. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/WHEEL +1 -1
  172. floodmodeller_api/libifcoremd.dll +0 -0
  173. floodmodeller_api/test/test_data/EX3.bmp +0 -0
  174. floodmodeller_api/test/test_data/test_output.csv +0 -87
  175. floodmodeller_api/zzn_read.dll +0 -0
  176. floodmodeller_api-0.4.2.post1.dist-info/RECORD +0 -164
  177. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/entry_points.txt +0 -0
  178. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.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,230 @@
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
+ import json
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ import pandas as pd
24
+ from pandas import Index
25
+
26
+ from .version import __version__
27
+
28
+
29
+ def to_json(obj: Any) -> str:
30
+ """
31
+ Function to convert any flood modeller object into a JSON object
32
+
33
+ Args:
34
+ obj (object): Any flood modeller object (dat, ied, ief, cross sections...)
35
+
36
+ Returns:
37
+ A JSON formatted string.
38
+ """
39
+ return json.dumps(recursive_to_json(obj), indent=2)
40
+
41
+
42
+ def is_jsonable(obj: Any) -> bool:
43
+ try:
44
+ json.dumps(obj)
45
+ return True
46
+ except Exception:
47
+ return False
48
+
49
+
50
+ def pandas_to_json(obj: pd.DataFrame | pd.Series) -> dict:
51
+ if isinstance(obj, pd.DataFrame):
52
+ if isinstance(obj.columns, pd.MultiIndex):
53
+ # Handle multi index columns (i.e. ZZN dataframe results)
54
+ obj.columns = pd.Index(map(str, obj.columns.values))
55
+
56
+ return {"class": "pandas.DataFrame", "object": obj.to_dict()}
57
+
58
+ # otherwise returns as series
59
+ return {
60
+ "class": "pandas.Series",
61
+ "variable_name": obj.name,
62
+ "index_name": obj.index.name,
63
+ "object": obj.to_dict(),
64
+ }
65
+
66
+
67
+ def recursive_to_json(obj: Any, is_top_level: bool = True) -> Any: # noqa: PLR0911
68
+ """
69
+ Function to undertake a recursion through the different elements of the python object
70
+
71
+ Args:
72
+ Obj (object): Any flood modeller object (dat, ied, ief, cross sections...)
73
+
74
+ Returns:
75
+ if the object is serializable, it creates the object to go to the function to_json and to create the JSON file,
76
+ otherwise, it will move back through this function recursively until the object is finally serializable.
77
+ """
78
+ from ._base import FMFile
79
+ from .backup import File
80
+ from .units import IIC
81
+ from .units._base import Unit
82
+ from .urban1d._base import UrbanSubsection, UrbanUnit
83
+
84
+ if is_jsonable(obj):
85
+ return obj
86
+
87
+ if isinstance(obj, (pd.DataFrame, pd.Series)):
88
+ return pandas_to_json(obj)
89
+
90
+ # To convert WindowsPath, no serializable, objects to string, serializable.
91
+ if isinstance(obj, Path):
92
+ return str(obj)
93
+
94
+ if isinstance(obj, set):
95
+ # create list and append
96
+ return {"python_set": [recursive_to_json(item, is_top_level=False) for item in sorted(obj)]}
97
+
98
+ # Case list or dict of non-jsonable stuff
99
+ if isinstance(obj, list):
100
+ # create list and append
101
+ return [recursive_to_json(item, is_top_level=False) for item in obj]
102
+
103
+ # Dictionary to the all the serializable objects
104
+ if isinstance(obj, dict):
105
+ return {key: recursive_to_json(value, is_top_level=False) for key, value in obj.items()}
106
+
107
+ # Either a type of FM API Class
108
+ if isinstance(obj, (FMFile, Unit, IIC, File, UrbanSubsection, UrbanUnit)): # noqa: RET503
109
+ # Information from the flood modeller object will be included in the JSON output
110
+ # slicing undertaken to remove quotation marks
111
+ return_dict: dict[str, Any] = {"API Class": str(obj.__class__)[8:-2]}
112
+ if is_top_level:
113
+ return_dict["API Version"] = __version__
114
+
115
+ return_dict["Object Attributes"] = {
116
+ key: recursive_to_json(value, is_top_level=False) for key, value in obj.__dict__.items()
117
+ }
118
+
119
+ return return_dict
120
+
121
+
122
+ def from_json(obj: str | dict) -> dict:
123
+ """
124
+ Function to convert a JSON string back into Python objects
125
+
126
+ Args:
127
+ json_str (str): A JSON string
128
+
129
+ Returns:
130
+ A FMP object
131
+ """
132
+ # To convert a JSON string into a python dictionary
133
+ if isinstance(obj, str):
134
+ obj_dict = json.loads(obj)["Object Attributes"]
135
+ else:
136
+ obj_dict = obj["Object Attributes"]
137
+
138
+ return recursive_from_json(obj_dict)
139
+
140
+
141
+ def recursive_from_json(obj: dict | Any) -> Any:
142
+ """
143
+ Function to undertake a recursion through the different elements of the JSON object
144
+
145
+ Args:
146
+ obj (dict): A JSON dict. IT was converted from str to dict in from_json
147
+
148
+ Returns:
149
+ A FMP object
150
+ """
151
+
152
+ if "API Class" in obj:
153
+ class_type = obj["API Class"]
154
+ from .mapping import api_class_mapping
155
+
156
+ return api_class_mapping[class_type].from_json(obj)
157
+
158
+ if "class" in obj and obj["class"] == "pandas.DataFrame":
159
+ df = pd.DataFrame.from_dict(obj["object"])
160
+ df.index = convert_dataframe_index(df.index)
161
+ return df
162
+ if "class" in obj and obj["class"] == "pandas.Series":
163
+ sr = pd.Series(obj["object"])
164
+ sr.index = convert_dataframe_index(sr.index)
165
+ sr.index.name = obj["index_name"]
166
+ sr.name = obj["variable_name"]
167
+ return sr
168
+
169
+ if "python_set" in obj:
170
+ return set(obj["python_set"])
171
+
172
+ for key, value in obj.items():
173
+ if isinstance(value, dict):
174
+ obj[key] = recursive_from_json(value)
175
+ elif isinstance(value, list):
176
+ new_list = []
177
+ for item in value:
178
+ if isinstance(item, dict):
179
+ new_list.append(recursive_from_json(item))
180
+ else:
181
+ new_list.append(item)
182
+
183
+ obj[key] = new_list
184
+
185
+ return obj
186
+
187
+
188
+ def convert_dataframe_index(index: Index) -> Index:
189
+ try:
190
+ return index.astype("int")
191
+ except ValueError:
192
+ pass
193
+ try:
194
+ return index.astype("float")
195
+ except ValueError:
196
+ return index
197
+
198
+
199
+ class Jsonable:
200
+ """Base class used to provide underlying to_json and from_json methods"""
201
+
202
+ def __init__(self, **kwargs):
203
+ pass
204
+
205
+ def to_json(self) -> str:
206
+ """Converts the object instance into a JSON string representation.
207
+
208
+ Returns:
209
+ str: A JSON string representing the object instance.
210
+ """
211
+ return to_json(self)
212
+
213
+ @classmethod
214
+ def from_json(cls, json_string: str):
215
+ """Creates an object instance from a JSON string.
216
+
217
+ Args:
218
+ json_string (str): A JSON string representation of the object.
219
+
220
+ Returns:
221
+ An object instance of the class.
222
+ """
223
+ object_dict = from_json(json_string)
224
+ api_object = cls(from_json=True)
225
+
226
+ # Loop through the dictionary and update the object
227
+ for key, value in object_dict.items():
228
+ setattr(api_object, key, value)
229
+
230
+ return api_object