cmgraph 0.0.1__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 (104) hide show
  1. cmgraph-0.0.1/.clang-format +4 -0
  2. cmgraph-0.0.1/.github/workflows/ci.yml +126 -0
  3. cmgraph-0.0.1/.github/workflows/publish.yml +104 -0
  4. cmgraph-0.0.1/.gitignore +36 -0
  5. cmgraph-0.0.1/CLAUDE.md +20 -0
  6. cmgraph-0.0.1/CMakeLists.txt +76 -0
  7. cmgraph-0.0.1/LICENSE +21 -0
  8. cmgraph-0.0.1/PKG-INFO +257 -0
  9. cmgraph-0.0.1/PLAN.md +355 -0
  10. cmgraph-0.0.1/README.md +217 -0
  11. cmgraph-0.0.1/Specifications.md +30 -0
  12. cmgraph-0.0.1/docs/annotations.md +36 -0
  13. cmgraph-0.0.1/docs/design/01-data-structures.md +871 -0
  14. cmgraph-0.0.1/docs/design/02-engine.md +475 -0
  15. cmgraph-0.0.1/docs/design/03-index-pair-isolation.md +473 -0
  16. cmgraph-0.0.1/docs/design/04-conley-index-map.md +555 -0
  17. cmgraph-0.0.1/docs/index.md +31 -0
  18. cmgraph-0.0.1/docs/performance.md +103 -0
  19. cmgraph-0.0.1/examples/.gitkeep +0 -0
  20. cmgraph-0.0.1/examples/conley_index.py +157 -0
  21. cmgraph-0.0.1/examples/henon.py +105 -0
  22. cmgraph-0.0.1/examples/interactive_refinement.py +117 -0
  23. cmgraph-0.0.1/examples/leslie_2d.py +113 -0
  24. cmgraph-0.0.1/examples/leslie_3d.py +111 -0
  25. cmgraph-0.0.1/examples/leslie_dataset.py +132 -0
  26. cmgraph-0.0.1/examples/skip_conley.py +80 -0
  27. cmgraph-0.0.1/include/cmgraph/adapter/adapter.hpp +32 -0
  28. cmgraph-0.0.1/include/cmgraph/adapter/conley.hpp +286 -0
  29. cmgraph-0.0.1/include/cmgraph/adapter/error.hpp +40 -0
  30. cmgraph-0.0.1/include/cmgraph/adapter/index_pair.hpp +360 -0
  31. cmgraph-0.0.1/include/cmgraph/adapter/marshal.hpp +164 -0
  32. cmgraph-0.0.1/include/cmgraph/adapter/uniformize.hpp +434 -0
  33. cmgraph-0.0.1/include/cmgraph/dynamics/cache.hpp +313 -0
  34. cmgraph-0.0.1/include/cmgraph/dynamics/cell_map.hpp +916 -0
  35. cmgraph-0.0.1/include/cmgraph/dynamics/dataset.hpp +475 -0
  36. cmgraph-0.0.1/include/cmgraph/dynamics/dynamics.hpp +23 -0
  37. cmgraph-0.0.1/include/cmgraph/dynamics/error.hpp +22 -0
  38. cmgraph-0.0.1/include/cmgraph/dynamics/oracle.hpp +258 -0
  39. cmgraph-0.0.1/include/cmgraph/engine/engine.hpp +23 -0
  40. cmgraph-0.0.1/include/cmgraph/engine/error.hpp +24 -0
  41. cmgraph-0.0.1/include/cmgraph/engine/morse_engine.hpp +914 -0
  42. cmgraph-0.0.1/include/cmgraph/engine/morse_graph.hpp +288 -0
  43. cmgraph-0.0.1/include/cmgraph/engine/region.hpp +119 -0
  44. cmgraph-0.0.1/include/cmgraph/engine/spurious.hpp +348 -0
  45. cmgraph-0.0.1/include/cmgraph/geometry/box.hpp +46 -0
  46. cmgraph-0.0.1/include/cmgraph/geometry/error.hpp +22 -0
  47. cmgraph-0.0.1/include/cmgraph/geometry/geometry.hpp +23 -0
  48. cmgraph-0.0.1/include/cmgraph/geometry/grid.hpp +1081 -0
  49. cmgraph-0.0.1/include/cmgraph/geometry/key.hpp +581 -0
  50. cmgraph-0.0.1/include/cmgraph/geometry/lattice.hpp +86 -0
  51. cmgraph-0.0.1/include/cmgraph/geometry/refine.hpp +203 -0
  52. cmgraph-0.0.1/include/cmgraph/geometry/rounding.hpp +348 -0
  53. cmgraph-0.0.1/include/cmgraph/graph/digraph.hpp +358 -0
  54. cmgraph-0.0.1/include/cmgraph/graph/error.hpp +22 -0
  55. cmgraph-0.0.1/include/cmgraph/graph/graph.hpp +20 -0
  56. cmgraph-0.0.1/include/cmgraph/graph/scc.hpp +367 -0
  57. cmgraph-0.0.1/include/cmgraph/topology/bigint.hpp +300 -0
  58. cmgraph-0.0.1/include/cmgraph/topology/complex.hpp +319 -0
  59. cmgraph-0.0.1/include/cmgraph/topology/coreduce.hpp +144 -0
  60. cmgraph-0.0.1/include/cmgraph/topology/cube.hpp +216 -0
  61. cmgraph-0.0.1/include/cmgraph/topology/error.hpp +23 -0
  62. cmgraph-0.0.1/include/cmgraph/topology/field.hpp +171 -0
  63. cmgraph-0.0.1/include/cmgraph/topology/fp.hpp +158 -0
  64. cmgraph-0.0.1/include/cmgraph/topology/homology.hpp +264 -0
  65. cmgraph-0.0.1/include/cmgraph/topology/index_map.hpp +754 -0
  66. cmgraph-0.0.1/include/cmgraph/topology/linalg.hpp +244 -0
  67. cmgraph-0.0.1/include/cmgraph/topology/rcf.hpp +328 -0
  68. cmgraph-0.0.1/include/cmgraph/topology/snf.hpp +269 -0
  69. cmgraph-0.0.1/include/cmgraph/topology/topology.hpp +38 -0
  70. cmgraph-0.0.1/pyproject.toml +47 -0
  71. cmgraph-0.0.1/python/cmgraph/__init__.py +79 -0
  72. cmgraph-0.0.1/python/cmgraph/plot.py +456 -0
  73. cmgraph-0.0.1/scripts/benchmark.py +222 -0
  74. cmgraph-0.0.1/scripts/check_isolation.py +150 -0
  75. cmgraph-0.0.1/src/bindings.cpp +1747 -0
  76. cmgraph-0.0.1/tasks.md +1375 -0
  77. cmgraph-0.0.1/tests/cpp/CMakeLists.txt +35 -0
  78. cmgraph-0.0.1/tests/cpp/leslie_fixture.hpp +86 -0
  79. cmgraph-0.0.1/tests/cpp/test_cell_map.cpp +325 -0
  80. cmgraph-0.0.1/tests/cpp/test_conley.cpp +533 -0
  81. cmgraph-0.0.1/tests/cpp/test_conley_indexmap.cpp +225 -0
  82. cmgraph-0.0.1/tests/cpp/test_dataset.cpp +379 -0
  83. cmgraph-0.0.1/tests/cpp/test_dynamics_cache.cpp +390 -0
  84. cmgraph-0.0.1/tests/cpp/test_engine.cpp +601 -0
  85. cmgraph-0.0.1/tests/cpp/test_engine_refine.cpp +1316 -0
  86. cmgraph-0.0.1/tests/cpp/test_graph.cpp +592 -0
  87. cmgraph-0.0.1/tests/cpp/test_grid.cpp +468 -0
  88. cmgraph-0.0.1/tests/cpp/test_index_map.cpp +1033 -0
  89. cmgraph-0.0.1/tests/cpp/test_index_pair.cpp +501 -0
  90. cmgraph-0.0.1/tests/cpp/test_key_codec.cpp +438 -0
  91. cmgraph-0.0.1/tests/cpp/test_parallel.cpp +201 -0
  92. cmgraph-0.0.1/tests/cpp/test_refine.cpp +520 -0
  93. cmgraph-0.0.1/tests/cpp/test_rounding.cpp +500 -0
  94. cmgraph-0.0.1/tests/cpp/test_sanity.cpp +25 -0
  95. cmgraph-0.0.1/tests/cpp/test_spurious.cpp +251 -0
  96. cmgraph-0.0.1/tests/cpp/test_topology.cpp +764 -0
  97. cmgraph-0.0.1/tests/cpp/vendor/doctest/doctest.h +7106 -0
  98. cmgraph-0.0.1/tests/python/test_callback_oracle.py +94 -0
  99. cmgraph-0.0.1/tests/python/test_examples.py +138 -0
  100. cmgraph-0.0.1/tests/python/test_gc_lifetime.py +111 -0
  101. cmgraph-0.0.1/tests/python/test_import.py +15 -0
  102. cmgraph-0.0.1/tests/python/test_plot.py +356 -0
  103. cmgraph-0.0.1/tests/python/test_public_api.py +460 -0
  104. cmgraph-0.0.1/tests/python/test_threads.py +56 -0
