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.
- cmgraph-0.0.1/.clang-format +4 -0
- cmgraph-0.0.1/.github/workflows/ci.yml +126 -0
- cmgraph-0.0.1/.github/workflows/publish.yml +104 -0
- cmgraph-0.0.1/.gitignore +36 -0
- cmgraph-0.0.1/CLAUDE.md +20 -0
- cmgraph-0.0.1/CMakeLists.txt +76 -0
- cmgraph-0.0.1/LICENSE +21 -0
- cmgraph-0.0.1/PKG-INFO +257 -0
- cmgraph-0.0.1/PLAN.md +355 -0
- cmgraph-0.0.1/README.md +217 -0
- cmgraph-0.0.1/Specifications.md +30 -0
- cmgraph-0.0.1/docs/annotations.md +36 -0
- cmgraph-0.0.1/docs/design/01-data-structures.md +871 -0
- cmgraph-0.0.1/docs/design/02-engine.md +475 -0
- cmgraph-0.0.1/docs/design/03-index-pair-isolation.md +473 -0
- cmgraph-0.0.1/docs/design/04-conley-index-map.md +555 -0
- cmgraph-0.0.1/docs/index.md +31 -0
- cmgraph-0.0.1/docs/performance.md +103 -0
- cmgraph-0.0.1/examples/.gitkeep +0 -0
- cmgraph-0.0.1/examples/conley_index.py +157 -0
- cmgraph-0.0.1/examples/henon.py +105 -0
- cmgraph-0.0.1/examples/interactive_refinement.py +117 -0
- cmgraph-0.0.1/examples/leslie_2d.py +113 -0
- cmgraph-0.0.1/examples/leslie_3d.py +111 -0
- cmgraph-0.0.1/examples/leslie_dataset.py +132 -0
- cmgraph-0.0.1/examples/skip_conley.py +80 -0
- cmgraph-0.0.1/include/cmgraph/adapter/adapter.hpp +32 -0
- cmgraph-0.0.1/include/cmgraph/adapter/conley.hpp +286 -0
- cmgraph-0.0.1/include/cmgraph/adapter/error.hpp +40 -0
- cmgraph-0.0.1/include/cmgraph/adapter/index_pair.hpp +360 -0
- cmgraph-0.0.1/include/cmgraph/adapter/marshal.hpp +164 -0
- cmgraph-0.0.1/include/cmgraph/adapter/uniformize.hpp +434 -0
- cmgraph-0.0.1/include/cmgraph/dynamics/cache.hpp +313 -0
- cmgraph-0.0.1/include/cmgraph/dynamics/cell_map.hpp +916 -0
- cmgraph-0.0.1/include/cmgraph/dynamics/dataset.hpp +475 -0
- cmgraph-0.0.1/include/cmgraph/dynamics/dynamics.hpp +23 -0
- cmgraph-0.0.1/include/cmgraph/dynamics/error.hpp +22 -0
- cmgraph-0.0.1/include/cmgraph/dynamics/oracle.hpp +258 -0
- cmgraph-0.0.1/include/cmgraph/engine/engine.hpp +23 -0
- cmgraph-0.0.1/include/cmgraph/engine/error.hpp +24 -0
- cmgraph-0.0.1/include/cmgraph/engine/morse_engine.hpp +914 -0
- cmgraph-0.0.1/include/cmgraph/engine/morse_graph.hpp +288 -0
- cmgraph-0.0.1/include/cmgraph/engine/region.hpp +119 -0
- cmgraph-0.0.1/include/cmgraph/engine/spurious.hpp +348 -0
- cmgraph-0.0.1/include/cmgraph/geometry/box.hpp +46 -0
- cmgraph-0.0.1/include/cmgraph/geometry/error.hpp +22 -0
- cmgraph-0.0.1/include/cmgraph/geometry/geometry.hpp +23 -0
- cmgraph-0.0.1/include/cmgraph/geometry/grid.hpp +1081 -0
- cmgraph-0.0.1/include/cmgraph/geometry/key.hpp +581 -0
- cmgraph-0.0.1/include/cmgraph/geometry/lattice.hpp +86 -0
- cmgraph-0.0.1/include/cmgraph/geometry/refine.hpp +203 -0
- cmgraph-0.0.1/include/cmgraph/geometry/rounding.hpp +348 -0
- cmgraph-0.0.1/include/cmgraph/graph/digraph.hpp +358 -0
- cmgraph-0.0.1/include/cmgraph/graph/error.hpp +22 -0
- cmgraph-0.0.1/include/cmgraph/graph/graph.hpp +20 -0
- cmgraph-0.0.1/include/cmgraph/graph/scc.hpp +367 -0
- cmgraph-0.0.1/include/cmgraph/topology/bigint.hpp +300 -0
- cmgraph-0.0.1/include/cmgraph/topology/complex.hpp +319 -0
- cmgraph-0.0.1/include/cmgraph/topology/coreduce.hpp +144 -0
- cmgraph-0.0.1/include/cmgraph/topology/cube.hpp +216 -0
- cmgraph-0.0.1/include/cmgraph/topology/error.hpp +23 -0
- cmgraph-0.0.1/include/cmgraph/topology/field.hpp +171 -0
- cmgraph-0.0.1/include/cmgraph/topology/fp.hpp +158 -0
- cmgraph-0.0.1/include/cmgraph/topology/homology.hpp +264 -0
- cmgraph-0.0.1/include/cmgraph/topology/index_map.hpp +754 -0
- cmgraph-0.0.1/include/cmgraph/topology/linalg.hpp +244 -0
- cmgraph-0.0.1/include/cmgraph/topology/rcf.hpp +328 -0
- cmgraph-0.0.1/include/cmgraph/topology/snf.hpp +269 -0
- cmgraph-0.0.1/include/cmgraph/topology/topology.hpp +38 -0
- cmgraph-0.0.1/pyproject.toml +47 -0
- cmgraph-0.0.1/python/cmgraph/__init__.py +79 -0
- cmgraph-0.0.1/python/cmgraph/plot.py +456 -0
- cmgraph-0.0.1/scripts/benchmark.py +222 -0
- cmgraph-0.0.1/scripts/check_isolation.py +150 -0
- cmgraph-0.0.1/src/bindings.cpp +1747 -0
- cmgraph-0.0.1/tasks.md +1375 -0
- cmgraph-0.0.1/tests/cpp/CMakeLists.txt +35 -0
- cmgraph-0.0.1/tests/cpp/leslie_fixture.hpp +86 -0
- cmgraph-0.0.1/tests/cpp/test_cell_map.cpp +325 -0
- cmgraph-0.0.1/tests/cpp/test_conley.cpp +533 -0
- cmgraph-0.0.1/tests/cpp/test_conley_indexmap.cpp +225 -0
- cmgraph-0.0.1/tests/cpp/test_dataset.cpp +379 -0
- cmgraph-0.0.1/tests/cpp/test_dynamics_cache.cpp +390 -0
- cmgraph-0.0.1/tests/cpp/test_engine.cpp +601 -0
- cmgraph-0.0.1/tests/cpp/test_engine_refine.cpp +1316 -0
- cmgraph-0.0.1/tests/cpp/test_graph.cpp +592 -0
- cmgraph-0.0.1/tests/cpp/test_grid.cpp +468 -0
- cmgraph-0.0.1/tests/cpp/test_index_map.cpp +1033 -0
- cmgraph-0.0.1/tests/cpp/test_index_pair.cpp +501 -0
- cmgraph-0.0.1/tests/cpp/test_key_codec.cpp +438 -0
- cmgraph-0.0.1/tests/cpp/test_parallel.cpp +201 -0
- cmgraph-0.0.1/tests/cpp/test_refine.cpp +520 -0
- cmgraph-0.0.1/tests/cpp/test_rounding.cpp +500 -0
- cmgraph-0.0.1/tests/cpp/test_sanity.cpp +25 -0
- cmgraph-0.0.1/tests/cpp/test_spurious.cpp +251 -0
- cmgraph-0.0.1/tests/cpp/test_topology.cpp +764 -0
- cmgraph-0.0.1/tests/cpp/vendor/doctest/doctest.h +7106 -0
- cmgraph-0.0.1/tests/python/test_callback_oracle.py +94 -0
- cmgraph-0.0.1/tests/python/test_examples.py +138 -0
- cmgraph-0.0.1/tests/python/test_gc_lifetime.py +111 -0
- cmgraph-0.0.1/tests/python/test_import.py +15 -0
- cmgraph-0.0.1/tests/python/test_plot.py +356 -0
- cmgraph-0.0.1/tests/python/test_public_api.py +460 -0
- cmgraph-0.0.1/tests/python/test_threads.py +56 -0
|
@@ -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
|
cmgraph-0.0.1/.gitignore
ADDED
|
@@ -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/
|
cmgraph-0.0.1/CLAUDE.md
ADDED
|
@@ -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).
|