pantr 0.6.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 (263) hide show
  1. pantr-0.6.0/.claude/settings.json +14 -0
  2. pantr-0.6.0/.claude/skills/new-feature/SKILL.md +195 -0
  3. pantr-0.6.0/.claude/skills/pre-pr-checks/SKILL.md +86 -0
  4. pantr-0.6.0/.github/workflows/ci.yaml +318 -0
  5. pantr-0.6.0/.github/workflows/release.yaml +185 -0
  6. pantr-0.6.0/.gitignore +80 -0
  7. pantr-0.6.0/.pre-commit-config.yaml +48 -0
  8. pantr-0.6.0/.pre-commit-hooks/conditional_check.py +57 -0
  9. pantr-0.6.0/.readthedocs.yaml +28 -0
  10. pantr-0.6.0/CITATION.cff +22 -0
  11. pantr-0.6.0/CLAUDE.md +189 -0
  12. pantr-0.6.0/CONTRIBUTING.md +88 -0
  13. pantr-0.6.0/LICENSE +22 -0
  14. pantr-0.6.0/Makefile +59 -0
  15. pantr-0.6.0/PKG-INFO +178 -0
  16. pantr-0.6.0/README.md +106 -0
  17. pantr-0.6.0/RELEASING.md +104 -0
  18. pantr-0.6.0/coverage.toml +12 -0
  19. pantr-0.6.0/docs/Makefile +30 -0
  20. pantr-0.6.0/docs/_static/.gitkeep +2 -0
  21. pantr-0.6.0/docs/_templates/.gitkeep +2 -0
  22. pantr-0.6.0/docs/api/reference.md +94 -0
  23. pantr-0.6.0/docs/changelog.md +142 -0
  24. pantr-0.6.0/docs/conf.py +268 -0
  25. pantr-0.6.0/docs/getting-started.md +69 -0
  26. pantr-0.6.0/docs/guide/cad.md +197 -0
  27. pantr-0.6.0/docs/guide/concepts.md +152 -0
  28. pantr-0.6.0/docs/guide/distributed.md +242 -0
  29. pantr-0.6.0/docs/guide/parallelism.md +152 -0
  30. pantr-0.6.0/docs/guide/spaces-knots.md +198 -0
  31. pantr-0.6.0/docs/guide/visualization.md +211 -0
  32. pantr-0.6.0/docs/index.md +107 -0
  33. pantr-0.6.0/docs/references.bib +216 -0
  34. pantr-0.6.0/docs/references.md +35 -0
  35. pantr-0.6.0/mypy.ini +47 -0
  36. pantr-0.6.0/pyproject.toml +134 -0
  37. pantr-0.6.0/pytest.ini +13 -0
  38. pantr-0.6.0/ruff.toml +45 -0
  39. pantr-0.6.0/src/pantr/__init__.py +130 -0
  40. pantr-0.6.0/src/pantr/_array_utils.py +86 -0
  41. pantr-0.6.0/src/pantr/_control_points_utils.py +84 -0
  42. pantr-0.6.0/src/pantr/_interpolation_utils.py +71 -0
  43. pantr-0.6.0/src/pantr/_numba_compat.py +57 -0
  44. pantr-0.6.0/src/pantr/_parallel.py +155 -0
  45. pantr-0.6.0/src/pantr/_transform_control_points.py +77 -0
  46. pantr-0.6.0/src/pantr/basis/__init__.py +35 -0
  47. pantr-0.6.0/src/pantr/basis/_basis_1D.py +280 -0
  48. pantr-0.6.0/src/pantr/basis/_basis_core.py +469 -0
  49. pantr-0.6.0/src/pantr/basis/_basis_lagrange.py +130 -0
  50. pantr-0.6.0/src/pantr/basis/_basis_multidim.py +258 -0
  51. pantr-0.6.0/src/pantr/basis/_basis_tabulate.py +354 -0
  52. pantr-0.6.0/src/pantr/basis/_basis_utils.py +170 -0
  53. pantr-0.6.0/src/pantr/bezier/__init__.py +32 -0
  54. pantr-0.6.0/src/pantr/bezier/_batch_core.py +190 -0
  55. pantr-0.6.0/src/pantr/bezier/_bezier.py +917 -0
  56. pantr-0.6.0/src/pantr/bezier/_bezier_collapse.py +94 -0
  57. pantr-0.6.0/src/pantr/bezier/_bezier_compose.py +256 -0
  58. pantr-0.6.0/src/pantr/bezier/_bezier_core.py +657 -0
  59. pantr-0.6.0/src/pantr/bezier/_bezier_degree.py +279 -0
  60. pantr-0.6.0/src/pantr/bezier/_bezier_derivative.py +274 -0
  61. pantr-0.6.0/src/pantr/bezier/_bezier_eval.py +714 -0
  62. pantr-0.6.0/src/pantr/bezier/_bezier_interpolate.py +756 -0
  63. pantr-0.6.0/src/pantr/bezier/_bezier_product.py +265 -0
  64. pantr-0.6.0/src/pantr/bezier/_bezier_restrict.py +73 -0
  65. pantr-0.6.0/src/pantr/bezier/_bezier_slice.py +76 -0
  66. pantr-0.6.0/src/pantr/bezier/_bezier_split.py +66 -0
  67. pantr-0.6.0/src/pantr/bezier/_bezier_utils.py +44 -0
  68. pantr-0.6.0/src/pantr/bezier/_clipping_core.py +416 -0
  69. pantr-0.6.0/src/pantr/bezier/_find_roots.py +341 -0
  70. pantr-0.6.0/src/pantr/bezier/_root_finding.py +167 -0
  71. pantr-0.6.0/src/pantr/bezier/_root_finding_core.py +408 -0
  72. pantr-0.6.0/src/pantr/bezier/_yuksel_core.py +397 -0
  73. pantr-0.6.0/src/pantr/bspline/__init__.py +107 -0
  74. pantr-0.6.0/src/pantr/bspline/_bspline.py +1191 -0
  75. pantr-0.6.0/src/pantr/bspline/_bspline_basis_core.py +851 -0
  76. pantr-0.6.0/src/pantr/bspline/_bspline_basis_multidim.py +344 -0
  77. pantr-0.6.0/src/pantr/bspline/_bspline_blossom.py +72 -0
  78. pantr-0.6.0/src/pantr/bspline/_bspline_blossom_core.py +111 -0
  79. pantr-0.6.0/src/pantr/bspline/_bspline_degree.py +255 -0
  80. pantr-0.6.0/src/pantr/bspline/_bspline_degree_core.py +470 -0
  81. pantr-0.6.0/src/pantr/bspline/_bspline_derivative.py +471 -0
  82. pantr-0.6.0/src/pantr/bspline/_bspline_eval.py +1287 -0
  83. pantr-0.6.0/src/pantr/bspline/_bspline_extraction.py +377 -0
  84. pantr-0.6.0/src/pantr/bspline/_bspline_interpolate.py +1198 -0
  85. pantr-0.6.0/src/pantr/bspline/_bspline_knot_insertion.py +701 -0
  86. pantr-0.6.0/src/pantr/bspline/_bspline_knot_insertion_core.py +148 -0
  87. pantr-0.6.0/src/pantr/bspline/_bspline_knot_removal.py +155 -0
  88. pantr-0.6.0/src/pantr/bspline/_bspline_knot_removal_core.py +196 -0
  89. pantr-0.6.0/src/pantr/bspline/_bspline_knots.py +663 -0
  90. pantr-0.6.0/src/pantr/bspline/_bspline_product.py +729 -0
  91. pantr-0.6.0/src/pantr/bspline/_bspline_product_nd.py +538 -0
  92. pantr-0.6.0/src/pantr/bspline/_bspline_quasi_interpolation.py +297 -0
  93. pantr-0.6.0/src/pantr/bspline/_bspline_restrict.py +258 -0
  94. pantr-0.6.0/src/pantr/bspline/_bspline_slice.py +154 -0
  95. pantr-0.6.0/src/pantr/bspline/_bspline_space_1d.py +797 -0
  96. pantr-0.6.0/src/pantr/bspline/_bspline_space_factory.py +499 -0
  97. pantr-0.6.0/src/pantr/bspline/_bspline_space_nd.py +292 -0
  98. pantr-0.6.0/src/pantr/bspline/_bspline_split.py +146 -0
  99. pantr-0.6.0/src/pantr/bspline/_bspline_to_beziers.py +199 -0
  100. pantr-0.6.0/src/pantr/bspline/_coupling_graph.py +253 -0
  101. pantr-0.6.0/src/pantr/bspline/_extraction_helpers.py +672 -0
  102. pantr-0.6.0/src/pantr/bspline/_extraction_kernels.py +1934 -0
  103. pantr-0.6.0/src/pantr/bspline/_local_space.py +433 -0
  104. pantr-0.6.0/src/pantr/bspline/_partition_graph.py +312 -0
  105. pantr-0.6.0/src/pantr/bspline/_thb_eval_core.py +71 -0
  106. pantr-0.6.0/src/pantr/bspline/_thb_quasi_interpolation.py +162 -0
  107. pantr-0.6.0/src/pantr/bspline/_thb_spline.py +374 -0
  108. pantr-0.6.0/src/pantr/bspline/_thb_spline_space.py +2045 -0
  109. pantr-0.6.0/src/pantr/bspline/multilevel_extraction.py +398 -0
  110. pantr-0.6.0/src/pantr/bspline/spanwise_element_extraction.py +1147 -0
  111. pantr-0.6.0/src/pantr/cad/__init__.py +40 -0
  112. pantr-0.6.0/src/pantr/cad/_compat.py +262 -0
  113. pantr-0.6.0/src/pantr/cad/_coons.py +372 -0
  114. pantr-0.6.0/src/pantr/cad/_derived.py +130 -0
  115. pantr-0.6.0/src/pantr/cad/_join.py +206 -0
  116. pantr-0.6.0/src/pantr/cad/_operations.py +358 -0
  117. pantr-0.6.0/src/pantr/cad/_primitives.py +366 -0
  118. pantr-0.6.0/src/pantr/cad/_validation.py +62 -0
  119. pantr-0.6.0/src/pantr/change_basis.py +400 -0
  120. pantr-0.6.0/src/pantr/geometry.py +513 -0
  121. pantr-0.6.0/src/pantr/grid/__init__.py +73 -0
  122. pantr-0.6.0/src/pantr/grid/_bvh.py +335 -0
  123. pantr-0.6.0/src/pantr/grid/_bvh_core.py +329 -0
  124. pantr-0.6.0/src/pantr/grid/_cell_index.py +96 -0
  125. pantr-0.6.0/src/pantr/grid/_cell_quadrature.py +110 -0
  126. pantr-0.6.0/src/pantr/grid/_grid.py +620 -0
  127. pantr-0.6.0/src/pantr/grid/_grid_utils.py +35 -0
  128. pantr-0.6.0/src/pantr/grid/_hier_core.py +367 -0
  129. pantr-0.6.0/src/pantr/grid/_hierarchical_grid.py +1433 -0
  130. pantr-0.6.0/src/pantr/grid/_locate_core.py +111 -0
  131. pantr-0.6.0/src/pantr/grid/_overlay.py +129 -0
  132. pantr-0.6.0/src/pantr/grid/_partition.py +113 -0
  133. pantr-0.6.0/src/pantr/grid/_partition_grid.py +403 -0
  134. pantr-0.6.0/src/pantr/grid/_tags.py +495 -0
  135. pantr-0.6.0/src/pantr/grid/_tensor_product_grid.py +497 -0
  136. pantr-0.6.0/src/pantr/mpi/__init__.py +124 -0
  137. pantr-0.6.0/src/pantr/mpi/_collocation.py +462 -0
  138. pantr-0.6.0/src/pantr/mpi/_create.py +112 -0
  139. pantr-0.6.0/src/pantr/mpi/_distributed_function.py +204 -0
  140. pantr-0.6.0/src/pantr/mpi/_distributed_space.py +256 -0
  141. pantr-0.6.0/src/pantr/mpi/_from_dolfinx.py +178 -0
  142. pantr-0.6.0/src/pantr/mpi/_l2.py +297 -0
  143. pantr-0.6.0/src/pantr/mpi/_qi.py +151 -0
  144. pantr-0.6.0/src/pantr/mpi/_thb_qi.py +165 -0
  145. pantr-0.6.0/src/pantr/mpi/_thread_policy.py +138 -0
  146. pantr-0.6.0/src/pantr/py.typed +0 -0
  147. pantr-0.6.0/src/pantr/quad.py +746 -0
  148. pantr-0.6.0/src/pantr/tolerance.py +239 -0
  149. pantr-0.6.0/src/pantr/transform.py +511 -0
  150. pantr-0.6.0/src/pantr/viz/__init__.py +46 -0
  151. pantr-0.6.0/src/pantr/viz/_common.py +59 -0
  152. pantr-0.6.0/src/pantr/viz/_control_points.py +263 -0
  153. pantr-0.6.0/src/pantr/viz/_grid.py +99 -0
  154. pantr-0.6.0/src/pantr/viz/_knot_lines.py +233 -0
  155. pantr-0.6.0/src/pantr/viz/_lazy_import.py +23 -0
  156. pantr-0.6.0/src/pantr/viz/_scene.py +351 -0
  157. pantr-0.6.0/src/pantr/viz/_vtk_cells.py +582 -0
  158. pantr-0.6.0/src/pantr/viz/_vtk_ordering.py +320 -0
  159. pantr-0.6.0/tests/__init__.py +4 -0
  160. pantr-0.6.0/tests/_thb_assembly.py +152 -0
  161. pantr-0.6.0/tests/conftest.py +50 -0
  162. pantr-0.6.0/tests/data/tanh_sinh_golden.npz +0 -0
  163. pantr-0.6.0/tests/mpi/test_distributed_mpi.py +422 -0
  164. pantr-0.6.0/tests/mpi/test_thread_policy_mpi.py +56 -0
  165. pantr-0.6.0/tests/test_aabb.py +359 -0
  166. pantr-0.6.0/tests/test_basis.py +919 -0
  167. pantr-0.6.0/tests/test_bezier.py +647 -0
  168. pantr-0.6.0/tests/test_bezier_collapse.py +311 -0
  169. pantr-0.6.0/tests/test_bezier_compose.py +358 -0
  170. pantr-0.6.0/tests/test_bezier_interpolate.py +698 -0
  171. pantr-0.6.0/tests/test_bezier_reverse_permute.py +188 -0
  172. pantr-0.6.0/tests/test_bezier_slice.py +295 -0
  173. pantr-0.6.0/tests/test_bezier_split.py +364 -0
  174. pantr-0.6.0/tests/test_bspline.py +436 -0
  175. pantr-0.6.0/tests/test_bspline_basis_derivatives_1D.py +543 -0
  176. pantr-0.6.0/tests/test_bspline_blossom.py +205 -0
  177. pantr-0.6.0/tests/test_bspline_conversion.py +973 -0
  178. pantr-0.6.0/tests/test_bspline_derivative.py +604 -0
  179. pantr-0.6.0/tests/test_bspline_evaluate_derivatives_multi_dim.py +579 -0
  180. pantr-0.6.0/tests/test_bspline_evaluate_multi_dim.py +449 -0
  181. pantr-0.6.0/tests/test_bspline_evaluation.py +428 -0
  182. pantr-0.6.0/tests/test_bspline_interpolate.py +523 -0
  183. pantr-0.6.0/tests/test_bspline_product.py +588 -0
  184. pantr-0.6.0/tests/test_bspline_product_nd.py +683 -0
  185. pantr-0.6.0/tests/test_bspline_reverse_permute.py +243 -0
  186. pantr-0.6.0/tests/test_bspline_slice.py +365 -0
  187. pantr-0.6.0/tests/test_bspline_space.py +1103 -0
  188. pantr-0.6.0/tests/test_bspline_space_1D.py +2374 -0
  189. pantr-0.6.0/tests/test_bspline_split.py +238 -0
  190. pantr-0.6.0/tests/test_cad_compat.py +193 -0
  191. pantr-0.6.0/tests/test_cad_coons.py +172 -0
  192. pantr-0.6.0/tests/test_cad_derived.py +151 -0
  193. pantr-0.6.0/tests/test_cad_join.py +107 -0
  194. pantr-0.6.0/tests/test_cad_operations.py +171 -0
  195. pantr-0.6.0/tests/test_cad_primitives.py +362 -0
  196. pantr-0.6.0/tests/test_cad_revolve_sweep.py +171 -0
  197. pantr-0.6.0/tests/test_cardinal_bspline_1D.py +106 -0
  198. pantr-0.6.0/tests/test_change_basis_1D.py +622 -0
  199. pantr-0.6.0/tests/test_coupling_graph.py +272 -0
  200. pantr-0.6.0/tests/test_degree_elevation.py +241 -0
  201. pantr-0.6.0/tests/test_degree_reduction.py +395 -0
  202. pantr-0.6.0/tests/test_distributed_space.py +461 -0
  203. pantr-0.6.0/tests/test_extraction_kernels.py +359 -0
  204. pantr-0.6.0/tests/test_from_dolfinx.py +213 -0
  205. pantr-0.6.0/tests/test_grid_abc.py +166 -0
  206. pantr-0.6.0/tests/test_grid_bvh.py +294 -0
  207. pantr-0.6.0/tests/test_grid_cell_quadrature.py +213 -0
  208. pantr-0.6.0/tests/test_grid_hierarchical.py +1009 -0
  209. pantr-0.6.0/tests/test_grid_overlay.py +119 -0
  210. pantr-0.6.0/tests/test_grid_partition.py +89 -0
  211. pantr-0.6.0/tests/test_grid_tags.py +241 -0
  212. pantr-0.6.0/tests/test_grid_tensor_product.py +588 -0
  213. pantr-0.6.0/tests/test_knot_insertion.py +680 -0
  214. pantr-0.6.0/tests/test_knot_removal.py +307 -0
  215. pantr-0.6.0/tests/test_knots_1D.py +478 -0
  216. pantr-0.6.0/tests/test_lagrange_1D.py +162 -0
  217. pantr-0.6.0/tests/test_legendre_1D.py +123 -0
  218. pantr-0.6.0/tests/test_local_space.py +531 -0
  219. pantr-0.6.0/tests/test_metadata.py +70 -0
  220. pantr-0.6.0/tests/test_mpi.py +132 -0
  221. pantr-0.6.0/tests/test_mpi_collocation.py +466 -0
  222. pantr-0.6.0/tests/test_mpi_l2.py +498 -0
  223. pantr-0.6.0/tests/test_mpi_qi.py +417 -0
  224. pantr-0.6.0/tests/test_mpi_thb_qi.py +403 -0
  225. pantr-0.6.0/tests/test_mpi_thread_policy.py +197 -0
  226. pantr-0.6.0/tests/test_multilevel_extraction.py +400 -0
  227. pantr-0.6.0/tests/test_parallel.py +117 -0
  228. pantr-0.6.0/tests/test_partition_graph.py +376 -0
  229. pantr-0.6.0/tests/test_partition_grid.py +431 -0
  230. pantr-0.6.0/tests/test_quad.py +620 -0
  231. pantr-0.6.0/tests/test_quad_tanh_sinh.py +239 -0
  232. pantr-0.6.0/tests/test_quasi_interpolation.py +446 -0
  233. pantr-0.6.0/tests/test_restrict.py +330 -0
  234. pantr-0.6.0/tests/test_review_regressions.py +729 -0
  235. pantr-0.6.0/tests/test_root_finding.py +643 -0
  236. pantr-0.6.0/tests/test_root_finding_stress.py +314 -0
  237. pantr-0.6.0/tests/test_spanwise_element_extraction.py +1473 -0
  238. pantr-0.6.0/tests/test_thb_spline_space.py +1926 -0
  239. pantr-0.6.0/tests/test_thb_validation_basis.py +202 -0
  240. pantr-0.6.0/tests/test_thb_validation_identities.py +243 -0
  241. pantr-0.6.0/tests/test_thb_validation_l2.py +142 -0
  242. pantr-0.6.0/tests/test_thb_validation_qi.py +165 -0
  243. pantr-0.6.0/tests/test_to_beziers_multiplicity.py +312 -0
  244. pantr-0.6.0/tests/test_tolerance.py +239 -0
  245. pantr-0.6.0/tests/test_transform.py +661 -0
  246. pantr-0.6.0/tests/test_tutorials.py +462 -0
  247. pantr-0.6.0/tests/test_viz_cells.py +338 -0
  248. pantr-0.6.0/tests/test_viz_grid.py +68 -0
  249. pantr-0.6.0/tests/test_viz_knot_lines.py +205 -0
  250. pantr-0.6.0/tests/test_viz_ordering.py +199 -0
  251. pantr-0.6.0/tests/test_viz_scene.py +147 -0
  252. pantr-0.6.0/tests/test_viz_thb.py +321 -0
  253. pantr-0.6.0/tutorials/01_first_bspline.py +87 -0
  254. pantr-0.6.0/tutorials/02_visualization.py +80 -0
  255. pantr-0.6.0/tutorials/03_knot_operations.py +67 -0
  256. pantr-0.6.0/tutorials/04_cad_modeling.py +68 -0
  257. pantr-0.6.0/tutorials/05_approximation.py +91 -0
  258. pantr-0.6.0/tutorials/06_polynomial_bases.py +78 -0
  259. pantr-0.6.0/tutorials/07_bezier_and_roots.py +69 -0
  260. pantr-0.6.0/tutorials/08_thb_adaptive_refinement.py +104 -0
  261. pantr-0.6.0/tutorials/09_grids_and_quadrature.py +66 -0
  262. pantr-0.6.0/tutorials/10_transforms.py +56 -0
  263. pantr-0.6.0/tutorials/GALLERY_HEADER.rst +52 -0
