pysurgery 2.2.2__tar.gz → 2.2.3__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 (223) hide show
  1. {pysurgery-2.2.2/pysurgery.egg-info → pysurgery-2.2.3}/PKG-INFO +1 -1
  2. {pysurgery-2.2.2 → pysurgery-2.2.3}/pyproject.toml +2 -2
  3. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/__init__.py +1 -1
  4. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/point_cloud.py +84 -0
  5. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/homology_generators.py +14 -13
  6. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/complexes.py +22 -1
  7. {pysurgery-2.2.2 → pysurgery-2.2.3/pysurgery.egg-info}/PKG-INFO +1 -1
  8. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_point_cloud.py +27 -0
  9. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_point_cloud_mappings.py +35 -0
  10. {pysurgery-2.2.2 → pysurgery-2.2.3}/LICENSE +0 -0
  11. {pysurgery-2.2.2 → pysurgery-2.2.3}/README.md +0 -0
  12. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/__init__.py +0 -0
  13. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/adams_data/sphere_p2.json +0 -0
  14. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/adams_data/sphere_p3.json +0 -0
  15. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/adams_data/sphere_p5.json +0 -0
  16. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/differentials.py +0 -0
  17. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/e_infinity_exhaustive.py +0 -0
  18. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/e_infinity_resolver.py +0 -0
  19. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/extension_solver.py +0 -0
  20. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/interactive_resolver.py +0 -0
  21. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/lambda_algebra.py +0 -0
  22. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/lean_resolver.py +0 -0
  23. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/odd_prime_cobar.py +0 -0
  24. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/spectral_sequence.py +0 -0
  25. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/u_resolution.py +0 -0
  26. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/unstable.py +0 -0
  27. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/__init__.py +0 -0
  28. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/exact_algebra.py +0 -0
  29. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/exact_sequences.py +0 -0
  30. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/exact_snf_julia.py +0 -0
  31. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/group_rings.py +0 -0
  32. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/intersection_forms.py +0 -0
  33. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/k_theory.py +0 -0
  34. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/math_core.py +0 -0
  35. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/quadratic_forms.py +0 -0
  36. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/auto_surgery.py +0 -0
  37. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/bridge/__init__.py +0 -0
  38. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/bridge/julia_bridge.py +0 -0
  39. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/bridge/juliapkg.json +0 -0
  40. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/bridge/surgery_backend.jl +0 -0
  41. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/core/__init__.py +0 -0
  42. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/core/exceptions.py +0 -0
  43. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/core/foundations.py +0 -0
  44. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/core/generator_models.py +0 -0
  45. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/core/homology_generators.py +0 -0
  46. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/core/theorem_tags.py +0 -0
  47. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/coverage_matrix.yaml +0 -0
  48. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/__init__.py +0 -0
  49. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/bundles.py +0 -0
  50. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/characteristic_classes.py +0 -0
  51. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/embedding.py +0 -0
  52. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/gauss_bonnet.py +0 -0
  53. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/geometrization_3d.py +0 -0
  54. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/immersion_obstructions.py +0 -0
  55. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/intrinsic_dimension.py +0 -0
  56. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/metrics.py +0 -0
  57. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/uniformization.py +0 -0
  58. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homeomorphism.py +0 -0
  59. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homeomorphism_witness.py +0 -0
  60. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/__init__.py +0 -0
  61. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/algebraic_poincare.py +0 -0
  62. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/controlled_cohomology.py +0 -0
  63. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/cup_product.py +0 -0
  64. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/ext_cocycles.py +0 -0
  65. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/ext_yoneda_product.py +0 -0
  66. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/poincare_duality_verification.py +0 -0
  67. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/topological_sequences.py +0 -0
  68. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/__init__.py +0 -0
  69. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/higher_homotopy_groups.py +0 -0
  70. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/homotopy_verifier.py +0 -0
  71. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/known_homotopy_tables.py +0 -0
  72. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/multi_prime_synthesis.py +0 -0
  73. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/rational_homotopy.py +0 -0
  74. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/sullivan_models.py +0 -0
  75. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/integrations/__init__.py +0 -0
  76. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/integrations/jax_bridge.py +0 -0
  77. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/integrations/lean_export.py +0 -0
  78. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/integrations/pytorch_geometric_bridge.py +0 -0
  79. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/integrations/trimesh_bridge.py +0 -0
  80. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/knots/__init__.py +0 -0
  81. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/knots/analysis.py +0 -0
  82. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/knots/constructors.py +0 -0
  83. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/knots/invariants.py +0 -0
  84. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/knots/linking.py +0 -0
  85. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/manifolds/__init__.py +0 -0
  86. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/manifolds/handle_decompositions.py +0 -0
  87. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/manifolds/handle_surgery.py +0 -0
  88. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/manifolds/kirby_calculus.py +0 -0
  89. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/manifolds/rational_surgery.py +0 -0
  90. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/manifolds/surgery.py +0 -0
  91. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/spectral/__init__.py +0 -0
  92. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/spectral/spectral_sequences.py +0 -0
  93. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/structure_set.py +0 -0
  94. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/surgery.py +0 -0
  95. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/__init__.py +0 -0
  96. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/filtration_tools.py +0 -0
  97. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/filtration_values.py +0 -0
  98. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/fundamental_group.py +0 -0
  99. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/fundamental_polyhedron.py +0 -0
  100. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/persistent_homology.py +0 -0
  101. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/pi1_group_ring_scaffold.py +0 -0
  102. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/temporal_topology.py +0 -0
  103. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/utils/__init__.py +0 -0
  104. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/utils/signature.py +0 -0
  105. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/utils/tutorial_coverage_validator.py +0 -0
  106. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/wall_groups.py +0 -0
  107. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery.egg-info/SOURCES.txt +0 -0
  108. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery.egg-info/dependency_links.txt +0 -0
  109. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery.egg-info/requires.txt +0 -0
  110. {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery.egg-info/top_level.txt +0 -0
  111. {pysurgery-2.2.2 → pysurgery-2.2.3}/setup.cfg +0 -0
  112. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_3_manifolds.py +0 -0
  113. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_4_manifolds.py +0 -0
  114. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_adams_bound_tightening.py +0 -0
  115. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_adams_differentials.py +0 -0
  116. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_adams_e2_page.py +0 -0
  117. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_adams_extension_solver.py +0 -0
  118. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_adams_u_resolution.py +0 -0
  119. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_advanced_surfaces.py +0 -0
  120. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_algebraic_poincare.py +0 -0
  121. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_algebraic_surgery.py +0 -0
  122. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_alpha_complex.py +0 -0
  123. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_auto_surgery_contractible.py +0 -0
  124. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_auto_surgery_detection.py +0 -0
  125. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_auto_surgery_kill.py +0 -0
  126. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_auto_surgery_obstruction.py +0 -0
  127. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_auto_surgery_separate.py +0 -0
  128. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_auto_surgery_unlink.py +0 -0
  129. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_backend_consistency.py +0 -0
  130. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_backend_consistency_comprehensive.py +0 -0
  131. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_basis_change.py +0 -0
  132. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_canonical_spaces_and_homotopy.py +0 -0
  133. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_characteristic_classes.py +0 -0
  134. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_cobordism_complex.py +0 -0
  135. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_complexes.py +0 -0
  136. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_concatenate_glue_reuse.py +0 -0
  137. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_connected_component_optimization.py +0 -0
  138. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_controlled_cohomology.py +0 -0
  139. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_cup_product.py +0 -0
  140. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_decision_key_shared.py +0 -0
  141. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_dynamic_complexes.py +0 -0
  142. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_e_infinity_resolution.py +0 -0
  143. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_embedding.py +0 -0
  144. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_exact_algebra.py +0 -0
  145. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_exact_algebra_sequences.py +0 -0
  146. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_exact_intrinsic_dimension.py +0 -0
  147. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_exact_metadata_consistency.py +0 -0
  148. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_exact_snf_julia.py +0 -0
  149. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_ext_cocycles.py +0 -0
  150. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_ext_yoneda_product.py +0 -0
  151. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_filtration_persistence_fast.py +0 -0
  152. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_filtration_tools.py +0 -0
  153. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_formality_detection.py +0 -0
  154. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_foundations.py +0 -0
  155. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_fundamental_group.py +0 -0
  156. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_fundamental_polyhedron.py +0 -0
  157. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_gauss_bonnet.py +0 -0
  158. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_geometrization_3d.py +0 -0
  159. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_global_backend_consistency.py +0 -0
  160. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_group_rings.py +0 -0
  161. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_handle_decompositions.py +0 -0
  162. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_handle_decompositions_morse.py +0 -0
  163. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_handle_surgery.py +0 -0
  164. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_homeomorphism.py +0 -0
  165. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_homeomorphism_witness.py +0 -0
  166. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_homotopy_oop.py +0 -0
  167. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_homotopy_verifier.py +0 -0
  168. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_immersion_integration.py +0 -0
  169. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_immersion_obstructions.py +0 -0
  170. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_integration_scope.py +0 -0
  171. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_interactive_resolver.py +0 -0
  172. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_intersection_forms.py +0 -0
  173. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_intrinsic_dimension.py +0 -0
  174. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_invariants_cross_validation.py +0 -0
  175. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_isotopy_hierarchy.py +0 -0
  176. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_jax_bridge.py +0 -0
  177. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_jax_bridge_extended.py +0 -0
  178. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_julia_bridge.py +0 -0
  179. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_k_theory.py +0 -0
  180. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_kirby_calculus_advanced.py +0 -0
  181. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_knots.py +0 -0
  182. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_known_homotopy_tables.py +0 -0
  183. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_lean_export.py +0 -0
  184. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_lean_formal_resolver.py +0 -0
  185. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_math_core.py +0 -0
  186. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_module_c_integration.py +0 -0
  187. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_modules_a_b_integration.py +0 -0
  188. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_multi_prime_synthesis.py +0 -0
  189. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_odd_prime_adams.py +0 -0
  190. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_path_intersection.py +0 -0
  191. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_persistence_backend_dispatch.py +0 -0
  192. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_persistence_wrapper.py +0 -0
  193. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_pi1_and_homology_generators.py +0 -0
  194. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_pi1_group_ring_scaffold.py +0 -0
  195. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_poincare_duality_verification.py +0 -0
  196. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_property_backend.py +0 -0
  197. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_property_complex_construction.py +0 -0
  198. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_property_surgery.py +0 -0
  199. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_property_topology.py +0 -0
  200. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_public_api_v2.py +0 -0
  201. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_quadratic_forms.py +0 -0
  202. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_quick_mapper.py +0 -0
  203. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_rational_dga.py +0 -0
  204. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_rational_homotopy_group_schema.py +0 -0
  205. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_rational_surgery.py +0 -0
  206. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_slides_cancellation.py +0 -0
  207. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_spectral_sequences.py +0 -0
  208. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_structure_set.py +0 -0
  209. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_sullivan_models.py +0 -0
  210. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_sullivan_phase2_integration.py +0 -0
  211. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_surgery.py +0 -0
  212. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_surgery_atomicity.py +0 -0
  213. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_surgery_session.py +0 -0
  214. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_temporal_topology.py +0 -0
  215. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_theorem_tags.py +0 -0
  216. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_topological_loss.py +0 -0
  217. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_topological_sequences.py +0 -0
  218. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_triangulation.py +0 -0
  219. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_tutorial_coverage_matrix.py +0 -0
  220. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_uniformization.py +0 -0
  221. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_wall_groups.py +0 -0
  222. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_witness_scaling.py +0 -0
  223. {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_zigzag_persistence.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pysurgery
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Summary: A high-performance Python library for Computational Surgery Theory.
5
5
  Author-email: Gabriel Ribeiro <gabriel.ribeiro@dcc.ufmg.br>
6
6
  Requires-Python: >=3.11
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pysurgery"
7
- version = "2.2.2"
7
+ version = "2.2.3"
8
8
  description = "A high-performance Python library for Computational Surgery Theory."
9
9
  dependencies = [
10
10
  "numpy>=2.4.4",
@@ -83,7 +83,7 @@ python_files = ["test_*.py"]
83
83
  "*" = ["*.jl", "*.yaml", "*.json"]
84
84
 
85
85
  [tool.bumpversion]
86
- current_version = "2.2.2"
86
+ current_version = "2.2.3"
87
87
  commit = true
88
88
  tag = true
89
89
 
@@ -316,7 +316,7 @@ from .auto_surgery import (
316
316
 
317
317
  from . import integrations
318
318
 
319
- __version__ = "2.2.2"
319
+ __version__ = "2.2.3"
320
320
 
321
321
  def __getattr__(name):
322
322
  if name == "JuliaBridge":
@@ -635,6 +635,90 @@ class PointCloud:
635
635
  self._update_parent(new_points)
636
636
  return PointCloud(new_points, parent=self._parent)
637
637
 
638
+ def unbend(
639
+ self,
640
+ curvature: float,
641
+ axis: int,
642
+ control_axis: int,
643
+ anchor: Optional[Any] = None
644
+ ) -> "PointCloud":
645
+ """Reverses a bending transformation along a specified axis.
646
+
647
+ Mathematical Formulation:
648
+ Let c be the curvature (c = 1/R, where R is the bending radius).
649
+ For bend axis d_1 and control axis d_2, and anchor point 'a':
650
+ For each point P_i, let:
651
+ X = P_{i, d_1} - a_{d_1}
652
+ Y = P_{i, d_2} - a_{d_2}
653
+
654
+ If curvature is non-zero (or very small):
655
+ r = 1/c
656
+ theta = atan2(X * sign(c), (r - Y) * sign(c))
657
+ d = sqrt(X^2 + (r - Y)^2)
658
+ x = theta / c
659
+ y = r - sign(c) * d
660
+
661
+ P'_{i, d_1} = a_{d_1} + x
662
+ P'_{i, d_2} = a_{d_2} + y
663
+ If curvature is zero (or near zero), we use first-order Taylor expansion:
664
+ P'_{i, d_1} = a_{d_1} + X + c * X * Y
665
+ P'_{i, d_2} = a_{d_2} + Y - 0.5 * c * X^2
666
+
667
+ For all other coordinate axes j not in {d_1, d_2}:
668
+ P'_{i, j} = P_{i, j}
669
+
670
+ Args:
671
+ curvature: Bending curvature to undo.
672
+ axis: The primary axis along which the unbent points will be aligned.
673
+ control_axis: The axis perpendicular to the bend axis in which bending occurred.
674
+ anchor: The anchor point marking the origin of the bend. Can be:
675
+ - None / "center": Defaults to the center of mass.
676
+ - "min" / "max": Extreme point along the bend axis.
677
+ - np.ndarray: Custom coordinates array.
678
+
679
+ Returns:
680
+ A new PointCloud instance with unbent points.
681
+ """
682
+ if axis < 0 or axis >= self.dimension or control_axis < 0 or control_axis >= self.dimension:
683
+ raise ValueError("Axis indices are out of bounds.")
684
+ if axis == control_axis:
685
+ raise ValueError("Bend axis and control axis must be distinct.")
686
+
687
+ # Determine anchor point coordinates
688
+ if anchor is None or (isinstance(anchor, str) and anchor == "center"):
689
+ anchor_pt = self.center_of_mass
690
+ elif isinstance(anchor, str) and anchor in ("min", "max"):
691
+ anchor_pt = self.get_extreme_points(axis=axis, extreme=anchor)
692
+ elif isinstance(anchor, np.ndarray):
693
+ if anchor.shape != (self.dimension,):
694
+ raise ValueError("Anchor shape must match dimension.")
695
+ anchor_pt = anchor
696
+ else:
697
+ raise ValueError(f"Invalid anchor: {anchor}")
698
+
699
+ X = self.points[:, axis] - anchor_pt[axis]
700
+ Y = self.points[:, control_axis] - anchor_pt[control_axis]
701
+
702
+ new_points = self.points.copy()
703
+
704
+ # Handle very small curvature using first-order approximation
705
+ if np.abs(curvature) < 1e-8:
706
+ new_points[:, axis] = anchor_pt[axis] + X + curvature * X * Y
707
+ new_points[:, control_axis] = anchor_pt[control_axis] + Y - 0.5 * curvature * (X**2)
708
+ else:
709
+ r = 1.0 / curvature
710
+ sign_c = np.sign(curvature)
711
+ theta = np.arctan2(X * sign_c, (r - Y) * sign_c)
712
+ d = np.sqrt(X**2 + (r - Y)**2)
713
+ x = theta / curvature
714
+ y = r - sign_c * d
715
+
716
+ new_points[:, axis] = anchor_pt[axis] + x
717
+ new_points[:, control_axis] = anchor_pt[control_axis] + y
718
+
719
+ self._update_parent(new_points)
720
+ return PointCloud(new_points, parent=self._parent)
721
+
638
722
  def taper(
639
723
  self,
640
724
  factor: float,
@@ -35,6 +35,7 @@ from typing import TYPE_CHECKING, Dict, Iterable, List, Literal, Optional, Tuple
35
35
 
36
36
  if TYPE_CHECKING:
37
37
  from pysurgery.topology.complexes import SimplicialComplex, ChainComplex
38
+ from pysurgery.geometry.point_cloud import PointCloud
38
39
 
39
40
  import numpy as np
40
41
  import scipy.sparse as sp
@@ -273,7 +274,7 @@ def _rank_mod2(A: np.ndarray) -> int:
273
274
  def _components_h0_generators(
274
275
  edges: list[Edge],
275
276
  num_vertices: int,
276
- point_cloud: Optional[np.ndarray] = None,
277
+ point_cloud: Optional[Union[np.ndarray, "PointCloud"]] = None,
277
278
  ) -> HomologyBasisResult:
278
279
  """Compute H0 generators as one representative per connected component.
279
280
 
@@ -357,7 +358,7 @@ def _components_h0_generators(
357
358
  def _weight_k_chain(
358
359
  chain: np.ndarray,
359
360
  k_simplices: list[tuple[int, ...]],
360
- point_cloud: Optional[np.ndarray],
361
+ point_cloud: Optional[Union[np.ndarray, "PointCloud"]],
361
362
  ) -> float:
362
363
  """Compute a geometric/algebraic proxy weight for an active k-chain.
363
364
 
@@ -411,7 +412,7 @@ def _hk_generators_mod2(
411
412
  simplices: Iterable[Tuple[int, ...]],
412
413
  num_vertices: int,
413
414
  dimension: int,
414
- point_cloud: Optional[np.ndarray],
415
+ point_cloud: Optional[Union[np.ndarray, "PointCloud"]],
415
416
  mode: Literal["valid", "optimal"],
416
417
  ) -> HomologyBasisResult:
417
418
  """Compute H_k representatives over Z/2 via kernel/image quotient.
@@ -520,7 +521,7 @@ def _hk_generators_mod2(
520
521
  )
521
522
 
522
523
 
523
- def _edge_weight(u: int, v: int, points: Optional[np.ndarray]) -> float:
524
+ def _edge_weight(u: int, v: int, points: Optional[Union[np.ndarray, "PointCloud"]]) -> float:
524
525
  """Return edge length weight (or unit weight when no geometry is provided).
525
526
 
526
527
  Args:
@@ -766,7 +767,7 @@ def _path_edges(path_vertices: List[int]) -> List[Edge]:
766
767
 
767
768
 
768
769
  def _cycle_weight(
769
- cycle: Cycle, edge_weights: Dict[Edge, float], point_cloud: Optional[np.ndarray]
770
+ cycle: Cycle, edge_weights: Dict[Edge, float], point_cloud: Optional[Union[np.ndarray, "PointCloud"]]
770
771
  ) -> float:
771
772
  """Compute cycle weight using supplied edge weights or geometric fallback.
772
773
 
@@ -791,7 +792,7 @@ def _cycle_weight(
791
792
  def generator_cycles_from_simplices(
792
793
  simplices: Iterable[Tuple[int, ...]],
793
794
  num_vertices: int,
794
- point_cloud: Optional[np.ndarray] = None,
795
+ point_cloud: Optional[Union[np.ndarray, "PointCloud"]] = None,
795
796
  *,
796
797
  max_roots: Optional[int] = None,
797
798
  root_stride: int = 1,
@@ -844,7 +845,7 @@ def _generator_cycles_from_normalized_edges(
844
845
  edges: list[Edge],
845
846
  vertex_ids: set[int],
846
847
  num_vertices: int,
847
- point_cloud: Optional[np.ndarray] = None,
848
+ point_cloud: Optional[Union[np.ndarray, "PointCloud"]] = None,
848
849
  *,
849
850
  max_roots: Optional[int] = None,
850
851
  root_stride: int = 1,
@@ -998,7 +999,7 @@ def greedy_h1_basis(
998
999
  cycles: List[Cycle],
999
1000
  simplices: Iterable[Tuple[int, ...]],
1000
1001
  num_vertices: int,
1001
- point_cloud: Optional[np.ndarray] = None,
1002
+ point_cloud: Optional[Union[np.ndarray, "PointCloud"]] = None,
1002
1003
  backend: str = "auto",
1003
1004
  ) -> List[Cycle]:
1004
1005
  """Select a greedy independent H1 basis from cycle candidates.
@@ -1052,7 +1053,7 @@ def _greedy_h1_basis_from_normalized(
1052
1053
  triangles: list[Triangle],
1053
1054
  vertex_ids: set[int],
1054
1055
  num_vertices: int,
1055
- point_cloud: Optional[np.ndarray] = None,
1056
+ point_cloud: Optional[Union[np.ndarray, "PointCloud"]] = None,
1056
1057
  ) -> List[Cycle]:
1057
1058
  """Internal greedy H1 basis selection on normalized complexes.
1058
1059
 
@@ -1112,7 +1113,7 @@ def _greedy_h1_basis_from_normalized(
1112
1113
  def compute_optimal_h1_basis_from_simplices(
1113
1114
  simplices: Iterable[Tuple[int, ...]],
1114
1115
  num_vertices: int,
1115
- point_cloud: Optional[np.ndarray] = None,
1116
+ point_cloud: Optional[Union[np.ndarray, "PointCloud"]] = None,
1116
1117
  *,
1117
1118
  max_roots: Optional[int] = None,
1118
1119
  root_stride: int = 1,
@@ -1237,7 +1238,7 @@ def compute_homology_basis_from_simplices(
1237
1238
  simplices: Iterable[Tuple[int, ...]],
1238
1239
  num_vertices: int,
1239
1240
  dimension: int,
1240
- point_cloud: Optional[np.ndarray] = None,
1241
+ point_cloud: Optional[Union[np.ndarray, "PointCloud"]] = None,
1241
1242
  *,
1242
1243
  mode: Literal["valid", "optimal"] = "valid",
1243
1244
  max_roots: Optional[int] = None,
@@ -1369,7 +1370,7 @@ def compute_homology_basis_from_simplices(
1369
1370
  def compute_homology_basis_from_complex(
1370
1371
  complex: "SimplicialComplex",
1371
1372
  dimension: int,
1372
- point_cloud: Optional[np.ndarray] = None,
1373
+ point_cloud: Optional[Union[np.ndarray, "PointCloud"]] = None,
1373
1374
  *,
1374
1375
  mode: Literal["valid", "optimal"] = "valid",
1375
1376
  max_roots: Optional[int] = None,
@@ -1439,7 +1440,7 @@ def compute_homology_basis_from_complex(
1439
1440
 
1440
1441
  def compute_optimal_h1_basis_from_complex(
1441
1442
  complex: "SimplicialComplex",
1442
- point_cloud: Optional[np.ndarray] = None,
1443
+ point_cloud: Optional[Union[np.ndarray, "PointCloud"]] = None,
1443
1444
  *,
1444
1445
  max_roots: Optional[int] = None,
1445
1446
  root_stride: int = 1,
@@ -2478,6 +2478,7 @@ class SimplicialComplex(ChainComplex):
2478
2478
  """
2479
2479
  from ..bridge.julia_bridge import julia_engine
2480
2480
 
2481
+ original_pc = points
2481
2482
  points = np.asarray(points, dtype=np.float64)
2482
2483
  n_pts = points.shape[0]
2483
2484
 
@@ -2489,6 +2490,7 @@ class SimplicialComplex(ChainComplex):
2489
2490
  )
2490
2491
  sc._coordinates = points
2491
2492
  sc._generate_point_cloud_mappings(points)
2493
+ sc._link_point_cloud(original_pc)
2492
2494
  return sc
2493
2495
  except Exception as e:
2494
2496
  warnings.warn(f"Julia Vietoris-Rips failed: {e!r}. Falling back to Python.")
@@ -2509,6 +2511,7 @@ class SimplicialComplex(ChainComplex):
2509
2511
  sc._coordinates = points
2510
2512
 
2511
2513
  sc._generate_point_cloud_mappings(points)
2514
+ sc._link_point_cloud(original_pc)
2512
2515
  return sc
2513
2516
 
2514
2517
  @classmethod
@@ -2692,6 +2695,7 @@ class SimplicialComplex(ChainComplex):
2692
2695
  sc = cls.from_simplices([], coefficient_ring=coefficient_ring)
2693
2696
  sc._coordinates = pts
2694
2697
  sc._generate_point_cloud_mappings(pts)
2698
+ sc._link_point_cloud(points)
2695
2699
  return sc
2696
2700
  if k >= n:
2697
2701
  k = n - 1
@@ -2699,6 +2703,7 @@ class SimplicialComplex(ChainComplex):
2699
2703
  sc = cls.from_simplices([(i,) for i in range(n)], coefficient_ring=coefficient_ring)
2700
2704
  sc._coordinates = pts
2701
2705
  sc._generate_point_cloud_mappings(pts)
2706
+ sc._link_point_cloud(points)
2702
2707
  return sc
2703
2708
 
2704
2709
  simplices = [(i,) for i in range(n)]
@@ -2744,6 +2749,7 @@ class SimplicialComplex(ChainComplex):
2744
2749
  sc = sc.expand(max_dimension)
2745
2750
  sc._coordinates = pts
2746
2751
  sc._generate_point_cloud_mappings(pts)
2752
+ sc._link_point_cloud(points)
2747
2753
  return sc
2748
2754
  @classmethod
2749
2755
  def from_alpha_complex(
@@ -2780,6 +2786,7 @@ class SimplicialComplex(ChainComplex):
2780
2786
  sc = cls.from_simplices([[i] for i in range(n_pts)], coefficient_ring=coefficient_ring)
2781
2787
  sc._coordinates = pts
2782
2788
  sc._generate_point_cloud_mappings(pts)
2789
+ sc._link_point_cloud(points)
2783
2790
  return sc
2784
2791
 
2785
2792
  dt = Delaunay(pts, qhull_options="QJ")
@@ -2835,6 +2842,7 @@ class SimplicialComplex(ChainComplex):
2835
2842
  sc = cls.from_simplices(valid_simplices_list, coefficient_ring=coefficient_ring, close_under_faces=True)
2836
2843
  sc._coordinates = pts
2837
2844
  sc._generate_point_cloud_mappings(pts)
2845
+ sc._link_point_cloud(points)
2838
2846
  return sc
2839
2847
  except Exception as e:
2840
2848
  if backend_norm == "julia":
@@ -2852,6 +2860,7 @@ class SimplicialComplex(ChainComplex):
2852
2860
  sc = cls.from_simplices(valid_simplices_final, coefficient_ring=coefficient_ring, close_under_faces=True)
2853
2861
  sc._coordinates = pts
2854
2862
  sc._generate_point_cloud_mappings(pts)
2863
+ sc._link_point_cloud(points)
2855
2864
  return sc
2856
2865
 
2857
2866
  @classmethod
@@ -2884,6 +2893,7 @@ class SimplicialComplex(ChainComplex):
2884
2893
  sc = cls.from_simplices([[i] for i in range(n_pts)], coefficient_ring=coefficient_ring)
2885
2894
  sc._coordinates = pts
2886
2895
  sc._generate_point_cloud_mappings(pts)
2896
+ sc._link_point_cloud(points)
2887
2897
  return sc
2888
2898
 
2889
2899
  # 1. Compute Voronoi diagram to get poles (Voronoi vertices)
@@ -2912,6 +2922,7 @@ class SimplicialComplex(ChainComplex):
2912
2922
  sc = cls.from_simplices(valid_simplices, coefficient_ring=coefficient_ring, close_under_faces=True)
2913
2923
  sc._coordinates = pts
2914
2924
  sc._generate_point_cloud_mappings(pts)
2925
+ sc._link_point_cloud(points)
2915
2926
  return sc
2916
2927
 
2917
2928
  @classmethod
@@ -2941,6 +2952,7 @@ class SimplicialComplex(ChainComplex):
2941
2952
  sc = cls.from_simplices([], coefficient_ring=coefficient_ring)
2942
2953
  sc._coordinates = pts
2943
2954
  sc.filtration = {}
2955
+ sc._link_point_cloud(points)
2944
2956
  return sc
2945
2957
  dim = pts.shape[1]
2946
2958
  if n < dim + 1:
@@ -2949,6 +2961,7 @@ class SimplicialComplex(ChainComplex):
2949
2961
  sc._coordinates = pts
2950
2962
  sc.filtration = {(i,): 0.0 for i in range(n)}
2951
2963
  sc._generate_point_cloud_mappings(pts)
2964
+ sc._link_point_cloud(points)
2952
2965
  return sc
2953
2966
 
2954
2967
  from scipy.spatial import Delaunay
@@ -2977,6 +2990,7 @@ class SimplicialComplex(ChainComplex):
2977
2990
 
2978
2991
  sc.filtration = vals
2979
2992
  sc._generate_point_cloud_mappings(pts)
2993
+ sc._link_point_cloud(points)
2980
2994
  return sc
2981
2995
 
2982
2996
  @classmethod
@@ -3154,6 +3168,7 @@ class SimplicialComplex(ChainComplex):
3154
3168
  pts = np.asarray(pc, dtype=np.float64)
3155
3169
  self._coordinates = pts
3156
3170
  self._generate_point_cloud_mappings(pts)
3171
+ self._link_point_cloud(pc)
3157
3172
 
3158
3173
  @property
3159
3174
  def point_cloud_to_simplices(self) -> Dict[int, List[Tuple[int, ...]]]:
@@ -3181,6 +3196,12 @@ class SimplicialComplex(ChainComplex):
3181
3196
  if vertex in self._point_cloud_to_simplices:
3182
3197
  self._point_cloud_to_simplices[vertex].append(simplex)
3183
3198
 
3199
+ def _link_point_cloud(self, points: Any) -> None:
3200
+ """Link a PointCloud back to this complex as its parent."""
3201
+ from ..geometry.point_cloud import PointCloud
3202
+ if isinstance(points, PointCloud):
3203
+ points._parent = self
3204
+
3184
3205
  @classmethod
3185
3206
  def concatenate(cls, complexes: Iterable["SimplicialComplex"]) -> "SimplicialComplex":
3186
3207
  """Concatenate multiple simplicial complexes (simplex trees) into a single larger complex.
@@ -5640,7 +5661,7 @@ class SimplicialComplex(ChainComplex):
5640
5661
 
5641
5662
  def compute_optimal_h1_basis(
5642
5663
  self,
5643
- point_cloud: Optional[np.ndarray] = None,
5664
+ point_cloud: Optional[Union[np.ndarray, "PointCloud"]] = None,
5644
5665
  *,
5645
5666
  max_roots: Optional[int] = None,
5646
5667
  root_stride: int = 1,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pysurgery
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Summary: A high-performance Python library for Computational Surgery Theory.
5
5
  Author-email: Gabriel Ribeiro <gabriel.ribeiro@dcc.ufmg.br>
6
6
  Requires-Python: >=3.11
@@ -296,6 +296,33 @@ def test_bend():
296
296
  pc_flat = pc.bend(curvature=0.0, axis=0, control_axis=1, anchor="min")
297
297
  np.testing.assert_allclose(pc_flat.points, pts)
298
298
 
299
+ def test_unbend():
300
+ # 1. Test unbending a bent point cloud
301
+ pts = np.array([
302
+ [0.0, 0.0],
303
+ [np.pi / 2, 0.5],
304
+ [np.pi, -0.2]
305
+ ])
306
+ pc = PointCloud(pts)
307
+
308
+ # Bend with curvature 0.5
309
+ pc_bent = pc.bend(curvature=0.5, axis=0, control_axis=1, anchor="min")
310
+
311
+ # Unbend with curvature 0.5
312
+ pc_unbent = pc_bent.unbend(curvature=0.5, axis=0, control_axis=1, anchor="min")
313
+ np.testing.assert_allclose(pc_unbent.points, pts, atol=1e-7)
314
+
315
+ # 2. Test negative curvature
316
+ pc_bent_neg = pc.bend(curvature=-0.5, axis=0, control_axis=1, anchor="min")
317
+ pc_unbent_neg = pc_bent_neg.unbend(curvature=-0.5, axis=0, control_axis=1, anchor="min")
318
+ np.testing.assert_allclose(pc_unbent_neg.points, pts, atol=1e-7)
319
+
320
+ # 3. Test near-zero curvature (Taylor series)
321
+ pc_bent_zero = pc.bend(curvature=1e-9, axis=0, control_axis=1, anchor="min")
322
+ pc_unbent_zero = pc_bent_zero.unbend(curvature=1e-9, axis=0, control_axis=1, anchor="min")
323
+ np.testing.assert_allclose(pc_unbent_zero.points, pts, atol=1e-7)
324
+
325
+
299
326
  def test_taper():
300
327
  pts_offset = np.array([
301
328
  [2.0, 3.0, 0.0],
@@ -120,3 +120,38 @@ def test_simplicial_complex_concatenation():
120
120
  assert sc_concat.filtration[(0, 1)] == 0.5
121
121
  assert sc_concat.filtration[(3,)] == 0.2
122
122
  assert sc_concat.filtration[(3, 4)] == 0.6
123
+
124
+ def test_point_cloud_parent_linkage():
125
+ from pysurgery.geometry import PointCloud
126
+ pts = np.array([
127
+ [0.0, 0.0],
128
+ [1.0, 0.0],
129
+ [0.0, 1.0],
130
+ [1.0, 1.0]
131
+ ])
132
+ pc = PointCloud(pts)
133
+ assert pc._parent is None
134
+
135
+ # 1. Test from_vietoris_rips links pc
136
+ sc_rips = SimplicialComplex.from_vietoris_rips(pc, epsilon=1.5, max_dimension=1)
137
+ assert pc._parent is sc_rips
138
+
139
+ # Rotate pc, check that coordinates of sc_rips are updated!
140
+ pc_rot = pc.rotate(90.0, plane="xy", center=np.zeros(2))
141
+ np.testing.assert_allclose(sc_rips._coordinates, pc_rot.points)
142
+
143
+ # 2. Test from_alpha_complex links pc
144
+ pc2 = PointCloud(pts)
145
+ sc_alpha = SimplicialComplex.from_alpha_complex(pc2, alpha=2.0)
146
+ assert pc2._parent is sc_alpha
147
+ pc2_trans = pc2.translate([10.0, 10.0])
148
+ np.testing.assert_allclose(sc_alpha._coordinates, pc2_trans.points)
149
+
150
+ # 3. Test setter
151
+ pc3 = PointCloud(pts)
152
+ sc_empty = SimplicialComplex.from_simplices([[0], [1], [2]])
153
+ sc_empty.point_cloud = pc3
154
+ assert pc3._parent is sc_empty
155
+ pc3_trans = pc3.translate([-5.0, 0.0])
156
+ np.testing.assert_allclose(sc_empty._coordinates, pc3_trans.points)
157
+
File without changes
File without changes