refined-quads 0.1.0__tar.gz

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 (143) hide show
  1. refined_quads-0.1.0/.gitattributes +4 -0
  2. refined_quads-0.1.0/.gitignore +18 -0
  3. refined_quads-0.1.0/.hypothesis/.gitignore +9 -0
  4. refined_quads-0.1.0/.hypothesis/constants/00ac74e5f24a8f23 +4 -0
  5. refined_quads-0.1.0/.hypothesis/constants/00b099b28ab69719 +4 -0
  6. refined_quads-0.1.0/.hypothesis/constants/03071a56d568c4ff +4 -0
  7. refined_quads-0.1.0/.hypothesis/constants/0ca6499cddf7bb4e +4 -0
  8. refined_quads-0.1.0/.hypothesis/constants/0d3d930a8cf43bfe +4 -0
  9. refined_quads-0.1.0/.hypothesis/constants/17ee048a05047466 +4 -0
  10. refined_quads-0.1.0/.hypothesis/constants/1f66879254faba2d +4 -0
  11. refined_quads-0.1.0/.hypothesis/constants/25497450f93ddf0a +4 -0
  12. refined_quads-0.1.0/.hypothesis/constants/28260f4e3ba16f80 +4 -0
  13. refined_quads-0.1.0/.hypothesis/constants/39328d456621aedd +4 -0
  14. refined_quads-0.1.0/.hypothesis/constants/3e605759b9d714fe +4 -0
  15. refined_quads-0.1.0/.hypothesis/constants/3f2b180b637013e0 +4 -0
  16. refined_quads-0.1.0/.hypothesis/constants/46f7bb725c91ddeb +4 -0
  17. refined_quads-0.1.0/.hypothesis/constants/60081ff99c20a9d1 +4 -0
  18. refined_quads-0.1.0/.hypothesis/constants/6de9487054c298a0 +4 -0
  19. refined_quads-0.1.0/.hypothesis/constants/8814f21e6484e0fc +4 -0
  20. refined_quads-0.1.0/.hypothesis/constants/88daa58db1ff125a +4 -0
  21. refined_quads-0.1.0/.hypothesis/constants/9182c701def2d4d5 +4 -0
  22. refined_quads-0.1.0/.hypothesis/constants/989da628c6044bfc +4 -0
  23. refined_quads-0.1.0/.hypothesis/constants/a5631cf79c8a54c4 +4 -0
  24. refined_quads-0.1.0/.hypothesis/constants/b197677f5f08a04f +4 -0
  25. refined_quads-0.1.0/.hypothesis/constants/b747fbea3787abd2 +4 -0
  26. refined_quads-0.1.0/.hypothesis/constants/b7542176cfbb5401 +4 -0
  27. refined_quads-0.1.0/.hypothesis/constants/bdb6ab1d246d2c55 +4 -0
  28. refined_quads-0.1.0/.hypothesis/constants/c70157c5272366f2 +4 -0
  29. refined_quads-0.1.0/.hypothesis/constants/cbaeb266738a869f +4 -0
  30. refined_quads-0.1.0/.hypothesis/constants/dd7ed633ab48ff73 +4 -0
  31. refined_quads-0.1.0/.hypothesis/constants/e5330bdc0ad9bcb0 +4 -0
  32. refined_quads-0.1.0/.hypothesis/constants/fc51d84a26877bba +4 -0
  33. refined_quads-0.1.0/.pre-commit-config.yaml +40 -0
  34. refined_quads-0.1.0/.python-version +1 -0
  35. refined_quads-0.1.0/.yamllint.yaml +9 -0
  36. refined_quads-0.1.0/CHANGELOG.md +147 -0
  37. refined_quads-0.1.0/CITATION.cff +22 -0
  38. refined_quads-0.1.0/CLAUDE.md +53 -0
  39. refined_quads-0.1.0/CONTEXT.md +80 -0
  40. refined_quads-0.1.0/LICENSE +674 -0
  41. refined_quads-0.1.0/PKG-INFO +139 -0
  42. refined_quads-0.1.0/README.md +110 -0
  43. refined_quads-0.1.0/docs/.gitignore +8 -0
  44. refined_quads-0.1.0/docs/_quarto.yml +112 -0
  45. refined_quads-0.1.0/docs/adr/0001-refine-from-existing-mesh.qmd +31 -0
  46. refined_quads-0.1.0/docs/adr/0002-output-contract-same-cells.qmd +30 -0
  47. refined_quads-0.1.0/docs/adr/index.qmd +9 -0
  48. refined_quads-0.1.0/docs/agents/domain.md +43 -0
  49. refined_quads-0.1.0/docs/agents/issue-tracker.md +21 -0
  50. refined_quads-0.1.0/docs/agents/triage-labels.md +19 -0
  51. refined_quads-0.1.0/docs/explanation/orthogonality.qmd +52 -0
  52. refined_quads-0.1.0/docs/explanation/refine.qmd +57 -0
  53. refined_quads-0.1.0/docs/explanation/refined-quad-mesh.qmd +72 -0
  54. refined_quads-0.1.0/docs/how-to/d-hydro.qmd +75 -0
  55. refined_quads-0.1.0/docs/how-to/gis-layers.qmd +103 -0
  56. refined_quads-0.1.0/docs/how-to/refine.qmd +116 -0
  57. refined_quads-0.1.0/docs/how-to/toml-cli.qmd +119 -0
  58. refined_quads-0.1.0/docs/how-to/water-areas.qmd +88 -0
  59. refined_quads-0.1.0/docs/index.qmd +74 -0
  60. refined_quads-0.1.0/docs/snippets/plot_mesh.py +71 -0
  61. refined_quads-0.1.0/docs/tutorial/first-mesh.qmd +96 -0
  62. refined_quads-0.1.0/pyproject.toml +127 -0
  63. refined_quads-0.1.0/src/refined_quads/__init__.py +52 -0
  64. refined_quads-0.1.0/src/refined_quads/__main__.py +7 -0
  65. refined_quads-0.1.0/src/refined_quads/_check/__init__.py +1 -0
  66. refined_quads-0.1.0/src/refined_quads/_check/check.py +196 -0
  67. refined_quads-0.1.0/src/refined_quads/_common/__init__.py +1 -0
  68. refined_quads-0.1.0/src/refined_quads/_common/grid.py +138 -0
  69. refined_quads-0.1.0/src/refined_quads/_common/progress.py +51 -0
  70. refined_quads-0.1.0/src/refined_quads/_common/recognise.py +343 -0
  71. refined_quads-0.1.0/src/refined_quads/_common/topology.py +142 -0
  72. refined_quads-0.1.0/src/refined_quads/_generate/__init__.py +1 -0
  73. refined_quads-0.1.0/src/refined_quads/_generate/areas.py +221 -0
  74. refined_quads-0.1.0/src/refined_quads/_generate/balance.py +223 -0
  75. refined_quads-0.1.0/src/refined_quads/_generate/clip.py +122 -0
  76. refined_quads-0.1.0/src/refined_quads/_generate/conformity.py +274 -0
  77. refined_quads-0.1.0/src/refined_quads/_generate/engine.py +353 -0
  78. refined_quads-0.1.0/src/refined_quads/_generate/faces.py +197 -0
  79. refined_quads-0.1.0/src/refined_quads/_generate/generate.py +359 -0
  80. refined_quads-0.1.0/src/refined_quads/_generate/orthogonality.py +623 -0
  81. refined_quads-0.1.0/src/refined_quads/_generate/quadtree.py +154 -0
  82. refined_quads-0.1.0/src/refined_quads/_generate/select.py +72 -0
  83. refined_quads-0.1.0/src/refined_quads/_io/__init__.py +1 -0
  84. refined_quads-0.1.0/src/refined_quads/_io/gis.py +414 -0
  85. refined_quads-0.1.0/src/refined_quads/_io/ugrid.py +312 -0
  86. refined_quads-0.1.0/src/refined_quads/_refine/__init__.py +1 -0
  87. refined_quads-0.1.0/src/refined_quads/_refine/refine.py +308 -0
  88. refined_quads-0.1.0/src/refined_quads/cli.py +245 -0
  89. refined_quads-0.1.0/src/refined_quads/errors.py +70 -0
  90. refined_quads-0.1.0/src/refined_quads/mesh.py +234 -0
  91. refined_quads-0.1.0/src/refined_quads/py.typed +0 -0
  92. refined_quads-0.1.0/src/refined_quads/spec.py +602 -0
  93. refined_quads-0.1.0/tests/check/test_check.py +173 -0
  94. refined_quads-0.1.0/tests/generate/test_benchmarks.py +48 -0
  95. refined_quads-0.1.0/tests/generate/test_generate.py +131 -0
  96. refined_quads-0.1.0/tests/generate/test_golden_masters.py +96 -0
  97. refined_quads-0.1.0/tests/generate/test_progress_and_origin.py +121 -0
  98. refined_quads-0.1.0/tests/generate/test_repair.py +75 -0
  99. refined_quads-0.1.0/tests/generate/test_repair_failure.py +100 -0
  100. refined_quads-0.1.0/tests/generate/test_water_areas.py +176 -0
  101. refined_quads-0.1.0/tests/golden/__init__.py +0 -0
  102. refined_quads-0.1.0/tests/golden/benchmarks.py +111 -0
  103. refined_quads-0.1.0/tests/golden/compare.py +87 -0
  104. refined_quads-0.1.0/tests/golden/masters/cell_size_12_5.npz +0 -0
  105. refined_quads-0.1.0/tests/golden/masters/cell_size_24_four_levels.npz +0 -0
  106. refined_quads-0.1.0/tests/golden/masters/cell_size_25.npz +0 -0
  107. refined_quads-0.1.0/tests/golden/masters/cell_size_32.npz +0 -0
  108. refined_quads-0.1.0/tests/golden/masters/domain_with_hole.npz +0 -0
  109. refined_quads-0.1.0/tests/golden/masters/head_to_toe_boxes.npz +0 -0
  110. refined_quads-0.1.0/tests/golden/masters/head_to_toe_circles.npz +0 -0
  111. refined_quads-0.1.0/tests/golden/masters/l_shaped_domain.npz +0 -0
  112. refined_quads-0.1.0/tests/golden/masters/many_small_water_areas.npz +0 -0
  113. refined_quads-0.1.0/tests/golden/masters/refinement_at_domain_edge.npz +0 -0
  114. refined_quads-0.1.0/tests/golden/masters/transition_rows_3_2_1_1.npz +0 -0
  115. refined_quads-0.1.0/tests/golden/masters/water_removal_centre.npz +0 -0
  116. refined_quads-0.1.0/tests/golden/masters/water_removal_inside.npz +0 -0
  117. refined_quads-0.1.0/tests/golden/masters/water_removal_touches.npz +0 -0
  118. refined_quads-0.1.0/tests/golden/masters.py +90 -0
  119. refined_quads-0.1.0/tests/golden/realdata_manifest.example.toml +18 -0
  120. refined_quads-0.1.0/tests/golden/record.py +463 -0
  121. refined_quads-0.1.0/tests/golden/repair/adjacent_wrong.npz +0 -0
  122. refined_quads-0.1.0/tests/golden/repair/fan_from_corner.npz +0 -0
  123. refined_quads-0.1.0/tests/golden/repair/fan_from_south.npz +0 -0
  124. refined_quads-0.1.0/tests/golden/repair/fan_from_south_10_1.npz +0 -0
  125. refined_quads-0.1.0/tests/golden/repair/wrong_diagonal.npz +0 -0
  126. refined_quads-0.1.0/tests/golden/repair_cases.py +93 -0
  127. refined_quads-0.1.0/tests/golden/runtime_baseline.json +96 -0
  128. refined_quads-0.1.0/tests/golden/scenarios.py +240 -0
  129. refined_quads-0.1.0/tests/golden/test_benchmark_report.py +78 -0
  130. refined_quads-0.1.0/tests/golden/test_compare.py +71 -0
  131. refined_quads-0.1.0/tests/golden/test_realdata.py +38 -0
  132. refined_quads-0.1.0/tests/golden/test_realdata_selection.py +38 -0
  133. refined_quads-0.1.0/tests/golden/ugrid/domain_with_hole_legacy.nc +0 -0
  134. refined_quads-0.1.0/tests/handmade.py +58 -0
  135. refined_quads-0.1.0/tests/io/test_gis.py +208 -0
  136. refined_quads-0.1.0/tests/io/test_ugrid.py +227 -0
  137. refined_quads-0.1.0/tests/refine/test_refine.py +241 -0
  138. refined_quads-0.1.0/tests/refine/test_refine_foreign.py +155 -0
  139. refined_quads-0.1.0/tests/test_cli.py +217 -0
  140. refined_quads-0.1.0/tests/test_cli_refine.py +138 -0
  141. refined_quads-0.1.0/tests/test_mesh.py +72 -0
  142. refined_quads-0.1.0/tests/test_package.py +27 -0
  143. refined_quads-0.1.0/uv.lock +2745 -0
