Skip to content

feat(experiments)!: modernize Python experiment workflows - #145

Open
acgetchell wants to merge 2 commits into
mainfrom
feat/143-python-314-pytorch-migration
Open

feat(experiments)!: modernize Python experiment workflows#145
acgetchell wants to merge 2 commits into
mainfrom
feat/143-python-314-pytorch-migration

Conversation

@acgetchell

@acgetchell acgetchell commented Aug 2, 2026

Copy link
Copy Markdown
Owner
  • migrate the retained MNIST example from TensorFlow to a deterministic CPU PyTorch baseline on Python 3.14
  • make MNIST and initializer runs local-first and failure-atomic, with hashed artifacts, source provenance, and optional Comet mirroring
  • lock cross-platform CPU dependencies while preserving the lightweight default development environment
  • clarify CDT++'s continued role as a maintained scientific reference

BREAKING CHANGE: Optional Python tooling now requires CPython 3.14 and uses PyTorch instead of TensorFlow for MNIST.

Closes #143

Summary by CodeRabbit

  • New Features
    • Added a CPU-portable PyTorch MNIST experiment with deterministic results, configurable training options, checkpoints, run records, and optional Comet integration.
    • Added local-first experiment artifact generation, provenance tracking, manifests, and offline/online reporting options.
  • Tests
    • Added automated checks for experiments, packaged entry points, reproducibility, artifact handling, and cleanup behavior across major operating systems.
  • Documentation
    • Clarified maintenance, contribution, experiment, release, and repository lifecycle guidance.
  • Chores
    • Updated supported Python version to 3.14 and refreshed experiment dependencies and tooling.
    • Added spelling validation and expanded continuous integration checks.

- migrate the retained MNIST example from TensorFlow to a deterministic CPU PyTorch baseline on Python 3.14
- make MNIST and initializer runs local-first and failure-atomic, with hashed artifacts, source provenance, and optional Comet mirroring
- lock cross-platform CPU dependencies while preserving the lightweight default development environment
- clarify CDT++'s continued role as a maintained scientific reference

BREAKING CHANGE: Optional Python tooling now requires CPython 3.14 and uses PyTorch instead of TensorFlow for MNIST.

Closes #143
@acgetchell acgetchell self-assigned this Aug 2, 2026
@acgetchell
acgetchell enabled auto-merge August 2, 2026 05:40
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The project moves to Python 3.14, replaces TensorFlow experiments with PyTorch, adds deterministic local artifact workflows with optional Comet integration, expands package and experiment validation, and updates repository lifecycle documentation.

Changes

Python experiment modernization