@@ -0,0 +1,14 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(conda run:*)",
5
+ "Bash(conda activate:*)",
6
+ "Bash(pytest:*)",
7
+ "Bash(ruff:*)",
8
+ "Bash(mypy:*)",
9
+ "Bash(sphinx-build:*)",
10
+ "Bash(make:*)",
11
+ "Bash(lint-imports:*)"
12
+ ]
13
+ }
14
+ }
@@ -0,0 +1,195 @@
1
+ ---
2
+ name: new-feature
3
+ description: End-to-end workflow for implementing a new feature, bug fix, or refactor — from creating a worktree and branch through implementation, testing, validation, PR creation, and user notification. Use this skill whenever the user asks you to implement, add, build, create, or fix something that will result in a PR. Trigger on requests like "add support for X", "implement Y", "fix bug Z", "refactor W", or any task that involves writing code that should be merged. Even if the user doesn't mention PRs or branches, if the task involves code changes that need review, use this skill.
4
+ ---
5
+
6
+ # New Feature Workflow
7
+
8
+ This skill orchestrates the full lifecycle of a code change — from branch creation to PR. Follow these phases in order.
9
+
10
+ ## Phase 1: Worktree and branch
11
+
12
+ Create an isolated worktree to work in. Use the `EnterWorktree` tool — it will create a new worktree under `.claude/worktrees/` with a fresh branch based on HEAD.
13
+
14
+ After entering the worktree, rename the branch to follow the convention:
15
+
16
+ ```bash
17
+ git branch -m <type>/<short-description>
18
+ ```
19
+
20
+ Where `<type>` matches the conventional commit type (`feat`, `fix`, `refactor`, etc.) and `<short-description>` is a kebab-case summary (e.g., `feat/periodic-bspline-product`, `fix/bernstein-derivative-at-boundary`).
21
+
22
+ ## Phase 2: Plan
23
+
24
+ Before writing code, understand the task and form a plan:
25
+
26
+ 1. Read the relevant existing code to understand the architecture and conventions
27
+ 2. Identify which layer(s) the change touches (Layer 1 / 2 / 3 — see CLAUDE.md)
28
+ 3. Outline the approach — what files to create or modify, what the public API looks like
29
+ 4. Share the plan with the user and get confirmation before proceeding
30
+
31
+ For non-trivial features, use `EnterPlanMode` to align on the approach.
32
+
33
+ ## Phase 3: Implement
34
+
35
+ Write the code following the project's architecture and conventions:
36
+
37
+ - **Layer 3 (kernels)**: Pure Numba computation, no validation, `@nb_jit(nopython=True, cache=True)`
38
+ - **Layer 2 (helpers)**: Input validation, array allocation, calls Layer 3
39
+ - **Layer 1 (public API)**: Lightweight validation, delegates to Layer 2
40
+ - Follow strict mypy typing, Google-style docstrings, and all conventions in CLAUDE.md
41
+
42
+ ### Commit conventions
43
+
44
+ Commit regularly as you complete logical units of work. Use **conventional commits**:
45
+
46
+ ```
47
+ <type>(<scope>): <imperative summary>
48
+
49
+ Optional body explaining why, not what.
50
+
51
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
52
+ ```
53
+
54
+ **Types**: `feat`, `fix`, `refactor`, `test`, `docs`, `style`, `perf`, `chore`
55
+ **Scope**: the module or area affected (e.g., `bspline`, `basis`, `quad`, `docs`)
56
+
57
+ Examples:
58
+ - `feat(bspline): add exact 1D B-spline product via Bezier multiplication`
59
+ - `fix(basis): correct off-by-one in Cox-de Boor recursion`
60
+ - `refactor(bspline): move to_open_bspline to Layer 2`
61
+ - `test(bspline): add boundary case tests for derivative evaluation`
62
+
63
+ Keep commits focused — one logical change per commit. Do not bundle unrelated changes.
64
+
65
+ ## Phase 4: Test
66
+
67
+ Write tests for the new code:
68
+
69
+ - Place tests in the appropriate file under `tests/` (follow existing naming: `test_<module>.py`)
70
+ - Cover normal cases, edge cases, and error cases
71
+ - Test public API (Layer 1) primarily; test Layer 2 directly only if it has complex validation logic
72
+ - Use `pytest --no-cov -v` to run tests during development
73
+ - Commit tests in a dedicated commit (e.g., `test(bspline): add tests for exact product`)
74
+
75
+ ## Phase 5: Pre-PR validation
76
+
77
+ Before creating the PR, run the full check suite. Invoke the `pre-pr-checks` skill (or run the checks manually):
78
+
79
+ 1. `ruff check .` — lint
80
+ 2. `ruff format --check .` — formatting
81
+ 3. `mypy --config-file mypy.ini src tests` — type checking
82
+ 4. `pytest --no-cov -v` — tests
83
+ 5. `NUMBA_DISABLE_JIT=1 sphinx-build -M html docs/ docs/_build -W --keep-going -j auto` — docs
84
+
85
+ Fix any issues and commit the fixes before proceeding. All checks must pass.
86
+
87
+ **Always run every check on the whole repo, not single files.** Running
88
+ `ruff check <one_file>` while skipping `ruff format --check .` (or vice versa) has
89
+ let real failures through. Run all five commands, every push — no shortcuts.
90
+
91
+ **Local green ≠ CI green.** The local environment can differ from CI in ways that
92
+ hide failures; the local suite passing is necessary but not sufficient. Known traps:
93
+
94
+ - **mypy runs on a Python matrix (3.11–3.14).** `mypy.ini` pins `python_version = 3.11`,
95
+ but the *numpy stub version* is whatever is installed locally. Be defensive with numpy
96
+ typing: no bare `np.ndarray`; `np.einsum` still returns `Any` in current stubs, so wrap
97
+ its result with `np.asarray(..., dtype=np.float64)` where the dtype matters.
98
+ - **Headless GL.** The CI test job runs without a display. pyvista/VTK tests must not
99
+ call `.show()` or `pantr.viz.plot()` (they force a render and segfault headless) —
100
+ build the scene with `to_plotter()` instead. The full suite runs under coverage with
101
+ `NUMBA_DISABLE_JIT=1`; a segfault there crashes the whole run.
102
+ - If a dependency the suite needs (e.g. `pyvista`, `mpi4py`) is missing locally, the
103
+ relevant tests silently skip — install it so they actually run before you rely on a
104
+ green local result.
105
+
106
+ ## Phase 5.5: Automated review (fresh Sonnet reviewers)
107
+
108
+ After checks pass and before pushing, run a fresh-context review of the branch diff.
109
+
110
+ **Carve-out — skip this phase entirely** if the diff is trivial: docs-only,
111
+ formatting-only, or comment-only changes (`git diff main...HEAD` touches no
112
+ executable logic). For these, proceed straight to Phase 6.
113
+
114
+ Otherwise, launch the pr-review-toolkit agents **in parallel** via the Task tool,
115
+ each with **`model: sonnet`** (the orchestrator stays on Opus; reviewers get clean,
116
+ uncontaminated context):
117
+
118
+ - `pr-review-toolkit:code-reviewer` — always
119
+ - `pr-review-toolkit:pr-test-analyzer` — if test files changed
120
+ - `pr-review-toolkit:comment-analyzer` — if comments/docstrings changed
121
+ - `pr-review-toolkit:silent-failure-hunter` — if error handling changed
122
+ - `pr-review-toolkit:type-design-analyzer` — if new types added
123
+
124
+ Tell each agent to review the branch diff against `main` (`git diff main...HEAD`).
125
+
126
+ Aggregate findings into **Critical / Important / Suggestion** buckets, then:
127
+
128
+ 1. Fix every **Critical** and **Important** finding. Commit the fixes
129
+ (`fix(<scope>): address review findings`).
130
+ 2. Re-run Phase 5 checks — they must pass.
131
+ 3. Re-run the review on the new diff. Repeat until no Critical/Important findings
132
+ remain, or two consecutive rounds surface nothing new (**hard cap: 3 rounds**).
133
+ 4. List any unaddressed **Suggestions** in the PR body under a "Review notes"
134
+ heading — do not silently drop them.
135
+
136
+ This phase is **unattended** — do not pause for approval. Proceed to Phase 6 when clean.
137
+
138
+ > Note: findings come from fresh Sonnet reviewers, but fixes are applied by this
139
+ > (Opus) orchestrator, which carries implementation context. The re-review in
140
+ > step 3 is what guards against the implementer rationalizing away a real finding.
141
+
142
+ ## Phase 6: PR and notify
143
+
144
+ Once all checks pass:
145
+
146
+ 1. Push the branch:
147
+ ```bash
148
+ git push -u origin HEAD
149
+ ```
150
+
151
+ 2. Create the PR using `gh pr create`:
152
+ - Title: concise, under 70 characters
153
+ - Body: summary of changes, test plan, and the generated-by footer
154
+ - Target the `main` branch
155
+
156
+ ```bash
157
+ gh pr create --title "<type>(<scope>): <summary>" --body "$(cat <<'EOF'
158
+ ## Summary
159
+ <bullet points describing the changes>
160
+
161
+ ## Test plan
162
+ - [ ] All existing tests pass
163
+ - [ ] New tests added for <feature>
164
+ - [ ] Pre-PR checks pass (ruff, mypy, pytest, docs)
165
+
166
+ Generated with [Claude Code](https://claude.com/claude-code)
167
+ EOF
168
+ )"
169
+ ```
170
+
171
+ 3. **Verify CI is green before declaring done.** Pushing is not the finish line —
172
+ local checks can pass while CI fails (see the local-vs-CI traps in Phase 5). After
173
+ creating the PR, watch the checks to completion:
174
+
175
+ ```bash
176
+ gh pr checks <pr-number> --watch # or poll `gh pr checks <pr-number>`
177
+ ```
178
+
179
+ If any job fails, read its log (`gh run view --job <id> --log`), fix the cause,
180
+ commit, and push again — then re-watch. Do not report the PR as ready until every
181
+ required check passes. Avoid pushing twice in quick succession: a second push can
182
+ cancel the first run's in-progress jobs (showing a spurious "failure"); wait for the
183
+ run to settle, or confirm a job was *canceled* (not a real failure) before reacting.
184
+
185
+ 4. **Notify the user**: Tell them the PR is ready *and CI is green*, provide the URL,
186
+ and give a brief summary of what was done.
187
+
188
+ ## Important notes
189
+
190
+ - If the user asks you to **plan only** (not implement), stop after Phase 2 and present the plan.
191
+ - If at any point you're unsure about a design decision, ask the user rather than guessing.
192
+ - Do not push or create a PR without running all checks first (every command, whole repo).
193
+ - Do not push or create a PR without running Phase 5.5 review first (unless the diff hit the trivial carve-out).
194
+ - The work is not done until **CI is green**, not merely when local checks pass — always confirm with `gh pr checks` before declaring the PR ready.
195
+ - Keep the user informed at natural milestones (plan ready, implementation done, tests passing, PR created, CI green).
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: pre-pr-checks
3
+ description: Run the full pre-PR validation suite (ruff lint, ruff format check, mypy type checking, pytest tests, and Sphinx documentation build). Use this skill whenever you need to verify code quality before pushing, creating a PR, or when the user asks to "run checks", "validate", "run CI locally", or "make sure everything passes". Also trigger when about to push code or create a pull request — always validate first.
4
+ ---
5
+
6
+ # Pre-PR Checks
7
+
8
+ Run all quality checks that CI would run, and report results. The goal is to catch issues before they reach the remote, saving CI time and avoiding back-and-forth.
9
+
10
+ ## Environment setup
11
+
12
+ Activate the conda environment first:
13
+
14
+ ```bash
15
+ conda activate pantr
16
+ ```
17
+
18
+ ## Checks to run
19
+
20
+ Run these checks **sequentially** in this order — later checks are pointless if earlier ones fail on syntax or type errors.
21
+
22
+ ### 1. Ruff lint
23
+
24
+ ```bash
25
+ ruff check .
26
+ ```
27
+
28
+ If there are auto-fixable issues, fix them with `ruff check --fix .` and re-run to confirm. Stage the fixes.
29
+
30
+ ### 2. Ruff format check
31
+
32
+ ```bash
33
+ ruff format --check .
34
+ ```
35
+
36
+ If formatting issues are found, run `ruff format .` to fix them and stage the changes.
37
+
38
+ ### 3. mypy type checking
39
+
40
+ ```bash
41
+ mypy --config-file mypy.ini src tests
42
+ ```
43
+
44
+ If there are type errors, fix them before proceeding. Type errors often indicate real bugs.
45
+
46
+ ### 4. Pytest (without coverage)
47
+
48
+ ```bash
49
+ pytest --no-cov -v
50
+ ```
51
+
52
+ Use `--no-cov` to keep things fast. All tests must pass.
53
+
54
+ ### 5. Documentation build
55
+
56
+ ```bash
57
+ NUMBA_DISABLE_JIT=1 sphinx-build -M html docs/ docs/_build -W --keep-going -j auto
58
+ ```
59
+
60
+ The `-W` flag treats warnings as errors (matching CI). `NUMBA_DISABLE_JIT=1` avoids Numba compilation during doc build.
61
+
62
+ ## Reporting
63
+
64
+ After running all checks, present a clear summary:
65
+
66
+ ```
67
+ Pre-PR Checks Summary
68
+ ---------------------
69
+ Ruff lint: PASS / FAIL (N issues)
70
+ Ruff format: PASS / FAIL (N files)
71
+ mypy: PASS / FAIL (N errors)
72
+ pytest: PASS / FAIL (N passed, M failed)
73
+ docs build: PASS / FAIL (N warnings)
74
+ ```
75
+
76
+ If any check failed and you were unable to fix it, explain what went wrong and suggest next steps. If all checks pass, say so clearly — the code is ready to push.
77
+
78
+ ## When auto-fixing
79
+
80
+ If you fix lint, format, or type issues during this process, commit those fixes in a dedicated commit:
81
+
82
+ ```
83
+ style: fix lint and formatting issues
84
+ ```
85
+
86
+ Keep these commits separate from feature work so the git history stays clean.
@@ -0,0 +1,318 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ lint:
15
+ name: Lint (Ruff)
16
+ runs-on: ubuntu-latest
17
+ strategy:
18
+ matrix:
19
+ python-version: ["3.11", "3.13", "3.14"]
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@v7
23
+
24
+ - name: Set up Python
25
+ id: setup-python
26
+ uses: actions/setup-python@v6
27
+ with:
28
+ python-version: ${{ matrix.python-version }}
29
+ cache: "pip"
30
+ cache-dependency-path: |
31
+ pyproject.toml
32
+
33
+ - name: Cache Ruff cache
34
+ uses: actions/cache@v5
35
+ with:
36
+ path: ~/.cache/ruff
37
+ key: ${{ runner.os }}-ruff-${{ hashFiles('ruff.toml', '.ruff.toml') }}
38
+ restore-keys: |
39
+ ${{ runner.os }}-ruff-
40
+
41
+ - name: Cache virtualenv
42
+ id: cache-venv
43
+ uses: actions/cache@v5
44
+ with:
45
+ path: .venv
46
+ key: ${{ runner.os }}-venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('pyproject.toml') }}
47
+ restore-keys: |
48
+ ${{ runner.os }}-venv-${{ steps.setup-python.outputs.python-version }}-
49
+
50
+ - name: Create venv and install dev deps (on cache miss)
51
+ if: steps.cache-venv.outputs.cache-hit != 'true'
52
+ run: |
53
+ python -m venv .venv
54
+ . .venv/bin/activate
55
+ python -m pip install --upgrade pip
56
+ pip install -e ".[dev]"
57
+
58
+ - name: Ruff lint
59
+ run: |
60
+ . .venv/bin/activate
61
+ make ruff-lint
62
+
63
+ - name: Ruff formatting check
64
+ run: |
65
+ . .venv/bin/activate
66
+ make ruff-format-check
67
+
68
+ - name: Import boundary check (import-linter)
69
+ run: |
70
+ . .venv/bin/activate
71
+ make import-lint
72
+
73
+ type-check:
74
+ name: Type check (mypy)
75
+ runs-on: ubuntu-latest
76
+ strategy:
77
+ matrix:
78
+ python-version: ["3.11", "3.13", "3.14"]
79
+ steps:
80
+ - name: Checkout
81
+ uses: actions/checkout@v7
82
+
83
+ - name: Set up Python
84
+ id: setup-python
85
+ uses: actions/setup-python@v6
86
+ with:
87
+ python-version: ${{ matrix.python-version }}
88
+ cache: "pip"
89
+ cache-dependency-path: |
90
+ pyproject.toml
91
+
92
+ - name: Cache mypy cache
93
+ uses: actions/cache@v5
94
+ with:
95
+ path: .mypy_cache
96
+ key: ${{ runner.os }}-mypy-${{ hashFiles('pyproject.toml', 'mypy.ini') }}
97
+ restore-keys: |
98
+ ${{ runner.os }}-mypy-
99
+
100
+ - name: Cache virtualenv
101
+ id: cache-venv
102
+ uses: actions/cache@v5
103
+ with:
104
+ path: .venv
105
+ key: ${{ runner.os }}-venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('pyproject.toml') }}
106
+ restore-keys: |
107
+ ${{ runner.os }}-venv-${{ steps.setup-python.outputs.python-version }}-
108
+
109
+ - name: Create venv and install dev deps (on cache miss)
110
+ if: steps.cache-venv.outputs.cache-hit != 'true'
111
+ run: |
112
+ python -m venv .venv
113
+ . .venv/bin/activate
114
+ python -m pip install --upgrade pip
115
+ pip install -e ".[dev]"
116
+
117
+ - name: Type check
118
+ run: |
119
+ . .venv/bin/activate
120
+ make type-check
121
+
122
+ tests:
123
+ name: Tests with coverage
124
+ needs: [lint, type-check]
125
+ runs-on: ubuntu-latest
126
+ strategy:
127
+ matrix:
128
+ python-version: ["3.11", "3.13", "3.14"]
129
+ steps:
130
+ - name: Checkout
131
+ uses: actions/checkout@v7
132
+
133
+ - name: Set up Python
134
+ id: setup-python
135
+ uses: actions/setup-python@v6
136
+ with:
137
+ python-version: ${{ matrix.python-version }}
138
+ cache: "pip"
139
+ cache-dependency-path: |
140
+ pyproject.toml
141
+
142
+ - name: Cache virtualenv
143
+ id: cache-venv
144
+ uses: actions/cache@v5
145
+ with:
146
+ path: .venv
147
+ key: ${{ runner.os }}-venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('pyproject.toml') }}
148
+ restore-keys: |
149
+ ${{ runner.os }}-venv-${{ steps.setup-python.outputs.python-version }}-
150
+
151
+ - name: Cache Numba compiled objects
152
+ uses: actions/cache@v5
153
+ with:
154
+ path: |
155
+ src/pantr/basis/__pycache__
156
+ src/pantr/bezier/__pycache__
157
+ src/pantr/bspline/__pycache__
158
+ key: ${{ runner.os }}-numba-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('src/**/*.py') }}
159
+ restore-keys: |
160
+ ${{ runner.os }}-numba-${{ steps.setup-python.outputs.python-version }}-
161
+
162
+ - name: Install OpenMPI
163
+ run: |
164
+ sudo apt-get update -q
165
+ sudo apt-get install -y libopenmpi-dev openmpi-bin
166
+
167
+ - name: Create venv and install dev deps (on cache miss)
168
+ if: steps.cache-venv.outputs.cache-hit != 'true'
169
+ run: |
170
+ python -m venv .venv
171
+ . .venv/bin/activate
172
+ python -m pip install --upgrade pip
173
+ pip install -e ".[dev]"
174
+
175
+ - name: Run tests with JIT (populates Numba cache)
176
+ run: |
177
+ . .venv/bin/activate
178
+ make test
179
+
180
+ - name: Run pytest with coverage
181
+ run: |
182
+ . .venv/bin/activate
183
+ make coverage
184
+
185
+ - name: Upload coverage xml
186
+ uses: actions/upload-artifact@v7
187
+ with:
188
+ name: coverage-xml-py${{ matrix.python-version }}-run${{ github.run_attempt }}
189
+ path: coverage.xml
190
+ if-no-files-found: error
191
+
192
+ docs:
193
+ name: Build documentation
194
+ needs: [lint, type-check]
195
+ runs-on: ubuntu-latest
196
+ steps:
197
+ - name: Checkout
198
+ uses: actions/checkout@v7
199
+
200
+ - name: Cache Sphinx doctrees
201
+ uses: actions/cache@v5
202
+ with:
203
+ path: docs/_build/doctrees
204
+ key: ${{ runner.os }}-sphinx-${{ hashFiles('docs/**/*.md', 'docs/conf.py', 'pyproject.toml', 'src/**/*.py') }}
205
+
206
+ - name: Cache intersphinx inventories
207
+ id: cache-intersphinx
208
+ uses: actions/cache@v5
209
+ with:
210
+ path: docs/_intersphinx
211
+ key: ${{ runner.os }}-intersphinx-${{ hashFiles('docs/conf.py') }}
212
+ restore-keys: |
213
+ ${{ runner.os }}-intersphinx-
214
+
215
+ - name: Set up Python
216
+ id: setup-python
217
+ uses: actions/setup-python@v6
218
+ with:
219
+ python-version: "3.11"
220
+ cache: "pip"
221
+ cache-dependency-path: |
222
+ pyproject.toml
223
+
224
+ - name: Cache virtualenv
225
+ id: cache-venv
226
+ uses: actions/cache@v5
227
+ with:
228
+ path: .venv
229
+ key: ${{ runner.os }}-venv-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('pyproject.toml', 'docs/**/*.md', 'docs/conf.py') }}
230
+ restore-keys: |
231
+ ${{ runner.os }}-venv-${{ steps.setup-python.outputs.python-version }}-
232
+
233
+ - name: Create venv and install docs deps (on cache miss)
234
+ if: steps.cache-venv.outputs.cache-hit != 'true'
235
+ run: |
236
+ python -m venv .venv
237
+ . .venv/bin/activate
238
+ python -m pip install --upgrade pip
239
+ pip install -e ".[docs]"
240
+
241
+ - name: Download intersphinx inventories (on cache miss)
242
+ if: steps.cache-intersphinx.outputs.cache-hit != 'true'
243
+ run: |
244
+ mkdir -p docs/_intersphinx
245
+ curl -fsSL --retry 3 --retry-delay 2 https://docs.python.org/3/objects.inv \
246
+ -o docs/_intersphinx/python.inv || rm -f docs/_intersphinx/python.inv
247
+ curl -fsSL --retry 3 --retry-delay 2 https://numpy.org/doc/stable/objects.inv \
248
+ -o docs/_intersphinx/numpy.inv || rm -f docs/_intersphinx/numpy.inv
249
+ curl -fsSL --retry 3 --retry-delay 2 https://docs.scipy.org/doc/scipy/objects.inv \
250
+ -o docs/_intersphinx/scipy.inv || rm -f docs/_intersphinx/scipy.inv
251
+ curl -fsSL --retry 3 --retry-delay 2 https://matplotlib.org/stable/objects.inv \
252
+ -o docs/_intersphinx/matplotlib.inv || rm -f docs/_intersphinx/matplotlib.inv
253
+
254
+ - name: Build docs
255
+ run: |
256
+ . .venv/bin/activate
257
+ make docs SPHINXOPTS="-W --keep-going -j auto"
258
+
259
+ mpi-tests:
260
+ name: MPI tests (mpiexec)
261
+ needs: [lint, type-check]
262
+ runs-on: ubuntu-latest
263
+ # The only job that exercises the real distributed stack under multiple ranks. It
264
+ # builds mpi4py from source against the system OpenMPI -- the prebuilt wheels are not
265
+ # guaranteed to match the system launcher's wire protocol. PANTR_RUN_MPI enables the
266
+ # otherwise-skipped tests under tests/mpi/.
267
+ env:
268
+ PANTR_RUN_MPI: "1"
269
+ PIP_NO_BINARY: "mpi4py"
270
+ steps:
271
+ - name: Checkout
272
+ uses: actions/checkout@v7
273
+
274
+ - name: Set up Python
275
+ id: setup-python
276
+ uses: actions/setup-python@v6
277
+ with:
278
+ python-version: "3.12"
279
+ cache: "pip"
280
+ cache-dependency-path: |
281
+ pyproject.toml
282
+
283
+ - name: Install OpenMPI
284
+ run: |
285
+ sudo apt-get update -q
286
+ sudo apt-get install -y libopenmpi-dev openmpi-bin
287
+
288
+ - name: Cache virtualenv
289
+ id: cache-venv
290
+ uses: actions/cache@v5
291
+ with:
292
+ path: .venv
293
+ key: ${{ runner.os }}-venv-${{ steps.setup-python.outputs.python-version }}-mpi-${{ hashFiles('pyproject.toml') }}
294
+ restore-keys: |
295
+ ${{ runner.os }}-venv-${{ steps.setup-python.outputs.python-version }}-mpi-
296
+
297
+ - name: Create venv and install (with mpi4py built from source, on cache miss)
298
+ if: steps.cache-venv.outputs.cache-hit != 'true'
299
+ run: |
300
+ python -m venv .venv
301
+ . .venv/bin/activate
302
+ python -m pip install --upgrade pip
303
+ pip install -e ".[dev]"
304
+
305
+ - name: Verify mpi4py matches the launcher
306
+ run: |
307
+ . .venv/bin/activate
308
+ mpiexec --oversubscribe -n 2 python -c "from mpi4py import MPI; assert MPI.COMM_WORLD.size == 2"
309
+
310
+ - name: Run MPI smoke tests (2 ranks)
311
+ run: |
312
+ . .venv/bin/activate
313
+ mpiexec --oversubscribe -n 2 python -m pytest tests/mpi/ --no-cov -p no:cacheprovider
314
+
315
+ - name: Run MPI smoke tests (3 ranks)
316
+ run: |
317
+ . .venv/bin/activate
318
+ mpiexec --oversubscribe -n 3 python -m pytest tests/mpi/ --no-cov -p no:cacheprovider