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.
- {pysurgery-2.2.2/pysurgery.egg-info → pysurgery-2.2.3}/PKG-INFO +1 -1
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pyproject.toml +2 -2
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/__init__.py +1 -1
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/point_cloud.py +84 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/homology_generators.py +14 -13
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/complexes.py +22 -1
- {pysurgery-2.2.2 → pysurgery-2.2.3/pysurgery.egg-info}/PKG-INFO +1 -1
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_point_cloud.py +27 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_point_cloud_mappings.py +35 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/LICENSE +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/README.md +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/__init__.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/adams_data/sphere_p2.json +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/adams_data/sphere_p3.json +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/adams_data/sphere_p5.json +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/differentials.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/e_infinity_exhaustive.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/e_infinity_resolver.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/extension_solver.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/interactive_resolver.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/lambda_algebra.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/lean_resolver.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/odd_prime_cobar.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/spectral_sequence.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/u_resolution.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/adams/unstable.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/__init__.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/exact_algebra.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/exact_sequences.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/exact_snf_julia.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/group_rings.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/intersection_forms.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/k_theory.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/math_core.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/algebra/quadratic_forms.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/auto_surgery.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/bridge/__init__.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/bridge/julia_bridge.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/bridge/juliapkg.json +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/bridge/surgery_backend.jl +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/core/__init__.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/core/exceptions.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/core/foundations.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/core/generator_models.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/core/homology_generators.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/core/theorem_tags.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/coverage_matrix.yaml +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/__init__.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/bundles.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/characteristic_classes.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/embedding.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/gauss_bonnet.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/geometrization_3d.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/immersion_obstructions.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/intrinsic_dimension.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/metrics.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/geometry/uniformization.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homeomorphism.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homeomorphism_witness.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/__init__.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/algebraic_poincare.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/controlled_cohomology.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/cup_product.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/ext_cocycles.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/ext_yoneda_product.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/poincare_duality_verification.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homology/topological_sequences.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/__init__.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/higher_homotopy_groups.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/homotopy_verifier.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/known_homotopy_tables.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/multi_prime_synthesis.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/rational_homotopy.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/homotopy/sullivan_models.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/integrations/__init__.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/integrations/jax_bridge.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/integrations/lean_export.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/integrations/pytorch_geometric_bridge.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/integrations/trimesh_bridge.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/knots/__init__.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/knots/analysis.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/knots/constructors.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/knots/invariants.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/knots/linking.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/manifolds/__init__.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/manifolds/handle_decompositions.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/manifolds/handle_surgery.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/manifolds/kirby_calculus.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/manifolds/rational_surgery.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/manifolds/surgery.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/spectral/__init__.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/spectral/spectral_sequences.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/structure_set.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/surgery.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/__init__.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/filtration_tools.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/filtration_values.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/fundamental_group.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/fundamental_polyhedron.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/persistent_homology.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/pi1_group_ring_scaffold.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/topology/temporal_topology.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/utils/__init__.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/utils/signature.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/utils/tutorial_coverage_validator.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery/wall_groups.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery.egg-info/SOURCES.txt +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery.egg-info/dependency_links.txt +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery.egg-info/requires.txt +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/pysurgery.egg-info/top_level.txt +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/setup.cfg +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_3_manifolds.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_4_manifolds.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_adams_bound_tightening.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_adams_differentials.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_adams_e2_page.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_adams_extension_solver.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_adams_u_resolution.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_advanced_surfaces.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_algebraic_poincare.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_algebraic_surgery.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_alpha_complex.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_auto_surgery_contractible.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_auto_surgery_detection.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_auto_surgery_kill.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_auto_surgery_obstruction.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_auto_surgery_separate.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_auto_surgery_unlink.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_backend_consistency.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_backend_consistency_comprehensive.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_basis_change.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_canonical_spaces_and_homotopy.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_characteristic_classes.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_cobordism_complex.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_complexes.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_concatenate_glue_reuse.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_connected_component_optimization.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_controlled_cohomology.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_cup_product.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_decision_key_shared.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_dynamic_complexes.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_e_infinity_resolution.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_embedding.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_exact_algebra.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_exact_algebra_sequences.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_exact_intrinsic_dimension.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_exact_metadata_consistency.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_exact_snf_julia.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_ext_cocycles.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_ext_yoneda_product.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_filtration_persistence_fast.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_filtration_tools.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_formality_detection.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_foundations.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_fundamental_group.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_fundamental_polyhedron.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_gauss_bonnet.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_geometrization_3d.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_global_backend_consistency.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_group_rings.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_handle_decompositions.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_handle_decompositions_morse.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_handle_surgery.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_homeomorphism.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_homeomorphism_witness.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_homotopy_oop.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_homotopy_verifier.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_immersion_integration.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_immersion_obstructions.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_integration_scope.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_interactive_resolver.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_intersection_forms.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_intrinsic_dimension.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_invariants_cross_validation.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_isotopy_hierarchy.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_jax_bridge.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_jax_bridge_extended.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_julia_bridge.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_k_theory.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_kirby_calculus_advanced.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_knots.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_known_homotopy_tables.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_lean_export.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_lean_formal_resolver.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_math_core.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_module_c_integration.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_modules_a_b_integration.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_multi_prime_synthesis.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_odd_prime_adams.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_path_intersection.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_persistence_backend_dispatch.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_persistence_wrapper.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_pi1_and_homology_generators.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_pi1_group_ring_scaffold.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_poincare_duality_verification.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_property_backend.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_property_complex_construction.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_property_surgery.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_property_topology.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_public_api_v2.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_quadratic_forms.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_quick_mapper.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_rational_dga.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_rational_homotopy_group_schema.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_rational_surgery.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_slides_cancellation.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_spectral_sequences.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_structure_set.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_sullivan_models.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_sullivan_phase2_integration.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_surgery.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_surgery_atomicity.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_surgery_session.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_temporal_topology.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_theorem_tags.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_topological_loss.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_topological_sequences.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_triangulation.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_tutorial_coverage_matrix.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_uniformization.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_wall_groups.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_witness_scaling.py +0 -0
- {pysurgery-2.2.2 → pysurgery-2.2.3}/tests/test_zigzag_persistence.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pysurgery"
|
|
7
|
-
version = "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.
|
|
86
|
+
current_version = "2.2.3"
|
|
87
87
|
commit = true
|
|
88
88
|
tag = true
|
|
89
89
|
|
|
@@ -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,
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|