@@ -0,0 +1,4 @@
1
+ # cmgraph C++ style: LLVM base, 100-column limit (PLAN.md §8).
2
+ BasedOnStyle: LLVM
3
+ ColumnLimit: 100
4
+ Standard: c++20
@@ -0,0 +1,126 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ # Cancel superseded runs on the same ref.
10
+ concurrency:
11
+ group: ci-${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ # Use bash on every runner (git-bash on Windows) so all run steps are uniform.
15
+ defaults:
16
+ run:
17
+ shell: bash
18
+
19
+ jobs:
20
+ # ---------------------------------------------------------------------------
21
+ # C++ core + doctest suite across Linux / macOS / Windows (MSVC). This is the
22
+ # cross-platform build check: the whole header tree is compiled into the
23
+ # doctest binary on each OS's native toolchain, then ctest runs it plus the
24
+ # include-graph isolation lint.
25
+ # ---------------------------------------------------------------------------
26
+ cpp:
27
+ name: C++ ${{ matrix.os }}
28
+ runs-on: ${{ matrix.os }}
29
+ strategy:
30
+ fail-fast: false
31
+ matrix:
32
+ os: [ubuntu-latest, macos-latest, windows-latest]
33
+ steps:
34
+ - uses: actions/checkout@v7
35
+
36
+ - uses: actions/setup-python@v6
37
+ with:
38
+ python-version: "3.11"
39
+ cache: pip
40
+
41
+ # pybind11 is REQUIRED to configure (it defines the _core target); the
42
+ # isolation-lint ctest also needs Python. The doctest suite itself is
43
+ # standalone C++ and does not link _core.
44
+ - name: Install configure deps
45
+ run: python -m pip install --upgrade pip pybind11
46
+
47
+ - name: Configure
48
+ run: cmake -S . -B build -DCMGRAPH_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release
49
+
50
+ - name: Build C++ tests
51
+ run: cmake --build build --config Release --parallel --target cmgraph_cpp_tests
52
+
53
+ - name: ctest (doctest + isolation lint)
54
+ run: ctest --test-dir build -C Release --output-on-failure
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # The compiled extension + Python test suite across OS x Python. The Windows
58
+ # leg here is the real MSVC build of cmgraph._core via scikit-build-core.
59
+ # ---------------------------------------------------------------------------
60
+ python:
61
+ name: Py ${{ matrix.python-version }} ${{ matrix.os }}
62
+ runs-on: ${{ matrix.os }}
63
+ strategy:
64
+ fail-fast: false
65
+ matrix:
66
+ os: [ubuntu-latest, macos-latest, windows-latest]
67
+ python-version: ["3.10", "3.11", "3.12"]
68
+ steps:
69
+ - uses: actions/checkout@v7
70
+
71
+ - uses: actions/setup-python@v6
72
+ with:
73
+ python-version: ${{ matrix.python-version }}
74
+ cache: pip
75
+
76
+ # The Graphviz `dot` binary for the viz tests (the python `graphviz`
77
+ # package comes from the [viz] extra). Tests degrade gracefully without
78
+ # it, but CI installs it to exercise real rendering.
79
+ - name: Install Graphviz
80
+ run: |
81
+ case "$RUNNER_OS" in
82
+ Linux) sudo apt-get update && sudo apt-get install -y graphviz ;;
83
+ macOS) brew install graphviz ;;
84
+ Windows) choco install graphviz --no-progress ;;
85
+ esac
86
+
87
+ - name: Install cmgraph[viz]
88
+ run: |
89
+ python -m pip install --upgrade pip
90
+ python -m pip install ".[viz]" pytest
91
+
92
+ - name: pytest (headless)
93
+ env:
94
+ MPLBACKEND: Agg
95
+ run: python -m pytest tests/ -v
96
+
97
+ # ---------------------------------------------------------------------------
98
+ # Lint once on Linux (formatters are platform-independent). clang-format is
99
+ # pinned to the 19.x line the tree was formatted with, for determinism.
100
+ # ---------------------------------------------------------------------------
101
+ lint:
102
+ name: Lint
103
+ runs-on: ubuntu-latest
104
+ steps:
105
+ - uses: actions/checkout@v7
106
+
107
+ - uses: actions/setup-python@v6
108
+ with:
109
+ python-version: "3.11"
110
+ cache: pip
111
+
112
+ - name: Install linters
113
+ run: python -m pip install --upgrade pip ruff "clang-format~=19.1"
114
+
115
+ - name: ruff
116
+ run: |
117
+ ruff check python/ tests/ examples/ scripts/
118
+ ruff format --check python/ tests/ examples/ scripts/
119
+
120
+ - name: clang-format
121
+ run: |
122
+ find include src tests/cpp \( -name '*.hpp' -o -name '*.cpp' \) \
123
+ -not -path '*/vendor/*' | xargs clang-format --dry-run --Werror
124
+
125
+ - name: isolation lint
126
+ run: python scripts/check_isolation.py
@@ -0,0 +1,104 @@
1
+ name: Publish
2
+
3
+ # Build platform wheels + sdist and publish to PyPI on a version tag (vX.Y.Z).
4
+ # workflow_dispatch builds the artifacts too (for a dry run) but never publishes.
5
+ #
6
+ # Publishing uses PyPI Trusted Publishing (OIDC) — no API token in secrets. Before
7
+ # the first release, configure a trusted publisher on PyPI (or a *pending* one if the
8
+ # project does not exist yet): https://pypi.org/manage/account/publishing/
9
+ # PyPI project name: cmgraph
10
+ # Owner: marciogameiro
11
+ # Repository: cmgraph
12
+ # Workflow name: publish.yml
13
+ # (Alternatively, replace the publish step's OIDC with `password: ${{ secrets.PYPI_API_TOKEN }}`.)
14
+
15
+ on:
16
+ push:
17
+ tags: ["v*"]
18
+ workflow_dispatch: {}
19
+
20
+ permissions:
21
+ contents: read
22
+
23
+ defaults:
24
+ run:
25
+ shell: bash
26
+
27
+ jobs:
28
+ wheels:
29
+ name: Wheels ${{ matrix.os }}
30
+ runs-on: ${{ matrix.os }}
31
+ strategy:
32
+ fail-fast: false
33
+ matrix:
34
+ os: [ubuntu-latest, macos-latest, windows-latest]
35
+ steps:
36
+ - uses: actions/checkout@v7
37
+
38
+ - uses: actions/setup-python@v6
39
+ with:
40
+ python-version: "3.11"
41
+
42
+ - name: Install cibuildwheel
43
+ run: python -m pip install --upgrade pip "cibuildwheel~=2.21"
44
+
45
+ - name: Build wheels
46
+ run: python -m cibuildwheel --output-dir wheelhouse
47
+ env:
48
+ # No CIBW_BUILD filter: build cibuildwheel's full default set of
49
+ # CPython versions, automatically limited to those satisfying
50
+ # requires-python (>=3.10) from pyproject.toml — so cp310, cp311,
51
+ # cp312, cp313, ... are all built and new CPython releases are picked
52
+ # up without editing this workflow.
53
+ #
54
+ # CIBW_SKIP below removes only non-CPython-version axes: the musllinux
55
+ # (Alpine) and 32-bit platform variants, and PyPy (pybind11 C++20
56
+ # extensions on PyPy are unsupported/high-risk here). Drop entries to
57
+ # widen the matrix.
58
+ CIBW_SKIP: "*-musllinux* *_i686 *-win32 pp*"
59
+ # gcc 12 image for full C++20 support on Linux.
60
+ CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
61
+ # macos-latest runners are Apple Silicon (arm64); also cross-build the
62
+ # Intel (x86_64) macOS wheels so both architectures are published. The
63
+ # x86_64 build is a recompile of identical code; its import smoke test
64
+ # is skipped on the arm64 runner because Rosetta-based CI testing is
65
+ # unreliable — drop CIBW_TEST_SKIP to test x86_64 under Rosetta.
66
+ CIBW_ARCHS_MACOS: "x86_64 arm64"
67
+ CIBW_TEST_SKIP: "*-macosx_x86_64"
68
+ # Smoke-test every wheel: import the compiled module and check the version.
69
+ CIBW_TEST_COMMAND: 'python -c "import cmgraph; print(cmgraph.__version__)"'
70
+
71
+ - uses: actions/upload-artifact@v7
72
+ with:
73
+ name: wheels-${{ matrix.os }}
74
+ path: wheelhouse/*.whl
75
+
76
+ sdist:
77
+ name: Source distribution
78
+ runs-on: ubuntu-latest
79
+ steps:
80
+ - uses: actions/checkout@v7
81
+ - uses: actions/setup-python@v6
82
+ with:
83
+ python-version: "3.11"
84
+ - run: python -m pip install --upgrade pip build
85
+ - run: python -m build --sdist
86
+ - uses: actions/upload-artifact@v7
87
+ with:
88
+ name: sdist
89
+ path: dist/*.tar.gz
90
+
91
+ publish:
92
+ name: Publish to PyPI
93
+ needs: [wheels, sdist]
94
+ runs-on: ubuntu-latest
95
+ # Only for an actual version-tag push — never on branches or manual dispatch.
96
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
97
+ permissions:
98
+ id-token: write # OIDC for PyPI Trusted Publishing
99
+ steps:
100
+ - uses: actions/download-artifact@v7
101
+ with:
102
+ path: dist
103
+ merge-multiple: true # collect every wheel + the sdist into dist/
104
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,36 @@
1
+ # Build / packaging artifacts
2
+ build/
3
+ build-*/
4
+ dist/
5
+ wheelhouse/
6
+ _skbuild/
7
+ *.egg-info/
8
+ *.egg
9
+
10
+ # Compiled extension modules
11
+ *.so
12
+ *.pyd
13
+ *.dylib
14
+
15
+ # Python caches / environments
16
+ __pycache__/
17
+ *.py[cod]
18
+ .pytest_cache/
19
+ .ruff_cache/
20
+ venv/
21
+ .venv/
22
+ env/
23
+
24
+ # CMake / C++ build scratch
25
+ CMakeCache.txt
26
+ CMakeFiles/
27
+ compile_commands.json
28
+
29
+ # OS / editor cruft
30
+ .DS_Store
31
+ *.swp
32
+
33
+ # Claude Code local config — keep the agent charters (they document the
34
+ # multi-agent build methodology), ignore any local settings.
35
+ .claude/*
36
+ !.claude/agents/
@@ -0,0 +1,20 @@
1
+ # Project Name: cmgraph (Conley Morse Graph)
2
+
3
+ ## Core Purpose
4
+ - High-performance C++20 computational dynamics package with a Python interface (pybind11).
5
+ - Computes cell maps, Morse graphs, and homological Conley indices from continuous maps or datasets.
6
+
7
+ ## Tech Stack & Standards
8
+ - Language: C++20 (GCC 11+, Clang 13+), Python 3.10+
9
+ - Binding: pybind11
10
+ - Math Dependencies: Standard C++ STL. Use other dependences only if strictly necessary.
11
+ - Testing: Provide unit tests.
12
+
13
+ ## Key Commands
14
+ - Build C++ / Bindings: Make it a Python package that can be built with pip install
15
+ - Run Python Tests: `pytest tests/`
16
+ - Lint: Provide lint command
17
+
18
+ ## Strict Rules
19
+ - Documentation: All core algorithms must have structural annotations explaining mathematical intent.
20
+ - Examples: Provide several examples illustrating how to use the code.
@@ -0,0 +1,76 @@
1
+ # cmgraph — C++20 core + pybind11 extension module.
2
+ # Layout and commands: PLAN.md §7–§8.
3
+ cmake_minimum_required(VERSION 3.22)
4
+ # The project() VERSION below is a DEV FALLBACK only (used by standalone CMake/ctest
5
+ # builds). The single source of truth for the package version is pyproject.toml; under a
6
+ # `pip install`/sdist build scikit-build-core injects it as SKBUILD_PROJECT_VERSION, which
7
+ # takes precedence when defining CMGRAPH_VERSION for the module (see below). Keep this
8
+ # number in sync with pyproject.toml so a standalone dev build reports the same version.
9
+ project(cmgraph VERSION 0.0.1 LANGUAGES CXX)
10
+
11
+ set(CMAKE_CXX_STANDARD 20)
12
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
13
+ set(CMAKE_CXX_EXTENSIONS OFF)
14
+
15
+ # C++ unit tests must NOT build during `pip install .` — dev-only, opt-in via ctest.
16
+ option(CMGRAPH_BUILD_TESTS "Build C++ unit tests and register them with ctest" OFF)
17
+
18
+ find_package(Python REQUIRED COMPONENTS Interpreter Development.Module)
19
+
20
+ # T14 deterministic parallel cell-map build uses std::thread (STL only). Some Linux
21
+ # toolchains require -pthread to link/run threaded code; prefer the pthread flag and link
22
+ # the imported Threads::Threads target on every C++ target that compiles the core headers.
23
+ set(THREADS_PREFER_PTHREAD_FLAG ON)
24
+ find_package(Threads REQUIRED)
25
+
26
+ # Standalone configure (outside scikit-build-core): locate pybind11's CMake config
27
+ # through the active Python interpreter, so the PLAN.md §8 command works as written.
28
+ if(NOT DEFINED pybind11_DIR)
29
+ execute_process(
30
+ COMMAND "${Python_EXECUTABLE}" -m pybind11 --cmakedir
31
+ OUTPUT_VARIABLE _cmgraph_pybind11_cmakedir
32
+ OUTPUT_STRIP_TRAILING_WHITESPACE
33
+ ERROR_QUIET)
34
+ if(_cmgraph_pybind11_cmakedir)
35
+ set(pybind11_DIR "${_cmgraph_pybind11_cmakedir}")
36
+ endif()
37
+ endif()
38
+ find_package(pybind11 CONFIG REQUIRED)
39
+
40
+ # The compiled core: cmgraph._core (the full T2–T9 pipeline surface).
41
+ pybind11_add_module(_core src/bindings.cpp)
42
+ target_include_directories(_core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
43
+ target_link_libraries(_core PRIVATE Threads::Threads)
44
+
45
+ # MSVC portability: the template-heavy homology/index-map translation unit can exceed the
46
+ # COFF object-file section limit (C1128) — /bigobj lifts it; and std::getenv
47
+ # (CMGRAPH_NUM_THREADS) trips the C4996 deprecation warning (build has no /WX, so it is
48
+ # warning-only) — silence it. No-ops on GCC/Clang.
49
+ if(MSVC)
50
+ target_compile_options(_core PRIVATE /bigobj)
51
+ target_compile_definitions(_core PRIVATE _CRT_SECURE_NO_WARNINGS)
52
+ endif()
53
+
54
+ # Single-source the runtime __version__ from pyproject.toml. scikit-build-core resolves
55
+ # the pyproject version and passes it as SKBUILD_PROJECT_VERSION at configure time; a
56
+ # standalone CMake build falls back to the project() VERSION (the dev placeholder above).
57
+ # bindings.cpp reads the CMGRAPH_VERSION macro — there is no hardcoded version in C++.
58
+ if(DEFINED SKBUILD_PROJECT_VERSION)
59
+ set(_cmgraph_version "${SKBUILD_PROJECT_VERSION}")
60
+ else()
61
+ set(_cmgraph_version "${PROJECT_VERSION}")
62
+ endif()
63
+ target_compile_definitions(_core PRIVATE "CMGRAPH_VERSION=\"${_cmgraph_version}\"")
64
+
65
+ install(TARGETS _core DESTINATION cmgraph)
66
+
67
+ if(CMGRAPH_BUILD_TESTS)
68
+ enable_testing()
69
+ add_subdirectory(tests/cpp)
70
+ # Geometry/topology isolation rule (PLAN.md §3, design 01 §2.8) — mechanical
71
+ # include-graph lint, wired into ctest so CI fails on any violating edge.
72
+ add_test(
73
+ NAME isolation_lint
74
+ COMMAND "${Python_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/check_isolation.py"
75
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
76
+ endif()
cmgraph-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marcio Gameiro and the cmgraph authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
cmgraph-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,257 @@
1
+ Metadata-Version: 2.1
2
+ Name: cmgraph
3
+ Version: 0.0.1
4
+ Summary: Conley Morse Graph: cell maps, Morse graphs, and homological Conley indices for discrete-time dynamical systems.
5
+ Author-Email: Marcio Gameiro <marciogameiro@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Marcio Gameiro and the cmgraph authors
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Classifier: Development Status :: 2 - Pre-Alpha
29
+ Classifier: Intended Audience :: Science/Research
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Programming Language :: C++
32
+ Classifier: Programming Language :: Python :: 3
33
+ Classifier: Programming Language :: Python :: 3.10
34
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
35
+ Requires-Python: >=3.10
36
+ Provides-Extra: viz
37
+ Requires-Dist: matplotlib>=3.5; extra == "viz"
38
+ Requires-Dist: graphviz>=0.20; extra == "viz"
39
+ Description-Content-Type: text/markdown
40
+
41
+ # cmgraph — Conley Morse Graph
42
+
43
+ High-performance C++20 + pybind11 package that computes **cell maps, Morse graphs, and
44
+ homological Conley indices** for discrete-time dynamical systems, from either a continuous
45
+ map (given as an outer-enclosure box map) or a sampled dataset.
46
+
47
+ The compiled core (`cmgraph._core`) carries the whole pipeline —
48
+ outer-approximation cell map ⟶ strongly connected components ⟶ Morse decomposition +
49
+ partial order ⟶ (on demand) cubical relative homology and the Leray-reduced index map. The
50
+ Python surface is a thin, ergonomic layer over it. Cells are exposed as **stable keys**
51
+ (Python ints) that survive refinement.
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ pip install . # core (no plotting dependencies)
57
+ pip install ".[viz]" # + matplotlib and graphviz for cmgraph.plot
58
+ ```
59
+
60
+ - `import cmgraph` needs **no** third-party runtime dependency. `numpy` is used for numeric
61
+ outputs *when it is importable* and is otherwise optional (outputs fall back to nested
62
+ Python lists; cell keys are always Python ints).
63
+ - Plotting backends (`matplotlib`, `graphviz`) are the optional `viz` extra: `import
64
+ cmgraph` and `import cmgraph.plot` both succeed without them, and each plot function
65
+ raises a clear `pip install cmgraph[viz]` hint only when called without its backend.
66
+ - Build stack: `scikit-build-core` + CMake + `pybind11`, C++20 (GCC 11+, Clang 13+),
67
+ Python 3.10+. The package version is single-sourced from `pyproject.toml`.
68
+
69
+ ## Quickstart — Leslie 2D end to end
70
+
71
+ ```python
72
+ import math
73
+ import cmgraph as cm
74
+
75
+ TH1 = TH2 = 20.0; PHI = 0.1; P = 0.7
76
+ _up = lambda v: math.nextafter(v, math.inf)
77
+ _dn = lambda v: math.nextafter(v, -math.inf)
78
+
79
+ def leslie_box(box):
80
+ """Outward-rounded interval enclosure of the 2D Leslie map (a genuine outer bound)."""
81
+ (xlo, xhi), (ylo, yhi) = box
82
+ ulo = _dn(_dn(TH1 * xlo) + _dn(TH2 * ylo)); uhi = _up(_up(TH1 * xhi) + _up(TH2 * yhi))
83
+ slo = _dn(xlo + ylo); shi = _up(xhi + yhi)
84
+ elo = _dn(math.exp(_dn(-PHI * shi))); ehi = _up(math.exp(_up(-PHI * slo)))
85
+ prods = [ulo * elo, ulo * ehi, uhi * elo, uhi * ehi]
86
+ return [[_dn(min(prods)), _up(max(prods))], [_dn(P * xlo), _up(P * xhi)]]
87
+
88
+ model = cm.Model(
89
+ bounds=[[-0.001, 90.0], [-0.001, 70.0]],
90
+ map=cm.BoxMap(leslie_box, batched=False),
91
+ grid=cm.Grid(size=[128, 128]),
92
+ )
93
+
94
+ mg = model.morse_graph() # fast path: Morse graph, no homology
95
+ print(mg.summary()) # node count, per-node cell counts, partial order
96
+ mg.morse_sets() # per-node cell KEYS (stable across refinement)
97
+ ```
98
+
99
+ ### Refine, inspect, retain state
100
+
101
+ Refinement mutates the engine in place and keeps the cached image box of every cell that
102
+ already exists — only newly created cells trigger a map evaluation. The per-round report's
103
+ f-evaluation count proves it:
104
+
105
+ ```python
106
+ report = mg.refine(subdivisions=1, region="morse_sets") # subdivide + recompute in place
107
+ # For a box map (BoxMap), every new leaf is evaluated exactly once and no old cell is
108
+ # re-evaluated, so the per-round f-evaluation count equals the number of new leaves:
109
+ assert report["f_evaluations"] == report["new_leaves"] # unchanged cells are not re-evaluated
110
+ ```
111
+
112
+ For a dataset map (`BoxMapData`), children that fall in an empty region are classified
113
+ `NO_DATA` and are *not* counted as evaluations, so the identity above becomes
114
+ `f_evaluations == new_leaves - (NO_DATA children)`; the retention guarantee (old cells are
115
+ never re-evaluated) is unchanged.
116
+
117
+ ### Conley index on demand
118
+
119
+ A Morse set typically does not isolate at a coarse resolution, so `conley_index` raises a
120
+ recoverable `cm.IndexPairInvalid` (refine the offending set and retry). Guard the call:
121
+
122
+ ```python
123
+ try:
124
+ ci = mg.conley_index(node=0) # RCF of the Leray-reduced f* over Z/5
125
+ ci = mg.conley_index(node=0, want_homology=True) # + relative homology H_*(P1, P0)
126
+ ci = mg.conley_index(node=0, coefficients="Z") # arithmetic over Q, integer homology
127
+ except cm.IndexPairInvalid:
128
+ ... # node 0 does not isolate at this resolution — refine it first
129
+ ```
130
+
131
+ The full refine-to-isolate loop that drives a Morse set to a valid index pair and computes
132
+ a genuine degree-1 Conley index lives in
133
+ [`examples/conley_index.py`](examples/conley_index.py).
134
+
135
+ ## Rigor contract
136
+
137
+ The library's own arithmetic is **rigorous**: every conversion from a floating-point image
138
+ box to integer cells rounds *outward*, so the computed cell map is a genuine outer
139
+ approximation of the true dynamics.
140
+
141
+ - **B1** — the box handed to your map is the cell's real box widened outward (directed
142
+ rounding + k-ulp margin), so `f(box_true(c)) ⊆ f(box_given(c))`.
143
+ - **B2** — your enclosure box is converted to the *union of every cell it meets* under
144
+ outward, closed-cube rounding, so `enclosure ⊆ |F(c)|`.
145
+
146
+ What the library **cannot** verify is your map itself. The rigor of the result therefore
147
+ rests on the path you choose:
148
+
149
+ - **`cm.BoxMap(f)` — rigorous iff `f` is a true outer enclosure.** `f` must map a box to a
150
+ box that *contains* the image of every point in it: `f(box) ⊇ image(box)`. The examples
151
+ build these by interval arithmetic with outward rounding and each documents why the bound
152
+ holds. (A libm caveat: bounding a transcendental like `exp` by a fixed outward ulp
153
+ widening assumes the platform libm is accurate to ≤ 1 ulp — true on glibc ≥ 2.28 and the
154
+ macOS system libm; a looser libm needs more ulps.)
155
+ - **`cm.BoxMap(point_map, padding=[...])` — non-rigorous.** A convenience that samples a
156
+ point map at cell corners and inflates by a fixed padding. It is an outer approximation
157
+ *only if* the padding dominates the map's sub-cell variation (this is not checked).
158
+ - **`cm.BoxMapData(X, Y, padding=...)` — non-rigorous** unless dominated. Builds a cell map
159
+ from sampled transitions `x_i → y_i`: the image of a cell is the convex hull of the images
160
+ of the samples in it, inflated by `padding`. It is an outer approximation only if the
161
+ padding (or a supplied `lipschitz=...` bound, which makes each image a ball around every
162
+ sample) dominates the sub-cell variation.
163
+
164
+ Rigor propagates: given a valid box map, the cell map, Morse decomposition, and Conley
165
+ indices all carry the outer-approximation guarantees of the combinatorial-dynamics
166
+ literature.
167
+
168
+ ## API tour
169
+
170
+ | Object / call | Role |
171
+ | --- | --- |
172
+ | `cm.Model(bounds, map, grid, threads=0)` | Binds a bounding box, a map spec, and a grid spec into the compute–inspect–refine engine. `.size`, `.dimension`, `.bounds`, `.cells_under(key)`. `threads`: cell-map-build worker count (`0` = all cores, default; `1` = serial) — results are bit-identical for any value; env `CMGRAPH_NUM_THREADS` overrides the auto default. |
173
+ | `cm.BoxMap(f, batched=True)` | A box→box outer enclosure (or `BoxMap(point_map, padding=[...])` for the non-rigorous point-map path). |
174
+ | `cm.BoxMapData(X, Y, padding=..., lipschitz=..., borrow_rings=0)` | A dataset cell map from sampled transitions; empty-cell policy is NO_DATA by default (`borrow_rings=0`) or ring-budgeted collar borrowing (`borrow_rings ≥ 1`, requires a Lipschitz bound). |
175
+ | `cm.Grid(size=[...])` / `cm.Grid(subdivisions=k)` | Grid shape spec — anisotropic per-dimension sizes, or `k` uniform binary subdivisions. |
176
+ | `model.morse_graph(conley_index=False)` | Compute the Morse graph (GIL released). Returns a `MorseGraph` handle. |
177
+ | `mg.summary()` / `mg.morse_sets()` / `mg.cells(n)` / `mg.node_boxes(n)` / `mg.node_stats(n)` | Inspection: node/cell counts, per-node cell **keys**, per-node cell boxes, stats. |
178
+ | `mg.order_pairs()` / `mg.reaches(p, q)` | The Morse-graph partial order (reachability on the condensation). |
179
+ | `mg.refine(subdivisions= / to_level= / size=, region=, eval=, collar=, recompute=)` | Refine + recompute in place. `region`: `None` (whole grid), `"morse_sets"`, `"invariant_set"`, `mg.morse_set(i)` / `mg.connections(i, j)` handles, or an iterable of cell keys. `to_level` is idempotent. `recompute=False` batches rounds; settle with `mg.recompute()`. |
180
+ | `mg.conley_index(node, coefficients=None, want_homology=False, want_full_index_map=False)` | The homological Conley index of a node, on demand (see below). Raises `cm.IndexPairInvalid` when the node does not isolate. |
181
+ | `mg.spurious(node, budget=, certify=)` | The spurious-set protocol: refine a node until its recurrence vanishes (spurious) or a budget is spent. |
182
+ | `plot.morse_graph(mg, path=...)` / `plot.morse_sets(mg, dims=(0,1), path=...)` | Optional `viz`: Graphviz DOT and matplotlib (headless Agg) with shared node colours. |
183
+
184
+ ### Conley-index output form
185
+
186
+ `conley_index` returns, per homology degree, the **rational canonical form of the Leray
187
+ reduction of the induced index map `f*`**, computed over a field. The default field is
188
+ **𝔽₅** (`coefficients=None`); pass `coefficients="Z/p"`, `("Z/p", p)`, or an int prime `p`
189
+ for another prime, or `coefficients="Z"` for arithmetic over ℚ with integer relative
190
+ homology (torsion reported). `want_homology=True` adds `H_*(P1, P0)`; `want_full_index_map`
191
+ adds the full (unreduced) `f*` matrices. The RCF over any prime presents the shift-
192
+ equivalence class of `f*` (Franks–Richeson 2000) canonically and index-pair-independently.
193
+
194
+ ### Practical scale and dimension limits
195
+
196
+ - **Dimension:** typical `d = 2–4`; supported to `d = 8`, hard cap `d = 16`. The
197
+ Morse-graph pipeline is viable to the cap; collar-based index pairs and cubical homology
198
+ are practically confined to `d ≲ 8–10` by the `3^d` closure factors.
199
+ - **Cells:** up to ~10⁸ leaves; use the implicit edge mode at large scale (edges dominate
200
+ memory). Keys pack into 128 bits at these depths (`Key128`), narrower grids use `uint64`.
201
+
202
+ ### Performance & parallelism
203
+
204
+ The cell-map build parallelizes deterministically (`Model(threads=...)`, default all
205
+ cores): map evaluation over cells for C++ analytic oracles, plus CSR coverage assembly for
206
+ any map. Results are **bit-identical to serial for any thread count**. Measured cell-map-
207
+ build speedup ≥ 3× at 8 threads on a 6-physical-core Intel Mac (Leslie 2D/3D, ~10⁶ cells);
208
+ Python-callback maps evaluate serially under the GIL (batch them; a C++ oracle is the hot
209
+ path). Reproduce with `python scripts/benchmark.py`. Full numbers, latencies, memory-per-
210
+ cell vs design §4, and the honest `(d, depth, N)` envelope:
211
+ [`docs/performance.md`](docs/performance.md).
212
+
213
+ ## Examples
214
+
215
+ All scripts are standalone, headless, and deterministic (`python examples/<name>.py`;
216
+ outputs go to `$CMGRAPH_EXAMPLE_OUTDIR`, default a temp dir):
217
+
218
+ | Script | Shows |
219
+ | --- | --- |
220
+ | [`leslie_2d.py`](examples/leslie_2d.py) | 2D Leslie map: rigorous box map, Morse graph + plots |
221
+ | [`leslie_3d.py`](examples/leslie_3d.py) | 3D Leslie map: Morse sets projected + a 3D view |
222
+ | [`henon.py`](examples/henon.py) | Hénon map: coarse recurrent set merging attractor + exterior saddle |
223
+ | [`leslie_dataset.py`](examples/leslie_dataset.py) | dataset-driven (`BoxMapData`) vs the analytic map |
224
+ | [`interactive_refinement.py`](examples/interactive_refinement.py) | compute → inspect → refine, state retention via the f-counter |
225
+ | [`skip_conley.py`](examples/skip_conley.py) | the skip-homology fast path vs eager index |
226
+ | [`conley_index.py`](examples/conley_index.py) | a genuine degree-1 Conley index of the Hénon saddle |
227
+
228
+ ## Documentation
229
+
230
+ - [`docs/index.md`](docs/index.md) — documentation entry point.
231
+ - Design records (the theory reference):
232
+ [`docs/design/01-data-structures.md`](docs/design/01-data-structures.md),
233
+ [`02-engine.md`](docs/design/02-engine.md),
234
+ [`03-index-pair-isolation.md`](docs/design/03-index-pair-isolation.md),
235
+ [`04-conley-index-map.md`](docs/design/04-conley-index-map.md).
236
+ - [`docs/annotations.md`](docs/annotations.md) — the A1–A10 structural-annotation audit map.
237
+ - [`docs/performance.md`](docs/performance.md) — measured parallel speedup, latencies, and
238
+ the memory/scale envelope (reproduce with `scripts/benchmark.py`).
239
+
240
+ ## Test
241
+
242
+ ```bash
243
+ pytest tests/ # Python + example integration tests
244
+ ```
245
+
246
+ C++ unit tests run through CTest from a `-DCMGRAPH_BUILD_TESTS=ON` CMake build.
247
+
248
+ ## Persistence (planned)
249
+
250
+ Saving and loading the full computed state (grid keys, image caches, Morse graph) is a
251
+ planned future feature, not shipped in this version. The design does not preclude it: the
252
+ flat-array data structures (design 01 §2.2 / §6) are laid out so a versioned save/load can
253
+ be added without changing the core. For now, re-run the pipeline from the model spec.
254
+
255
+ ## License
256
+
257
+ MIT — see [`LICENSE`](LICENSE).