Layer / File(s) Summary
Lifecycle and contribution policy
.github/CONTRIBUTING.md, .github/ISSUE_TEMPLATE/feature_request.md, README.md, docs/RELEASING.md, docs/multithreading.md
Documentation now describes maintained repository status, separate archival decisions, release deposits, and Python experiment ownership.
Python tooling and CI checks
.github/workflows/*, .python-version, Justfile, pyproject.toml, ty.toml, scripts/pkgx-build.sh, scripts/tests/*, typos.toml, semgrep.yaml, docs/Doxyfile, include/Utilities.hpp, tests/*
Python 3.14 and PyTorch tooling are configured. Just recipes build and smoke-test packages, run offline experiment checks, and enforce spelling and workflow rules.
PyTorch MNIST experiment
scripts/mnist_experiment.py, scripts/experiment_tests/*, scripts/tests/test_mnist_experiment.py
The MNIST command validates inputs, runs deterministic CPU training, publishes artifacts atomically, and supports optional Comet logging.
Initializer artifact lifecycle
scripts/optimize_initialize.py, scripts/tests/test_optimize_initialize.py
Initializer sweeps now create staged local artifacts with provenance, hashes, manifests, cleanup, and optional Comet mirroring.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

  • acgetchell/CDT-plusplus#104 — The experiment modernization overlaps the issue’s objectives around the Python and hosted-experiment surface.

Possibly related PRs

Suggested labels: dependencies, github_actions

Poem

Python climbs to fourteen bright,
Torch trains by CPU light.
Local records safely land,
Comet waits at your command.
Checks run clean across the suite.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses most objectives in issue #143, but no uv.lock update is shown for the required Python 3.14 dependency migration. Regenerate and commit uv.lock with Python 3.14-compatible PyTorch and Comet dependencies, then verify the lockfile checks.
Out of Scope Changes check ⚠️ Warning The PR includes unrelated typo-only edits in Doxygen, C++ documentation, and C++ test descriptions. Remove unrelated typo and test-description edits, or link them to separate issues.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary Python experiment workflow modernization.
Docstring Coverage ✅ Passed Docstring coverage is 94.52% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/143-python-314-pytorch-migration

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Justfile`:
- Around line 260-293: Update the venv creation command in the
python-package-check recipe to use the pinned {{ python_version }} variable
instead of the hard-coded 3.14 value, preserving the existing consumer
environment and smoke-test flow.

In `@scripts/experiment_tests/test_comet_pytorch.py`:
- Around line 59-66: Update the checkpoint filename comparison in the upload
assertion to normalize platform-specific separators before comparing, using the
recorded fileName value emitted by Comet on Windows (backslash) and POSIX
systems (slash). Preserve the existing groupingName and upload_type checks, and
ensure the normalized path still matches the model-data parent segment and
comet-torch-model.pth basename.
- Line 58: Update the histogram assertion in the test to verify that at least
one upload has upload_type equal to "histogram3d", matching the neighbouring
any-based assertions, instead of requiring exactly four uploads.

In `@scripts/experiment_tests/test_mnist_training.py`:
- Around line 31-41: Update test_training_is_replayable_on_synthetic_cpu_data
and the _train_once return type to use a NamedTuple with named metric and
weights fields. Replace positional tuple slicing and indexing with the
corresponding named fields while preserving the existing equality checks and
per-parameter weight comparisons.

In `@scripts/mnist_experiment.py`:
- Around line 328-331: Remove the initial _write_json call for
configuration.json inside the _staged_run_directory block, and define a single
configuration_path variable for that artifact. Reuse configuration_path at the
later configuration payload write near the Comet/finalization flow, preserving
the payload that includes torch and torchvision versions.
- Around line 222-232: Update _dataset_manifest to sort retained files by their
recorded POSIX path string rather than by Path object ordering, ensuring
deterministic cross-platform manifest and run.json output while preserving the
existing manifest fields.
- Around line 281-323: Replace the ANN401-triggering Any annotations in
_build_model, _train_epoch, and _evaluate with concrete torch-related types
declared through the existing TYPE_CHECKING pattern, using ModuleType for
torch_module and appropriate module, data-loader, loss-function, and optimizer
types. Preserve runtime importability without importing torch; if Any must
remain, add scoped ANN401 suppressions to each affected parameter.
- Around line 409-423: Handle the MNIST dataset-construction failure within the
experiment flow used by main, including RuntimeError raised by datasets.MNIST
when raw files are missing or invalid, and convert it to the existing
user-facing ValueError path. Preserve the current exit code 2 behavior and avoid
exposing a traceback for unavailable or incomplete data.

In `@scripts/optimize_initialize.py`:
- Around line 470-472: Introduce a dedicated OutputDirectoryExistsError subclass
of ValueError and have _staged_run_directory raise it for the existing
output-directory condition. Update the handler around the sweep to catch only
OutputDirectoryExistsError, preserving its current stderr message and exit code
while allowing unrelated ValueError failures from _write_json or
_experiment_provenance to propagate with diagnostics.

In `@scripts/tests/test_mnist_experiment.py`:
- Around line 62-73: Update test_failed_run_does_not_publish_partial_artifacts
to assert that the temporary root directory contains no entries after the failed
staged run, rather than globbing for the _staged_run_directory prefix. Preserve
the existing assertions for the final output directory and exception.

In `@scripts/tests/test_optimize_initialize.py`:
- Around line 188-189: Update the test around _experiment_provenance to also
patch scripts.optimize_initialize.shutil.which, returning a valid git path so
the test does not depend on git being installed or available on PATH. Keep the
existing qx mock and provenance assertions unchanged.
- Around line 107-128: Extend the test around _run_parameter_sweep to assert
that plotter.clf() is called once after each parameter pair and that
experiment.log_figure is invoked for the Comet mirror. Keep the existing local
artifact and provenance assertions unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 02d36201-716a-4b5a-913a-106f011341be

📥 Commits

Reviewing files that changed from the base of the PR and between 9cf5b9d and e171691.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (36)
  • .github/CONTRIBUTING.md
  • .github/ISSUE_TEMPLATE/feature_request.md
  • .github/workflows/python-experiments.yml
  • .python-version
  • Justfile
  • README.md
  • docs/RELEASING.md
  • docs/multithreading.md
  • pyproject.toml
  • scripts/bootstrap_vcpkg.py
  • scripts/experiment_tests/__init__.py
  • scripts/experiment_tests/test_comet_pytorch.py
  • scripts/experiment_tests/test_mnist_training.py
  • scripts/generate_changelog.py
  • scripts/generate_reference_fixtures.py
  • scripts/mnist_experiment.py
  • scripts/optimize_initialize.py
  • scripts/pkgx-build.sh
  • scripts/release_check.py
  • scripts/semgrep_fixture_config.py
  • scripts/subprocess_utils.py
  • scripts/sync_vcpkg_tool_pins.py
  • scripts/tag_release.py
  • scripts/tests/test_bootstrap_vcpkg.py
  • scripts/tests/test_experiment_imports.py
  • scripts/tests/test_generate_changelog.py
  • scripts/tests/test_generate_reference_fixtures.py
  • scripts/tests/test_justfile_discoverability.py
  • scripts/tests/test_mnist_experiment.py
  • scripts/tests/test_optimize_initialize.py
  • scripts/tests/test_release_check.py
  • scripts/tests/test_sync_vcpkg_tool_pins.py
  • scripts/tests/test_tag_release.py
  • scripts/tests/test_validate_reference_fixtures.py
  • scripts/validate_reference_fixtures.py
  • ty.toml
💤 Files with no reviewable changes (15)
  • scripts/validate_reference_fixtures.py
  • scripts/tests/test_validate_reference_fixtures.py
  • scripts/subprocess_utils.py
  • scripts/bootstrap_vcpkg.py
  • scripts/tests/test_sync_vcpkg_tool_pins.py
  • scripts/tag_release.py
  • scripts/tests/test_tag_release.py
  • scripts/tests/test_generate_reference_fixtures.py
  • scripts/release_check.py
  • scripts/tests/test_generate_changelog.py
  • scripts/tests/test_release_check.py
  • scripts/generate_reference_fixtures.py
  • scripts/tests/test_bootstrap_vcpkg.py
  • scripts/generate_changelog.py
  • scripts/sync_vcpkg_tool_pins.py

Comment thread Justfile
Comment thread scripts/experiment_tests/test_comet_pytorch.py Outdated
Comment thread scripts/experiment_tests/test_comet_pytorch.py
Comment thread scripts/experiment_tests/test_mnist_training.py Outdated
Comment thread scripts/mnist_experiment.py
Comment on lines 409 to 423
def main(argv: Sequence[str] | None = None) -> int:
"""Run the MNIST experiment from an installed uv entry point."""
_parse_args(sys.argv[1:] if argv is None else argv)
config = _config_from_args(_parse_args(sys.argv[1:] if argv is None else argv))
try:
_run_experiment()
_run_experiment(config)
except ModuleNotFoundError as error:
print(
f"Missing experiment dependency {error.name!r}; run `just python-sync-experiments`, then retry with `uv run --no-sync cdt-mnist-experiment`.",
file=sys.stderr,
)
return 2
except ValueError as error:
print(str(error), file=sys.stderr)
return 2
return 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect the torchvision MNIST download/verify path for the failure type raised when the dataset is absent.
set -euo pipefail

# Locate the installed torchvision MNIST module, if vendored or available in the repo tree.
fd -i 'mnist.py' --type f | head -20

# Confirm how the repository documents the --no-download failure mode.
rg -n -C3 -- '--no-download|no_download|Dataset not found' --glob '!**/.git/**'

Repository: acgetchell/CDT-plusplus

Length of output: 268


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- candidate MNIST files ---'
fd -i 'mnist.py' --type f | head -20 || true

printf '%s\n' '--- experiment and dependency references ---'
rg -n -C3 --glob '!**/.git/**' -- '--no-download|no_download|torchvision|MNIST|Dataset not found' . | head -240

printf '%s\n' '--- dependency declarations ---'
rg -n -C3 --glob 'pyproject.toml' --glob '*lock*' --glob '*.txt' -- 'torchvision|torch==' . | head -160

Repository: acgetchell/CDT-plusplus

Length of output: 21348


🌐 Web query:

torchvision 0.28.0 MNIST source download verify RuntimeError "Dataset not found. You can use download=True to download it"

💡 Result:

The RuntimeError: Dataset not found. You can use download=True to download it typically occurs because the MNIST dataset files are missing, corrupted, or unreachable due to network or mirror issues [1][2][3]. If you are encountering this error despite setting download=True, consider the following troubleshooting steps: 1. Clear Corrupted Data: If files exist but are invalid, torchvision may fail to load them [4]. Delete the existing MNIST directory (usually located in your specified root/MNIST) and attempt to run the script again to trigger a fresh download [4]. 2. Network or Mirror Issues: The MNIST dataset download relies on pre-defined mirrors [1][5]. Historically, these mirrors (including the official LeCun repository) have faced intermittent outages, SSL certificate issues, or 403 Forbidden errors [6][7]. Recent updates to torchvision (such as PR #9411, merged in early 2026) have improved error handling for these downloads [8][9]. Ensure you are using the latest version of torchvision to benefit from these fixes. 3. Verify Directory Structure: torchvision expects a specific directory structure [1][10]. It looks for raw files (e.g., train-images-idx3-ubyte) in the root/MNIST/raw folder [10]. If you are manually providing the dataset, ensure your directory paths and file names strictly follow this expected structure [3]. 4. Manual Download: If automated downloads continue to fail, you can manually download the standard MNIST files and place them into the correct directory (root/MNIST/raw) [4]. Once the files are present, you can set download=False in your dataset initialization [3]. If you are still unable to download the dataset, verify that your environment has stable internet access and is not behind a proxy or firewall that might be blocking the download mirrors [6][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

url='https://raw.githubusercontent.com/pytorch/vision/v0.28.0/torchvision/datasets/mnist.py'
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
curl --fail --silent --show-error "$url" -o "$tmp"

printf '%s\n' '--- torchvision v0.28.0 MNIST failure path ---'
rg -n -C8 'def _check_exists|Dataset not found|def __init__|raw_folder|resources' "$tmp"

printf '%s\n' '--- repository implementation context ---'
sed -n '326,347p' scripts/mnist_experiment.py
sed -n '116,134p' scripts/mnist_experiment.py

Repository: acgetchell/CDT-plusplus

Length of output: 14418


Handle missing or incomplete MNIST data with a user-facing error.

With torchvision 0.28.0, datasets.MNIST(..., download=False) raises RuntimeError("Dataset not found. You can use download=True to download it") when the required raw files are absent or fail integrity checks. Checking only data_directory / "MNIST" is insufficient for incomplete or corrupt data. Convert this constructor failure to ValueError, or handle it explicitly in main, so the command returns exit code 2 without a traceback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/mnist_experiment.py` around lines 409 - 423, Handle the MNIST
dataset-construction failure within the experiment flow used by main, including
RuntimeError raised by datasets.MNIST when raw files are missing or invalid, and
convert it to the existing user-facing ValueError path. Preserve the current
exit code 2 behavior and avoid exposing a traceback for unavailable or
incomplete data.

Comment thread scripts/optimize_initialize.py Outdated
Comment thread scripts/tests/test_mnist_experiment.py Outdated
Comment thread scripts/tests/test_optimize_initialize.py
Comment thread scripts/tests/test_optimize_initialize.py Outdated
- reject overlapping MNIST data and output paths before filesystem effects
- make experiment manifests, Comet integration, and failure handling portable
- align Just with 1.58.0 and add pinned spelling checks
- restrict CI to its required actions and separate security dependency updates

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
scripts/tests/test_justfile_discoverability.py (1)

109-131: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Cover every UV-backed recipe in the guard test.

The test checks only the hard-coded recipe lists. The Justfile also contains UV-backed recipes such as release-check, changelog-unreleased, tag-check, tag, semgrep, and semgrep-test. A future omission of _sync-python-dev or _ensure-uv in one of those recipes can pass this test.

Derive candidates from the parsed recipe bodies, or keep the list exhaustive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/tests/test_justfile_discoverability.py` around lines 109 - 131,
Expand test_uv_backed_recipes_reuse_pinned_guards to cover every recipe that
invokes UV, preferably by deriving candidates from parsed recipe bodies and
asserting each reaches _ensure-uv through the appropriate sync dependency. If
retaining explicit lists, add all UV-backed recipes such as release-check,
changelog-unreleased, tag-check, tag, semgrep, and semgrep-test, preserving the
existing guard assertions.
Justfile (1)

285-298: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add .exe to Windows entry-point paths.

When the Windows branch is selected, invoke the four commands as .venv/Scripts/*.exe. uv installs [project.scripts] console entry points as Windows executables, and Bash does not resolve the extensionless absolute paths through PATHEXT. The smoke test can therefore fail on Windows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Justfile` around lines 285 - 298, Update the Windows branch of the Python
entry-point setup so scripts_directory points to the executable paths under
.venv/Scripts, including the .exe suffix. Ensure the invocations of
cdt-bootstrap-vcpkg, cdt-optimize-initialize, cdt-mnist-experiment, and
cdt-tag-release use those Windows executable paths while preserving the Unix
branch unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Justfile`:
- Around line 285-298: Update the Windows branch of the Python entry-point setup
so scripts_directory points to the executable paths under .venv/Scripts,
including the .exe suffix. Ensure the invocations of cdt-bootstrap-vcpkg,
cdt-optimize-initialize, cdt-mnist-experiment, and cdt-tag-release use those
Windows executable paths while preserving the Unix branch unchanged.

In `@scripts/tests/test_justfile_discoverability.py`:
- Around line 109-131: Expand test_uv_backed_recipes_reuse_pinned_guards to
cover every recipe that invokes UV, preferably by deriving candidates from
parsed recipe bodies and asserting each reaches _ensure-uv through the
appropriate sync dependency. If retaining explicit lists, add all UV-backed
recipes such as release-check, changelog-unreleased, tag-check, tag, semgrep,
and semgrep-test, preserving the existing guard assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3bfc8c8a-af62-4a0b-8d49-879ad80cc23b

📥 Commits

Reviewing files that changed from the base of the PR and between e171691 and b3acb49.

📒 Files selected for processing (18)
  • .github/dependabot.yml
  • .github/workflows/ci.yml
  • .github/workflows/python-experiments.yml
  • Justfile
  • README.md
  • docs/Doxyfile
  • include/Utilities.hpp
  • scripts/experiment_tests/test_comet_pytorch.py
  • scripts/experiment_tests/test_mnist_training.py
  • scripts/mnist_experiment.py
  • scripts/optimize_initialize.py
  • scripts/tests/test_justfile_discoverability.py
  • scripts/tests/test_mnist_experiment.py
  • scripts/tests/test_optimize_initialize.py
  • semgrep.yaml
  • tests/Foliated_triangulation_test.cpp
  • tests/Manifold_test.cpp
  • typos.toml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate Python to 3.14 and replace TensorFlow with PyTorch

1 participant