@@ -0,0 +1,4 @@
1
+ * text=auto eol=lf
2
+ *.npz binary
3
+ *.nc binary
4
+ *.gpkg binary
@@ -0,0 +1,18 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .venv/
6
+ build/
7
+ dist/
8
+
9
+ # Tool caches
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .mypy_cache/
13
+ .benchmarks/
14
+ .coverage
15
+ htmlcov/
16
+
17
+ # Local issue tracker and scratch work (see docs/agents/issue-tracker.md)
18
+ .scratch/
@@ -0,0 +1,9 @@
1
+ # This .gitignore file was automatically created by Hypothesis. Hypothesis gitignores
2
+ # .hypothesis by default, because we generally recommend that .hypothesis not be checked
3
+ # into version control.
4
+ #
5
+ # If you *would* like to check .hypothesis into version control, you should delete this
6
+ # file. Hypothesis will not re-create this .gitignore unless .hypothesis is deleted (and
7
+ # if it does, that's a bug - please report it!)
8
+
9
+ *
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_generate/quadtree.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ ['ij']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_generate/select.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [1e-06]
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_common/grid.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ []
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_generate/conformity.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [0.35, 0.65, 1.0, 'stable']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_generate/balance.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [200, 2000, 'E', 'N', 'S', 'W']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_check/check.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [2.0, ', ']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_common/topology.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ []
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_generate/generate.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [100.0, 100, ', ', 'MultiPolygon', 'Polygon', 'Starting', 'centre', 'domain', 'inside', 'refinement areas', 'touches', 'water areas']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_refine/refine.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [1e-06, 2.0, ', ', 'centre', 'domain', 'intersects']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_common/recognise.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [1e-06]
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_io/gis.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [-1.0, 'EPSG_code', 'GPKG', 'MultiPolygon', 'Point', 'Polygon', 'Unknown projected', 'WKT1_GDAL', 'columns', 'crs', 'domain', 'epsg', 'fields', 'geometries', 'geometry', 'grid_mapping_name', 'ignore', 'index', 'inverse_flattening', 'levels', 'name', 'orth_e', 'proj4_params', 'projection_name', 'refinement area', 'semi_major_axis', 'semi_minor_axis', 'unknown', 'value', 'water area', 'wkt']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_generate/faces.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ ['E', 'N', 'NSEW', 'S', 'SNEW', 'W']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_generate/areas.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [8.0, 2000, 'structure']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/spec.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [', ', '.', 'WaterRemovalRule', 'cell_size', 'cell_size: missing', 'centre', 'crs', 'domain', 'domain: missing', 'layer', 'level', 'origin', 'output', 'path', 'refinement', 'refinement_areas', 'transition_rows', 'utf-8', 'water_areas', 'water_removal']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/mesh.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [1e-06, 'None', 'cell_size', 'crs', 'faces', 'levels', 'nodes', 'origin']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_check/__init__.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ []
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/__init__.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ ['0.0.0', 'Areas', 'Cancelled', 'InputError', 'Lattice', 'LayerSource', 'Mesh', 'MeshRecognitionError', 'MeshReport', 'MeshSpec', 'RefineResult', 'RefineSpec', 'RefinementSource', 'ReplacementMap', '__version__', 'check', 'generate', 'read_domain', 'read_ugrid', 'read_water_areas', 'refine', 'refined-quads', 'write_ugrid']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_generate/orthogonality.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [-1e-12, 1e-12, 0.3, 0.5, 1.0, 2.0, 3.0, 50.0, ', ', '2 (fan)', '5 (corner fan)', 'ENSW', 'ignore']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/cli.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [254, '+', '--cell-size', '--crs', '--origin', '--output', '--transition-rows', '--verbose', '--version', '--water-removal', '-o', '-v', ';', 'COMMAND', 'N', 'X', 'Y', 'cell_size', 'command', 'crs', 'generate', 'info', 'mesh', 'none', 'origin', 'output', 'problem', 'recognition_problems', 'refine', 'refined-quads', 'spec', 'store_true', 'transition_rows', 'version', 'water removal rule', 'water_removal']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_common/__init__.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ []
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/errors.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ []
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/.venv/bin/pytest
2
+ # hypothesis_version: 6.170.15
3
+
4
+ ['-script.pyw', '.exe', '__main__']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_generate/engine.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [100, 'centre', 'inside', 'the domain', 'touches']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_refine/__init__.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ []
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_common/progress.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ ['[QuadRef] %s%s%s']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_io/__init__.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ []
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_io/ugrid.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [-999.0, 0.5, 299.1528128, 6356078.962818189, 6377397.155, -999, 28992, '%Y-%m-%dT%H:%M:%S', ', ', '0', 'Amersfoort / RD New', 'Conventions', 'EPSG:0', 'EPSG:28992', 'EPSG_code', 'HKV / Svasek', 'Mesh2d', 'Mesh2d_edge_nodes', 'Mesh2d_edge_x', 'Mesh2d_edge_y', 'Mesh2d_face_nodes', 'Mesh2d_face_x', 'Mesh2d_face_x_bnd', 'Mesh2d_face_y', 'Mesh2d_face_y_bnd', 'Mesh2d_face_z', 'Mesh2d_nEdges', 'Mesh2d_nFaces', 'Mesh2d_nNodes', 'Mesh2d_node_x', 'Mesh2d_node_y', 'Mesh2d_node_z', 'NETCDF4', 'Two', 'Unknown projected', 'altitude', 'bounds', 'cf_role', 'crs_wkt', 'edge_coordinates', 'edge_dimension', 'epsg', 'f8', 'face_coordinates', 'face_dimension', 'grid_mapping_name', 'history', 'i4', 'institution', 'inverse_flattening', 'long_name', 'm', 'mesh_topology', 'name', 'node_coordinates', 'node_dimension', 'proj4_params', 'projection_name', 'r', 'references', 'semi_major_axis', 'semi_minor_axis', 'source', 'standard_name', 'start_index', 'topology_dimension', 'units', 'unknown', 'value', 'w', 'wkt', 'x', 'x-coordinate', 'y', 'y-coordinate']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_generate/clip.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ [-999.0, -998.0, 'centre', 'inside', 'touches']
@@ -0,0 +1,4 @@
1
+ # file: /home/runner/work/refined-quads/refined-quads/src/refined_quads/_generate/__init__.py
2
+ # hypothesis_version: 6.170.15
3
+
4
+ []
@@ -0,0 +1,40 @@
1
+ ---
2
+ # Install once with `uv run pre-commit install`; run on everything with
3
+ # `uv run pre-commit run --all-files`. CI runs the same hooks.
4
+ repos:
5
+ - repo: https://github.com/pre-commit/pre-commit-hooks
6
+ rev: v6.0.0
7
+ hooks:
8
+ - id: check-added-large-files
9
+ - id: check-ast
10
+ - id: check-case-conflict
11
+ - id: check-merge-conflict
12
+ - id: check-toml
13
+ - id: debug-statements
14
+ - id: end-of-file-fixer
15
+ - id: trailing-whitespace
16
+ args: [--markdown-linebreak-ext=md]
17
+
18
+ - repo: https://github.com/adrienverge/yamllint
19
+ rev: v1.38.0
20
+ hooks:
21
+ - id: yamllint
22
+ args: [--config-file=.yamllint.yaml]
23
+
24
+ # No pydoclint (in the HKV template): it compares the types in docstrings with the
25
+ # annotations, and the docstrings here describe types in words ("list of (float, float)").
26
+ # ruff checks the docstrings (numpy convention), mypy the annotations.
27
+
28
+ # ruff from the project environment, so the hooks and `uv run ruff` use one version.
29
+ - repo: local
30
+ hooks:
31
+ - id: ruff-check
32
+ name: ruff check
33
+ entry: uv run --frozen ruff check --fix --exit-non-zero-on-fix
34
+ language: system
35
+ types_or: [python, pyi]
36
+ - id: ruff-format
37
+ name: ruff format
38
+ entry: uv run --frozen ruff format
39
+ language: system
40
+ types_or: [python, pyi]
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,9 @@
1
+ ---
2
+ extends: default
3
+ rules:
4
+ new-lines: disable
5
+ line-length:
6
+ max: 100
7
+ # GitHub Actions uses the key `on`, which yamllint reads as a boolean.
8
+ truthy:
9
+ check-keys: false
@@ -0,0 +1,147 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [Semantic Versioning](https://semver.org/). Any intended change to the cells a given
6
+ input produces is at least a minor version (see `docs/adr/0002-output-contract-same-cells.qmd`).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-11-12
11
+
12
+ ### Added
13
+
14
+ - Project skeleton: uv project, ruff, mypy, pytest, CI workflow and documentation site.
15
+ - `Mesh` and `Lattice`: an immutable mesh (nodes, faces padded with -1) on its lattice.
16
+ - `check(mesh) -> MeshReport`: hanging nodes, 2:1 balance, FM orthogonality per internal
17
+ edge (the D-Flow FM measure), orientation, cells per refinement level and nodes off the
18
+ lattice.
19
+ - `InputError` for invalid input.
20
+ - Golden masters of the generator at qgis_dhydamo `9a324bc` for fourteen synthetic
21
+ scenarios, with the recording script and a runtime baseline (`tests/golden`); with
22
+ water or holes they are the old generator without water plus the MeshKernel clip of
23
+ the model workflow.
24
+ - `generate(domain, *, cell_size, refinement_areas, transition_rows) -> Mesh`, with the
25
+ engine of the generator at qgis_dhydamo `9a324bc`; it gives the same cells for the
26
+ same input (golden masters).
27
+ - `OrthogonalityRepairError` when the orthogonality repair does not close.
28
+ - Water areas in `generate` (`water_areas`, `water_removal`) with three water removal
29
+ rules: `"centre"` (default), `"inside"` and `"touches"`. The finished mesh is clipped
30
+ cell by cell with MeshKernel, exactly as the model workflow (hydrolib's
31
+ `mesh2d_clip`) does, so the remaining small cells follow the water's edge; MeshKernel
32
+ is a new dependency.
33
+ - `MeshReport.cell_levels`: the refinement level of the lattice square of every cell.
34
+ - `write_ugrid(mesh, path)` and `read_ugrid(path)`: UGRID-1.0 netCDF with the structure
35
+ of the old writer; for EPSG:28992 exactly the CRS attributes of the D-Flow FM plugin.
36
+ `read_ugrid` reads only the 2D mesh of a net file (a 1D network is ignored) and
37
+ derives the lattice from the square cells.
38
+ - `Mesh.crs`: the projected CRS of a mesh, or `None` when unknown; a geographic CRS
39
+ raises `InputError`.
40
+ - `read_domain`, `read_refinement_areas` (level from a column or one fixed level) and
41
+ `read_water_areas`: read a GeoPackage or shapefile layer with pyogrio, or take a
42
+ GeoDataFrame (geopandas is not a dependency), into `Areas` (polygons with a CRS).
43
+ - `generate` takes `Areas` and GeoDataFrames and has a `crs` parameter for input
44
+ without CRS. The mesh gets the CRS of the domain; other input is transformed to it.
45
+ - `write_ugrid` derives the CRS attributes of CRSs other than EPSG:28992 with pyproj.
46
+ - `MeshSpec`: the input of a run, one-to-one with a TOML file (`MeshSpec.from_toml`,
47
+ `replace`, `generate`); an invalid TOML file gives an `InputError` naming the key and
48
+ the reason.
49
+ - Command line: `refined-quads generate run.toml -o mesh_net.nc` (options override the
50
+ TOML values) and `refined-quads info mesh_net.nc` (lattice, cells per level and the
51
+ checks). Exit codes: 0 success, 1 the run failed, 2 invalid input or arguments.
52
+ - `generate(..., origin=(x, y))` (also `origin` in the TOML and `--origin X Y`): a
53
+ point of the lattice, so a mesh lines up with an existing mesh or raster; by
54
+ default the lower-left corner of the domain rounded down to a multiple of the cell
55
+ size, as before.
56
+ - `generate(..., progress=callback)`: `callback(fraction, message)` with a fraction
57
+ rising from 0 to 1 and English messages; returning `True` cancels the run with
58
+ `Cancelled` (the old generator raised a `RuntimeError`). `refined-quads generate -v`
59
+ prints the progress.
60
+ - `OrthogonalityRepairError.mesh`: the raw mesh before the repair, next to
61
+ `bad_points`. When the repair does not close, `refined-quads generate` writes
62
+ `<output>_raw.nc` and `<output>_problems.gpkg` (layer `orthogonality_problems`,
63
+ field `orth_e`, -1 for a degenerate edge) in the CRS of the mesh and exits with 1; a
64
+ failure to write them is reported without hiding the repair error.
65
+ - `refine(mesh, refinement_areas, ...) -> RefineResult`: refine an existing mesh
66
+ without its original input (ADR-0001). The lattice squares are recognised from the
67
+ cells, the new areas applied, the 2:1 balance restored where needed; squares that do
68
+ not change keep their cells and nodes exactly, the mesh never grows beyond its
69
+ footprint, and a domain or water areas only remove new cells. `RefineResult` has the
70
+ new mesh and a `ReplacementMap` (per new cell the old cell it lies in, which cells are
71
+ unchanged, which old cells went). `generate` is the same route from the base grid.
72
+ - `MeshRecognitionError` (an `InputError` with locations) when a mesh cannot be
73
+ refined: nodes off its lattice, cells that fill no lattice square, or a broken 2:1
74
+ balance. Duplicate nodes are accepted, and so are lattice squares that their cells
75
+ fill only partly (clipped along water); new cells in such a square stay only where
76
+ they lie completely within its old cells.
77
+ - Refine works on meshes from other programs: any lattice origin and base cell (derived
78
+ from the cells), and any transition cells (a pentagon, another triangulation) as long
79
+ as they fill a lattice square; squares that do not change keep their cells. Refine
80
+ judges the orthogonality only of edges next to new cells, so problems the mesh
81
+ already had do not stop it. Meshes of rectangles or rotated cells raise `InputError`.
82
+ - `refined-quads refine mesh_net.nc extra.toml -o mesh_v2_net.nc` and `RefineSpec` (the
83
+ TOML of a refine: new refinement areas, optional domain and water areas): same
84
+ options and exit codes as `generate`; a mesh that cannot be recognised exits with 2
85
+ and writes `<output>_recognition.gpkg` with the problem locations.
86
+ - `MeshReport.duplicate_nodes` (information only: cells that touch in a point lose
87
+ nothing) and `MeshReport.split_edges` (an edge on top of another edge with other
88
+ nodes: an internal wall; makes the mesh invalid).
89
+ - Benchmarks (`pytest -m benchmark`, pytest-benchmark) on three golden-master
90
+ scenarios, and `tests/golden/benchmarks.py` to compare results with a baseline and
91
+ report changes above 5%. CI runs slow tests and benchmarks on main and compares with
92
+ the previous run on main; results are kept as artifact.
93
+ - Documentation: a tutorial (a first mesh), how-to guides (GIS layers, TOML and the
94
+ command line, water areas, refining an existing mesh, D-HYDRO), explanations (refined
95
+ quad meshes, FM orthogonality, why refine starts from the mesh) and the API reference.
96
+ The site is built with Quarto, as in the HKV package template; the code on the pages
97
+ runs during the build and draws the mesh figures, and quartodoc writes the API
98
+ reference. CI publishes it to GitHub Pages from main once the repository is public.
99
+ - Licence: GNU GPL v3 or later (`LICENSE`), with `CITATION.cff`.
100
+ - pre-commit hooks (file hygiene, yamllint, pydoclint, ruff), also run in CI.
101
+ - Publishing to PyPI when a GitHub release is published (trusted publishing).
102
+
103
+ ### Changed compared with the generator at qgis_dhydamo `9a324bc`
104
+
105
+ - Intended output change: water areas are clipped cell by cell with MeshKernel after
106
+ generation, instead of removing whole lattice squares before the cells are formed
107
+ (the old `clip_water` option). Holes in the domain are clipped the same way; the old
108
+ generator kept every lattice square that overlaps the domain, so cells reached into
109
+ a hole. MeshKernel derives cells from edges again: a single deleted cell whose edges
110
+ all stay comes back, which is accepted.
111
+ - Intended output change: no minimum cell size. A 24 m base cell asked for level 5 now
112
+ gives 0.75 m cells; the old generator capped the levels so that the smallest cell
113
+ was at least 1 m (4 levels, 1.5 m).
114
+ - Every face is counterclockwise; the old generator wrote about 5% of its transition
115
+ triangles clockwise. The cells are the same.
116
+ - Transition rows are given per step from coarse to fine and must be at least 1; 0
117
+ raises `InputError` (the old generator took them from fine to coarse and counted 0
118
+ as 1). Callers get the same mesh by reversing their list and replacing 0 by 1.
119
+ - The highest refinement level follows from the refinement areas; there is no
120
+ `max_refinement`. A level that is not an integer of at least 1 raises `InputError`
121
+ instead of being clamped.
122
+ - Explicit refine zones without transition rows and support for shapely < 2 are gone.
123
+ - The orthogonality repair works on integer lattice indices: flips, convexity and the
124
+ redecomposition of lattice squares are exact, and the centre nodes it adds lie exactly
125
+ on the lattice (the old generator computed them as float midpoints). Only the FM
126
+ measure uses coordinates, as D-Flow FM does.
127
+ - Water areas that are given are always applied; there is no `clip_water` switch that
128
+ silently ignored them. An unknown water removal rule raises `InputError` instead of
129
+ falling back to the centre rule.
130
+ - `write_ugrid` writes the CRS of the mesh instead of always EPSG:28992: a mesh without
131
+ CRS gets no CRS variable. The `source` attribute names refined-quads and its version.
132
+ - No CRS is ever assumed: input without CRS (a layer, a GeoDataFrame or shapely
133
+ geometries) raises `InputError` unless `crs=` is given, and a geographic domain CRS
134
+ raises `InputError` (the old generator read a layer without CRS as RD New and
135
+ transformed everything else to RD New). A layer whose CRS contradicts `crs=` raises
136
+ `InputError`, as do a missing or invalid level (naming the feature), a feature
137
+ without geometry or that is not a polygon, and a file with several layers when no
138
+ layer is named.
139
+ - Intended output change for invalid polygons only: a domain, refinement area or water
140
+ area that is not valid is made valid with shapely's `make_valid` (structure method,
141
+ only polygonal parts) instead of `buffer(0)`, and a warning is logged. A
142
+ self-intersecting polygon such as a bowtie keeps all its loops, where `buffer(0)` kept
143
+ one. Valid input gives the same cells.
144
+ - The quadtree is built one generation at a time with the geometry tests in bulk
145
+ (shapely's vectorised predicates on prepared geometries, one STRtree query for all
146
+ start cells), and the selection of lattice squares in the domain clips in bulk. The
147
+ cells, and their order, are unchanged; on the benchmarks generate is 3-14% faster.
@@ -0,0 +1,22 @@
1
+ ---
2
+ cff-version: 1.2.0
3
+ message: "If you use this software, please cite it using these metadata."
4
+ title: refined-quads
5
+ version: 0.1.0
6
+ date-released: 2026-11-12
7
+ abstract: >-
8
+ Generate and refine refined quad meshes for D-Flow FM: axis-aligned squares on a
9
+ fixed lattice, locally refined by repeated halving, with transition cells only where
10
+ refinement levels meet.
11
+ authors:
12
+ - name: "HKV lijn in water"
13
+ - name: "Svasek Hydraulics"
14
+ repository-code: "https://github.com/HKV-products-services/refined-quads"
15
+ url: "https://hkv-products-services.github.io/refined-quads"
16
+ license: GPL-3.0-or-later
17
+ keywords:
18
+ - mesh
19
+ - quadtree
20
+ - UGRID
21
+ - D-Flow FM
22
+ - hydrodynamics
@@ -0,0 +1,53 @@
1
+ # CLAUDE.md — refined-quads
2
+
3
+ Python package that generates and refines refined quad meshes for D-Flow FM. It is the
4
+ single source of the generator formerly vendored as `meshgen` in qgis_dhydamo
5
+ (`tools/runtime/mesh/meshgen`) and DHYDRO_Varianten (`tools/standalone/measures_cli/rooster/meshgen`).
6
+ Plan: `.scratch/issues/refined-quads/PRD.md` and the issues next to it.
7
+
8
+ ## Rules
9
+
10
+ - Everything in English: code, docstrings (numpy style), error and log messages, docs.
11
+ Conversation with the user may be in Dutch.
12
+ - Output contract (ADR-0002): the same input gives the same cells as the generator at
13
+ qgis_dhydamo `9a324bc`, compared on integer lattice indices; cell and node order are
14
+ free. Golden masters guard this; an intended output change is at least a minor
15
+ version with a CHANGELOG entry.
16
+ - Replacing own code by shapely/numpy or vectorising: only if the golden masters still
17
+ give the same cells and the benchmark is not more than 5% slower.
18
+ - Invalid input raises an error; never silently adjust (levels, transition rows, CRS).
19
+ Exception: invalid polygons (domain, refinement areas, water areas) are made valid
20
+ with `make_valid` and a logged warning, not an error.
21
+ - Public API only via the top-level package. Internal code lives in subpackages whose
22
+ name starts with an underscore, grouped by task: `_generate/`, `_refine/`, `_check/`,
23
+ `_io/`, `_common/` (the modules inside have plain names). The test folders mirror them
24
+ (`tests/generate/`, ...); tests of the public layer (`cli`, `mesh`) stay in `tests/`.
25
+ - Use the terms from `CONTEXT.md`.
26
+
27
+ ## Commands
28
+
29
+ | Task | Command |
30
+ | --- | --- |
31
+ | Install | `uv sync` |
32
+ | Fast tests | `uv run pytest` (slow/benchmark/realdata via `-m <marker>`) |
33
+ | Benchmarks | `uv run pytest -m benchmark --benchmark-json=after.json`, then `uv run python tests/golden/benchmarks.py after.json --baseline before.json` (5% rule: measure before and after back to back on one machine) |
34
+ | Lint / format | `uv run pre-commit run --all-files` (ruff check and format, yamllint, file hygiene) |
35
+ | Types | `uv run mypy` |
36
+ | Docs | in `docs/`: `uv run quartodoc build`, then `uv run quarto render` (or `quarto preview`). Quarto is a separate install (`C:\Program Files\Quarto\bin`); CI fails on any `WARN` in the render output |
37
+
38
+ uv manages Python itself (`.python-version` = 3.12); the chocolatey Python shims on the
39
+ development PC are broken, so do not rely on a system Python.
40
+
41
+ ## Agent skills
42
+
43
+ ### Issue tracker
44
+
45
+ Local markdown in `.scratch/issues/<feature>/` (outside Git). See `docs/agents/issue-tracker.md`.
46
+
47
+ ### Triage labels
48
+
49
+ Default labels (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`) as a `Status:` line in the issue file. See `docs/agents/triage-labels.md`.
50
+
51
+ ### Domain docs
52
+
53
+ Single-context: `CONTEXT.md` at the root, ADRs in `docs/adr/`. See `docs/agents/domain.md`.
@@ -0,0 +1,80 @@
1
+ # refined-quads
2
+
3
+ Generates 2D computational meshes of axis-aligned square cells that are locally
4
+ refined by halving, for use as the 2D grid of a D-Flow FM model.
5
+
6
+ ## Language
7
+
8
+ **Refined quad mesh**:
9
+ A 2D mesh of axis-aligned squares on a fixed lattice, locally refined by
10
+ repeated halving, with triangles or irregular quads only where cell sizes change.
11
+ _Avoid_: quadtree grid, meshgen grid, Quad Grid
12
+
13
+ **Base cell**:
14
+ The coarsest square of a refined quad mesh; its size is the cell size the user
15
+ chooses, any positive length (not necessarily a power of 2).
16
+ _Avoid_: basiscel, coarse cell
17
+
18
+ **Refinement level**:
19
+ The number of halvings of the base cell; level 0 is the base cell, level N has
20
+ side length cell size / 2^N.
21
+ _Avoid_: halving level, refinement (as a number)
22
+
23
+ **Lattice**:
24
+ The fixed grid of points, given by an origin and the base cell size, on which
25
+ every square of a refined quad mesh has its corners.
26
+ _Avoid_: raster, grid origin
27
+
28
+ **Lattice square**:
29
+ A square of the quadtree: a base cell or one of the squares its halving gives,
30
+ aligned to the lattice at its refinement level. It becomes one square cell, or is
31
+ filled exactly by transition cells; after clipping along water it can be filled only
32
+ partly.
33
+ _Avoid_: parent square, quadtree cell, leaf
34
+
35
+ **Transition rows**:
36
+ For the step from refinement level k+1 down to level k, the number of rows of
37
+ level-(k+1) cells kept around the finer area before the mesh coarsens; at least 1.
38
+ Given per step from coarse to fine.
39
+ _Avoid_: buffer rows, bufferrijen, buffer
40
+
41
+ **Transition cell**:
42
+ A triangle or irregular quad that, together with the other transition cells in
43
+ the same lattice square, fills that square exactly where refinement levels meet.
44
+ _Avoid_: sliver, overgangsdriehoek, transition triangle
45
+
46
+ **Footprint**:
47
+ The area covered by the cells of an existing mesh; holes are not part of it.
48
+ Refining never adds cells outside it.
49
+ _Avoid_: extent, mesh outline
50
+
51
+ **Replacement map**:
52
+ The record of a refine: which existing cells were removed and, for every new
53
+ cell, the existing cell it lies in. Lets callers carry cell data over.
54
+ _Avoid_: diff, mapping, splice
55
+
56
+ ## Input
57
+
58
+ **Domain**:
59
+ The (multi)polygon that encloses the mesh and forms its edges (the enclosure in
60
+ D-HYDRO terms); usually a polygon layer of a GeoPackage, whose features are merged. A
61
+ lattice square that overlaps it is kept whole, so the outer edge of the mesh follows
62
+ the lattice along the domain rather than the polygon exactly. Holes in the domain are
63
+ clipped like water areas, cell by cell.
64
+ _Avoid_: boundary, extent, model area
65
+
66
+ **Refinement area**:
67
+ A polygon paired with the refinement level its cells must reach; transition
68
+ rows are added around it automatically.
69
+ _Avoid_: refinement polygon, refine zone, refinement (as a shape)
70
+
71
+ **Water area**:
72
+ An area where no 2D cells are kept, because the 1D network carries the flow there.
73
+ _Avoid_: water polygon, exclusion area, clip area
74
+
75
+ **Water removal rule**:
76
+ The test that decides whether a cell touching a water area (or a hole in the domain)
77
+ is removed from the finished mesh: by its circumcentre, only when fully inside, or as
78
+ soon as it touches; applied cell by cell with MeshKernel, as in the model workflow, so
79
+ the remaining small cells follow the water's edge.
80
+ _Avoid_: delete option, clip method