diff --git a/.github/workflows/matlab-parity-gate.yml b/.github/workflows/matlab-parity-gate.yml deleted file mode 100644 index 673994a..0000000 --- a/.github/workflows/matlab-parity-gate.yml +++ /dev/null @@ -1,144 +0,0 @@ -name: MATLAB Parity Gate - -on: - pull_request: - push: - branches: - - main - - master - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - parity-gate: - # Requires a self-hosted macOS runner with MATLAB installed at: - # /Applications/MATLAB_R2025b.app/bin/matlab - runs-on: [self-hosted, macOS] - env: - RUNNER_TOOL_CACHE: /Users/iahncajigas/actions-runner/_work/_tool - AGENT_TOOLSDIRECTORY: /Users/iahncajigas/actions-runner/_work/_tool - RUNNER_TEMP: /Users/iahncajigas/actions-runner/_work/_temp - TMPDIR: /Users/iahncajigas/actions-runner/_work/_temp - ACTIONS_RUNNER_SVC: "1" - NSTAT_MATLAB_EXTRA_ARGS: -maca64 -nodisplay -noFigureWindows -softwareopengl - NSTAT_FORCE_M_HELP_SCRIPTS: "1" - NSTAT_MATLAB_TOPIC_MAX_ATTEMPTS: "2" - NSTAT_PARITY_RETRY_TIMEOUT_BLOCKS: "1" - NSTAT_PARITY_TIMEOUT_RETRY_BLOCKS: timeout_front - NSTAT_PARITY_RETRY_RECOVERABLE_BLOCKS: "1" - NSTAT_PARITY_RECOVERABLE_RETRY_BLOCKS: graphics_mid,heavy_tail - steps: - - name: Prepare runner directories - run: | - mkdir -p "$RUNNER_TOOL_CACHE" "$RUNNER_TEMP" /Users/iahncajigas/actions-runner/_work - - - name: Checkout - uses: actions/checkout@v4 - with: - lfs: true - - - name: MATLAB smoke preflight - run: /Applications/MATLAB_R2025b.app/bin/matlab $NSTAT_MATLAB_EXTRA_ARGS -batch "disp(version); exit" - - - name: Pull LFS Objects - run: git lfs pull - - - name: Show Python runtime - run: | - python3 --version - python3 -m pip --version - - - name: Install package and dev dependencies - run: | - python3 -m pip install --upgrade pip - python3 -m pip install -e ./python[dev] - - - name: Generate docs/notebooks/parity prerequisites - run: | - python3 python/tools/generate_help_topic_docs.py - python3 python/tools/generate_example_notebooks.py - python3 python/tools/generate_method_parity_matrix.py - python3 python/tools/generate_implemented_method_coverage.py - - - name: Parity Stage A (core + timeout-front) - run: | - python/tools/run_parity_ladder.sh core_smoke timeout_front - - - name: Parity Stage B (graphics-mid + heavy-tail) - run: | - python/tools/run_parity_ladder.sh graphics_mid heavy_tail - - - name: Parity Stage C (full-suite gate report) - env: - NSTAT_MATLAB_FORCE_TOPIC_ISOLATION: "1" - NSTAT_MATLAB_HARD_CLEANUP_ON_FAILURE: "1" - run: | - python3 python/tools/verify_python_vs_matlab_similarity.py --enforce-gate - - - name: Summarize full-suite parity report - if: ${{ always() && !cancelled() }} - run: | - if [ -f python/reports/python_vs_matlab_similarity_report.json ]; then - python3 python/tools/summarize_parity_report.py python/reports/python_vs_matlab_similarity_report.json --json > python/reports/python_vs_matlab_similarity_summary.json - else - echo "Parity report missing; skipping summary." - fi - - - name: Freeze similarity baseline - if: ${{ always() && !cancelled() }} - run: | - if [ -f python/reports/python_vs_matlab_similarity_report.json ]; then - python3 python/tools/freeze_similarity_baseline.py - else - echo "Similarity report missing; skipping baseline freeze." - fi - - - name: Collect MATLAB crash diagnostics - if: ${{ failure() && !cancelled() }} - continue-on-error: true - run: | - set -euo pipefail - OUT_DIR="python/reports/matlab_crash_diagnostics" - mkdir -p "$OUT_DIR/diagnostic_reports" "$OUT_DIR/mathworks_crash_reports" - - # Collect macOS crash/ips reports (latest 20) when MATLAB crashes. - DIAG_DIR="$HOME/Library/Logs/DiagnosticReports" - if [ -d "$DIAG_DIR" ]; then - find "$DIAG_DIR" -maxdepth 1 -type f \( -name '*MATLAB*.crash' -o -name '*matlab*.crash' -o -name '*MATLAB*.ips' -o -name '*matlab*.ips' \) -print \ - | sort \ - | tail -n 20 \ - | while IFS= read -r f; do cp -f "$f" "$OUT_DIR/diagnostic_reports/" || true; done - fi - - # Collect MathWorks CrashReporter payload files (latest 50). - MW_APP_SUPPORT="$HOME/Library/Application Support/MathWorks" - if [ -d "$MW_APP_SUPPORT" ]; then - find "$MW_APP_SUPPORT" -type d -name crash_reports -print \ - | while IFS= read -r crash_dir; do - find "$crash_dir" -type f -print || true - done \ - | sort \ - | tail -n 50 \ - | while IFS= read -r f; do cp -f "$f" "$OUT_DIR/mathworks_crash_reports/" || true; done - fi - - # Keep staged block reports if present for triage context. - ls -1 python/reports/parity_block*.json > "$OUT_DIR/parity_block_report_manifest.txt" 2>/dev/null || true - - - name: Upload parity reports - if: ${{ always() && !cancelled() }} - continue-on-error: true - uses: actions/upload-artifact@v4 - with: - name: nstat-matlab-parity-reports - if-no-files-found: warn - path: | - python/reports/python_vs_matlab_similarity_report.json - python/reports/python_vs_matlab_similarity_baseline.json - python/reports/python_vs_matlab_similarity_summary.json - python/reports/parity_block*.json - python/reports/parity_retry_summary.json - python/reports/matlab_crash_diagnostics/** diff --git a/.github/workflows/matlab-repo-integrity.yml b/.github/workflows/matlab-repo-integrity.yml new file mode 100644 index 0000000..faadfb1 --- /dev/null +++ b/.github/workflows/matlab-repo-integrity.yml @@ -0,0 +1,103 @@ +name: MATLAB Repo Integrity + +on: + pull_request: + push: + branches: + - master + - main + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + integrity: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate MATLAB-only repository structure + run: | + python - <<'PY' + from __future__ import annotations + + import json + import sys + import xml.etree.ElementTree as ET + from pathlib import Path + + root = Path('.') + helpfiles = root / 'helpfiles' + toc_path = helpfiles / 'helptoc.xml' + + errors: list[str] = [] + warnings: list[str] = [] + + if not toc_path.exists(): + errors.append(f"Missing required help TOC: {toc_path}") + topics = [] + else: + tree = ET.parse(toc_path) + toc = tree.getroot() + examples_node = None + for item in toc.iter('tocitem'): + if item.attrib.get('id') == 'nstat_examples': + examples_node = item + break + if examples_node is None: + errors.append('Could not find examples node id="nstat_examples" in helptoc.xml') + topics = [] + else: + topics = [Path(item.attrib.get('target', '')).stem for item in examples_node.findall('tocitem') if item.attrib.get('target')] + + missing_m = [stem for stem in topics if not (helpfiles / f'{stem}.m').exists()] + missing_mlx = [stem for stem in topics if not (helpfiles / f'{stem}.mlx').exists()] + mlx_total = len(list(helpfiles.glob('*.mlx'))) if helpfiles.exists() else 0 + + if missing_m: + errors.append(f"Missing MATLAB .m example scripts for TOC example topics: {missing_m}") + if mlx_total == 0: + errors.append('No .mlx files found in helpfiles/; expected MATLAB live examples to be retained.') + if missing_mlx: + warnings.append(f"Missing .mlx for some TOC example topics ({len(missing_mlx)}): {missing_mlx}") + + if (root / 'python').exists(): + errors.append('Unexpected python/ subtree present in MATLAB-only repository.') + if (root / '.github' / 'workflows' / 'python-ci.yml').exists(): + errors.append('Unexpected Python CI workflow present in MATLAB-only repository.') + if (root / '.github' / 'workflows' / 'matlab-parity-gate.yml').exists(): + errors.append('Unexpected MATLAB parity gate workflow present in MATLAB-only repository.') + + payload = { + 'toc_exists': toc_path.exists(), + 'example_topic_count': len(topics), + 'helpfiles_m_count': len(list(helpfiles.glob('*.m'))) if helpfiles.exists() else 0, + 'helpfiles_mlx_count': mlx_total, + 'missing_example_m': missing_m, + 'missing_example_mlx': missing_mlx, + 'warnings': warnings, + 'errors': errors, + 'pass': len(errors) == 0, + } + + out = root / 'matlab_repo_integrity.json' + out.write_text(json.dumps(payload, indent=2), encoding='utf-8') + print(json.dumps(payload, indent=2)) + + for w in warnings: + print(f"::warning::{w}") + for e in errors: + print(f"::error::{e}") + + sys.exit(0 if not errors else 1) + PY + + - name: Upload integrity report + if: always() + uses: actions/upload-artifact@v4 + with: + name: matlab-repo-integrity + path: matlab_repo_integrity.json diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml deleted file mode 100644 index 5f1bed1..0000000 --- a/.github/workflows/python-ci.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: Python CI - -on: - pull_request: - push: - branches: - - main - - master - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - test-and-build: - runs-on: ubuntu-latest - env: - # Keep Python CI runnable even when Git LFS budget is exhausted. - # Data-heavy example loaders fall back to deterministic synthetic data in this mode. - NSTAT_ALLOW_SYNTHETIC_DATA: "1" - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - lfs: false - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install package and dev dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -e ./python[dev] - - - name: Generate docs/notebooks/parity reports - run: | - python python/tools/generate_help_topic_docs.py - python python/tools/generate_example_notebooks.py - python python/tools/freeze_port_baseline.py - python python/tools/generate_method_parity_matrix.py - python python/tools/generate_implemented_method_coverage.py - - - name: Verify help docs coverage - run: | - python python/tools/verify_help_docs_coverage.py - - - name: Validate notebooks - run: | - python python/tools/verify_examples_notebooks.py - - - name: Build Sphinx docs - run: | - sphinx-build -b html python/docs python/docs/_build/html - - - name: Run offline standalone check (quick) - run: | - python python/tools/verify_offline_standalone.py - - - name: Run pytest - id: pytest - continue-on-error: true - env: - NSTAT_CI_LIGHT: "1" - run: | - cd python - mkdir -p reports - set -o pipefail - python -m pytest -vv --maxfail=1 --durations=20 --junitxml=reports/pytest.xml 2>&1 | tee reports/pytest.log - - - name: Upload reports - if: always() - uses: actions/upload-artifact@v4 - with: - name: nstat-python-reports - path: | - python/reports/*.json - python/reports/pytest.xml - python/reports/pytest.log - - - name: Enforce pytest pass - if: steps.pytest.outcome == 'failure' - run: | - python - <<'PY' - from pathlib import Path - import xml.etree.ElementTree as ET - - xml_path = Path("python/reports/pytest.xml") - log_path = Path("python/reports/pytest.log") - - if xml_path.exists(): - root = ET.parse(xml_path).getroot() - emitted = False - for testcase in root.iter("testcase"): - failures = list(testcase.findall("failure")) + list(testcase.findall("error")) - if not failures: - continue - emitted = True - case_id = f"{testcase.attrib.get('classname', '')}.{testcase.attrib.get('name', '')}".strip(".") - for fail in failures: - msg = (fail.attrib.get("message", "") or "pytest failure").replace("\n", " ").strip() - print(f"::error title={case_id}::{msg}") - text = (fail.text or "").strip() - if text: - print(text[:3000]) - if not emitted: - print("::error title=pytest::Pytest failed without testcase-level failures in junit XML") - else: - print("::error title=pytest::Missing python/reports/pytest.xml") - - if log_path.exists(): - tail = log_path.read_text(encoding="utf-8", errors="ignore")[-4000:] - print("---- pytest.log tail ----") - print(tail) - PY - exit 1 diff --git a/.gitignore b/.gitignore index 891574a..18a1148 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,3 @@ .svn __pycache__/ *.pyc -python/plots/ - -python/docs/_build/ -python/reports/parity_block*.json diff --git a/README.md b/README.md index 148f8e1..f76fb84 100644 --- a/README.md +++ b/README.md @@ -28,15 +28,12 @@ You can download the example data file from the paper at: https://doi.org/10.608 Python port ----------- -This repository now includes a standalone Python version in `python/`. +The standalone Python port now lives in a separate repository: -Quick start: +- https://github.com/cajigaslab/nSTAT-python -```bash -cd python -python3 -m pip install -e . -python3 examples/nstat_paper_examples.py --repo-root .. -``` +This `nSTAT` repository is MATLAB-focused and retains: -The Python paper example script is the equivalent workflow to `helpfiles/nSTATPaperExamples.m` (starting from Experiment 2) and uses local files from `data/`. -By default it writes plots to `python/plots/nstat_paper_examples/`. Use `--no-plots` for a metrics-only run. +- Original MATLAB class/source files +- MATLAB helpfiles and help index (`helpfiles/helptoc.xml`) +- MATLAB example workflows, including `.mlx` examples diff --git a/python/README.md b/python/README.md deleted file mode 100644 index 62049ef..0000000 --- a/python/README.md +++ /dev/null @@ -1,136 +0,0 @@ -# Python nSTAT - -This directory contains the standalone Python implementation of nSTAT. - -## Canonical API modules - -- `nstat.signal`: `Signal`, `Covariate` -- `nstat.spikes`: `SpikeTrain`, `SpikeTrainCollection` -- `nstat.events`: `Events` -- `nstat.history`: `HistoryBasis` -- `nstat.trial`: `CovariateCollection`, `TrialConfig`, `ConfigCollection`, `Trial` -- `nstat.cif`: `CIFModel` -- `nstat.analysis`: `Analysis` -- `nstat.fit`: `FitResult`, `FitSummary` -- `nstat.decoding`: `DecoderSuite` -- `nstat.datasets`: dataset registry and checksum verification - -MATLAB-style entry points remain importable as compatibility adapters with `DeprecationWarning` messages. - -## Install - -```bash -cd python -python3 -m pip install -e . -``` - -## Run paper examples equivalent - -```bash -cd python -python3 examples/nstat_paper_examples.py --repo-root .. -``` - -## Generate docs and notebooks - -```bash -python3 python/tools/generate_help_topic_docs.py -python3 python/tools/generate_example_notebooks.py -``` - -## Validation - -```bash -python3 python/tools/freeze_port_baseline.py -python3 python/tools/generate_method_parity_matrix.py -python3 python/tools/generate_implemented_method_coverage.py -python3 python/tools/verify_examples_notebooks.py -NSTAT_MATLAB_EXTRA_ARGS='-maca64 -nodisplay -noFigureWindows -softwareopengl' \ - python3 python/tools/verify_python_vs_matlab_similarity.py --enforce-gate -python3 python/tools/freeze_similarity_baseline.py -python3 python/tools/verify_offline_standalone.py -cd python && python3 -m pytest -``` - -If Git LFS assets are unavailable (for example, CI quota exhaustion), set -`NSTAT_ALLOW_SYNTHETIC_DATA=1` to use deterministic synthetic fallbacks for -data-heavy paper example loaders. - -### Local parity block debugging - -```bash -python3 python/tools/debug_parity_blocks.py \ - --set-actions-runner-svc \ - --matlab-extra-args "-maca64 -nodisplay -noFigureWindows -softwareopengl" -``` - -Single wrapper command (fail-fast ladder): - -```bash -python/tools/run_parity_ladder.sh -``` - -Single preflight command (Stage A ladder + selected Stage B topics): - -```bash -python/tools/run_parity_preflight.sh -``` - -Notes: - -- Runs blocks in order: `core_smoke -> timeout_front -> graphics_mid -> heavy_tail -> full_suite`. -- Exits immediately if a block regresses (`python_ok`, `matlab_ok`, scalar overlap, parity contract, or regression gate). -- Includes runtime regression guard using machine baseline block times with multiplier `NSTAT_PARITY_RUNTIME_MULTIPLIER` (default `2.5`). -- Set `NSTAT_PARITY_RUNTIME_MULTIPLIER=0` to disable runtime regression checks. -- Pass specific block names as args to run subset ladders, e.g.: - `python/tools/run_parity_ladder.sh core_smoke timeout_front`. -- Ladder writes retry telemetry to `python/reports/parity_retry_summary.json` (block, attempt count, retry reason, timeout-topic list). -- Retry behavior is controlled by `NSTAT_PARITY_RETRY_TIMEOUT_BLOCKS` and `NSTAT_PARITY_TIMEOUT_RETRY_BLOCKS`. -- Set `NSTAT_MATLAB_TOPIC_MAX_ATTEMPTS=2` to retry per-topic MATLAB timeouts/crashes once before failing. -- Set `NSTAT_PARITY_RETRY_RECOVERABLE_BLOCKS=1` and `NSTAT_PARITY_RECOVERABLE_RETRY_BLOCKS` to retry block failures caused by recoverable MATLAB failures (timeouts/crash signatures). -- Preflight topic selection can be overridden with `NSTAT_PARITY_PREFLIGHT_STAGEB_TOPICS`. - -See `python/docs/parity_runbook.rst` for the exact locally validated parity command set. - -Use targeted blocks to debug delays locally before running remote CI: - -```bash -# 1) Fast API/parity smoke -python3 python/tools/debug_parity_blocks.py --blocks core_smoke \ - --set-actions-runner-svc --matlab-extra-args "-maca64 -nodisplay -noFigureWindows -softwareopengl" - -# 2) Former timeout-prone front topics -python3 python/tools/debug_parity_blocks.py --blocks timeout_front \ - --set-actions-runner-svc --matlab-extra-args "-maca64 -nodisplay -noFigureWindows -softwareopengl" - -# 3) Graphics-sensitive middle topics -python3 python/tools/debug_parity_blocks.py --blocks graphics_mid \ - --set-actions-runner-svc --matlab-extra-args "-maca64 -nodisplay -noFigureWindows -softwareopengl" - -# 4) Heavy tail topics -python3 python/tools/debug_parity_blocks.py --blocks heavy_tail \ - --set-actions-runner-svc --matlab-extra-args "-maca64 -nodisplay -noFigureWindows -softwareopengl" - -# 5) Full gate-equivalent suite -python3 python/tools/debug_parity_blocks.py --blocks full_suite \ - --set-actions-runner-svc --matlab-extra-args "-maca64 -nodisplay -noFigureWindows -softwareopengl" -``` - -Summarize a parity report quickly: - -```bash -python3 python/tools/summarize_parity_report.py python/reports/parity_block_full_suite.json -``` - -Recent local baseline on this machine (MATLAB R2025b, no figure windows, software OpenGL): - -- `core_smoke`: ~47s -- `timeout_front`: ~122s -- `graphics_mid`: ~291s -- `heavy_tail`: ~385s -- `full_suite` (25 topics): ~826s - -## CI - -- `.github/workflows/python-ci.yml` runs docs, notebook verification, offline standalone checks, and `pytest`. -- `.github/workflows/matlab-parity-gate.yml` runs MATLAB/Python parity gate on self-hosted macOS runners with MATLAB installed. diff --git a/python/RELEASE_CHECKLIST.md b/python/RELEASE_CHECKLIST.md deleted file mode 100644 index b923b0f..0000000 --- a/python/RELEASE_CHECKLIST.md +++ /dev/null @@ -1,41 +0,0 @@ -# nSTAT Python Release Checklist - -- [ ] Install from source in a clean environment: - - `python3 -m pip install -e python/` -- [ ] Refresh parity and coverage artifacts: - - `python3 python/tools/freeze_port_baseline.py` - - `python3 python/tools/generate_method_parity_matrix.py` - - `python3 python/tools/generate_implemented_method_coverage.py` -- [ ] Generate docs and notebooks: - - `python3 python/tools/generate_help_topic_docs.py` - - `python3 python/tools/generate_example_notebooks.py` -- [ ] Confirm docs build artifacts are not tracked: - - `python/docs/_build/` must remain gitignored and excluded from commits. -- [ ] Build docs and execute examples: - - `python3 python/tools/verify_examples_notebooks.py` - - `sphinx-build -b html python/docs python/docs/_build/html` -- [ ] Run similarity gate (requires MATLAB): - - `NSTAT_MATLAB_EXTRA_ARGS='-maca64 -nodisplay -noFigureWindows' python3 python/tools/verify_python_vs_matlab_similarity.py --enforce-gate` -- [ ] Freeze similarity baseline: - - `python3 python/tools/freeze_similarity_baseline.py` -- [ ] Verify standalone offline workflow: - - `python3 python/tools/verify_offline_standalone.py` - - Strict install gate (release hardening): `python3 python/tools/verify_offline_standalone.py --require-target-install` -- [ ] Run full test suite: - - `cd python && python3 -m pytest -q` -- [ ] Confirm release criteria: - - Class parity: `9/9` - - Help/notebook parity: `25/25` Python and `25/25` MATLAB - - Scalar parity contract: `25/25` help topics pass required keys - - Regression gate: pass with no allowlist-required failures -- [ ] Confirm compatibility adapters still import and emit `DeprecationWarning`. -- [ ] Update `python/RELEASE_NOTES.md` with API changes and known differences. -- [ ] Verify both release-gate workflows complete on the release candidate commit: - - `.github/workflows/python-ci.yml` - - `.github/workflows/matlab-parity-gate.yml` -- [ ] Run MATLAB preflight before parity verification: - - Trigger `.github/workflows/matlab-smoke.yml` on the target branch and confirm success. -- [ ] Verify required branch checks are enforced on `master`: - - `Python CI / test-and-build` - - `MATLAB Parity Gate / parity-gate` -- [ ] Create and tag GitHub release. diff --git a/python/RELEASE_NOTES.md b/python/RELEASE_NOTES.md deleted file mode 100644 index ed3d117..0000000 --- a/python/RELEASE_NOTES.md +++ /dev/null @@ -1,77 +0,0 @@ -# nSTAT Python Release Notes - -## Highlights - -- Introduced canonical Pythonic APIs under `nstat`: - - `nstat.signal.Signal`, `nstat.signal.Covariate` - - `nstat.spikes.SpikeTrain`, `nstat.spikes.SpikeTrainCollection` - - `nstat.trial.CovariateCollection`, `nstat.trial.TrialConfig`, `nstat.trial.ConfigCollection`, `nstat.trial.Trial` - - `nstat.analysis.Analysis` - - `nstat.fit.FitResult`, `nstat.fit.FitSummary` - - `nstat.cif.CIFModel` - - `nstat.decoding.DecoderSuite` - - `nstat.datasets` dataset registry/checksum API -- Added standalone simulation replacements in `nstat.simulators`. -- Added source-driven notebook generation for all MATLAB help `Examples` topics. -- Added Sphinx docs generation from `helpfiles/helptoc.xml`. -- Added parity/baseline reporting tools: - - `python/tools/freeze_port_baseline.py` - - `python/tools/generate_method_parity_matrix.py` - - `python/tools/verify_python_vs_matlab_similarity.py --enforce-gate` - - `python/tools/freeze_similarity_baseline.py` - - `python/tools/generate_implemented_method_coverage.py` - - `python/tools/verify_offline_standalone.py` -- Added CI workflows: - - `.github/workflows/python-ci.yml` - - `.github/workflows/matlab-parity-gate.yml` -- Standardized docs artifact policy: - - Generated Sphinx output under `python/docs/_build/` is now treated as a build artifact and excluded from source control. -- Expanded parity contract coverage: - - The MATLAB/Python scalar parity contract now requires one numeric parity key for all 25 help topics. - -## Release criteria (v1.0 gate) - -- Class similarity gate: `9/9` pass. -- Help-topic execution gate: Python `25/25`, MATLAB `25/25`. -- Parity contract gate: required keys pass for all `25/25` help topics. -- Regression gate must pass with no unexpected failures. -- Standalone/offline source checkout workflow must pass `verify_offline_standalone.py`. - -## MATLAB-to-Python API mapping (selected) - -| MATLAB | Python canonical | -|---|---| -| `SignalObj` | `nstat.signal.Signal` | -| `Covariate` | `nstat.signal.Covariate` | -| `nspikeTrain` | `nstat.spikes.SpikeTrain` | -| `nstColl` | `nstat.spikes.SpikeTrainCollection` | -| `CovColl` | `nstat.trial.CovariateCollection` | -| `TrialConfig` | `nstat.trial.TrialConfig` | -| `ConfigColl` | `nstat.trial.ConfigCollection` | -| `Analysis.RunAnalysisForAllNeurons` | `nstat.analysis.Analysis.run_analysis_for_all_neurons` | -| `FitResSummary` | `nstat.fit.FitSummary` | -| `CIF` | `nstat.cif.CIFModel` | -| `DecodingAlgorithms` | `nstat.decoding.DecoderSuite` | - -## Final migration status - -| Area | Status | Notes | -|---|---|---| -| Core classes | Complete | Canonical APIs implemented under `nstat.*`. | -| MATLAB-style import adapters | Transitional | Compatibility modules emit `DeprecationWarning` and forward to canonical APIs. | -| Help topic docs | Complete | All topics listed in `helpfiles/helptoc.xml` are represented in Sphinx docs. | -| Example notebooks | Complete | 25/25 help examples are generated as executable notebooks. | -| Parity contract | Complete | Scalar parity keys required for all 25 help topics. | -| Standalone runtime | Complete | No MATLAB runtime required for Python package execution. MATLAB is only required for parity validation workflows. | - -## Compatibility adapters - -MATLAB-style adapters remain importable under `nstat/*.py` compatibility modules and emit `DeprecationWarning` with migration targets. - -## Known differences / omissions - -| Topic | Current behavior | -|---|---| -| Legacy plotting helpers | Not all MATLAB plotting helpers are direct API ports; plotting is primarily notebook/doc-driven. | -| Parity tolerance | MATLAB/Python scalar comparisons are tolerance-based; stochastic workflows rely on seeded aggregate metrics. | -| Method surface differences | Some low-value legacy MATLAB methods are intentionally omitted and documented in `python/reports/method_parity_matrix.json`. | diff --git a/python/__init__.py b/python/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/docs/api.rst b/python/docs/api.rst deleted file mode 100644 index 4e0f123..0000000 --- a/python/docs/api.rst +++ /dev/null @@ -1,18 +0,0 @@ -API Reference -============ - -.. code-block:: python - - import nstat - print(nstat.__all__) - -Canonical modules include: - -- ``nstat.signal`` -- ``nstat.spikes`` -- ``nstat.trial`` -- ``nstat.analysis`` -- ``nstat.fit`` -- ``nstat.cif`` -- ``nstat.decoding`` -- ``nstat.datasets`` diff --git a/python/docs/conf.py b/python/docs/conf.py deleted file mode 100644 index ed1e91e..0000000 --- a/python/docs/conf.py +++ /dev/null @@ -1,6 +0,0 @@ -project = "nSTAT Python" -author = "Cajigas Lab" -release = "0.1.0" -extensions = [] -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] -master_doc = "index" diff --git a/python/docs/help_topics.rst b/python/docs/help_topics.rst deleted file mode 100644 index 27eb8b8..0000000 --- a/python/docs/help_topics.rst +++ /dev/null @@ -1,37 +0,0 @@ -Help Topics -=========== - -.. toctree:: - :maxdepth: 1 - - topics/neuralspikeanalysis_top - topics/documentationsetup2025b - topics/classdefinitions - topics/signalobj - topics/fitresult - topics/examples - topics/signalobjexamples - topics/covariateexamples - topics/covcollexamples - topics/nspiketrainexamples - topics/nstcollexamples - topics/eventsexamples - topics/historyexamples - topics/trialexamples - topics/trialconfigexamples - topics/configcollexamples - topics/analysisexamples - topics/fitresultexamples - topics/fitressummaryexamples - topics/ppthinning - topics/psthestimation - topics/validationdataset - topics/mepscanalysis - topics/ppsimexample - topics/explicitstimuluswhiskerdata - topics/hippocampalplacecellexample - topics/decodingexample - topics/decodingexamplewithhist - topics/stimulusdecode2d - topics/networktutorial - topics/nstatpaperexamples diff --git a/python/docs/index.rst b/python/docs/index.rst deleted file mode 100644 index ddfceb5..0000000 --- a/python/docs/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -nSTAT Python Documentation -========================== - -Standalone Python port of nSTAT with MATLAB-help topic coverage and executable notebooks. - -.. toctree:: - :maxdepth: 2 - - api - help_topics - parity_runbook diff --git a/python/docs/parity_runbook.rst b/python/docs/parity_runbook.rst deleted file mode 100644 index f3c0769..0000000 --- a/python/docs/parity_runbook.rst +++ /dev/null @@ -1,75 +0,0 @@ -Parity Runbook -============== - -Use this runbook for reproducible local/CI MATLAB parity checks on this machine. - -Validated environment ---------------------- - -- MATLAB binary: ``/Applications/MATLAB_R2025b.app/bin/matlab`` -- MATLAB args: ``-maca64 -nodisplay -noFigureWindows -softwareopengl`` -- Runner service mode: ``ACTIONS_RUNNER_SVC=1`` -- Force ``.m`` help scripts: ``NSTAT_FORCE_M_HELP_SCRIPTS=1`` -- Per-topic MATLAB retries: ``NSTAT_MATLAB_TOPIC_MAX_ATTEMPTS=2`` -- Stage C strict isolation: ``NSTAT_MATLAB_FORCE_TOPIC_ISOLATION=1`` -- Stage C hard cleanup on timeout/crash: ``NSTAT_MATLAB_HARD_CLEANUP_ON_FAILURE=1`` -- Ladder timeout-only retry: ``NSTAT_PARITY_RETRY_TIMEOUT_BLOCKS=1`` -- Ladder recoverable retry: ``NSTAT_PARITY_RETRY_RECOVERABLE_BLOCKS=1`` - -Preflight + staged parity -------------------------- - -Run Stage A + selected Stage B preflight: - -.. code-block:: bash - - ACTIONS_RUNNER_SVC=1 \ - NSTAT_FORCE_M_HELP_SCRIPTS=1 \ - NSTAT_MATLAB_EXTRA_ARGS='-maca64 -nodisplay -noFigureWindows -softwareopengl' \ - NSTAT_MATLAB_TOPIC_MAX_ATTEMPTS=2 \ - python/tools/run_parity_preflight.sh - -Run the staged ladder (core -> timeout -> graphics -> heavy-tail): - -.. code-block:: bash - - ACTIONS_RUNNER_SVC=1 \ - NSTAT_FORCE_M_HELP_SCRIPTS=1 \ - NSTAT_MATLAB_EXTRA_ARGS='-maca64 -nodisplay -noFigureWindows -softwareopengl' \ - NSTAT_MATLAB_TOPIC_MAX_ATTEMPTS=2 \ - NSTAT_PARITY_RETRY_TIMEOUT_BLOCKS=1 \ - NSTAT_PARITY_TIMEOUT_RETRY_BLOCKS=timeout_front \ - NSTAT_PARITY_RETRY_RECOVERABLE_BLOCKS=1 \ - NSTAT_PARITY_RECOVERABLE_RETRY_BLOCKS='graphics_mid,heavy_tail' \ - python/tools/run_parity_ladder.sh core_smoke timeout_front graphics_mid heavy_tail - -Full Stage C gate ------------------ - -Run full-suite parity gate report: - -.. code-block:: bash - - ACTIONS_RUNNER_SVC=1 \ - NSTAT_FORCE_M_HELP_SCRIPTS=1 \ - NSTAT_MATLAB_EXTRA_ARGS='-maca64 -nodisplay -noFigureWindows -softwareopengl' \ - NSTAT_MATLAB_TOPIC_MAX_ATTEMPTS=2 \ - NSTAT_MATLAB_FORCE_TOPIC_ISOLATION=1 \ - NSTAT_MATLAB_HARD_CLEANUP_ON_FAILURE=1 \ - python3 python/tools/verify_python_vs_matlab_similarity.py \ - --enforce-gate \ - --matlab-max-attempts 2 \ - --report-path python/reports/python_vs_matlab_similarity_report.json - -Useful outputs --------------- - -- Full report: ``python/reports/python_vs_matlab_similarity_report.json`` -- Ladder retry telemetry: ``python/reports/parity_retry_summary.json`` -- Block reports: ``python/reports/parity_block_*.json`` -- Summary helper: - -.. code-block:: bash - - python3 python/tools/summarize_parity_report.py \ - python/reports/python_vs_matlab_similarity_report.json --json diff --git a/python/docs/topics/analysisexamples.rst b/python/docs/topics/analysisexamples.rst deleted file mode 100644 index c6bf21c..0000000 --- a/python/docs/topics/analysisexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Using the Analysis Class -======================== - -MATLAB help target: ``AnalysisExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``Analysis`` - - ``nstat.analysis.Analysis`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/AnalysisExamples.ipynb``. diff --git a/python/docs/topics/classdefinitions.rst b/python/docs/topics/classdefinitions.rst deleted file mode 100644 index d38cff8..0000000 --- a/python/docs/topics/classdefinitions.rst +++ /dev/null @@ -1,43 +0,0 @@ -Class Definitions -================= - -MATLAB help target: ``ClassDefinitions.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``ClassDefinitions`` - - ``nstat (canonical module by topic)`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. diff --git a/python/docs/topics/configcollexamples.rst b/python/docs/topics/configcollexamples.rst deleted file mode 100644 index 02c6a30..0000000 --- a/python/docs/topics/configcollexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Using the ConfigColl Class -========================== - -MATLAB help target: ``ConfigCollExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``ConfigColl`` - - ``nstat.trial.ConfigCollection`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/ConfigCollExamples.ipynb``. diff --git a/python/docs/topics/covariateexamples.rst b/python/docs/topics/covariateexamples.rst deleted file mode 100644 index dfe8d74..0000000 --- a/python/docs/topics/covariateexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Using the Covariate Class -========================= - -MATLAB help target: ``CovariateExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``Covariate`` - - ``nstat.signal.Covariate`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/CovariateExamples.ipynb``. diff --git a/python/docs/topics/covcollexamples.rst b/python/docs/topics/covcollexamples.rst deleted file mode 100644 index a0bde73..0000000 --- a/python/docs/topics/covcollexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Using the CovColl Class -======================= - -MATLAB help target: ``CovCollExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``CovColl`` - - ``nstat.trial.CovariateCollection`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/CovCollExamples.ipynb``. diff --git a/python/docs/topics/decodingexample.rst b/python/docs/topics/decodingexample.rst deleted file mode 100644 index d5b4f7c..0000000 --- a/python/docs/topics/decodingexample.rst +++ /dev/null @@ -1,47 +0,0 @@ -Example Data Analysis - Decoding Univariate Simulated Stimuli (No History Effect) -================================================================================= - -MATLAB help target: ``DecodingExample.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``DecodingExample`` - - ``nstat.decoding.DecoderSuite`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/DecodingExample.ipynb``. diff --git a/python/docs/topics/decodingexamplewithhist.rst b/python/docs/topics/decodingexamplewithhist.rst deleted file mode 100644 index d76a825..0000000 --- a/python/docs/topics/decodingexamplewithhist.rst +++ /dev/null @@ -1,47 +0,0 @@ -Example Data Analysis - Decoding Univariate Simulated Stimuli with and without History Effect -============================================================================================= - -MATLAB help target: ``DecodingExampleWithHist.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``DecodingExampleWithHist`` - - ``nstat.decoding.DecoderSuite`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/DecodingExampleWithHist.ipynb``. diff --git a/python/docs/topics/documentationsetup2025b.rst b/python/docs/topics/documentationsetup2025b.rst deleted file mode 100644 index e850615..0000000 --- a/python/docs/topics/documentationsetup2025b.rst +++ /dev/null @@ -1,43 +0,0 @@ -MATLAB 2025b Help Integration -============================= - -MATLAB help target: ``DocumentationSetup2025b.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``DocumentationSetup2025b`` - - ``nstat (canonical module by topic)`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. diff --git a/python/docs/topics/eventsexamples.rst b/python/docs/topics/eventsexamples.rst deleted file mode 100644 index 296aceb..0000000 --- a/python/docs/topics/eventsexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Using the Events Class -====================== - -MATLAB help target: ``EventsExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``Events`` - - ``nstat.events.Events`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/EventsExamples.ipynb``. diff --git a/python/docs/topics/examples.rst b/python/docs/topics/examples.rst deleted file mode 100644 index cc089d4..0000000 --- a/python/docs/topics/examples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Examples Using the SignalObj Class Using the Covariate Class Using the CovColl Class Using the nSpikeTrain Class Using the nstColl Class Using the Events Class Using the History Class Using the Trial Class Using the TrialConfig Class Using the ConfigColl Class Using the Analysis Class Using the FitResult Class Using the FitResSummary Class Point Process Simulation via Thinning Example Data Analysis - Simulated Data - Computing a Peri-Stimulus Time Histogram (PSTH) Example Data Analysis - Simulated Constant (Piecewise Constant) Rate Poisson Example Data Analysis - Miniature Excitatory Post-Synaptic Currents (mEPSCs) Example Data Analysis - Simulated Explicit Stimulus and History Example Data Analysis - Explicit Stimulus Example Data Analysis - Hippocampal Place Cell Receptive Field Estimation Example Data Analysis - Decoding Univariate Simulated Stimuli (No History Effect) Example Data Analysis - Decoding Univariate Simulated Stimuli with and without History Effect Example Data Analysis - Decoding Bivariate Simulated Stimuli Example Data Analysis - Two Neuron Network Simulation and Estimation of Ensemble Effect nSTAT Paper Examples -============================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================ - -MATLAB help target: ``Examples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ```` - - ``nstat (canonical module by topic)`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/Examples.ipynb``. diff --git a/python/docs/topics/explicitstimuluswhiskerdata.rst b/python/docs/topics/explicitstimuluswhiskerdata.rst deleted file mode 100644 index 54d0c4b..0000000 --- a/python/docs/topics/explicitstimuluswhiskerdata.rst +++ /dev/null @@ -1,43 +0,0 @@ -Example Data Analysis - Explicit Stimulus -========================================= - -MATLAB help target: ``ExplicitStimulusWhiskerData.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``ExplicitStimulusWhiskerData`` - - ``nstat (canonical module by topic)`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. diff --git a/python/docs/topics/fitressummaryexamples.rst b/python/docs/topics/fitressummaryexamples.rst deleted file mode 100644 index 6c55814..0000000 --- a/python/docs/topics/fitressummaryexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Using the FitResSummary Class -============================= - -MATLAB help target: ``FitResSummaryExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``FitResSummary`` - - ``nstat.fit.FitSummary`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/FitResSummaryExamples.ipynb``. diff --git a/python/docs/topics/fitresult.rst b/python/docs/topics/fitresult.rst deleted file mode 100644 index ee3ae71..0000000 --- a/python/docs/topics/fitresult.rst +++ /dev/null @@ -1,43 +0,0 @@ -FitResult Reference -=================== - -MATLAB help target: ``FitResult.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``FitResult`` - - ``nstat.fit.FitResult`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. diff --git a/python/docs/topics/fitresultexamples.rst b/python/docs/topics/fitresultexamples.rst deleted file mode 100644 index b7bc0c1..0000000 --- a/python/docs/topics/fitresultexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Using the FitResult Class -========================= - -MATLAB help target: ``FitResultExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``FitResult`` - - ``nstat.fit.FitResult`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/FitResultExamples.ipynb``. diff --git a/python/docs/topics/hippocampalplacecellexample.rst b/python/docs/topics/hippocampalplacecellexample.rst deleted file mode 100644 index 2d6353c..0000000 --- a/python/docs/topics/hippocampalplacecellexample.rst +++ /dev/null @@ -1,47 +0,0 @@ -Example Data Analysis - Hippocampal Place Cell Receptive Field Estimation -========================================================================= - -MATLAB help target: ``HippocampalPlaceCellExample.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``HippocampalPlaceCellExample`` - - ``nstat (canonical module by topic)`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/HippocampalPlaceCellExample.ipynb``. diff --git a/python/docs/topics/historyexamples.rst b/python/docs/topics/historyexamples.rst deleted file mode 100644 index a2dc8fe..0000000 --- a/python/docs/topics/historyexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Using the History Class -======================= - -MATLAB help target: ``HistoryExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``History`` - - ``nstat.history.HistoryBasis`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/HistoryExamples.ipynb``. diff --git a/python/docs/topics/mepscanalysis.rst b/python/docs/topics/mepscanalysis.rst deleted file mode 100644 index 0bbb3c3..0000000 --- a/python/docs/topics/mepscanalysis.rst +++ /dev/null @@ -1,43 +0,0 @@ -Example Data Analysis - Miniature Excitatory Post-Synaptic Currents (mEPSCs) -============================================================================ - -MATLAB help target: ``mEPSCAnalysis.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``mEPSCAnalysis`` - - ``nstat (canonical module by topic)`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. diff --git a/python/docs/topics/networktutorial.rst b/python/docs/topics/networktutorial.rst deleted file mode 100644 index 6d183fd..0000000 --- a/python/docs/topics/networktutorial.rst +++ /dev/null @@ -1,43 +0,0 @@ -Example Data Analysis - Two Neuron Network Simulation and Estimation of Ensemble Effect -======================================================================================= - -MATLAB help target: ``NetworkTutorial.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``NetworkTutorial`` - - ``nstat (canonical module by topic)`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. diff --git a/python/docs/topics/neuralspikeanalysis_top.rst b/python/docs/topics/neuralspikeanalysis_top.rst deleted file mode 100644 index a6d2612..0000000 --- a/python/docs/topics/neuralspikeanalysis_top.rst +++ /dev/null @@ -1,43 +0,0 @@ -nSTAT Neural Spike Train Analysis Toolbox Overview MATLAB 2025b Help Integration Class Definitions SignalObj Reference FitResult Reference Examples Using the SignalObj Class Using the Covariate Class Using the CovColl Class Using the nSpikeTrain Class Using the nstColl Class Using the Events Class Using the History Class Using the Trial Class Using the TrialConfig Class Using the ConfigColl Class Using the Analysis Class Using the FitResult Class Using the FitResSummary Class Point Process Simulation via Thinning Example Data Analysis - Simulated Data - Computing a Peri-Stimulus Time Histogram (PSTH) Example Data Analysis - Simulated Constant (Piecewise Constant) Rate Poisson Example Data Analysis - Miniature Excitatory Post-Synaptic Currents (mEPSCs) Example Data Analysis - Simulated Explicit Stimulus and History Example Data Analysis - Explicit Stimulus Example Data Analysis - Hippocampal Place Cell Receptive Field Estimation Example Data Analysis - Decoding Univariate Simulated Stimuli (No History Effect) Example Data Analysis - Decoding Univariate Simulated Stimuli with and without History Effect Example Data Analysis - Decoding Bivariate Simulated Stimuli Example Data Analysis - Two Neuron Network Simulation and Estimation of Ensemble Effect nSTAT Paper Examples Neuroscience Statistics Research Laboratory -=================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== - -MATLAB help target: ``NeuralSpikeAnalysis_top.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``NeuralSpikeAnalysis_top`` - - ``nstat (canonical module by topic)`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. diff --git a/python/docs/topics/nspiketrainexamples.rst b/python/docs/topics/nspiketrainexamples.rst deleted file mode 100644 index f160111..0000000 --- a/python/docs/topics/nspiketrainexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Using the nSpikeTrain Class -=========================== - -MATLAB help target: ``nSpikeTrainExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``nSpikeTrain`` - - ``nstat.spikes.SpikeTrain`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/nSpikeTrainExamples.ipynb``. diff --git a/python/docs/topics/nstatpaperexamples.rst b/python/docs/topics/nstatpaperexamples.rst deleted file mode 100644 index 31ff623..0000000 --- a/python/docs/topics/nstatpaperexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -nSTAT Paper Examples -==================== - -MATLAB help target: ``nSTATPaperExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``nSTATPaper`` - - ``nstat (canonical module by topic)`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/nSTATPaperExamples.ipynb``. diff --git a/python/docs/topics/nstcollexamples.rst b/python/docs/topics/nstcollexamples.rst deleted file mode 100644 index 18c6854..0000000 --- a/python/docs/topics/nstcollexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Using the nstColl Class -======================= - -MATLAB help target: ``nstCollExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``nstColl`` - - ``nstat.spikes.SpikeTrainCollection`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/nstCollExamples.ipynb``. diff --git a/python/docs/topics/ppsimexample.rst b/python/docs/topics/ppsimexample.rst deleted file mode 100644 index 2024a9f..0000000 --- a/python/docs/topics/ppsimexample.rst +++ /dev/null @@ -1,47 +0,0 @@ -Example Data Analysis - Simulated Explicit Stimulus and History -=============================================================== - -MATLAB help target: ``PPSimExample.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``PPSimExample`` - - ``nstat (canonical module by topic)`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/PPSimExample.ipynb``. diff --git a/python/docs/topics/ppthinning.rst b/python/docs/topics/ppthinning.rst deleted file mode 100644 index ab76a96..0000000 --- a/python/docs/topics/ppthinning.rst +++ /dev/null @@ -1,43 +0,0 @@ -Point Process Simulation via Thinning -===================================== - -MATLAB help target: ``PPThinning.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``PPThinning`` - - ``nstat.cif.CIFModel.simulate`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. diff --git a/python/docs/topics/psthestimation.rst b/python/docs/topics/psthestimation.rst deleted file mode 100644 index 8aa1b28..0000000 --- a/python/docs/topics/psthestimation.rst +++ /dev/null @@ -1,43 +0,0 @@ -Example Data Analysis - Simulated Data - Computing a Peri-Stimulus Time Histogram (PSTH) -======================================================================================== - -MATLAB help target: ``PSTHEstimation.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``PSTHEstimation`` - - ``nstat.spikes.SpikeTrainCollection.psth`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. diff --git a/python/docs/topics/signalobj.rst b/python/docs/topics/signalobj.rst deleted file mode 100644 index b8734d8..0000000 --- a/python/docs/topics/signalobj.rst +++ /dev/null @@ -1,43 +0,0 @@ -SignalObj Reference -=================== - -MATLAB help target: ``SignalObj.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``SignalObj`` - - ``nstat.signal.Signal`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. diff --git a/python/docs/topics/signalobjexamples.rst b/python/docs/topics/signalobjexamples.rst deleted file mode 100644 index 979c404..0000000 --- a/python/docs/topics/signalobjexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Using the SignalObj Class -========================= - -MATLAB help target: ``SignalObjExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``SignalObj`` - - ``nstat.signal.Signal`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/SignalObjExamples.ipynb``. diff --git a/python/docs/topics/stimulusdecode2d.rst b/python/docs/topics/stimulusdecode2d.rst deleted file mode 100644 index d6fc5d6..0000000 --- a/python/docs/topics/stimulusdecode2d.rst +++ /dev/null @@ -1,43 +0,0 @@ -Example Data Analysis - Decoding Bivariate Simulated Stimuli -============================================================ - -MATLAB help target: ``StimulusDecode2D.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``StimulusDecode2D`` - - ``nstat.decoding.DecoderSuite`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. diff --git a/python/docs/topics/trialconfigexamples.rst b/python/docs/topics/trialconfigexamples.rst deleted file mode 100644 index 4ac9837..0000000 --- a/python/docs/topics/trialconfigexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Using the TrialConfig Class -=========================== - -MATLAB help target: ``TrialConfigExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``TrialConfig`` - - ``nstat.trial.TrialConfig`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/TrialConfigExamples.ipynb``. diff --git a/python/docs/topics/trialexamples.rst b/python/docs/topics/trialexamples.rst deleted file mode 100644 index 2842233..0000000 --- a/python/docs/topics/trialexamples.rst +++ /dev/null @@ -1,47 +0,0 @@ -Using the Trial Class -===================== - -MATLAB help target: ``TrialExamples.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``Trial`` - - ``nstat.trial.Trial`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. - -Notebook --------- -A generated executable notebook is available at ``python/notebooks/helpfiles/TrialExamples.ipynb``. diff --git a/python/docs/topics/validationdataset.rst b/python/docs/topics/validationdataset.rst deleted file mode 100644 index c345aa3..0000000 --- a/python/docs/topics/validationdataset.rst +++ /dev/null @@ -1,43 +0,0 @@ -Example Data Analysis - Simulated Constant (Piecewise Constant) Rate Poisson -============================================================================ - -MATLAB help target: ``ValidationDataSet.html`` - -Concept -------- -This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent. - -API Mapping (MATLAB -> Python) ------------------------------- -.. list-table:: - :header-rows: 1 - - * - MATLAB API - - Python API - * - ``ValidationDataSet`` - - ``nstat (canonical module by topic)`` - -Migration Callout ------------------ -- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``. -- Prefer canonical Python modules under ``nstat`` for new code. - -Python Usage ------------- -.. code-block:: python - - import nstat - print(nstat.__all__[:5]) - -Data Requirements ------------------ -Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets. - -Expected Outputs ----------------- -This topic should execute without MATLAB and produce deterministic summary metrics where applicable. - -Known Differences ------------------ -- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity. -- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults. diff --git a/python/examples/README.md b/python/examples/README.md deleted file mode 100644 index 8527de5..0000000 --- a/python/examples/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Python nSTAT Examples - -## Run the paper examples equivalent - -```bash -cd python -python3 examples/nstat_paper_examples.py --repo-root .. -``` - -This is the Python equivalent of `helpfiles/nSTATPaperExamples.m` starting from Experiment 2. -Plot PNG files are generated by default under `plots/nstat_paper_examples/`. Use `--no-plots` to disable plotting. - -## Other examples - -```bash -cd python -python3 examples/basic_data_workflow.py -python3 examples/fit_poisson_glm.py -python3 examples/simulate_population_psth.py -``` - -## MATLAB help-topic source scripts - -Executable source scripts used for notebook generation live in: - -- `python/examples/help_topics/` - -Regenerate all help-topic notebooks: - -```bash -python3 python/tools/generate_example_notebooks.py -``` diff --git a/python/examples/basic_data_workflow.py b/python/examples/basic_data_workflow.py deleted file mode 100644 index 47d27d5..0000000 --- a/python/examples/basic_data_workflow.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from pathlib import Path -import sys - -import numpy as np - -ROOT = Path(__file__).resolve().parents[1] -if str(ROOT) not in sys.path: - sys.path.insert(0, str(ROOT)) - -from nstat import Covariate, psth, simulate_cif_from_stimulus # noqa: E402 - - -def main() -> None: - rng = np.random.default_rng(42) - t = np.arange(0.0, 20.0, 0.001) - stim = np.sin(2.0 * np.pi * 2.0 * t) - - stimulus = Covariate(time=t, values=stim, name="sin_2hz", units="a.u.") - spike_train, rate_hz, _ = simulate_cif_from_stimulus( - time=stimulus.time, stimulus=stimulus.values, beta0=-1.7, beta1=0.9, rng=rng - ) - - edges = np.arange(0.0, 20.0 + 0.25, 0.25) - mean_rate_hz, counts = psth([spike_train], edges) - - print("Example: basic_data_workflow") - print(f"Duration (s): {spike_train.duration:.3f}") - print(f"Spikes: {spike_train.n_spikes}") - print(f"Mean simulated rate (Hz): {rate_hz.mean():.3f}") - print(f"PSTH bins: {counts.shape[1]}") - print(f"PSTH first 5 rates (Hz): {np.round(mean_rate_hz[:5], 3)}") - - -if __name__ == "__main__": - main() diff --git a/python/examples/fit_poisson_glm.py b/python/examples/fit_poisson_glm.py deleted file mode 100644 index 5fb9eb1..0000000 --- a/python/examples/fit_poisson_glm.py +++ /dev/null @@ -1,45 +0,0 @@ -from __future__ import annotations - -from pathlib import Path -import sys - -import numpy as np - -ROOT = Path(__file__).resolve().parents[1] -if str(ROOT) not in sys.path: - sys.path.insert(0, str(ROOT)) - -from nstat import fit_poisson_glm, simulate_cif_from_stimulus # noqa: E402 - - -def main() -> None: - rng = np.random.default_rng(123) - t = np.arange(0.0, 60.0, 0.001) - stim = np.sin(2.0 * np.pi * 1.0 * t) - - true_beta0 = -2.4 - true_beta1 = 0.9 - spikes, _, _ = simulate_cif_from_stimulus( - time=t, stimulus=stim, beta0=true_beta0, beta1=true_beta1, rng=rng - ) - bin_width = 0.01 - edges = np.arange(0.0, 60.0 + bin_width, bin_width) - y = spikes.to_binned_counts(edges) - - samples_per_bin = int(round(bin_width / (t[1] - t[0]))) - x = stim.reshape(-1, samples_per_bin).mean(axis=1)[:, None] - offset = np.full(y.shape, np.log(bin_width)) - - fit = fit_poisson_glm(x, y, offset=offset, l2=1e-4, max_iter=80, tol=1e-9) - - print("Example: fit_poisson_glm") - print(f"True intercept: {true_beta0:.4f}") - print(f"Estimated intercept: {fit.intercept:.4f}") - print(f"True stim beta: {true_beta1:.4f}") - print(f"Estimated stim beta: {fit.coefficients[0]:.4f}") - print(f"Converged: {fit.converged} in {fit.n_iter} iterations") - print(f"Log-likelihood: {fit.log_likelihood:.4f}") - - -if __name__ == "__main__": - main() diff --git a/python/examples/help_topics/AnalysisExamples.py b/python/examples/help_topics/AnalysisExamples.py deleted file mode 100644 index 05b8a60..0000000 --- a/python/examples/help_topics/AnalysisExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("AnalysisExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run AnalysisExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("AnalysisExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/ConfigCollExamples.py b/python/examples/help_topics/ConfigCollExamples.py deleted file mode 100644 index b509493..0000000 --- a/python/examples/help_topics/ConfigCollExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("ConfigCollExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run ConfigCollExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("ConfigCollExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/CovCollExamples.py b/python/examples/help_topics/CovCollExamples.py deleted file mode 100644 index 9762389..0000000 --- a/python/examples/help_topics/CovCollExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("CovCollExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run CovCollExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("CovCollExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/CovariateExamples.py b/python/examples/help_topics/CovariateExamples.py deleted file mode 100644 index b74b1b0..0000000 --- a/python/examples/help_topics/CovariateExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("CovariateExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run CovariateExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("CovariateExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/DecodingExample.py b/python/examples/help_topics/DecodingExample.py deleted file mode 100644 index 62650f4..0000000 --- a/python/examples/help_topics/DecodingExample.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("DecodingExample", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run DecodingExample Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("DecodingExample", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/DecodingExampleWithHist.py b/python/examples/help_topics/DecodingExampleWithHist.py deleted file mode 100644 index aca2294..0000000 --- a/python/examples/help_topics/DecodingExampleWithHist.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("DecodingExampleWithHist", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run DecodingExampleWithHist Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("DecodingExampleWithHist", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/EventsExamples.py b/python/examples/help_topics/EventsExamples.py deleted file mode 100644 index 2c2e170..0000000 --- a/python/examples/help_topics/EventsExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("EventsExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run EventsExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("EventsExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/ExplicitStimulusWhiskerData.py b/python/examples/help_topics/ExplicitStimulusWhiskerData.py deleted file mode 100644 index ba37ccd..0000000 --- a/python/examples/help_topics/ExplicitStimulusWhiskerData.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("ExplicitStimulusWhiskerData", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run ExplicitStimulusWhiskerData Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("ExplicitStimulusWhiskerData", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/FitResSummaryExamples.py b/python/examples/help_topics/FitResSummaryExamples.py deleted file mode 100644 index 07990aa..0000000 --- a/python/examples/help_topics/FitResSummaryExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("FitResSummaryExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run FitResSummaryExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("FitResSummaryExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/FitResultExamples.py b/python/examples/help_topics/FitResultExamples.py deleted file mode 100644 index f3dc92b..0000000 --- a/python/examples/help_topics/FitResultExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("FitResultExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run FitResultExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("FitResultExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/HippocampalPlaceCellExample.py b/python/examples/help_topics/HippocampalPlaceCellExample.py deleted file mode 100644 index 0cac2e6..0000000 --- a/python/examples/help_topics/HippocampalPlaceCellExample.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("HippocampalPlaceCellExample", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run HippocampalPlaceCellExample Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("HippocampalPlaceCellExample", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/HistoryExamples.py b/python/examples/help_topics/HistoryExamples.py deleted file mode 100644 index 9eb7a43..0000000 --- a/python/examples/help_topics/HistoryExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("HistoryExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run HistoryExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("HistoryExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/NetworkTutorial.py b/python/examples/help_topics/NetworkTutorial.py deleted file mode 100644 index 119c655..0000000 --- a/python/examples/help_topics/NetworkTutorial.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("NetworkTutorial", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run NetworkTutorial Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("NetworkTutorial", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/PPSimExample.py b/python/examples/help_topics/PPSimExample.py deleted file mode 100644 index a64db7a..0000000 --- a/python/examples/help_topics/PPSimExample.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("PPSimExample", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run PPSimExample Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("PPSimExample", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/PPThinning.py b/python/examples/help_topics/PPThinning.py deleted file mode 100644 index 442e407..0000000 --- a/python/examples/help_topics/PPThinning.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("PPThinning", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run PPThinning Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("PPThinning", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/PSTHEstimation.py b/python/examples/help_topics/PSTHEstimation.py deleted file mode 100644 index e32a962..0000000 --- a/python/examples/help_topics/PSTHEstimation.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("PSTHEstimation", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run PSTHEstimation Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("PSTHEstimation", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/SignalObjExamples.py b/python/examples/help_topics/SignalObjExamples.py deleted file mode 100644 index 779017c..0000000 --- a/python/examples/help_topics/SignalObjExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("SignalObjExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run SignalObjExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("SignalObjExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/StimulusDecode2D.py b/python/examples/help_topics/StimulusDecode2D.py deleted file mode 100644 index 7f59b39..0000000 --- a/python/examples/help_topics/StimulusDecode2D.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("StimulusDecode2D", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run StimulusDecode2D Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("StimulusDecode2D", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/TrialConfigExamples.py b/python/examples/help_topics/TrialConfigExamples.py deleted file mode 100644 index 4c81942..0000000 --- a/python/examples/help_topics/TrialConfigExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("TrialConfigExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run TrialConfigExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("TrialConfigExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/TrialExamples.py b/python/examples/help_topics/TrialExamples.py deleted file mode 100644 index 15ab82e..0000000 --- a/python/examples/help_topics/TrialExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("TrialExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run TrialExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("TrialExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/ValidationDataSet.py b/python/examples/help_topics/ValidationDataSet.py deleted file mode 100644 index 91b2505..0000000 --- a/python/examples/help_topics/ValidationDataSet.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("ValidationDataSet", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run ValidationDataSet Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("ValidationDataSet", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/__init__.py b/python/examples/help_topics/__init__.py deleted file mode 100644 index cf3b022..0000000 --- a/python/examples/help_topics/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Source scripts for MATLAB help-topic equivalents used to build executable notebooks.""" diff --git a/python/examples/help_topics/_common.py b/python/examples/help_topics/_common.py deleted file mode 100644 index 1fba78d..0000000 --- a/python/examples/help_topics/_common.py +++ /dev/null @@ -1,292 +0,0 @@ -from __future__ import annotations - -import json -from pathlib import Path -from typing import Any - -import numpy as np - -from nstat import ( - Analysis, - CIFModel, - ConfigCollection, - Covariate, - CovariateCollection, - DecoderSuite, - HistoryBasis, - Signal, - SpikeTrain, - SpikeTrainCollection, - Trial, - TrialConfig, - run_full_paper_examples, - simulate_two_neuron_network, -) -from nstat.paper_examples_full import ( - run_experiment1, - run_experiment2, - run_experiment3, - run_experiment4, - run_experiment5, - run_experiment5b, - run_experiment6, -) - - -def _resolve_repo_root(repo_root: Path | str | None) -> Path: - if repo_root is None: - return Path(__file__).resolve().parents[3] - return Path(repo_root).resolve() - - -def _result(topic: str, payload: dict[str, Any], parity: dict[str, float] | None = None) -> dict[str, Any]: - out = {"topic": topic, **payload} - if parity: - out["parity"] = {k: float(v) for k, v in parity.items()} - return out - - -MATLAB_FIGURE_BASELINE: dict[str, float] = { - "SignalObjExamples": 16.0, - "CovariateExamples": 2.0, - "CovCollExamples": 2.0, - "nSpikeTrainExamples": 4.0, - "nstCollExamples": 3.0, - "EventsExamples": 3.0, - "HistoryExamples": 3.0, - "TrialExamples": 6.0, - "TrialConfigExamples": 0.0, - "ConfigCollExamples": 0.0, - "AnalysisExamples": 4.0, - "FitResultExamples": 0.0, - "FitResSummaryExamples": 0.0, - "PPThinning": 3.0, - "PSTHEstimation": 2.0, - "ValidationDataSet": 8.0, - "mEPSCAnalysis": 4.0, - "PPSimExample": 3.0, - "ExplicitStimulusWhiskerData": 8.0, - "HippocampalPlaceCellExample": 9.0, - "DecodingExample": 5.0, - "DecodingExampleWithHist": 2.0, - "StimulusDecode2D": 4.0, - "NetworkTutorial": 4.0, - "nSTATPaperExamples": 1.0, -} - - -def _parity(topic: str, extra: dict[str, float] | None = None) -> dict[str, float]: - out = {"figs": float(MATLAB_FIGURE_BASELINE[topic])} - if extra: - out.update({k: float(v) for k, v in extra.items()}) - return out - - -def _toy_trial() -> tuple[Trial, ConfigCollection]: - time = np.arange(0.0, 2.0, 0.001) - stim = np.sin(2.0 * np.pi * 2.0 * time) - cov = Covariate(time, stim, "stim", "time", "s", "a.u.", ["stim"]) - - base_rate = 10.0 + 5.0 * np.maximum(stim, 0.0) - model = CIFModel(time=time, rate_hz=base_rate, name="lambda") - coll = model.simulate(num_realizations=3, seed=3) - - trial = Trial(spike_collection=coll, covariate_collection=CovariateCollection([cov])) - cfg = TrialConfig(covMask=["stim"], sampleRate=1000.0, name="stimulus_only") - cfgs = ConfigCollection([cfg]) - return trial, cfgs - - -def run_topic(topic: str, repo_root: Path | str | None = None) -> dict[str, Any]: - root = _resolve_repo_root(repo_root) - data_dir = root / "data" - - if topic == "SignalObjExamples": - sample_rate_hz = 5000.0 - dt = 1.0 / sample_rate_hz - t = np.arange(0.0, 1.0 + dt * 0.5, dt) - sig = Signal(t, np.column_stack([np.sin(2 * np.pi * t), np.cos(2 * np.pi * t)]), name="demo") - return _result( - topic, - {"dimension": sig.dimension, "sample_rate": sig.sample_rate}, - parity=_parity(topic, {"sample_rate_hz": sig.sample_rate}), - ) - - if topic == "CovariateExamples": - t = np.linspace(0.0, 1.0, 100) - cov = Covariate.from_values(t, np.sin(2 * np.pi * t), name="stim", units="a.u.") - cov_z = cov.standardize() - return _result(topic, {"mean": float(np.mean(cov_z.data)), "std": float(np.std(cov_z.data))}, parity=_parity(topic)) - - if topic == "CovCollExamples": - trial, _ = _toy_trial() - _, x, labels = trial.get_covariate_matrix() - return _result(topic, {"matrix_shape": list(x.shape), "labels": labels}, parity=_parity(topic)) - - if topic == "nSpikeTrainExamples": - st = SpikeTrain(np.array([0.1, 0.12, 0.25, 0.4]), binwidth=0.01) - return _result(topic, {"n_spikes": st.n_spikes, "rate_hz": st.firing_rate_hz}, parity=_parity(topic)) - - if topic == "nstCollExamples": - trial, _ = _toy_trial() - coll = trial.spike_collection - psth = coll.psth(0.05) - return _result(topic, {"num_trains": coll.num_spike_trains, "psth_points": int(psth.time.shape[0])}, parity=_parity(topic)) - - if topic == "EventsExamples": - from nstat.events import Events - - ev = Events([0.2, 0.9, 1.4], labels=["start", "cue", "reward"]) - return _result(topic, {"n_events": int(ev.event_times.shape[0])}, parity=_parity(topic)) - - if topic == "HistoryExamples": - basis = HistoryBasis([1, 2, 5, 10]) - y = np.random.default_rng(0).poisson(0.1, size=500) - x = basis.design_matrix(y) - return _result(topic, {"lags": basis.lags.tolist(), "design_shape": list(x.shape)}, parity=_parity(topic)) - - if topic == "TrialExamples": - trial, _ = _toy_trial() - _, x, _ = trial.get_covariate_matrix() - return _result( - topic, - {"covariate_rows": int(x.shape[0]), "neurons": trial.spike_collection.num_spike_trains}, - parity=_parity(topic), - ) - - if topic == "TrialConfigExamples": - cfg = TrialConfig(covMask=[["stim", "hist"]], sampleRate=1000.0, name="demo_cfg") - return _result(topic, {"covariates": cfg.covariate_names, "sample_rate": cfg.sampleRate}, parity=_parity(topic)) - - if topic == "ConfigCollExamples": - c1 = TrialConfig(covMask=["stim"], sampleRate=1000.0, name="cfg1") - c2 = TrialConfig(covMask=["stim", "hist"], sampleRate=1000.0, name="cfg2") - coll = ConfigCollection([c1, c2]) - return _result(topic, {"num_configs": coll.numConfigs, "names": coll.getConfigNames()}, parity=_parity(topic)) - - if topic == "AnalysisExamples": - trial, cfgs = _toy_trial() - out = Analysis.run_analysis_for_all_neurons(trial, cfgs) - return _result(topic, {"num_results": len(out), "first_aic": float(out[0].AIC[0])}, parity=_parity(topic)) - - if topic == "FitResultExamples": - trial, cfgs = _toy_trial() - fit = Analysis.run_analysis_for_neuron(trial, 0, cfgs) - return _result(topic, {"coeffs": fit.getCoeffs().tolist(), "bic": float(fit.BIC[0])}, parity=_parity(topic)) - - if topic == "FitResSummaryExamples": - trial, cfgs = _toy_trial() - fits = Analysis.run_analysis_for_all_neurons(trial, cfgs) - from nstat.fit import FitSummary - - summary = FitSummary(fits) - return _result(topic, {"mean_aic": summary.AIC.tolist(), "mean_bic": summary.BIC.tolist()}, parity=_parity(topic)) - - if topic == "PPThinning": - t = np.arange(0.0, 1.0, 0.001) - rate = 20.0 + 15.0 * np.sin(2 * np.pi * 3 * t) ** 2 - model = CIFModel(t, rate) - spikes = model.simulate(num_realizations=20, seed=1) - return _result( - topic, - {"num_realizations": spikes.num_spike_trains}, - parity=_parity(topic, {"num_realizations": spikes.num_spike_trains}), - ) - - if topic == "PSTHEstimation": - delta = 0.001 - tmax = 10.0 - time = np.arange(0.0, tmax + delta, delta) - rate_hz = 10.0 * np.sin(2.0 * np.pi * 0.2 * time) + 10.0 - coll = CIFModel(time, rate_hz, name="lambda").simulate(num_realizations=20, seed=17) - psth = coll.psth(0.5) - peak = float(np.max(psth.data[:, 0])) - return _result( - topic, - {"peak_rate": peak, "num_realizations": coll.num_spike_trains}, - parity=_parity(topic, {"num_realizations": coll.num_spike_trains}), - ) - - if topic == "ValidationDataSet": - summary = run_experiment3(seed=7) - return _result(topic, summary, parity=_parity(topic)) - - if topic == "mEPSCAnalysis": - summary = run_experiment1(data_dir) - return _result(topic, summary, parity=_parity(topic)) - - if topic == "PPSimExample": - summary = run_experiment2(data_dir) - return _result(topic, summary, parity=_parity(topic)) - - if topic == "ExplicitStimulusWhiskerData": - summary = run_experiment2(data_dir) - return _result(topic, summary, parity=_parity(topic)) - - if topic == "HippocampalPlaceCellExample": - summary = run_experiment4(data_dir) - return _result(topic, summary, parity=_parity(topic)) - - if topic == "DecodingExample": - summary = run_experiment5(seed=11) - return _result(topic, summary, parity=_parity(topic, {"num_cells": summary["num_cells"]})) - - if topic == "DecodingExampleWithHist": - summary = run_experiment5b(seed=19) - return _result(topic, summary, parity=_parity(topic, {"num_cells": summary["num_cells"]})) - - if topic == "StimulusDecode2D": - summary = run_experiment5b(seed=23, n_cells=80) - return _result( - topic, - summary, - parity=_parity( - topic, - { - "num_cells": summary["num_cells"], - "decode_rmse_x": summary["decode_rmse_x"], - "decode_rmse_y": summary["decode_rmse_y"], - }, - ), - ) - - if topic == "NetworkTutorial": - sim = simulate_two_neuron_network(duration_s=2.0, dt=0.001, seed=13) - psth = sim.spikes.psth(0.05) - return _result( - topic, - { - "samples": int(sim.time.shape[0]), - "neuron_count": sim.spikes.num_spike_trains, - "psth_peak": float(np.max(psth.data[:, 0])), - }, - parity=_parity(topic), - ) - - if topic == "nSTATPaperExamples": - # Keep this help-topic notebook fast and deterministic in CI by running - # a representative subset of paper experiments. - summary = { - "experiment2": run_experiment2(data_dir), - "experiment3": run_experiment3(seed=7), - "experiment5": run_experiment5(seed=11, n_cells=40), - } - return _result( - topic, - {"experiments": sorted(summary.keys()), "summary": summary}, - parity=_parity( - topic, - { - "num_cells": summary["experiment5"]["num_cells"], - "decode_rmse": summary["experiment5"]["decode_rmse"], - }, - ), - ) - - raise KeyError(f"Unknown help topic: {topic}") - - -def main(topic: str, repo_root: Path | str | None = None) -> int: - out = run_topic(topic, repo_root) - print(json.dumps(out, indent=2, default=str)) - return 0 diff --git a/python/examples/help_topics/mEPSCAnalysis.py b/python/examples/help_topics/mEPSCAnalysis.py deleted file mode 100644 index a0e5a00..0000000 --- a/python/examples/help_topics/mEPSCAnalysis.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("mEPSCAnalysis", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run mEPSCAnalysis Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("mEPSCAnalysis", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/nSTATPaperExamples.py b/python/examples/help_topics/nSTATPaperExamples.py deleted file mode 100644 index b2f2139..0000000 --- a/python/examples/help_topics/nSTATPaperExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("nSTATPaperExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run nSTATPaperExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("nSTATPaperExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/nSpikeTrainExamples.py b/python/examples/help_topics/nSpikeTrainExamples.py deleted file mode 100644 index 0915250..0000000 --- a/python/examples/help_topics/nSpikeTrainExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("nSpikeTrainExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run nSpikeTrainExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("nSpikeTrainExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/help_topics/nstCollExamples.py b/python/examples/help_topics/nstCollExamples.py deleted file mode 100644 index c75ae35..0000000 --- a/python/examples/help_topics/nstCollExamples.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import argparse - -from ._common import main as _main - - -def run(repo_root=None): - from ._common import run_topic - - return run_topic("nstCollExamples", repo_root) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Run nstCollExamples Python help-topic workflow") - parser.add_argument("--repo-root", default=None) - args = parser.parse_args() - return _main("nstCollExamples", args.repo_root) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/nSTATPaperExamples.py b/python/examples/nSTATPaperExamples.py deleted file mode 100644 index f02d46b..0000000 --- a/python/examples/nSTATPaperExamples.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import annotations - -import sys -from pathlib import Path - -THIS_DIR = Path(__file__).resolve().parent -PKG_ROOT = THIS_DIR.parent -if str(PKG_ROOT) not in sys.path: - sys.path.insert(0, str(PKG_ROOT)) - -from nstat.paper_examples_full import main - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/nstat_paper_examples.py b/python/examples/nstat_paper_examples.py deleted file mode 100644 index 12ad471..0000000 --- a/python/examples/nstat_paper_examples.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import annotations - -import sys -from pathlib import Path - -# Ensure local package import works when run directly. -THIS_DIR = Path(__file__).resolve().parent -PKG_ROOT = THIS_DIR.parent -if str(PKG_ROOT) not in sys.path: - sys.path.insert(0, str(PKG_ROOT)) - -from nstat.paper_examples import main - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/python/examples/simulate_population_psth.py b/python/examples/simulate_population_psth.py deleted file mode 100644 index f472c3e..0000000 --- a/python/examples/simulate_population_psth.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import annotations - -from pathlib import Path -import sys - -import numpy as np - -ROOT = Path(__file__).resolve().parents[1] -if str(ROOT) not in sys.path: - sys.path.insert(0, str(ROOT)) - -from nstat import psth, simulate_cif_from_stimulus # noqa: E402 - - -def main() -> None: - rng = np.random.default_rng(7) - t = np.arange(0.0, 20.0, 0.001) - stim = np.sin(2.0 * np.pi * 1.5 * t) - - trials = [] - n_trials = 20 - for _ in range(n_trials): - spikes, _, _ = simulate_cif_from_stimulus( - time=t, stimulus=stim, beta0=-2.0, beta1=1.1, rng=rng - ) - trials.append(spikes) - - edges = np.arange(0.0, 20.0 + 0.1, 0.1) - mean_rate_hz, counts = psth(trials, edges) - - print("Example: simulate_population_psth") - print(f"Trials: {n_trials}") - print(f"Total spikes: {int(counts.sum())}") - print(f"Average rate over all bins (Hz): {mean_rate_hz.mean():.3f}") - print(f"Peak bin rate (Hz): {mean_rate_hz.max():.3f}") - - -if __name__ == "__main__": - main() diff --git a/python/matlab_port/Analysis.py b/python/matlab_port/Analysis.py deleted file mode 100644 index ab56f9e..0000000 --- a/python/matlab_port/Analysis.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: Analysis.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class Analysis: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'Analysis.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/CIF.py b/python/matlab_port/CIF.py deleted file mode 100644 index b4c6324..0000000 --- a/python/matlab_port/CIF.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: CIF.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class CIF: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'CIF.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/ConfidenceInterval.py b/python/matlab_port/ConfidenceInterval.py deleted file mode 100644 index 4afcfbe..0000000 --- a/python/matlab_port/ConfidenceInterval.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: ConfidenceInterval.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class ConfidenceInterval: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'ConfidenceInterval.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/ConfigColl.py b/python/matlab_port/ConfigColl.py deleted file mode 100644 index 94ed331..0000000 --- a/python/matlab_port/ConfigColl.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: ConfigColl.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class ConfigColl: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'ConfigColl.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/CovColl.py b/python/matlab_port/CovColl.py deleted file mode 100644 index 7361212..0000000 --- a/python/matlab_port/CovColl.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: CovColl.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class CovColl: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'CovColl.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/Covariate.py b/python/matlab_port/Covariate.py deleted file mode 100644 index 75c2eba..0000000 --- a/python/matlab_port/Covariate.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: Covariate.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class Covariate: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'Covariate.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/DecodingAlgorithms.py b/python/matlab_port/DecodingAlgorithms.py deleted file mode 100644 index bfa9eee..0000000 --- a/python/matlab_port/DecodingAlgorithms.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: DecodingAlgorithms.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -from nstat.decoding_algorithms import DecodingAlgorithms - -__all__ = ['DecodingAlgorithms'] diff --git a/python/matlab_port/Events.py b/python/matlab_port/Events.py deleted file mode 100644 index 0fb6dce..0000000 --- a/python/matlab_port/Events.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: Events.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class Events: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'Events.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/FitResSummary.py b/python/matlab_port/FitResSummary.py deleted file mode 100644 index e686f7d..0000000 --- a/python/matlab_port/FitResSummary.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: FitResSummary.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class FitResSummary: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'FitResSummary.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/FitResult.py b/python/matlab_port/FitResult.py deleted file mode 100644 index 836accd..0000000 --- a/python/matlab_port/FitResult.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: FitResult.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class FitResult: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'FitResult.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/History.py b/python/matlab_port/History.py deleted file mode 100644 index 425eb9c..0000000 --- a/python/matlab_port/History.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: History.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class History: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'History.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/PointProcessSimulationThinning_mdl_r2011a.py b/python/matlab_port/PointProcessSimulationThinning_mdl_r2011a.py deleted file mode 100644 index 1d95f24..0000000 --- a/python/matlab_port/PointProcessSimulationThinning_mdl_r2011a.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: PointProcessSimulationThinning.mdl.r2011a -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -SOURCE_MODEL = Path(r'/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local/PointProcessSimulationThinning.mdl.r2011a') - -def load_text(path: str | Path | None = None) -> str: - p = Path(path) if path is not None else SOURCE_MODEL - return p.read_text(encoding='utf-8', errors='ignore') - -def summarize(path: str | Path | None = None) -> dict[str, object]: - text = load_text(path) - lines = text.splitlines() - frame = pd.DataFrame({'line_number': np.arange(1, len(lines) + 1), 'line_text': lines}) - return { - 'source': 'PointProcessSimulationThinning.mdl.r2011a', - 'line_count': int(frame.shape[0]), - 'block_count_guess': int(frame['line_text'].str.contains('Block {', regex=False).sum()), - } diff --git a/python/matlab_port/PointProcessSimulation_mdl_r2010b.py b/python/matlab_port/PointProcessSimulation_mdl_r2010b.py deleted file mode 100644 index 4ef6bf7..0000000 --- a/python/matlab_port/PointProcessSimulation_mdl_r2010b.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: PointProcessSimulation.mdl.r2010b -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -SOURCE_MODEL = Path(r'/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local/PointProcessSimulation.mdl.r2010b') - -def load_text(path: str | Path | None = None) -> str: - p = Path(path) if path is not None else SOURCE_MODEL - return p.read_text(encoding='utf-8', errors='ignore') - -def summarize(path: str | Path | None = None) -> dict[str, object]: - text = load_text(path) - lines = text.splitlines() - frame = pd.DataFrame({'line_number': np.arange(1, len(lines) + 1), 'line_text': lines}) - return { - 'source': 'PointProcessSimulation.mdl.r2010b', - 'line_count': int(frame.shape[0]), - 'block_count_guess': int(frame['line_text'].str.contains('Block {', regex=False).sum()), - } diff --git a/python/matlab_port/PointProcessSimulation_mdl_r2011a.py b/python/matlab_port/PointProcessSimulation_mdl_r2011a.py deleted file mode 100644 index 90cfcb6..0000000 --- a/python/matlab_port/PointProcessSimulation_mdl_r2011a.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: PointProcessSimulation.mdl.r2011a -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -SOURCE_MODEL = Path(r'/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local/PointProcessSimulation.mdl.r2011a') - -def load_text(path: str | Path | None = None) -> str: - p = Path(path) if path is not None else SOURCE_MODEL - return p.read_text(encoding='utf-8', errors='ignore') - -def summarize(path: str | Path | None = None) -> dict[str, object]: - text = load_text(path) - lines = text.splitlines() - frame = pd.DataFrame({'line_number': np.arange(1, len(lines) + 1), 'line_text': lines}) - return { - 'source': 'PointProcessSimulation.mdl.r2011a', - 'line_count': int(frame.shape[0]), - 'block_count_guess': int(frame['line_text'].str.contains('Block {', regex=False).sum()), - } diff --git a/python/matlab_port/PointProcessSimulation_mdl_r2011b.py b/python/matlab_port/PointProcessSimulation_mdl_r2011b.py deleted file mode 100644 index c0b8f25..0000000 --- a/python/matlab_port/PointProcessSimulation_mdl_r2011b.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: PointProcessSimulation.mdl.r2011b -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -SOURCE_MODEL = Path(r'/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local/PointProcessSimulation.mdl.r2011b') - -def load_text(path: str | Path | None = None) -> str: - p = Path(path) if path is not None else SOURCE_MODEL - return p.read_text(encoding='utf-8', errors='ignore') - -def summarize(path: str | Path | None = None) -> dict[str, object]: - text = load_text(path) - lines = text.splitlines() - frame = pd.DataFrame({'line_number': np.arange(1, len(lines) + 1), 'line_text': lines}) - return { - 'source': 'PointProcessSimulation.mdl.r2011b', - 'line_count': int(frame.shape[0]), - 'block_count_guess': int(frame['line_text'].str.contains('Block {', regex=False).sum()), - } diff --git a/python/matlab_port/PointProcessSimulation_mdl_r2013a.py b/python/matlab_port/PointProcessSimulation_mdl_r2013a.py deleted file mode 100644 index 5be503e..0000000 --- a/python/matlab_port/PointProcessSimulation_mdl_r2013a.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: PointProcessSimulation.mdl.r2013a -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -SOURCE_MODEL = Path(r'/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local/PointProcessSimulation.mdl.r2013a') - -def load_text(path: str | Path | None = None) -> str: - p = Path(path) if path is not None else SOURCE_MODEL - return p.read_text(encoding='utf-8', errors='ignore') - -def summarize(path: str | Path | None = None) -> dict[str, object]: - text = load_text(path) - lines = text.splitlines() - frame = pd.DataFrame({'line_number': np.arange(1, len(lines) + 1), 'line_text': lines}) - return { - 'source': 'PointProcessSimulation.mdl.r2013a', - 'line_count': int(frame.shape[0]), - 'block_count_guess': int(frame['line_text'].str.contains('Block {', regex=False).sum()), - } diff --git a/python/matlab_port/SignalObj.py b/python/matlab_port/SignalObj.py deleted file mode 100644 index bbcd09d..0000000 --- a/python/matlab_port/SignalObj.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: SignalObj.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class SignalObj: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'SignalObj.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/TRANSLATION_MAP.json b/python/matlab_port/TRANSLATION_MAP.json deleted file mode 100644 index 7ec8316..0000000 --- a/python/matlab_port/TRANSLATION_MAP.json +++ /dev/null @@ -1,339 +0,0 @@ -{ - "repo_root": "/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local", - "output_root": "/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local/python/matlab_port", - "counts": { - "total": 64, - "by_kind": { - "class_scaffold": 16, - "alias": 1, - "mdl_scaffold": 6, - "function_scaffold": 8, - "examples_script": 14, - "script_scaffold": 18, - "paper_examples_entrypoint": 1 - }, - "helpfile_notebooks": 32 - }, - "entries": [ - { - "source": "Analysis.m", - "target": "python/matlab_port/Analysis.py", - "kind": "class_scaffold" - }, - { - "source": "CIF.m", - "target": "python/matlab_port/CIF.py", - "kind": "class_scaffold" - }, - { - "source": "ConfidenceInterval.m", - "target": "python/matlab_port/ConfidenceInterval.py", - "kind": "class_scaffold" - }, - { - "source": "ConfigColl.m", - "target": "python/matlab_port/ConfigColl.py", - "kind": "class_scaffold" - }, - { - "source": "CovColl.m", - "target": "python/matlab_port/CovColl.py", - "kind": "class_scaffold" - }, - { - "source": "Covariate.m", - "target": "python/matlab_port/Covariate.py", - "kind": "class_scaffold" - }, - { - "source": "DecodingAlgorithms.m", - "target": "python/matlab_port/DecodingAlgorithms.py", - "kind": "alias" - }, - { - "source": "Events.m", - "target": "python/matlab_port/Events.py", - "kind": "class_scaffold" - }, - { - "source": "FitResSummary.m", - "target": "python/matlab_port/FitResSummary.py", - "kind": "class_scaffold" - }, - { - "source": "FitResult.m", - "target": "python/matlab_port/FitResult.py", - "kind": "class_scaffold" - }, - { - "source": "History.m", - "target": "python/matlab_port/History.py", - "kind": "class_scaffold" - }, - { - "source": "PointProcessSimulation.mdl.r2010b", - "target": "python/matlab_port/PointProcessSimulation_mdl_r2010b.py", - "kind": "mdl_scaffold" - }, - { - "source": "PointProcessSimulation.mdl.r2011a", - "target": "python/matlab_port/PointProcessSimulation_mdl_r2011a.py", - "kind": "mdl_scaffold" - }, - { - "source": "PointProcessSimulation.mdl.r2011b", - "target": "python/matlab_port/PointProcessSimulation_mdl_r2011b.py", - "kind": "mdl_scaffold" - }, - { - "source": "PointProcessSimulation.mdl.r2013a", - "target": "python/matlab_port/PointProcessSimulation_mdl_r2013a.py", - "kind": "mdl_scaffold" - }, - { - "source": "PointProcessSimulationThinning.mdl.r2011a", - "target": "python/matlab_port/PointProcessSimulationThinning_mdl_r2011a.py", - "kind": "mdl_scaffold" - }, - { - "source": "SignalObj.m", - "target": "python/matlab_port/SignalObj.py", - "kind": "class_scaffold" - }, - { - "source": "Trial.m", - "target": "python/matlab_port/Trial.py", - "kind": "class_scaffold" - }, - { - "source": "TrialConfig.m", - "target": "python/matlab_port/TrialConfig.py", - "kind": "class_scaffold" - }, - { - "source": "data/Explicit Stimulus/GenCovMat.m", - "target": "python/matlab_port/data/Explicit Stimulus/GenCovMat.py", - "kind": "function_scaffold" - }, - { - "source": "helpfiles/AnalysisExamples.m", - "target": "python/matlab_port/helpfiles/AnalysisExamples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/AnalysisExamples2.m", - "target": "python/matlab_port/helpfiles/AnalysisExamples2.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/ClassDefinitions.m", - "target": "python/matlab_port/helpfiles/ClassDefinitions.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/ConfigCollExamples.m", - "target": "python/matlab_port/helpfiles/ConfigCollExamples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/CovCollExamples.m", - "target": "python/matlab_port/helpfiles/CovCollExamples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/CovariateExamples.m", - "target": "python/matlab_port/helpfiles/CovariateExamples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/DecodingExample.m", - "target": "python/matlab_port/helpfiles/DecodingExample.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/DecodingExampleWithHist.m", - "target": "python/matlab_port/helpfiles/DecodingExampleWithHist.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/EventsExamples.m", - "target": "python/matlab_port/helpfiles/EventsExamples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/Examples.m", - "target": "python/matlab_port/helpfiles/Examples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/ExplicitStimulusWhiskerData.m", - "target": "python/matlab_port/helpfiles/ExplicitStimulusWhiskerData.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/FitResSummaryExamples.m", - "target": "python/matlab_port/helpfiles/FitResSummaryExamples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/FitResult.m", - "target": "python/matlab_port/helpfiles/FitResult.py", - "kind": "class_scaffold" - }, - { - "source": "helpfiles/FitResultExamples.m", - "target": "python/matlab_port/helpfiles/FitResultExamples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/HippocampalPlaceCellExample.m", - "target": "python/matlab_port/helpfiles/HippocampalPlaceCellExample.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/HistoryExamples.m", - "target": "python/matlab_port/helpfiles/HistoryExamples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/HybridFilterExample.m", - "target": "python/matlab_port/helpfiles/HybridFilterExample.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/NetworkTutorial.m", - "target": "python/matlab_port/helpfiles/NetworkTutorial.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/NeuralSpikeAnalysis_top.m", - "target": "python/matlab_port/helpfiles/NeuralSpikeAnalysis_top.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/PPSimExample.m", - "target": "python/matlab_port/helpfiles/PPSimExample.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/PPThinning.m", - "target": "python/matlab_port/helpfiles/PPThinning.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/PSTHEstimation.m", - "target": "python/matlab_port/helpfiles/PSTHEstimation.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/SignalObjExamples.m", - "target": "python/matlab_port/helpfiles/SignalObjExamples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/SimulatedNetwork2.mdl", - "target": "python/matlab_port/helpfiles/SimulatedNetwork2_mdl.py", - "kind": "mdl_scaffold" - }, - { - "source": "helpfiles/StimulusDecode2D.m", - "target": "python/matlab_port/helpfiles/StimulusDecode2D.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/TrialConfigExamples.m", - "target": "python/matlab_port/helpfiles/TrialConfigExamples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/TrialExamples.m", - "target": "python/matlab_port/helpfiles/TrialExamples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/ValidationDataSet.m", - "target": "python/matlab_port/helpfiles/ValidationDataSet.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/mEPSCAnalysis.m", - "target": "python/matlab_port/helpfiles/mEPSCAnalysis.py", - "kind": "script_scaffold" - }, - { - "source": "helpfiles/nSTATPaperExamples.m", - "target": "python/matlab_port/helpfiles/nSTATPaperExamples.py", - "kind": "paper_examples_entrypoint" - }, - { - "source": "helpfiles/nSpikeTrainExamples.m", - "target": "python/matlab_port/helpfiles/nSpikeTrainExamples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/nstCollExamples.m", - "target": "python/matlab_port/helpfiles/nstCollExamples.py", - "kind": "examples_script" - }, - { - "source": "helpfiles/temp.m", - "target": "python/matlab_port/helpfiles/temp.py", - "kind": "script_scaffold" - }, - { - "source": "libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD.m", - "target": "python/matlab_port/libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD.py", - "kind": "function_scaffold" - }, - { - "source": "libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD_demo.m", - "target": "python/matlab_port/libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD_demo.py", - "kind": "script_scaffold" - }, - { - "source": "libraries/fixPSlinestyle.m", - "target": "python/matlab_port/libraries/fixPSlinestyle.py", - "kind": "function_scaffold" - }, - { - "source": "libraries/rotateXLabels/rotateXLabels.m", - "target": "python/matlab_port/libraries/rotateXLabels/rotateXLabels.py", - "kind": "function_scaffold" - }, - { - "source": "libraries/xticklabel_rotate.m", - "target": "python/matlab_port/libraries/xticklabel_rotate.py", - "kind": "function_scaffold" - }, - { - "source": "libraries/zernike/zernfun.m", - "target": "python/matlab_port/libraries/zernike/zernfun.py", - "kind": "function_scaffold" - }, - { - "source": "libraries/zernike/zernfun2.m", - "target": "python/matlab_port/libraries/zernike/zernfun2.py", - "kind": "function_scaffold" - }, - { - "source": "libraries/zernike/zernpol.m", - "target": "python/matlab_port/libraries/zernike/zernpol.py", - "kind": "function_scaffold" - }, - { - "source": "nSTAT_Install.m", - "target": "python/matlab_port/nSTAT_Install.py", - "kind": "script_scaffold" - }, - { - "source": "nspikeTrain.m", - "target": "python/matlab_port/nspikeTrain.py", - "kind": "class_scaffold" - }, - { - "source": "nstColl.m", - "target": "python/matlab_port/nstColl.py", - "kind": "class_scaffold" - } - ] -} \ No newline at end of file diff --git a/python/matlab_port/Trial.py b/python/matlab_port/Trial.py deleted file mode 100644 index 5a2830b..0000000 --- a/python/matlab_port/Trial.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: Trial.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class Trial: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'Trial.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/TrialConfig.py b/python/matlab_port/TrialConfig.py deleted file mode 100644 index b0b8764..0000000 --- a/python/matlab_port/TrialConfig.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: TrialConfig.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class TrialConfig: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'TrialConfig.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/__init__.py b/python/matlab_port/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/matlab_port/data/Explicit Stimulus/GenCovMat.py b/python/matlab_port/data/Explicit Stimulus/GenCovMat.py deleted file mode 100644 index 0f536f3..0000000 --- a/python/matlab_port/data/Explicit Stimulus/GenCovMat.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: data/Explicit Stimulus/GenCovMat.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def GenCovMat(*, t=None, J=None, y=None, K=None) -> dict[str, object]: - frame = pd.DataFrame({'row': np.arange(3, dtype=int)}) - return { - 'source': 'data/Explicit Stimulus/GenCovMat.m', - 'function': 'GenCovMat', - 'rows': int(frame.shape[0]), - } - -def run(**kwargs) -> dict[str, object]: - return GenCovMat(**kwargs) diff --git a/python/matlab_port/data/Explicit Stimulus/__init__.py b/python/matlab_port/data/Explicit Stimulus/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/matlab_port/data/__init__.py b/python/matlab_port/data/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/matlab_port/helpfiles/AnalysisExamples.py b/python/matlab_port/helpfiles/AnalysisExamples.py deleted file mode 100644 index 6ab1229..0000000 --- a/python/matlab_port/helpfiles/AnalysisExamples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/AnalysisExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/AnalysisExamples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/AnalysisExamples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/AnalysisExamples2.py b/python/matlab_port/helpfiles/AnalysisExamples2.py deleted file mode 100644 index 30f8029..0000000 --- a/python/matlab_port/helpfiles/AnalysisExamples2.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/AnalysisExamples2.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/AnalysisExamples2.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/ClassDefinitions.py b/python/matlab_port/helpfiles/ClassDefinitions.py deleted file mode 100644 index 8b5b0cd..0000000 --- a/python/matlab_port/helpfiles/ClassDefinitions.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/ClassDefinitions.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/ClassDefinitions.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/ConfigCollExamples.py b/python/matlab_port/helpfiles/ConfigCollExamples.py deleted file mode 100644 index 1d89e69..0000000 --- a/python/matlab_port/helpfiles/ConfigCollExamples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/ConfigCollExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/ConfigCollExamples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/ConfigCollExamples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/CovCollExamples.py b/python/matlab_port/helpfiles/CovCollExamples.py deleted file mode 100644 index 19acac5..0000000 --- a/python/matlab_port/helpfiles/CovCollExamples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/CovCollExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/CovCollExamples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/CovCollExamples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/CovariateExamples.py b/python/matlab_port/helpfiles/CovariateExamples.py deleted file mode 100644 index 43715ec..0000000 --- a/python/matlab_port/helpfiles/CovariateExamples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/CovariateExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/CovariateExamples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/CovariateExamples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/DecodingExample.py b/python/matlab_port/helpfiles/DecodingExample.py deleted file mode 100644 index 2d6d5b4..0000000 --- a/python/matlab_port/helpfiles/DecodingExample.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/DecodingExample.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/DecodingExample.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/DecodingExampleWithHist.py b/python/matlab_port/helpfiles/DecodingExampleWithHist.py deleted file mode 100644 index e83aa02..0000000 --- a/python/matlab_port/helpfiles/DecodingExampleWithHist.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/DecodingExampleWithHist.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/DecodingExampleWithHist.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/EventsExamples.py b/python/matlab_port/helpfiles/EventsExamples.py deleted file mode 100644 index 4dbb7a2..0000000 --- a/python/matlab_port/helpfiles/EventsExamples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/EventsExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/EventsExamples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/EventsExamples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/Examples.py b/python/matlab_port/helpfiles/Examples.py deleted file mode 100644 index f88789a..0000000 --- a/python/matlab_port/helpfiles/Examples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/Examples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/Examples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/Examples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/ExplicitStimulusWhiskerData.py b/python/matlab_port/helpfiles/ExplicitStimulusWhiskerData.py deleted file mode 100644 index f6cdb5f..0000000 --- a/python/matlab_port/helpfiles/ExplicitStimulusWhiskerData.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/ExplicitStimulusWhiskerData.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/ExplicitStimulusWhiskerData.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/FitResSummaryExamples.py b/python/matlab_port/helpfiles/FitResSummaryExamples.py deleted file mode 100644 index 85d5dc0..0000000 --- a/python/matlab_port/helpfiles/FitResSummaryExamples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/FitResSummaryExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/FitResSummaryExamples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/FitResSummaryExamples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/FitResult.py b/python/matlab_port/helpfiles/FitResult.py deleted file mode 100644 index 72be1f1..0000000 --- a/python/matlab_port/helpfiles/FitResult.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/FitResult.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class FitResult: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'helpfiles/FitResult.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/helpfiles/FitResultExamples.py b/python/matlab_port/helpfiles/FitResultExamples.py deleted file mode 100644 index 7e32791..0000000 --- a/python/matlab_port/helpfiles/FitResultExamples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/FitResultExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/FitResultExamples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/FitResultExamples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/HippocampalPlaceCellExample.py b/python/matlab_port/helpfiles/HippocampalPlaceCellExample.py deleted file mode 100644 index c87e5ad..0000000 --- a/python/matlab_port/helpfiles/HippocampalPlaceCellExample.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/HippocampalPlaceCellExample.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/HippocampalPlaceCellExample.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/HistoryExamples.py b/python/matlab_port/helpfiles/HistoryExamples.py deleted file mode 100644 index 053112d..0000000 --- a/python/matlab_port/helpfiles/HistoryExamples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/HistoryExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/HistoryExamples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/HistoryExamples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/HybridFilterExample.py b/python/matlab_port/helpfiles/HybridFilterExample.py deleted file mode 100644 index 03f4065..0000000 --- a/python/matlab_port/helpfiles/HybridFilterExample.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/HybridFilterExample.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/HybridFilterExample.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/NetworkTutorial.py b/python/matlab_port/helpfiles/NetworkTutorial.py deleted file mode 100644 index aa8327a..0000000 --- a/python/matlab_port/helpfiles/NetworkTutorial.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/NetworkTutorial.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/NetworkTutorial.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/NeuralSpikeAnalysis_top.py b/python/matlab_port/helpfiles/NeuralSpikeAnalysis_top.py deleted file mode 100644 index 9a649eb..0000000 --- a/python/matlab_port/helpfiles/NeuralSpikeAnalysis_top.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/NeuralSpikeAnalysis_top.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/NeuralSpikeAnalysis_top.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/PPSimExample.py b/python/matlab_port/helpfiles/PPSimExample.py deleted file mode 100644 index 4ea60c3..0000000 --- a/python/matlab_port/helpfiles/PPSimExample.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/PPSimExample.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/PPSimExample.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/PPThinning.py b/python/matlab_port/helpfiles/PPThinning.py deleted file mode 100644 index d1ea647..0000000 --- a/python/matlab_port/helpfiles/PPThinning.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/PPThinning.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/PPThinning.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/PSTHEstimation.py b/python/matlab_port/helpfiles/PSTHEstimation.py deleted file mode 100644 index 155255b..0000000 --- a/python/matlab_port/helpfiles/PSTHEstimation.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/PSTHEstimation.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/PSTHEstimation.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/SignalObjExamples.py b/python/matlab_port/helpfiles/SignalObjExamples.py deleted file mode 100644 index ecd3818..0000000 --- a/python/matlab_port/helpfiles/SignalObjExamples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/SignalObjExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/SignalObjExamples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/SignalObjExamples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/SimulatedNetwork2_mdl.py b/python/matlab_port/helpfiles/SimulatedNetwork2_mdl.py deleted file mode 100644 index 12b0d86..0000000 --- a/python/matlab_port/helpfiles/SimulatedNetwork2_mdl.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/SimulatedNetwork2.mdl -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -SOURCE_MODEL = Path(r'/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local/helpfiles/SimulatedNetwork2.mdl') - -def load_text(path: str | Path | None = None) -> str: - p = Path(path) if path is not None else SOURCE_MODEL - return p.read_text(encoding='utf-8', errors='ignore') - -def summarize(path: str | Path | None = None) -> dict[str, object]: - text = load_text(path) - lines = text.splitlines() - frame = pd.DataFrame({'line_number': np.arange(1, len(lines) + 1), 'line_text': lines}) - return { - 'source': 'helpfiles/SimulatedNetwork2.mdl', - 'line_count': int(frame.shape[0]), - 'block_count_guess': int(frame['line_text'].str.contains('Block {', regex=False).sum()), - } diff --git a/python/matlab_port/helpfiles/StimulusDecode2D.py b/python/matlab_port/helpfiles/StimulusDecode2D.py deleted file mode 100644 index 7e6941c..0000000 --- a/python/matlab_port/helpfiles/StimulusDecode2D.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/StimulusDecode2D.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/StimulusDecode2D.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/TrialConfigExamples.py b/python/matlab_port/helpfiles/TrialConfigExamples.py deleted file mode 100644 index 10d09bd..0000000 --- a/python/matlab_port/helpfiles/TrialConfigExamples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/TrialConfigExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/TrialConfigExamples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/TrialConfigExamples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/TrialExamples.py b/python/matlab_port/helpfiles/TrialExamples.py deleted file mode 100644 index d26bba5..0000000 --- a/python/matlab_port/helpfiles/TrialExamples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/TrialExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/TrialExamples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/TrialExamples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/ValidationDataSet.py b/python/matlab_port/helpfiles/ValidationDataSet.py deleted file mode 100644 index b65de02..0000000 --- a/python/matlab_port/helpfiles/ValidationDataSet.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/ValidationDataSet.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/ValidationDataSet.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/__init__.py b/python/matlab_port/helpfiles/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/matlab_port/helpfiles/mEPSCAnalysis.py b/python/matlab_port/helpfiles/mEPSCAnalysis.py deleted file mode 100644 index aca3c6e..0000000 --- a/python/matlab_port/helpfiles/mEPSCAnalysis.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/mEPSCAnalysis.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/mEPSCAnalysis.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/helpfiles/nSTATPaperExamples.py b/python/matlab_port/helpfiles/nSTATPaperExamples.py deleted file mode 100644 index 52ffb41..0000000 --- a/python/matlab_port/helpfiles/nSTATPaperExamples.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/nSTATPaperExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import json -import sys - -THIS_FILE = Path(__file__).resolve() -PY_ROOT = THIS_FILE.parents[2] -if str(PY_ROOT) not in sys.path: - sys.path.insert(0, str(PY_ROOT)) - -from nstat.paper_examples_full import run_full_paper_examples - -def run(*, repo_root: str | Path | None = None) -> dict[str, dict[str, float]]: - root = Path(repo_root).resolve() if repo_root is not None else THIS_FILE.parents[3] - return run_full_paper_examples(root) - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 - -if __name__ == '__main__': - raise SystemExit(main()) diff --git a/python/matlab_port/helpfiles/nSpikeTrainExamples.py b/python/matlab_port/helpfiles/nSpikeTrainExamples.py deleted file mode 100644 index 7e3e8b1..0000000 --- a/python/matlab_port/helpfiles/nSpikeTrainExamples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/nSpikeTrainExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/nSpikeTrainExamples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/nSpikeTrainExamples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/nstCollExamples.py b/python/matlab_port/helpfiles/nstCollExamples.py deleted file mode 100644 index f210425..0000000 --- a/python/matlab_port/helpfiles/nstCollExamples.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/nstCollExamples.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -import html as _html -import json -import re - -from nstat import SpikeTrain, fit_poisson_glm, psth - -def _parse_html_reference(html_path: Path) -> dict[str, object]: - if not html_path.exists(): - return {'title': html_path.stem, 'sections': [], 'figures': [], 'code_outputs': []} - text = html_path.read_text(encoding='utf-8', errors='ignore') - title_m = re.search(r'(.*?)', text, flags=re.I | re.S) - title = _html.unescape(re.sub(r'<[^>]+>', '', title_m.group(1))).strip() if title_m else html_path.stem - sections = [_html.unescape(re.sub(r'<[^>]+>', '', s)).strip() for s in re.findall(r']*>(.*?)', text, flags=re.I | re.S)] - sections = [s for s in sections if s] - figures = sorted(dict.fromkeys(re.findall(r'src="([^"]+_\d+\.png)"', text, flags=re.I))) - raw_outputs = re.findall(r'
(.*?)
', text, flags=re.I | re.S) - code_outputs = [] - for b in raw_outputs: - cleaned = _html.unescape(re.sub(r'<[^>]+>', '', b)).replace('\xa0', ' ') - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - if cleaned: - code_outputs.append(cleaned) - return {'title': title, 'sections': sections, 'figures': figures, 'code_outputs': code_outputs} - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - html_ref = _parse_html_reference(root / 'helpfiles/nstCollExamples.html') - # Minimal nSTAT Python smoke using translated utilities. - x = np.linspace(-1.0, 1.0, 200) - y = (np.sin(2.0 * np.pi * x) > 0).astype(float) - fit = fit_poisson_glm(x[:, None], y, offset=np.zeros_like(y), max_iter=40) - trains = [SpikeTrain(np.array([0.1, 0.3, 0.7], dtype=float)), SpikeTrain(np.array([0.2, 0.4], dtype=float))] - psth_rate, _ = psth(trains, np.linspace(0.0, 1.0, 11)) - frame = pd.DataFrame({'section': html_ref['sections']}) - return { - 'source': 'helpfiles/nstCollExamples.m', - 'html_title': html_ref['title'], - 'section_count': int(len(html_ref['sections'])), - 'sections': html_ref['sections'], - 'figure_count': int(len(html_ref['figures'])), - 'figure_refs': html_ref['figures'], - 'expected_code_outputs': html_ref['code_outputs'][:8], - 'nstat_smoke': { - 'glm_log_likelihood': float(fit.log_likelihood), - 'psth_peak': float(np.max(psth_rate)), - }, - 'table_rows': int(frame.shape[0]), - } - -def main() -> int: - print(json.dumps(run(), indent=2)) - return 0 diff --git a/python/matlab_port/helpfiles/temp.py b/python/matlab_port/helpfiles/temp.py deleted file mode 100644 index ca5edb0..0000000 --- a/python/matlab_port/helpfiles/temp.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: helpfiles/temp.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'helpfiles/temp.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/__init__.py b/python/matlab_port/libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/matlab_port/libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD.py b/python/matlab_port/libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD.py deleted file mode 100644 index 3d61673..0000000 --- a/python/matlab_port/libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def nearestSPD(*, A=None) -> dict[str, object]: - frame = pd.DataFrame({'row': np.arange(3, dtype=int)}) - return { - 'source': 'libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD.m', - 'function': 'nearestSPD', - 'rows': int(frame.shape[0]), - } - -def run(**kwargs) -> dict[str, object]: - return nearestSPD(**kwargs) diff --git a/python/matlab_port/libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD_demo.py b/python/matlab_port/libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD_demo.py deleted file mode 100644 index e49939e..0000000 --- a/python/matlab_port/libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD_demo.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD_demo.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD_demo.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/libraries/NearestSymmetricPositiveDefinite/__init__.py b/python/matlab_port/libraries/NearestSymmetricPositiveDefinite/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/matlab_port/libraries/__init__.py b/python/matlab_port/libraries/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/matlab_port/libraries/fixPSlinestyle.py b/python/matlab_port/libraries/fixPSlinestyle.py deleted file mode 100644 index a1f0bbc..0000000 --- a/python/matlab_port/libraries/fixPSlinestyle.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: libraries/fixPSlinestyle.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def fixPSlinestyle(*, varargin=None) -> dict[str, object]: - frame = pd.DataFrame({'row': np.arange(3, dtype=int)}) - return { - 'source': 'libraries/fixPSlinestyle.m', - 'function': 'fixPSlinestyle', - 'rows': int(frame.shape[0]), - } - -def run(**kwargs) -> dict[str, object]: - return fixPSlinestyle(**kwargs) diff --git a/python/matlab_port/libraries/rotateXLabels/__init__.py b/python/matlab_port/libraries/rotateXLabels/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/matlab_port/libraries/rotateXLabels/rotateXLabels.py b/python/matlab_port/libraries/rotateXLabels/rotateXLabels.py deleted file mode 100644 index 1dbc2ad..0000000 --- a/python/matlab_port/libraries/rotateXLabels/rotateXLabels.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: libraries/rotateXLabels/rotateXLabels.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def rotateXLabels(*, ax=None, angle=None, varargin=None) -> dict[str, object]: - frame = pd.DataFrame({'row': np.arange(3, dtype=int)}) - return { - 'source': 'libraries/rotateXLabels/rotateXLabels.m', - 'function': 'rotateXLabels', - 'rows': int(frame.shape[0]), - } - -def run(**kwargs) -> dict[str, object]: - return rotateXLabels(**kwargs) diff --git a/python/matlab_port/libraries/xticklabel_rotate.py b/python/matlab_port/libraries/xticklabel_rotate.py deleted file mode 100644 index 5184192..0000000 --- a/python/matlab_port/libraries/xticklabel_rotate.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: libraries/xticklabel_rotate.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def xticklabel_rotate(*, XTick=None, rot=None, varargin=None) -> dict[str, object]: - frame = pd.DataFrame({'row': np.arange(3, dtype=int)}) - return { - 'source': 'libraries/xticklabel_rotate.m', - 'function': 'xticklabel_rotate', - 'rows': int(frame.shape[0]), - } - -def run(**kwargs) -> dict[str, object]: - return xticklabel_rotate(**kwargs) diff --git a/python/matlab_port/libraries/zernike/__init__.py b/python/matlab_port/libraries/zernike/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/matlab_port/libraries/zernike/zernfun.py b/python/matlab_port/libraries/zernike/zernfun.py deleted file mode 100644 index 2f969f9..0000000 --- a/python/matlab_port/libraries/zernike/zernfun.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: libraries/zernike/zernfun.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def zernfun(*, n=None, m=None, r=None, theta=None, nflag=None) -> dict[str, object]: - frame = pd.DataFrame({'row': np.arange(3, dtype=int)}) - return { - 'source': 'libraries/zernike/zernfun.m', - 'function': 'zernfun', - 'rows': int(frame.shape[0]), - } - -def run(**kwargs) -> dict[str, object]: - return zernfun(**kwargs) diff --git a/python/matlab_port/libraries/zernike/zernfun2.py b/python/matlab_port/libraries/zernike/zernfun2.py deleted file mode 100644 index 925ed51..0000000 --- a/python/matlab_port/libraries/zernike/zernfun2.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: libraries/zernike/zernfun2.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def zernfun2(*, p=None, r=None, theta=None, nflag=None) -> dict[str, object]: - frame = pd.DataFrame({'row': np.arange(3, dtype=int)}) - return { - 'source': 'libraries/zernike/zernfun2.m', - 'function': 'zernfun2', - 'rows': int(frame.shape[0]), - } - -def run(**kwargs) -> dict[str, object]: - return zernfun2(**kwargs) diff --git a/python/matlab_port/libraries/zernike/zernpol.py b/python/matlab_port/libraries/zernike/zernpol.py deleted file mode 100644 index ec63cd4..0000000 --- a/python/matlab_port/libraries/zernike/zernpol.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: libraries/zernike/zernpol.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def zernpol(*, n=None, m=None, r=None, nflag=None) -> dict[str, object]: - frame = pd.DataFrame({'row': np.arange(3, dtype=int)}) - return { - 'source': 'libraries/zernike/zernpol.m', - 'function': 'zernpol', - 'rows': int(frame.shape[0]), - } - -def run(**kwargs) -> dict[str, object]: - return zernpol(**kwargs) diff --git a/python/matlab_port/nSTAT_Install.py b/python/matlab_port/nSTAT_Install.py deleted file mode 100644 index dcad841..0000000 --- a/python/matlab_port/nSTAT_Install.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: nSTAT_Install.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -def run(*, repo_root: str | Path | None = None) -> dict[str, object]: - root = Path(repo_root).resolve() if repo_root is not None else Path.cwd() - frame = pd.DataFrame({'line': [1, 2, 3], 'value': [1.0, 2.0, 3.0]}) - return { - 'source': 'nSTAT_Install.m', - 'repo_root': str(root), - 'demo_mean': float(frame['value'].mean()), - } diff --git a/python/matlab_port/nspikeTrain.py b/python/matlab_port/nspikeTrain.py deleted file mode 100644 index b634a70..0000000 --- a/python/matlab_port/nspikeTrain.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: nspikeTrain.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class nspikeTrain: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'nspikeTrain.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/matlab_port/nstColl.py b/python/matlab_port/nstColl.py deleted file mode 100644 index d24bca4..0000000 --- a/python/matlab_port/nstColl.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Auto-generated MATLAB-to-Python scaffold. - -Source: nstColl.m -""" - -from __future__ import annotations - -from pathlib import Path -import numpy as np -import pandas as pd -from scipy.io import loadmat, savemat - -class nstColl: - """Scaffold translated from MATLAB classdef.""" - - def __init__(self, *args, **kwargs) -> None: - self.args = args - self.kwargs = kwargs - - def metadata(self) -> dict[str, object]: - return { - 'source': 'nstColl.m', - 'args_count': len(self.args), - 'kwargs': sorted(list(self.kwargs.keys())), - } diff --git a/python/notebooks/helpfiles/.idea/helpfiles.iml b/python/notebooks/helpfiles/.idea/helpfiles.iml deleted file mode 100644 index d0876a7..0000000 --- a/python/notebooks/helpfiles/.idea/helpfiles.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/python/notebooks/helpfiles/.idea/misc.xml b/python/notebooks/helpfiles/.idea/misc.xml deleted file mode 100644 index 2a99109..0000000 --- a/python/notebooks/helpfiles/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/python/notebooks/helpfiles/.idea/modules.xml b/python/notebooks/helpfiles/.idea/modules.xml deleted file mode 100644 index 3b3d2fa..0000000 --- a/python/notebooks/helpfiles/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/python/notebooks/helpfiles/.idea/vcs.xml b/python/notebooks/helpfiles/.idea/vcs.xml deleted file mode 100644 index c2365ab..0000000 --- a/python/notebooks/helpfiles/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/python/notebooks/helpfiles/.idea/workspace.xml b/python/notebooks/helpfiles/.idea/workspace.xml deleted file mode 100644 index 53cb95b..0000000 --- a/python/notebooks/helpfiles/.idea/workspace.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/python/notebooks/helpfiles/AnalysisExamples.ipynb b/python/notebooks/helpfiles/AnalysisExamples.ipynb deleted file mode 100644 index 4b353a5..0000000 --- a/python/notebooks/helpfiles/AnalysisExamples.ipynb +++ /dev/null @@ -1,73 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Using the Analysis Class\\n", - "\\n", - "Executable Python notebook generated from source help-topic scripts.\\n", - "MATLAB help target: `AnalysisExamples.html`\\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pathlib import Path\n", - "import sys\n", - "import json\n", - "\n", - "def find_repo_root(start: Path) -> Path:\n", - " cur = start.resolve()\n", - " for p in [cur, *cur.parents]:\n", - " if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n", - " return p\n", - " raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n", - "\n", - "repo_root = find_repo_root(Path.cwd())\n", - "py_root = repo_root / 'python'\n", - "if str(py_root) not in sys.path:\n", - " sys.path.insert(0, str(py_root))\n", - "print('repo_root =', repo_root)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from examples.help_topics.AnalysisExamples import run\n", - "out = run(repo_root=repo_root)\n", - "print(json.dumps(out, indent=2, default=str))\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "assert isinstance(out, dict)\n", - "assert 'topic' in out\n", - "print('Notebook execution check: PASS')\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/python/notebooks/helpfiles/AnalysisExamples2.ipynb b/python/notebooks/helpfiles/AnalysisExamples2.ipynb deleted file mode 100644 index 1365cdc..0000000 --- a/python/notebooks/helpfiles/AnalysisExamples2.ipynb +++ /dev/null @@ -1,127 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# AnalysisExamples2 (Python Translation)\\n", - "\\n", - "Source MATLAB file: `helpfiles/AnalysisExamples2.m`\\n", - "Reference HTML file: `helpfiles/AnalysisExamples2.html`\\n", - "\\n", - "## MATLAB HTML Reference\\n", - "- HTML title: **AnalysisExamples2**\\n", - "\\n", - "### Expected Sections (from HTML)\\n", - "- Contents\n- Analysis Examples 2\n- Toolbox vs. Standard GLM comparison\n- Compute the history effect\\n", - "\\n", - "### Figure References (from HTML)\\n", - "- `AnalysisExamples2_01.png`\n- `AnalysisExamples2_02.png`\n- `AnalysisExamples2_03.png`\n- `AnalysisExamples2_04.png`\\n", - "\\n", - "### Sample Code Outputs (from HTML)\\n", - "```\nAnalyzing Configuration #1: Neuron #1 Analyzing Configuration #2: Neuron #1 Analyzing Configuration #3: Neuron #1\n```\n\n```\nans = 3.5041 0.0099 0.0102 0.0210 0.0215 0.0172\n```\n\n```\nAnalyzing Configuration #1: Neuron #1 Analyzing Configuration #2: Neuron #1 Analyzing Configuration #3: Neuron #1 Analyzing Configuration #4: Neuron #1 Analyzing Configuration #5: Neuron #1 Analyzing Configuration #6: Neuron #1 Analyzing Configuration #7: Neuron #1 Analyzing Configuration #8: Neuron #1 Analyzing Configuration #9: Neuron #1 Analyzing Configuration #10: Neuron #1 Analyzing Configura\n```\\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pathlib import Path\n", - "import sys\n", - "import importlib\n", - "import json\n", - "\n", - "def find_repo_root(start: Path) -> Path:\n", - " cur = start.resolve()\n", - " for p in [cur, *cur.parents]:\n", - " if (p / '.git').exists() and (p / 'helpfiles').exists():\n", - " return p\n", - " raise RuntimeError('Could not find repository root')\n", - "\n", - "repo_root = find_repo_root(Path.cwd())\n", - "py_root = repo_root / 'python'\n", - "if str(py_root) not in sys.path:\n", - " sys.path.insert(0, str(py_root))\n", - "print('repo_root =', repo_root)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import re\n", - "import html as _html\n", - "\n", - "html_path = repo_root / 'helpfiles/AnalysisExamples2.html'\n", - "if not html_path.exists():\n", - " print('HTML reference missing:', html_path)\n", - "else:\n", - " html_text = html_path.read_text(encoding='utf-8', errors='ignore')\n", - " title_match = re.search(r'(.*?)', html_text, flags=re.I | re.S)\n", - " title = _html.unescape(re.sub(r'<[^>]+>', '', title_match.group(1))).strip() if title_match else html_path.stem\n", - " sections = [\n", - " _html.unescape(re.sub(r'<[^>]+>', '', s)).strip()\n", - " for s in re.findall(r']*>(.*?)', html_text, flags=re.I | re.S)\n", - " ]\n", - " sections = [s for s in sections if s]\n", - " figs = sorted(set(re.findall(r'src=\"([^\"]+_\\d+\\.png)\"', html_text, flags=re.I)))\n", - " print('HTML title:', title)\n", - " print('Section count:', len(sections))\n", - " for s in sections:\n", - " print(' -', s)\n", - " print('Figure refs:', len(figs))\n", - " for f in figs[:20]:\n", - " print(' -', f)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "module = importlib.import_module('matlab_port.helpfiles.AnalysisExamples2')\n", - "if hasattr(module, 'run'):\n", - " out = module.run(repo_root=repo_root)\n", - "elif hasattr(module, 'main'):\n", - " out = module.main()\n", - "else:\n", - " out = {'status': 'no run/main entrypoint'}\n", - "if isinstance(out, (dict, list)):\n", - " print(json.dumps(out, indent=2, default=str))\n", - "else:\n", - " print(out)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "comparison = {\n", - " 'python_output_type': type(out).__name__,\n", - " 'python_output_keys': sorted(list(out.keys())) if isinstance(out, dict) else [],\n", - "}\n", - "print(json.dumps(comparison, indent=2))\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/python/notebooks/helpfiles/ClassDefinitions.ipynb b/python/notebooks/helpfiles/ClassDefinitions.ipynb deleted file mode 100644 index f8394a8..0000000 --- a/python/notebooks/helpfiles/ClassDefinitions.ipynb +++ /dev/null @@ -1,127 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# ClassDefinitions (Python Translation)\\n", - "\\n", - "Source MATLAB file: `helpfiles/ClassDefinitions.m`\\n", - "Reference HTML file: `helpfiles/ClassDefinitions.html`\\n", - "\\n", - "## MATLAB HTML Reference\\n", - "- HTML title: **Class Definitions**\\n", - "\\n", - "### Expected Sections (from HTML)\\n", - "- (no sections found)\\n", - "\\n", - "### Figure References (from HTML)\\n", - "- (no figure files listed)\\n", - "\\n", - "### Sample Code Outputs (from HTML)\\n", - "_No `
` blocks found._\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import importlib\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find repository root')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "import re\n",
-        "import html as _html\n",
-        "\n",
-        "html_path = repo_root / 'helpfiles/ClassDefinitions.html'\n",
-        "if not html_path.exists():\n",
-        "    print('HTML reference missing:', html_path)\n",
-        "else:\n",
-        "    html_text = html_path.read_text(encoding='utf-8', errors='ignore')\n",
-        "    title_match = re.search(r'(.*?)', html_text, flags=re.I | re.S)\n",
-        "    title = _html.unescape(re.sub(r'<[^>]+>', '', title_match.group(1))).strip() if title_match else html_path.stem\n",
-        "    sections = [\n",
-        "        _html.unescape(re.sub(r'<[^>]+>', '', s)).strip()\n",
-        "        for s in re.findall(r']*>(.*?)', html_text, flags=re.I | re.S)\n",
-        "    ]\n",
-        "    sections = [s for s in sections if s]\n",
-        "    figs = sorted(set(re.findall(r'src=\"([^\"]+_\\d+\\.png)\"', html_text, flags=re.I)))\n",
-        "    print('HTML title:', title)\n",
-        "    print('Section count:', len(sections))\n",
-        "    for s in sections:\n",
-        "        print(' -', s)\n",
-        "    print('Figure refs:', len(figs))\n",
-        "    for f in figs[:20]:\n",
-        "        print(' -', f)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "module = importlib.import_module('matlab_port.helpfiles.ClassDefinitions')\n",
-        "if hasattr(module, 'run'):\n",
-        "    out = module.run(repo_root=repo_root)\n",
-        "elif hasattr(module, 'main'):\n",
-        "    out = module.main()\n",
-        "else:\n",
-        "    out = {'status': 'no run/main entrypoint'}\n",
-        "if isinstance(out, (dict, list)):\n",
-        "    print(json.dumps(out, indent=2, default=str))\n",
-        "else:\n",
-        "    print(out)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "comparison = {\n",
-        "    'python_output_type': type(out).__name__,\n",
-        "    'python_output_keys': sorted(list(out.keys())) if isinstance(out, dict) else [],\n",
-        "}\n",
-        "print(json.dumps(comparison, indent=2))\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/ConfigCollExamples.ipynb b/python/notebooks/helpfiles/ConfigCollExamples.ipynb
deleted file mode 100644
index a045994..0000000
--- a/python/notebooks/helpfiles/ConfigCollExamples.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Using the ConfigColl Class\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `ConfigCollExamples.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.ConfigCollExamples import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/CovCollExamples.ipynb b/python/notebooks/helpfiles/CovCollExamples.ipynb
deleted file mode 100644
index e0eb32f..0000000
--- a/python/notebooks/helpfiles/CovCollExamples.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Using the CovColl Class\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `CovCollExamples.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.CovCollExamples import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/CovariateExamples.ipynb b/python/notebooks/helpfiles/CovariateExamples.ipynb
deleted file mode 100644
index fc535f5..0000000
--- a/python/notebooks/helpfiles/CovariateExamples.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Using the Covariate Class\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `CovariateExamples.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.CovariateExamples import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/DecodingExample.ipynb b/python/notebooks/helpfiles/DecodingExample.ipynb
deleted file mode 100644
index 0c46b07..0000000
--- a/python/notebooks/helpfiles/DecodingExample.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Example Data Analysis - Decoding Univariate Simulated Stimuli (No History Effect)\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `DecodingExample.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.DecodingExample import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/DecodingExampleWithHist.ipynb b/python/notebooks/helpfiles/DecodingExampleWithHist.ipynb
deleted file mode 100644
index 988a852..0000000
--- a/python/notebooks/helpfiles/DecodingExampleWithHist.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Example Data Analysis - Decoding Univariate Simulated Stimuli with and without History Effect\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `DecodingExampleWithHist.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.DecodingExampleWithHist import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/EventsExamples.ipynb b/python/notebooks/helpfiles/EventsExamples.ipynb
deleted file mode 100644
index 769e6f6..0000000
--- a/python/notebooks/helpfiles/EventsExamples.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Using the Events Class\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `EventsExamples.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.EventsExamples import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/Examples.ipynb b/python/notebooks/helpfiles/Examples.ipynb
deleted file mode 100644
index f1bdde1..0000000
--- a/python/notebooks/helpfiles/Examples.ipynb
+++ /dev/null
@@ -1,127 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Examples (Python Translation)\\n",
-        "\\n",
-        "Source MATLAB file: `helpfiles/Examples.m`\\n",
-        "Reference HTML file: `helpfiles/Examples.html`\\n",
-        "\\n",
-        "## MATLAB HTML Reference\\n",
-        "- HTML title: **Examples**\\n",
-        "\\n",
-        "### Expected Sections (from HTML)\\n",
-        "- (no sections found)\\n",
-        "\\n",
-        "### Figure References (from HTML)\\n",
-        "- (no figure files listed)\\n",
-        "\\n",
-        "### Sample Code Outputs (from HTML)\\n",
-        "_No `
` blocks found._\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import importlib\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find repository root')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "import re\n",
-        "import html as _html\n",
-        "\n",
-        "html_path = repo_root / 'helpfiles/Examples.html'\n",
-        "if not html_path.exists():\n",
-        "    print('HTML reference missing:', html_path)\n",
-        "else:\n",
-        "    html_text = html_path.read_text(encoding='utf-8', errors='ignore')\n",
-        "    title_match = re.search(r'(.*?)', html_text, flags=re.I | re.S)\n",
-        "    title = _html.unescape(re.sub(r'<[^>]+>', '', title_match.group(1))).strip() if title_match else html_path.stem\n",
-        "    sections = [\n",
-        "        _html.unescape(re.sub(r'<[^>]+>', '', s)).strip()\n",
-        "        for s in re.findall(r']*>(.*?)', html_text, flags=re.I | re.S)\n",
-        "    ]\n",
-        "    sections = [s for s in sections if s]\n",
-        "    figs = sorted(set(re.findall(r'src=\"([^\"]+_\\d+\\.png)\"', html_text, flags=re.I)))\n",
-        "    print('HTML title:', title)\n",
-        "    print('Section count:', len(sections))\n",
-        "    for s in sections:\n",
-        "        print(' -', s)\n",
-        "    print('Figure refs:', len(figs))\n",
-        "    for f in figs[:20]:\n",
-        "        print(' -', f)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "module = importlib.import_module('matlab_port.helpfiles.Examples')\n",
-        "if hasattr(module, 'run'):\n",
-        "    out = module.run(repo_root=repo_root)\n",
-        "elif hasattr(module, 'main'):\n",
-        "    out = module.main()\n",
-        "else:\n",
-        "    out = {'status': 'no run/main entrypoint'}\n",
-        "if isinstance(out, (dict, list)):\n",
-        "    print(json.dumps(out, indent=2, default=str))\n",
-        "else:\n",
-        "    print(out)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "comparison = {\n",
-        "    'python_output_type': type(out).__name__,\n",
-        "    'python_output_keys': sorted(list(out.keys())) if isinstance(out, dict) else [],\n",
-        "}\n",
-        "print(json.dumps(comparison, indent=2))\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/ExplicitStimulusWhiskerData.ipynb b/python/notebooks/helpfiles/ExplicitStimulusWhiskerData.ipynb
deleted file mode 100644
index 8b9a01d..0000000
--- a/python/notebooks/helpfiles/ExplicitStimulusWhiskerData.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Example Data Analysis - Explicit Stimulus\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `ExplicitStimulusWhiskerData.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.ExplicitStimulusWhiskerData import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/FitResSummaryExamples.ipynb b/python/notebooks/helpfiles/FitResSummaryExamples.ipynb
deleted file mode 100644
index 534a834..0000000
--- a/python/notebooks/helpfiles/FitResSummaryExamples.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Using the FitResSummary Class\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `FitResSummaryExamples.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.FitResSummaryExamples import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/FitResult.ipynb b/python/notebooks/helpfiles/FitResult.ipynb
deleted file mode 100644
index 9c3065d..0000000
--- a/python/notebooks/helpfiles/FitResult.ipynb
+++ /dev/null
@@ -1,127 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# FitResult (Python Translation)\\n",
-        "\\n",
-        "Source MATLAB file: `helpfiles/FitResult.m`\\n",
-        "Reference HTML file: `helpfiles/FitResult.html`\\n",
-        "\\n",
-        "## MATLAB HTML Reference\\n",
-        "- HTML title: **FitResult**\\n",
-        "\\n",
-        "### Expected Sections (from HTML)\\n",
-        "- (no sections found)\\n",
-        "\\n",
-        "### Figure References (from HTML)\\n",
-        "- (no figure files listed)\\n",
-        "\\n",
-        "### Sample Code Outputs (from HTML)\\n",
-        "```\nInput argument \"spikeObj\" is undefined. Error in ==> FitResult>FitResult.FitResult at 86 if(isnumeric(spikeObj.name))\n```\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import importlib\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find repository root')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "import re\n",
-        "import html as _html\n",
-        "\n",
-        "html_path = repo_root / 'helpfiles/FitResult.html'\n",
-        "if not html_path.exists():\n",
-        "    print('HTML reference missing:', html_path)\n",
-        "else:\n",
-        "    html_text = html_path.read_text(encoding='utf-8', errors='ignore')\n",
-        "    title_match = re.search(r'(.*?)', html_text, flags=re.I | re.S)\n",
-        "    title = _html.unescape(re.sub(r'<[^>]+>', '', title_match.group(1))).strip() if title_match else html_path.stem\n",
-        "    sections = [\n",
-        "        _html.unescape(re.sub(r'<[^>]+>', '', s)).strip()\n",
-        "        for s in re.findall(r']*>(.*?)', html_text, flags=re.I | re.S)\n",
-        "    ]\n",
-        "    sections = [s for s in sections if s]\n",
-        "    figs = sorted(set(re.findall(r'src=\"([^\"]+_\\d+\\.png)\"', html_text, flags=re.I)))\n",
-        "    print('HTML title:', title)\n",
-        "    print('Section count:', len(sections))\n",
-        "    for s in sections:\n",
-        "        print(' -', s)\n",
-        "    print('Figure refs:', len(figs))\n",
-        "    for f in figs[:20]:\n",
-        "        print(' -', f)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "module = importlib.import_module('matlab_port.helpfiles.FitResult')\n",
-        "if hasattr(module, 'run'):\n",
-        "    out = module.run(repo_root=repo_root)\n",
-        "elif hasattr(module, 'main'):\n",
-        "    out = module.main()\n",
-        "else:\n",
-        "    out = {'status': 'no run/main entrypoint'}\n",
-        "if isinstance(out, (dict, list)):\n",
-        "    print(json.dumps(out, indent=2, default=str))\n",
-        "else:\n",
-        "    print(out)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "comparison = {\n",
-        "    'python_output_type': type(out).__name__,\n",
-        "    'python_output_keys': sorted(list(out.keys())) if isinstance(out, dict) else [],\n",
-        "}\n",
-        "print(json.dumps(comparison, indent=2))\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/FitResultExamples.ipynb b/python/notebooks/helpfiles/FitResultExamples.ipynb
deleted file mode 100644
index 2b21a03..0000000
--- a/python/notebooks/helpfiles/FitResultExamples.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Using the FitResult Class\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `FitResultExamples.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.FitResultExamples import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/HippocampalPlaceCellExample.ipynb b/python/notebooks/helpfiles/HippocampalPlaceCellExample.ipynb
deleted file mode 100644
index 8112138..0000000
--- a/python/notebooks/helpfiles/HippocampalPlaceCellExample.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Example Data Analysis - Hippocampal Place Cell Receptive Field Estimation\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `HippocampalPlaceCellExample.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.HippocampalPlaceCellExample import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/HistoryExamples.ipynb b/python/notebooks/helpfiles/HistoryExamples.ipynb
deleted file mode 100644
index 8b8c3f6..0000000
--- a/python/notebooks/helpfiles/HistoryExamples.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Using the History Class\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `HistoryExamples.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.HistoryExamples import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/HybridFilterExample.ipynb b/python/notebooks/helpfiles/HybridFilterExample.ipynb
deleted file mode 100644
index b5b55dc..0000000
--- a/python/notebooks/helpfiles/HybridFilterExample.ipynb
+++ /dev/null
@@ -1,127 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# HybridFilterExample (Python Translation)\\n",
-        "\\n",
-        "Source MATLAB file: `helpfiles/HybridFilterExample.m`\\n",
-        "Reference HTML file: `helpfiles/HybridFilterExample.html`\\n",
-        "\\n",
-        "## MATLAB HTML Reference\\n",
-        "- HTML title: **Hybrid Point Process Filter Example**\\n",
-        "\\n",
-        "### Expected Sections (from HTML)\\n",
-        "- Contents\n- Problem Statement\n- Generated Simulated Arm Reach\n- Simulate Neural Firing\\n",
-        "\\n",
-        "### Figure References (from HTML)\\n",
-        "- `HybridFilterExample_01.png`\n- `HybridFilterExample_02.png`\\n",
-        "\\n",
-        "### Sample Code Outputs (from HTML)\\n",
-        "_No `
` blocks found._\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import importlib\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find repository root')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "import re\n",
-        "import html as _html\n",
-        "\n",
-        "html_path = repo_root / 'helpfiles/HybridFilterExample.html'\n",
-        "if not html_path.exists():\n",
-        "    print('HTML reference missing:', html_path)\n",
-        "else:\n",
-        "    html_text = html_path.read_text(encoding='utf-8', errors='ignore')\n",
-        "    title_match = re.search(r'(.*?)', html_text, flags=re.I | re.S)\n",
-        "    title = _html.unescape(re.sub(r'<[^>]+>', '', title_match.group(1))).strip() if title_match else html_path.stem\n",
-        "    sections = [\n",
-        "        _html.unescape(re.sub(r'<[^>]+>', '', s)).strip()\n",
-        "        for s in re.findall(r']*>(.*?)', html_text, flags=re.I | re.S)\n",
-        "    ]\n",
-        "    sections = [s for s in sections if s]\n",
-        "    figs = sorted(set(re.findall(r'src=\"([^\"]+_\\d+\\.png)\"', html_text, flags=re.I)))\n",
-        "    print('HTML title:', title)\n",
-        "    print('Section count:', len(sections))\n",
-        "    for s in sections:\n",
-        "        print(' -', s)\n",
-        "    print('Figure refs:', len(figs))\n",
-        "    for f in figs[:20]:\n",
-        "        print(' -', f)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "module = importlib.import_module('matlab_port.helpfiles.HybridFilterExample')\n",
-        "if hasattr(module, 'run'):\n",
-        "    out = module.run(repo_root=repo_root)\n",
-        "elif hasattr(module, 'main'):\n",
-        "    out = module.main()\n",
-        "else:\n",
-        "    out = {'status': 'no run/main entrypoint'}\n",
-        "if isinstance(out, (dict, list)):\n",
-        "    print(json.dumps(out, indent=2, default=str))\n",
-        "else:\n",
-        "    print(out)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "comparison = {\n",
-        "    'python_output_type': type(out).__name__,\n",
-        "    'python_output_keys': sorted(list(out.keys())) if isinstance(out, dict) else [],\n",
-        "}\n",
-        "print(json.dumps(comparison, indent=2))\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/NetworkTutorial.ipynb b/python/notebooks/helpfiles/NetworkTutorial.ipynb
deleted file mode 100644
index 7512d5d..0000000
--- a/python/notebooks/helpfiles/NetworkTutorial.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Example Data Analysis - Two Neuron Network Simulation and Estimation of Ensemble Effect\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `NetworkTutorial.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.NetworkTutorial import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/NeuralSpikeAnalysis_top.ipynb b/python/notebooks/helpfiles/NeuralSpikeAnalysis_top.ipynb
deleted file mode 100644
index b09b603..0000000
--- a/python/notebooks/helpfiles/NeuralSpikeAnalysis_top.ipynb
+++ /dev/null
@@ -1,127 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# NeuralSpikeAnalysis_top (Python Translation)\\n",
-        "\\n",
-        "Source MATLAB file: `helpfiles/NeuralSpikeAnalysis_top.m`\\n",
-        "Reference HTML file: `helpfiles/NeuralSpikeAnalysis_top.html`\\n",
-        "\\n",
-        "## MATLAB HTML Reference\\n",
-        "- HTML title: **Neural Spike Train Analysis Toolbox (nSTAT)**\\n",
-        "\\n",
-        "### Expected Sections (from HTML)\\n",
-        "- (no sections found)\\n",
-        "\\n",
-        "### Figure References (from HTML)\\n",
-        "- (no figure files listed)\\n",
-        "\\n",
-        "### Sample Code Outputs (from HTML)\\n",
-        "_No `
` blocks found._\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import importlib\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find repository root')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "import re\n",
-        "import html as _html\n",
-        "\n",
-        "html_path = repo_root / 'helpfiles/NeuralSpikeAnalysis_top.html'\n",
-        "if not html_path.exists():\n",
-        "    print('HTML reference missing:', html_path)\n",
-        "else:\n",
-        "    html_text = html_path.read_text(encoding='utf-8', errors='ignore')\n",
-        "    title_match = re.search(r'(.*?)', html_text, flags=re.I | re.S)\n",
-        "    title = _html.unescape(re.sub(r'<[^>]+>', '', title_match.group(1))).strip() if title_match else html_path.stem\n",
-        "    sections = [\n",
-        "        _html.unescape(re.sub(r'<[^>]+>', '', s)).strip()\n",
-        "        for s in re.findall(r']*>(.*?)', html_text, flags=re.I | re.S)\n",
-        "    ]\n",
-        "    sections = [s for s in sections if s]\n",
-        "    figs = sorted(set(re.findall(r'src=\"([^\"]+_\\d+\\.png)\"', html_text, flags=re.I)))\n",
-        "    print('HTML title:', title)\n",
-        "    print('Section count:', len(sections))\n",
-        "    for s in sections:\n",
-        "        print(' -', s)\n",
-        "    print('Figure refs:', len(figs))\n",
-        "    for f in figs[:20]:\n",
-        "        print(' -', f)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "module = importlib.import_module('matlab_port.helpfiles.NeuralSpikeAnalysis_top')\n",
-        "if hasattr(module, 'run'):\n",
-        "    out = module.run(repo_root=repo_root)\n",
-        "elif hasattr(module, 'main'):\n",
-        "    out = module.main()\n",
-        "else:\n",
-        "    out = {'status': 'no run/main entrypoint'}\n",
-        "if isinstance(out, (dict, list)):\n",
-        "    print(json.dumps(out, indent=2, default=str))\n",
-        "else:\n",
-        "    print(out)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "comparison = {\n",
-        "    'python_output_type': type(out).__name__,\n",
-        "    'python_output_keys': sorted(list(out.keys())) if isinstance(out, dict) else [],\n",
-        "}\n",
-        "print(json.dumps(comparison, indent=2))\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/PPSimExample.ipynb b/python/notebooks/helpfiles/PPSimExample.ipynb
deleted file mode 100644
index e2a9f4e..0000000
--- a/python/notebooks/helpfiles/PPSimExample.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Example Data Analysis - Simulated Explicit Stimulus and History\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `PPSimExample.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.PPSimExample import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/PPThinning.ipynb b/python/notebooks/helpfiles/PPThinning.ipynb
deleted file mode 100644
index 94890f1..0000000
--- a/python/notebooks/helpfiles/PPThinning.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Point Process Simulation via Thinning\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `PPThinning.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.PPThinning import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/PSTHEstimation.ipynb b/python/notebooks/helpfiles/PSTHEstimation.ipynb
deleted file mode 100644
index 19a6d5e..0000000
--- a/python/notebooks/helpfiles/PSTHEstimation.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Example Data Analysis - Simulated Data - Computing a Peri-Stimulus Time Histogram (PSTH)\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `PSTHEstimation.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.PSTHEstimation import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/SignalObjExamples.ipynb b/python/notebooks/helpfiles/SignalObjExamples.ipynb
deleted file mode 100644
index 5872179..0000000
--- a/python/notebooks/helpfiles/SignalObjExamples.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Using the SignalObj Class\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `SignalObjExamples.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.SignalObjExamples import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/StimulusDecode2D.ipynb b/python/notebooks/helpfiles/StimulusDecode2D.ipynb
deleted file mode 100644
index 8ad6d65..0000000
--- a/python/notebooks/helpfiles/StimulusDecode2D.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Example Data Analysis - Decoding Bivariate Simulated Stimuli\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `StimulusDecode2D.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.StimulusDecode2D import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/TrialConfigExamples.ipynb b/python/notebooks/helpfiles/TrialConfigExamples.ipynb
deleted file mode 100644
index a2a5ea9..0000000
--- a/python/notebooks/helpfiles/TrialConfigExamples.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Using the TrialConfig Class\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `TrialConfigExamples.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.TrialConfigExamples import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/TrialExamples.ipynb b/python/notebooks/helpfiles/TrialExamples.ipynb
deleted file mode 100644
index c5e5f35..0000000
--- a/python/notebooks/helpfiles/TrialExamples.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Using the Trial Class\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `TrialExamples.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.TrialExamples import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/ValidationDataSet.ipynb b/python/notebooks/helpfiles/ValidationDataSet.ipynb
deleted file mode 100644
index 916a4ef..0000000
--- a/python/notebooks/helpfiles/ValidationDataSet.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Example Data Analysis - Simulated Constant (Piecewise Constant) Rate Poisson\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `ValidationDataSet.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.ValidationDataSet import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/mEPSCAnalysis.ipynb b/python/notebooks/helpfiles/mEPSCAnalysis.ipynb
deleted file mode 100644
index 9438356..0000000
--- a/python/notebooks/helpfiles/mEPSCAnalysis.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Example Data Analysis - Miniature Excitatory Post-Synaptic Currents (mEPSCs)\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `mEPSCAnalysis.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.mEPSCAnalysis import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/nSTATPaperExamples.ipynb b/python/notebooks/helpfiles/nSTATPaperExamples.ipynb
deleted file mode 100644
index 3b0569a..0000000
--- a/python/notebooks/helpfiles/nSTATPaperExamples.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# nSTAT Paper Examples\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `nSTATPaperExamples.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.nSTATPaperExamples import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/nSpikeTrainExamples.ipynb b/python/notebooks/helpfiles/nSpikeTrainExamples.ipynb
deleted file mode 100644
index 175a6d1..0000000
--- a/python/notebooks/helpfiles/nSpikeTrainExamples.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Using the nSpikeTrain Class\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `nSpikeTrainExamples.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.nSpikeTrainExamples import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/nstCollExamples.ipynb b/python/notebooks/helpfiles/nstCollExamples.ipynb
deleted file mode 100644
index 60703f8..0000000
--- a/python/notebooks/helpfiles/nstCollExamples.ipynb
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# Using the nstColl Class\\n",
-        "\\n",
-        "Executable Python notebook generated from source help-topic scripts.\\n",
-        "MATLAB help target: `nstCollExamples.html`\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from examples.help_topics.nstCollExamples import run\n",
-        "out = run(repo_root=repo_root)\n",
-        "print(json.dumps(out, indent=2, default=str))\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "assert isinstance(out, dict)\n",
-        "assert 'topic' in out\n",
-        "print('Notebook execution check: PASS')\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/notebooks/helpfiles/temp.ipynb b/python/notebooks/helpfiles/temp.ipynb
deleted file mode 100644
index 01c023c..0000000
--- a/python/notebooks/helpfiles/temp.ipynb
+++ /dev/null
@@ -1,127 +0,0 @@
-{
-  "cells": [
-    {
-      "cell_type": "markdown",
-      "metadata": {},
-      "source": [
-        "# temp (Python Translation)\\n",
-        "\\n",
-        "Source MATLAB file: `helpfiles/temp.m`\\n",
-        "Reference HTML file: `helpfiles/temp.html`\\n",
-        "\\n",
-        "## MATLAB HTML Reference\\n",
-        "- HTML title: **(missing)**\\n",
-        "\\n",
-        "### Expected Sections (from HTML)\\n",
-        "- (no sections found)\\n",
-        "\\n",
-        "### Figure References (from HTML)\\n",
-        "- (no figure files listed)\\n",
-        "\\n",
-        "### Sample Code Outputs (from HTML)\\n",
-        "_No `
` blocks found._\\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "from pathlib import Path\n",
-        "import sys\n",
-        "import importlib\n",
-        "import json\n",
-        "\n",
-        "def find_repo_root(start: Path) -> Path:\n",
-        "    cur = start.resolve()\n",
-        "    for p in [cur, *cur.parents]:\n",
-        "        if (p / '.git').exists() and (p / 'helpfiles').exists():\n",
-        "            return p\n",
-        "    raise RuntimeError('Could not find repository root')\n",
-        "\n",
-        "repo_root = find_repo_root(Path.cwd())\n",
-        "py_root = repo_root / 'python'\n",
-        "if str(py_root) not in sys.path:\n",
-        "    sys.path.insert(0, str(py_root))\n",
-        "print('repo_root =', repo_root)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "import re\n",
-        "import html as _html\n",
-        "\n",
-        "html_path = repo_root / 'helpfiles/temp.html'\n",
-        "if not html_path.exists():\n",
-        "    print('HTML reference missing:', html_path)\n",
-        "else:\n",
-        "    html_text = html_path.read_text(encoding='utf-8', errors='ignore')\n",
-        "    title_match = re.search(r'(.*?)', html_text, flags=re.I | re.S)\n",
-        "    title = _html.unescape(re.sub(r'<[^>]+>', '', title_match.group(1))).strip() if title_match else html_path.stem\n",
-        "    sections = [\n",
-        "        _html.unescape(re.sub(r'<[^>]+>', '', s)).strip()\n",
-        "        for s in re.findall(r']*>(.*?)', html_text, flags=re.I | re.S)\n",
-        "    ]\n",
-        "    sections = [s for s in sections if s]\n",
-        "    figs = sorted(set(re.findall(r'src=\"([^\"]+_\\d+\\.png)\"', html_text, flags=re.I)))\n",
-        "    print('HTML title:', title)\n",
-        "    print('Section count:', len(sections))\n",
-        "    for s in sections:\n",
-        "        print(' -', s)\n",
-        "    print('Figure refs:', len(figs))\n",
-        "    for f in figs[:20]:\n",
-        "        print(' -', f)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "module = importlib.import_module('matlab_port.helpfiles.temp')\n",
-        "if hasattr(module, 'run'):\n",
-        "    out = module.run(repo_root=repo_root)\n",
-        "elif hasattr(module, 'main'):\n",
-        "    out = module.main()\n",
-        "else:\n",
-        "    out = {'status': 'no run/main entrypoint'}\n",
-        "if isinstance(out, (dict, list)):\n",
-        "    print(json.dumps(out, indent=2, default=str))\n",
-        "else:\n",
-        "    print(out)\n"
-      ]
-    },
-    {
-      "cell_type": "code",
-      "execution_count": null,
-      "metadata": {},
-      "outputs": [],
-      "source": [
-        "comparison = {\n",
-        "    'python_output_type': type(out).__name__,\n",
-        "    'python_output_keys': sorted(list(out.keys())) if isinstance(out, dict) else [],\n",
-        "}\n",
-        "print(json.dumps(comparison, indent=2))\n"
-      ]
-    }
-  ],
-  "metadata": {
-    "kernelspec": {
-      "display_name": "Python 3",
-      "language": "python",
-      "name": "python3"
-    },
-    "language_info": {
-      "name": "python",
-      "version": "3"
-    }
-  },
-  "nbformat": 4,
-  "nbformat_minor": 5
-}
\ No newline at end of file
diff --git a/python/nstat/ConfidenceInterval.py b/python/nstat/ConfidenceInterval.py
deleted file mode 100644
index eba4760..0000000
--- a/python/nstat/ConfidenceInterval.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ._compat import warn_deprecated_adapter
-from .confidence_interval import ConfidenceInterval as _ConfidenceInterval
-
-
-class ConfidenceInterval(_ConfidenceInterval):
-    def __init__(self, *args, **kwargs) -> None:
-        warn_deprecated_adapter("nstat.ConfidenceInterval.ConfidenceInterval", "nstat.confidence_interval.ConfidenceInterval")
-        super().__init__(*args, **kwargs)
-
-
-__all__ = ["ConfidenceInterval"]
diff --git a/python/nstat/ConfigColl.py b/python/nstat/ConfigColl.py
deleted file mode 100644
index ddeef33..0000000
--- a/python/nstat/ConfigColl.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ._compat import warn_deprecated_adapter
-from .trial import ConfigCollection
-
-
-class ConfigColl(ConfigCollection):
-    def __init__(self, *args, **kwargs) -> None:
-        warn_deprecated_adapter("nstat.ConfigColl.ConfigColl", "nstat.trial.ConfigCollection")
-        super().__init__(*args, **kwargs)
-
-
-__all__ = ["ConfigColl"]
diff --git a/python/nstat/CovColl.py b/python/nstat/CovColl.py
deleted file mode 100644
index 76b4ff9..0000000
--- a/python/nstat/CovColl.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ._compat import warn_deprecated_adapter
-from .trial import CovariateCollection
-
-
-class CovColl(CovariateCollection):
-    def __init__(self, *args, **kwargs) -> None:
-        warn_deprecated_adapter("nstat.CovColl.CovColl", "nstat.trial.CovariateCollection")
-        super().__init__(*args, **kwargs)
-
-
-__all__ = ["CovColl"]
diff --git a/python/nstat/Covariate.py b/python/nstat/Covariate.py
deleted file mode 100644
index e04d682..0000000
--- a/python/nstat/Covariate.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ._compat import warn_deprecated_adapter
-from .signal import Covariate as _Covariate
-
-
-class Covariate(_Covariate):
-    def __init__(self, *args, **kwargs) -> None:
-        warn_deprecated_adapter("nstat.Covariate.Covariate", "nstat.signal.Covariate")
-        super().__init__(*args, **kwargs)
-
-
-__all__ = ["Covariate"]
diff --git a/python/nstat/DecodingAlgorithms.py b/python/nstat/DecodingAlgorithms.py
deleted file mode 100644
index 0cc8eb7..0000000
--- a/python/nstat/DecodingAlgorithms.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ._compat import warn_deprecated_adapter
-from .decoding_algorithms import DecodingAlgorithms as _DecodingAlgorithms
-
-
-class DecodingAlgorithms(_DecodingAlgorithms):
-    def __init__(self, *args, **kwargs) -> None:
-        warn_deprecated_adapter("nstat.DecodingAlgorithms.DecodingAlgorithms", "nstat.decoding.DecoderSuite")
-        super().__init__(*args, **kwargs)
-
-
-__all__ = ["DecodingAlgorithms"]
diff --git a/python/nstat/FitResSummary.py b/python/nstat/FitResSummary.py
deleted file mode 100644
index 7305bbb..0000000
--- a/python/nstat/FitResSummary.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ._compat import warn_deprecated_adapter
-from .fit import FitResSummary as _FitResSummary
-
-
-class FitResSummary(_FitResSummary):
-    def __init__(self, *args, **kwargs) -> None:
-        warn_deprecated_adapter("nstat.FitResSummary.FitResSummary", "nstat.fit.FitSummary")
-        super().__init__(*args, **kwargs)
-
-
-__all__ = ["FitResSummary"]
diff --git a/python/nstat/FitResult.py b/python/nstat/FitResult.py
deleted file mode 100644
index fb4dc3d..0000000
--- a/python/nstat/FitResult.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ._compat import warn_deprecated_adapter
-from .fit import FitResult as _FitResult
-
-
-class FitResult(_FitResult):
-    def __init__(self, *args, **kwargs) -> None:
-        warn_deprecated_adapter("nstat.FitResult.FitResult", "nstat.fit.FitResult")
-        super().__init__(*args, **kwargs)
-
-
-__all__ = ["FitResult"]
diff --git a/python/nstat/SignalObj.py b/python/nstat/SignalObj.py
deleted file mode 100644
index 32e333a..0000000
--- a/python/nstat/SignalObj.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ._compat import warn_deprecated_adapter
-from .signal import Signal
-
-
-class SignalObj(Signal):
-    def __init__(self, *args, **kwargs) -> None:
-        warn_deprecated_adapter("nstat.SignalObj.SignalObj", "nstat.signal.Signal")
-        super().__init__(*args, **kwargs)
-
-
-__all__ = ["SignalObj"]
diff --git a/python/nstat/TrialConfig.py b/python/nstat/TrialConfig.py
deleted file mode 100644
index a110e4c..0000000
--- a/python/nstat/TrialConfig.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ._compat import warn_deprecated_adapter
-from .trial import TrialConfig as _TrialConfig
-
-
-class TrialConfig(_TrialConfig):
-    def __init__(self, *args, **kwargs) -> None:
-        warn_deprecated_adapter("nstat.TrialConfig.TrialConfig", "nstat.trial.TrialConfig")
-        super().__init__(*args, **kwargs)
-
-
-__all__ = ["TrialConfig"]
diff --git a/python/nstat/__init__.py b/python/nstat/__init__.py
deleted file mode 100644
index 57e4577..0000000
--- a/python/nstat/__init__.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from .ConfidenceInterval import ConfidenceInterval
-from .ConfigColl import ConfigColl
-from .CovColl import CovColl
-from .SignalObj import SignalObj
-from .analysis import Analysis, psth
-from .cif import CIF, CIFModel
-from .datasets import get_dataset_path, list_datasets, verify_checksums
-from .decoding import DecoderSuite
-from .decoding_algorithms import DecodingAlgorithms
-from .errors import DataNotFoundError, ParityValidationError, UnsupportedWorkflowError
-from .fit import FitResSummary, FitResult, FitSummary
-from .glm import PoissonGLMResult, fit_poisson_glm
-from .history import History, HistoryBasis
-from .paper_examples_full import run_full_paper_examples
-from .signal import Covariate, Signal
-from .simulation import simulate_poisson_from_rate
-from .simulators import (
-    NetworkSimulationResult,
-    PointProcessSimulation,
-    simulate_point_process,
-    simulate_two_neuron_network,
-)
-from .spikes import SpikeTrain, SpikeTrainCollection
-from .trial import ConfigCollection, CovariateCollection, Trial, TrialConfig
-from .nspikeTrain import nspikeTrain
-from .nstColl import nstColl
-
-__all__ = [
-    "Analysis",
-    "CIF",
-    "CIFModel",
-    "ConfidenceInterval",
-    "ConfigColl",
-    "ConfigCollection",
-    "Covariate",
-    "CovColl",
-    "CovariateCollection",
-    "DataNotFoundError",
-    "DecoderSuite",
-    "DecodingAlgorithms",
-    "FitResSummary",
-    "FitResult",
-    "FitSummary",
-    "History",
-    "HistoryBasis",
-    "NetworkSimulationResult",
-    "ParityValidationError",
-    "PointProcessSimulation",
-    "PoissonGLMResult",
-    "SignalObj",
-    "Signal",
-    "SpikeTrain",
-    "SpikeTrainCollection",
-    "Trial",
-    "TrialConfig",
-    "UnsupportedWorkflowError",
-    "fit_poisson_glm",
-    "get_dataset_path",
-    "list_datasets",
-    "psth",
-    "run_full_paper_examples",
-    "simulate_point_process",
-    "simulate_poisson_from_rate",
-    "simulate_two_neuron_network",
-    "nspikeTrain",
-    "nstColl",
-    "verify_checksums",
-]
diff --git a/python/nstat/_compat.py b/python/nstat/_compat.py
deleted file mode 100644
index 6d541ab..0000000
--- a/python/nstat/_compat.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from __future__ import annotations
-
-import warnings
-
-
-def warn_deprecated_adapter(old: str, new: str) -> None:
-    warnings.warn(
-        f"{old} is deprecated and will be removed in a future major release; use {new} instead.",
-        DeprecationWarning,
-        stacklevel=3,
-    )
diff --git a/python/nstat/analysis.py b/python/nstat/analysis.py
deleted file mode 100644
index 0f581c5..0000000
--- a/python/nstat/analysis.py
+++ /dev/null
@@ -1,120 +0,0 @@
-from __future__ import annotations
-
-from typing import Sequence
-
-import numpy as np
-
-from .fit import FitResult, _SingleFit
-from .glm import fit_poisson_glm
-from .signal import Covariate
-from .trial import ConfigCollection, Trial
-
-
-def psth(spike_trains: Sequence[object], bin_edges: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
-    edges = np.asarray(bin_edges, dtype=float)
-    if edges.ndim != 1 or edges.size < 2:
-        raise ValueError("bin_edges must be 1D and length >= 2")
-
-    counts = np.zeros(edges.size - 1, dtype=float)
-    if len(spike_trains) == 0:
-        return counts.copy(), counts
-
-    for tr in spike_trains:
-        spikes = np.asarray(getattr(tr, "spikeTimes"), dtype=float).reshape(-1)
-        c, _ = np.histogram(spikes, bins=edges)
-        counts += c
-
-    widths = np.diff(edges)
-    mean_rate_hz = counts / (len(spike_trains) * widths)
-    return mean_rate_hz, counts
-
-
-class Analysis:
-    """Canonical analysis entry points preserving the paper's workflow semantics."""
-
-    @staticmethod
-    def psth(spike_trains: Sequence[object], bin_edges: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
-        return psth(spike_trains, bin_edges)
-
-    @staticmethod
-    def run_analysis_for_neuron(
-        trial: Trial,
-        neuron_index: int,
-        config_collection: ConfigCollection,
-        *,
-        l2: float = 1e-6,
-        max_iter: int = 120,
-    ) -> FitResult:
-        time, x_all, labels = trial.get_covariate_matrix()
-        spike_train = trial.spike_collection.get_nst(neuron_index)
-
-        dt = float(np.median(np.diff(time))) if time.shape[0] > 1 else 1.0
-        edges = np.concatenate([time, [time[-1] + dt]])
-        y = spike_train.to_binned_counts(edges)
-        offset = np.full(y.shape[0], np.log(max(dt, 1e-12)), dtype=float)
-
-        fits: list[_SingleFit] = []
-        for idx, cfg in enumerate(config_collection.configs, start=1):
-            names = cfg.covariate_names
-            if names:
-                cols = [i for i, lab in enumerate(labels) if lab in set(names)]
-                x = x_all[:, cols] if cols else np.zeros((x_all.shape[0], 0), dtype=float)
-            else:
-                x = x_all
-
-            glm_res = fit_poisson_glm(x, y, offset=offset, l2=l2, max_iter=max_iter)
-            n_params = x.shape[1] + 1
-            aic = 2.0 * n_params - 2.0 * glm_res.log_likelihood
-            bic = np.log(max(y.shape[0], 1)) * n_params - 2.0 * glm_res.log_likelihood
-            fit_name = cfg.name if cfg.name else f"Fit {idx}"
-            fits.append(
-                _SingleFit(
-                    name=fit_name,
-                    coefficients=np.asarray(glm_res.coefficients, dtype=float),
-                    intercept=float(glm_res.intercept),
-                    log_likelihood=float(glm_res.log_likelihood),
-                    aic=float(aic),
-                    bic=float(bic),
-                )
-            )
-
-        if x_all.shape[1] == 0:
-            x_for_rate = np.zeros((y.shape[0], 0), dtype=float)
-        else:
-            x_for_rate = x_all
-        rate = fit_poisson_glm(x_for_rate, y, offset=offset, l2=l2, max_iter=max_iter).predict_rate(x_for_rate, offset=offset)
-        lambda_signal = Covariate(time, rate, "lambda", "time", "s", "spikes/sec", ["lambda"])
-        return FitResult(spike_train, lambda_signal, fits)
-
-    @staticmethod
-    def run_analysis_for_all_neurons(
-        trial: Trial,
-        config_collection: ConfigCollection,
-        *,
-        l2: float = 1e-6,
-        max_iter: int = 120,
-    ) -> list[FitResult]:
-        out: list[FitResult] = []
-        for i in range(trial.spike_collection.num_spike_trains):
-            out.append(
-                Analysis.run_analysis_for_neuron(
-                    trial,
-                    i,
-                    config_collection,
-                    l2=l2,
-                    max_iter=max_iter,
-                )
-            )
-        return out
-
-    # MATLAB-compatible method names.
-    @staticmethod
-    def RunAnalysisForNeuron(tObj: Trial, neuronNumber: int, configColl: ConfigCollection):
-        return Analysis.run_analysis_for_neuron(tObj, neuronNumber - 1, configColl)
-
-    @staticmethod
-    def RunAnalysisForAllNeurons(tObj: Trial, configs: ConfigCollection, *_):
-        return Analysis.run_analysis_for_all_neurons(tObj, configs)
-
-
-__all__ = ["Analysis", "psth"]
diff --git a/python/nstat/cif.py b/python/nstat/cif.py
deleted file mode 100644
index 8b64b0a..0000000
--- a/python/nstat/cif.py
+++ /dev/null
@@ -1,77 +0,0 @@
-from __future__ import annotations
-
-from dataclasses import dataclass
-
-import numpy as np
-
-from .signal import Covariate
-from .simulation import simulate_poisson_from_rate
-from .trial import SpikeTrainCollection
-
-
-@dataclass
-class CIFModel:
-    """Conditional intensity function abstraction used by standalone workflows."""
-
-    time: np.ndarray
-    rate_hz: np.ndarray
-    name: str = "lambda"
-
-    def __post_init__(self) -> None:
-        self.time = np.asarray(self.time, dtype=float).reshape(-1)
-        self.rate_hz = np.asarray(self.rate_hz, dtype=float).reshape(-1)
-        if self.time.shape[0] != self.rate_hz.shape[0]:
-            raise ValueError("time and rate_hz length mismatch")
-
-    def to_covariate(self) -> Covariate:
-        return Covariate(self.time, self.rate_hz, self.name, "time", "s", "spikes/sec", [self.name])
-
-    def simulate(self, num_realizations: int = 1, *, seed: int | None = None) -> SpikeTrainCollection:
-        if num_realizations < 1:
-            raise ValueError("num_realizations must be >= 1")
-        rng = np.random.default_rng(seed)
-        trains = []
-        for i in range(num_realizations):
-            st = simulate_poisson_from_rate(self.time, self.rate_hz, rng=rng)
-            st.setName(str(i + 1))
-            trains.append(st)
-        return SpikeTrainCollection(trains)
-
-    @classmethod
-    def from_linear_terms(
-        cls,
-        time: np.ndarray,
-        intercept: float,
-        coefficients: np.ndarray,
-        design_matrix: np.ndarray,
-        dt: float,
-        name: str = "lambda",
-    ) -> "CIFModel":
-        eta = intercept + np.asarray(design_matrix, dtype=float) @ np.asarray(coefficients, dtype=float)
-        p = np.exp(np.clip(eta, -20.0, 20.0))
-        p = p / (1.0 + p)
-        rate = p / max(float(dt), 1e-12)
-        return cls(np.asarray(time, dtype=float).reshape(-1), rate, name)
-
-
-class CIF:
-    """MATLAB-compatible CIF static API wrapper."""
-
-    @staticmethod
-    def simulateCIFByThinningFromLambda(lambda_covariate: Covariate, numRealizations: int = 1) -> SpikeTrainCollection:
-        model = CIFModel(lambda_covariate.time, lambda_covariate.data[:, 0], getattr(lambda_covariate, "name", "lambda"))
-        return model.simulate(num_realizations=numRealizations)
-
-    @staticmethod
-    def from_linear_terms(
-        time: np.ndarray,
-        intercept: float,
-        coefficients: np.ndarray,
-        design_matrix: np.ndarray,
-        dt: float,
-        name: str = "lambda",
-    ) -> Covariate:
-        return CIFModel.from_linear_terms(time, intercept, coefficients, design_matrix, dt, name).to_covariate()
-
-
-__all__ = ["CIFModel", "CIF"]
diff --git a/python/nstat/confidence_interval.py b/python/nstat/confidence_interval.py
deleted file mode 100644
index 0da57c2..0000000
--- a/python/nstat/confidence_interval.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from __future__ import annotations
-
-from dataclasses import dataclass
-
-import numpy as np
-
-
-@dataclass
-class ConfidenceInterval:
-    time: np.ndarray
-    bounds: np.ndarray
-    color: str = "b"
-
-    def __init__(self, time, bounds, color: str = "b") -> None:
-        t = np.asarray(time, dtype=float).reshape(-1)
-        b = np.asarray(bounds, dtype=float)
-        if b.ndim != 2 or b.shape[1] != 2:
-            raise ValueError("bounds must have shape (n, 2)")
-        if b.shape[0] != t.shape[0]:
-            raise ValueError("bounds rows must match time length")
-        self.time = t
-        self.bounds = b
-        self.color = color
-
-    @property
-    def lower(self) -> np.ndarray:
-        return self.bounds[:, 0]
-
-    @property
-    def upper(self) -> np.ndarray:
-        return self.bounds[:, 1]
-
-    def setColor(self, color: str) -> None:
-        self.color = str(color)
diff --git a/python/nstat/core.py b/python/nstat/core.py
deleted file mode 100644
index a1836b1..0000000
--- a/python/nstat/core.py
+++ /dev/null
@@ -1,294 +0,0 @@
-from __future__ import annotations
-
-from dataclasses import dataclass
-from typing import Iterable, Sequence
-
-import numpy as np
-
-
-def _as_1d_float(values: Sequence[float] | np.ndarray, name: str) -> np.ndarray:
-    array = np.asarray(values, dtype=float).reshape(-1)
-    if array.size == 0:
-        raise ValueError(f"{name} must be non-empty.")
-    return array
-
-
-class SignalObj:
-    """Python approximation of nSTAT SignalObj.
-
-    The class stores a time vector and one or more aligned signal channels.
-    """
-
-    def __init__(
-        self,
-        time: Sequence[float],
-        data: Sequence[float] | Sequence[Sequence[float]] | np.ndarray,
-        name: str = "signal",
-        xlabel: str = "time",
-        xunits: str = "s",
-        yunits: str = "",
-        data_labels: Sequence[str] | None = None,
-    ) -> None:
-        t = _as_1d_float(time, "time")
-        if np.any(np.diff(t) <= 0):
-            raise ValueError("time must be strictly increasing.")
-
-        x = np.asarray(data, dtype=float)
-        if x.ndim == 1:
-            x = x[:, None]
-        if x.shape[0] != t.shape[0]:
-            raise ValueError("data must have same first dimension as time.")
-
-        self.time = t
-        self.data = x
-        self.name = name
-        self.xlabelval = xlabel
-        self.xunits = xunits
-        self.yunits = yunits
-
-        if data_labels is None:
-            labels = [f"{name}_{k+1}" for k in range(self.data.shape[1])]
-        else:
-            labels = list(data_labels)
-            if len(labels) != self.data.shape[1]:
-                raise ValueError("data_labels length must match signal dimension.")
-        self.dataLabels = labels
-        self.conf_interval: tuple[np.ndarray, np.ndarray] | None = None
-
-    @property
-    def dimension(self) -> int:
-        return int(self.data.shape[1])
-
-    @property
-    def values(self) -> np.ndarray:
-        if self.dimension == 1:
-            return self.data[:, 0]
-        return self.data
-
-    @property
-    def units(self) -> str:
-        return self.yunits
-
-    @property
-    def sample_rate(self) -> float:
-        if self.time.shape[0] < 2:
-            return 0.0
-        dt = np.median(np.diff(self.time))
-        if dt <= 0:
-            return 0.0
-        return float(1.0 / dt)
-
-    def copySignal(self) -> "SignalObj":
-        out = SignalObj(
-            self.time.copy(),
-            self.data.copy(),
-            self.name,
-            self.xlabelval,
-            self.xunits,
-            self.yunits,
-            self.dataLabels,
-        )
-        out.conf_interval = None if self.conf_interval is None else (
-            self.conf_interval[0].copy(),
-            self.conf_interval[1].copy(),
-        )
-        return out
-
-    def setName(self, name: str) -> None:
-        self.name = str(name)
-
-    def setDataLabels(self, labels: Sequence[str]) -> None:
-        labels = list(labels)
-        if len(labels) != self.dimension:
-            raise ValueError("labels length must equal number of signal channels.")
-        self.dataLabels = labels
-
-    def setConfInterval(self, bounds: tuple[np.ndarray, np.ndarray]) -> None:
-        low, high = bounds
-        low = np.asarray(low, dtype=float)
-        high = np.asarray(high, dtype=float)
-        if low.shape[0] != self.time.shape[0] or high.shape[0] != self.time.shape[0]:
-            raise ValueError("confidence interval bounds must align with time.")
-        self.conf_interval = (low, high)
-
-    def getSubSignal(self, idx: int) -> "SignalObj":
-        if idx < 1 or idx > self.dimension:
-            raise IndexError("Signal index out of range. Indexing is 1-based.")
-        j = idx - 1
-        return SignalObj(
-            self.time,
-            self.data[:, j],
-            self.name,
-            self.xlabelval,
-            self.xunits,
-            self.yunits,
-            [self.dataLabels[j]],
-        )
-
-    def getSigInTimeWindow(self, t0: float, t1: float) -> "SignalObj":
-        mask = (self.time >= t0) & (self.time <= t1)
-        if not np.any(mask):
-            raise ValueError("Requested time window has no samples.")
-        return SignalObj(
-            self.time[mask],
-            self.data[mask, :],
-            self.name,
-            self.xlabelval,
-            self.xunits,
-            self.yunits,
-            self.dataLabels,
-        )
-
-    def merge(self, other: "SignalObj") -> "SignalObj":
-        if self.time.shape != other.time.shape or np.max(np.abs(self.time - other.time)) > 1e-9:
-            raise ValueError("Signals must share an identical time grid to merge.")
-        return SignalObj(
-            self.time,
-            np.column_stack([self.data, other.data]),
-            self.name,
-            self.xlabelval,
-            self.xunits,
-            self.yunits,
-            [*self.dataLabels, *other.dataLabels],
-        )
-
-    def resample(self, sample_rate: float) -> "SignalObj":
-        if sample_rate <= 0:
-            raise ValueError("sample_rate must be > 0.")
-        dt = 1.0 / float(sample_rate)
-        t_new = np.arange(self.time[0], self.time[-1] + 0.5 * dt, dt)
-        x_new = np.column_stack(
-            [np.interp(t_new, self.time, self.data[:, i]) for i in range(self.dimension)]
-        )
-        return SignalObj(
-            t_new,
-            x_new,
-            self.name,
-            self.xlabelval,
-            self.xunits,
-            self.yunits,
-            self.dataLabels,
-        )
-
-    @property
-    def derivative(self) -> "SignalObj":
-        dt = np.gradient(self.time)
-        deriv = np.column_stack([np.gradient(self.data[:, i], self.time) for i in range(self.dimension)])
-        # Avoid numerical noise spikes where dt is near 0.
-        deriv[~np.isfinite(deriv)] = 0.0
-        return SignalObj(
-            self.time,
-            deriv,
-            f"d/dt({self.name})",
-            self.xlabelval,
-            self.xunits,
-            self.yunits,
-            [f"d_{lbl}" for lbl in self.dataLabels],
-        )
-
-    def plot(self, *_, **__) -> None:
-        # Intentionally lightweight: plotting is handled in examples where needed.
-        return None
-
-
-class Covariate(SignalObj):
-    """MATLAB-compatible alias for SignalObj.
-
-    Accepts both MATLAB-style positional arguments and Pythonic keywords:
-    `Covariate(time=t, values=x, name='stim', units='a.u.')`.
-    """
-
-    def __init__(self, *args, **kwargs) -> None:
-        if "values" in kwargs and "data" not in kwargs:
-            kwargs["data"] = kwargs.pop("values")
-        if "units" in kwargs and "yunits" not in kwargs:
-            kwargs["yunits"] = kwargs.pop("units")
-        super().__init__(*args, **kwargs)
-
-
-@dataclass
-class nspikeTrain:
-    """Python approximation of MATLAB nspikeTrain."""
-
-    spikeTimes: np.ndarray
-    name: str = ""
-    binwidth: float = 0.001
-    minTime: float | None = None
-    maxTime: float | None = None
-
-    def __post_init__(self) -> None:
-        spikes = np.asarray(self.spikeTimes, dtype=float).reshape(-1)
-        spikes = np.sort(spikes)
-        self.spikeTimes = spikes
-
-        if self.minTime is None:
-            self.minTime = float(spikes[0]) if spikes.size else 0.0
-        if self.maxTime is None:
-            self.maxTime = float(spikes[-1]) if spikes.size else self.minTime
-
-        self.minTime = float(self.minTime)
-        self.maxTime = float(self.maxTime)
-        self.sampleRate = float(1.0 / self.binwidth)
-
-    @property
-    def times(self) -> np.ndarray:
-        return self.spikeTimes
-
-    @property
-    def n_spikes(self) -> int:
-        return int(self.spikeTimes.shape[0])
-
-    @property
-    def duration(self) -> float:
-        return float(self.maxTime - self.minTime)
-
-    @property
-    def firing_rate_hz(self) -> float:
-        d = self.duration
-        if d <= 0:
-            return 0.0
-        return float(self.n_spikes / d)
-
-    def setName(self, name: str) -> None:
-        self.name = str(name)
-
-    def setMinTime(self, value: float) -> None:
-        self.minTime = float(value)
-
-    def setMaxTime(self, value: float) -> None:
-        self.maxTime = float(value)
-
-    def getISIs(self) -> np.ndarray:
-        if self.n_spikes < 2:
-            return np.array([], dtype=float)
-        return np.diff(self.spikeTimes)
-
-    def getSigRep(
-        self,
-        binwidth: float | None = None,
-        minTime: float | None = None,
-        maxTime: float | None = None,
-    ) -> SignalObj:
-        bw = self.binwidth if binwidth is None else float(binwidth)
-        t0 = self.minTime if minTime is None else float(minTime)
-        t1 = self.maxTime if maxTime is None else float(maxTime)
-        if bw <= 0:
-            raise ValueError("binwidth must be > 0")
-        if t1 < t0:
-            raise ValueError("maxTime must be >= minTime")
-
-        edges = np.arange(t0, t1 + 1.5 * bw, bw)
-        if edges.shape[0] < 2:
-            edges = np.array([t0, t0 + bw], dtype=float)
-        counts, _ = np.histogram(self.spikeTimes, bins=edges)
-        centers = edges[:-1] + 0.5 * bw
-        return SignalObj(centers, counts.astype(float), self.name or "spikes", "time", "s", "count", ["counts"])
-
-    def to_binned_counts(self, bin_edges: Sequence[float]) -> np.ndarray:
-        edges = np.asarray(bin_edges, dtype=float).reshape(-1)
-        counts, _ = np.histogram(self.spikeTimes, bins=edges)
-        return counts.astype(float)
-
-
-# Backward-compatible alias used by earlier Python scaffolding.
-SpikeTrain = nspikeTrain
diff --git a/python/nstat/data/__init__.py b/python/nstat/data/__init__.py
deleted file mode 100644
index aade638..0000000
--- a/python/nstat/data/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Package data used by nSTAT runtime helpers."""
diff --git a/python/nstat/data/manifest.json b/python/nstat/data/manifest.json
deleted file mode 100644
index 0f24741..0000000
--- a/python/nstat/data/manifest.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
-  "datasets": {
-    "mepcs_epsc2": {
-      "path": "data/mEPSCs/epsc2.txt",
-      "sha256": "65a130b7af4bc34eeaf943da76df6ebf9ba0ae9720eb98b6813f9f44a43ff435"
-    },
-    "mepcs_washout1": {
-      "path": "data/mEPSCs/washout1.txt",
-      "sha256": "4791531b54dd3f98c8cf756bd253d61d09113338b0dbe98ed09ef2df2d18e00e"
-    },
-    "mepcs_washout2": {
-      "path": "data/mEPSCs/washout2.txt",
-      "sha256": "ab35f26d4bccc48d2dc91e5007ba64d2527891666d2653e0a6118e397a4985fa"
-    },
-    "explicit_stimulus_dir3_neuron1_stim2_train": {
-      "path": "data/Explicit Stimulus/Dir3/Neuron1/Stim2/trngdataBis.mat",
-      "sha256": "f4b4299a1e977db37bab8a11d107b6d2a73541d3e5bb017ab68b1d9885def3f2"
-    },
-    "ssglm_example": {
-      "path": "data/SSGLMExampleData.mat",
-      "sha256": "fc1d4730267b49e3af534f6572b28ce1ba746708a49a1e4f1e8fb082cdabc360"
-    },
-    "place_cell_animal1": {
-      "path": "data/Place Cells/PlaceCellDataAnimal1.mat",
-      "sha256": "8806ab38c62a04ee1bec9cabab907a8f9fa3d66e0799fa09de8acd01789fc77b"
-    },
-    "hybrid_filter_example": {
-      "path": "helpfiles/paperHybridFilterExample.mat",
-      "sha256": "0a9941cc9cf564f1aaffdcd6dac766ada1f514926b610949bf0545c1b1bf401d"
-    }
-  },
-  "notes": "Core offline datasets required by canonical paper/help workflows."
-}
\ No newline at end of file
diff --git a/python/nstat/datasets.py b/python/nstat/datasets.py
deleted file mode 100644
index 04b5738..0000000
--- a/python/nstat/datasets.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from __future__ import annotations
-
-import hashlib
-import json
-from pathlib import Path
-
-from .errors import DataNotFoundError
-
-MANIFEST_PATH = Path(__file__).resolve().parent / "data" / "manifest.json"
-
-
-def _repo_root() -> Path:
-    cur = Path(__file__).resolve()
-    for candidate in [cur, *cur.parents]:
-        if (candidate / "data").exists() and (candidate / "helpfiles").exists():
-            return candidate
-    raise RuntimeError("Could not locate nSTAT repository root from installed package path.")
-
-
-def _load_manifest() -> dict[str, dict[str, str]]:
-    if not MANIFEST_PATH.exists():
-        raise DataNotFoundError(f"Dataset manifest not found: {MANIFEST_PATH}")
-    payload = json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
-    entries = payload.get("datasets", {})
-    if not isinstance(entries, dict):
-        raise ValueError("Invalid dataset manifest format; 'datasets' must be a mapping")
-    return entries
-
-
-def _sha256(path: Path) -> str:
-    h = hashlib.sha256()
-    with path.open("rb") as f:
-        for chunk in iter(lambda: f.read(1024 * 1024), b""):
-            h.update(chunk)
-    return h.hexdigest()
-
-
-def list_datasets() -> list[str]:
-    return sorted(_load_manifest().keys())
-
-
-def get_dataset_path(name: str) -> Path:
-    entries = _load_manifest()
-    if name not in entries:
-        raise DataNotFoundError(f"Unknown dataset '{name}'. Available: {', '.join(sorted(entries))}")
-
-    rel = entries[name]["path"]
-    path = _repo_root() / rel
-    if not path.exists():
-        raise DataNotFoundError(f"Dataset '{name}' not found at expected path: {path}")
-    return path
-
-
-def verify_checksums() -> dict[str, bool]:
-    entries = _load_manifest()
-    root = _repo_root()
-    result: dict[str, bool] = {}
-    for name, item in entries.items():
-        path = root / item["path"]
-        expected = item.get("sha256", "")
-        if not path.exists() or not expected:
-            result[name] = False
-            continue
-        result[name] = _sha256(path) == expected
-    return result
diff --git a/python/nstat/decoding.py b/python/nstat/decoding.py
deleted file mode 100644
index 07ee86d..0000000
--- a/python/nstat/decoding.py
+++ /dev/null
@@ -1,58 +0,0 @@
-from __future__ import annotations
-
-import numpy as np
-
-from .decoding_algorithms import DecodingAlgorithms
-
-
-class DecoderSuite:
-    """Canonical decoding API for the Python nSTAT package."""
-
-    @staticmethod
-    def linear(spike_counts: np.ndarray, stimulus: np.ndarray) -> dict[str, np.ndarray]:
-        return DecodingAlgorithms.linear_decode(spike_counts, stimulus)
-
-    @staticmethod
-    def kalman(
-        observations: np.ndarray,
-        transition: np.ndarray,
-        observation_matrix: np.ndarray,
-        q_cov: np.ndarray,
-        r_cov: np.ndarray,
-        x0: np.ndarray,
-        p0: np.ndarray,
-    ) -> dict[str, np.ndarray]:
-        """Basic linear Kalman filter used for standalone decoding workflows."""
-        y = np.asarray(observations, dtype=float)
-        a = np.asarray(transition, dtype=float)
-        h = np.asarray(observation_matrix, dtype=float)
-        q = np.asarray(q_cov, dtype=float)
-        r = np.asarray(r_cov, dtype=float)
-        x_prev = np.asarray(x0, dtype=float).reshape(-1)
-        p_prev = np.asarray(p0, dtype=float)
-
-        n_t = y.shape[0]
-        n_x = x_prev.shape[0]
-        xs = np.zeros((n_t, n_x), dtype=float)
-        ps = np.zeros((n_t, n_x, n_x), dtype=float)
-
-        for t in range(n_t):
-            x_pred = a @ x_prev
-            p_pred = a @ p_prev @ a.T + q
-
-            innovation = y[t] - h @ x_pred
-            s_cov = h @ p_pred @ h.T + r
-            k_gain = p_pred @ h.T @ np.linalg.pinv(s_cov)
-
-            x_post = x_pred + k_gain @ innovation
-            p_post = (np.eye(n_x) - k_gain @ h) @ p_pred
-
-            xs[t] = x_post
-            ps[t] = p_post
-            x_prev = x_post
-            p_prev = p_post
-
-        return {"state": xs, "cov": ps}
-
-
-__all__ = ["DecoderSuite"]
diff --git a/python/nstat/decoding_algorithms.py b/python/nstat/decoding_algorithms.py
deleted file mode 100644
index f302ce2..0000000
--- a/python/nstat/decoding_algorithms.py
+++ /dev/null
@@ -1,70 +0,0 @@
-from __future__ import annotations
-
-import numpy as np
-
-
-class DecodingAlgorithms:
-    @staticmethod
-    def linear_decode(spike_counts: np.ndarray, stimulus: np.ndarray) -> dict[str, np.ndarray]:
-        x = np.asarray(spike_counts, dtype=float)
-        y = np.asarray(stimulus, dtype=float).reshape(-1)
-        if x.ndim == 1:
-            x = x[:, None]
-        if x.shape[0] != y.shape[0]:
-            raise ValueError("spike_counts and stimulus must align")
-
-        x_aug = np.column_stack([np.ones(x.shape[0]), x])
-        beta, *_ = np.linalg.lstsq(x_aug, y, rcond=None)
-        y_hat = x_aug @ beta
-        resid = y - y_hat
-        sigma = float(np.std(resid))
-        ci = np.column_stack([y_hat - 1.96 * sigma, y_hat + 1.96 * sigma])
-        return {"coefficients": beta, "decoded": y_hat, "residual": resid, "ci": ci}
-
-    @staticmethod
-    def kalman_filter(
-        observations: np.ndarray,
-        transition: np.ndarray,
-        observation_matrix: np.ndarray,
-        q_cov: np.ndarray,
-        r_cov: np.ndarray,
-        x0: np.ndarray,
-        p0: np.ndarray,
-    ) -> dict[str, np.ndarray]:
-        y = np.asarray(observations, dtype=float)
-        a = np.asarray(transition, dtype=float)
-        h = np.asarray(observation_matrix, dtype=float)
-        q = np.asarray(q_cov, dtype=float)
-        r = np.asarray(r_cov, dtype=float)
-        x_prev = np.asarray(x0, dtype=float).reshape(-1)
-        p_prev = np.asarray(p0, dtype=float)
-
-        n_t = y.shape[0]
-        n_x = x_prev.shape[0]
-        xs = np.zeros((n_t, n_x), dtype=float)
-        ps = np.zeros((n_t, n_x, n_x), dtype=float)
-
-        for t in range(n_t):
-            x_pred = a @ x_prev
-            p_pred = a @ p_prev @ a.T + q
-
-            innovation = y[t] - h @ x_pred
-            s_cov = h @ p_pred @ h.T + r
-            k_gain = p_pred @ h.T @ np.linalg.pinv(s_cov)
-
-            x_post = x_pred + k_gain @ innovation
-            p_post = (np.eye(n_x) - k_gain @ h) @ p_pred
-
-            xs[t] = x_post
-            ps[t] = p_post
-            x_prev = x_post
-            p_prev = p_post
-
-        return {"state": xs, "cov": ps}
-
-    # MATLAB-style API aliases.
-    PPDecodeFilterLinear = kalman_filter
-    PPDecodeFilter = kalman_filter
-
-
-__all__ = ["DecodingAlgorithms"]
diff --git a/python/nstat/errors.py b/python/nstat/errors.py
deleted file mode 100644
index 4ada586..0000000
--- a/python/nstat/errors.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from __future__ import annotations
-
-
-class NSTATError(Exception):
-    """Base exception type for the Python nSTAT package."""
-
-
-class DataNotFoundError(NSTATError, FileNotFoundError):
-    """Raised when a required dataset is missing from the local checkout."""
-
-
-class ParityValidationError(NSTATError):
-    """Raised when MATLAB/Python parity validation fails."""
-
-
-class UnsupportedWorkflowError(NSTATError, NotImplementedError):
-    """Raised when a legacy workflow has not yet been ported."""
diff --git a/python/nstat/events.py b/python/nstat/events.py
deleted file mode 100644
index 969b99a..0000000
--- a/python/nstat/events.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from __future__ import annotations
-
-from dataclasses import dataclass
-from typing import Any
-
-import numpy as np
-
-
-@dataclass
-class Events:
-    event_times: np.ndarray
-    labels: list[str] | None = None
-
-    def __init__(self, event_times, labels=None) -> None:
-        self.event_times = np.asarray(event_times, dtype=float).reshape(-1)
-        self.labels = None if labels is None else list(labels)
-
-    def toStructure(self) -> dict[str, Any]:
-        return {
-            "event_times": self.event_times.tolist(),
-            "labels": list(self.labels) if self.labels is not None else None,
-        }
-
-    @staticmethod
-    def fromStructure(structure: dict[str, Any]) -> "Events":
-        return Events(structure.get("event_times", []), labels=structure.get("labels"))
-
-    def plot(self, *_, **__) -> None:
-        return None
-
-
-__all__ = ["Events"]
diff --git a/python/nstat/fit.py b/python/nstat/fit.py
deleted file mode 100644
index ad6021b..0000000
--- a/python/nstat/fit.py
+++ /dev/null
@@ -1,211 +0,0 @@
-from __future__ import annotations
-
-from dataclasses import dataclass
-from typing import Any, Iterable
-
-import numpy as np
-
-from .core import nspikeTrain
-from .signal import Covariate
-
-
-@dataclass
-class _SingleFit:
-    name: str
-    coefficients: np.ndarray
-    intercept: float
-    log_likelihood: float
-    aic: float
-    bic: float
-
-
-class FitResult:
-    """Simplified Python FitResult compatible with nSTAT workflows."""
-
-    def __init__(
-        self,
-        neuralSpikeTrain: nspikeTrain,
-        lambda_signal: Covariate,
-        fits: list[_SingleFit],
-    ) -> None:
-        self.neuralSpikeTrain = neuralSpikeTrain
-        self.lambda_signal = lambda_signal
-        self.fits = fits
-        self.numResults = len(fits)
-        self.AIC = np.asarray([f.aic for f in fits], dtype=float)
-        self.BIC = np.asarray([f.bic for f in fits], dtype=float)
-        self.logLL = np.asarray([f.log_likelihood for f in fits], dtype=float)
-        self.KSStats = np.zeros((self.numResults, 1), dtype=float)
-        self.configNames = [f.name for f in fits]
-        self.lambda_ = lambda_signal
-
-    @property
-    def lambdaSignal(self) -> Covariate:
-        return self.lambda_signal
-
-    @property
-    def lambda_sig(self) -> Covariate:
-        return self.lambda_signal
-
-    @property
-    def lambdaCov(self) -> Covariate:
-        return self.lambda_signal
-
-    @property
-    def lambdaObj(self) -> Covariate:
-        return self.lambda_signal
-
-    @property
-    def lambda_data(self) -> np.ndarray:
-        return self.lambda_signal.data
-
-    @property
-    def lambda_values(self) -> np.ndarray:
-        return self.lambda_signal.data
-
-    @property
-    def lambda_time(self) -> np.ndarray:
-        return self.lambda_signal.time
-
-    @property
-    def lambda_rate(self) -> np.ndarray:
-        return self.lambda_signal.data
-
-    @property
-    def lambda_model(self) -> Covariate:
-        return self.lambda_signal
-
-    @property
-    def lambda_result(self) -> Covariate:
-        return self.lambda_signal
-
-    @property
-    def lambda_(self) -> Covariate:
-        return self.lambda_signal
-
-    @lambda_.setter
-    def lambda_(self, value: Covariate) -> None:
-        self.lambda_signal = value
-
-    def getCoeffs(self, fit_num: int = 1) -> np.ndarray:
-        return self.fits[fit_num - 1].coefficients.copy()
-
-    def getHistCoeffs(self, fit_num: int = 1) -> np.ndarray:
-        return np.array([], dtype=float)
-
-    def mergeResults(self, other: "FitResult") -> "FitResult":
-        merged_fits = [*self.fits, *other.fits]
-        merged_lambda = self.lambda_signal.merge(other.lambda_signal)
-        out = FitResult(self.neuralSpikeTrain, merged_lambda, merged_fits)
-        return out
-
-    def plotResults(self, *_, **__) -> None:
-        return None
-
-    def KSPlot(self, *_, **__) -> None:
-        return None
-
-    def plotResidual(self, *_, **__) -> None:
-        return None
-
-    def plotInvGausTrans(self, *_, **__) -> None:
-        return None
-
-    def plotSeqCorr(self, *_, **__) -> None:
-        return None
-
-    def plotCoeffs(self, *_, **__) -> None:
-        return None
-
-    @property
-    def lambda_obj(self) -> Covariate:
-        return self.lambda_signal
-
-    def toStructure(self) -> dict[str, Any]:
-        return {
-            "fits": [
-                {
-                    "name": f.name,
-                    "coefficients": f.coefficients.tolist(),
-                    "intercept": f.intercept,
-                    "log_likelihood": f.log_likelihood,
-                    "aic": f.aic,
-                    "bic": f.bic,
-                }
-                for f in self.fits
-            ],
-            "lambda_time": self.lambda_signal.time.tolist(),
-            "lambda_data": self.lambda_signal.data.tolist(),
-            "neural_spike_times": self.neuralSpikeTrain.spikeTimes.tolist(),
-            "neural_name": self.neuralSpikeTrain.name,
-            "neural_min_time": self.neuralSpikeTrain.minTime,
-            "neural_max_time": self.neuralSpikeTrain.maxTime,
-        }
-
-    @staticmethod
-    def fromStructure(structure: dict[str, Any]) -> "FitResult":
-        train = nspikeTrain(
-            structure["neural_spike_times"],
-            name=structure.get("neural_name", ""),
-            minTime=structure.get("neural_min_time"),
-            maxTime=structure.get("neural_max_time"),
-        )
-        lam = Covariate(
-            structure["lambda_time"],
-            np.asarray(structure["lambda_data"], dtype=float),
-            "lambda",
-            "time",
-            "s",
-            "spikes/sec",
-        )
-        fits = []
-        for f in structure["fits"]:
-            fits.append(
-                _SingleFit(
-                    name=f["name"],
-                    coefficients=np.asarray(f["coefficients"], dtype=float),
-                    intercept=float(f["intercept"]),
-                    log_likelihood=float(f["log_likelihood"]),
-                    aic=float(f["aic"]),
-                    bic=float(f["bic"]),
-                )
-            )
-        return FitResult(train, lam, fits)
-
-
-class FitSummary:
-    """Cross-fit summary statistics for one or more FitResult objects."""
-
-    def __init__(self, fit_results: FitResult | Iterable[FitResult]) -> None:
-        if isinstance(fit_results, FitResult):
-            self.fit_results = [fit_results]
-        else:
-            self.fit_results = list(fit_results)
-            if not self.fit_results:
-                raise ValueError("FitSummary requires at least one FitResult")
-
-        aic = np.vstack([fr.AIC for fr in self.fit_results])
-        bic = np.vstack([fr.BIC for fr in self.fit_results])
-        ks = np.vstack([fr.KSStats.reshape(1, -1) for fr in self.fit_results])
-        self.AIC = np.mean(aic, axis=0)
-        self.BIC = np.mean(bic, axis=0)
-        self.KSStats = np.column_stack([np.mean(ks, axis=0), np.std(ks, axis=0)])
-        self.numNeurons = len(self.fit_results)
-
-    def getDiffAIC(self, idx: int = 1) -> np.ndarray:
-        base = self.AIC[idx - 1]
-        return self.AIC - base
-
-    def getDiffBIC(self, idx: int = 1) -> np.ndarray:
-        base = self.BIC[idx - 1]
-        return self.BIC - base
-
-    def plotSummary(self, *_, **__) -> None:
-        return None
-
-
-class FitResSummary(FitSummary):
-    """MATLAB-compatible alias for FitSummary."""
-
-
-__all__ = ["FitResult", "FitSummary", "FitResSummary", "_SingleFit"]
diff --git a/python/nstat/glm.py b/python/nstat/glm.py
deleted file mode 100644
index 560521d..0000000
--- a/python/nstat/glm.py
+++ /dev/null
@@ -1,89 +0,0 @@
-from __future__ import annotations
-
-from dataclasses import dataclass
-from typing import Sequence
-
-import numpy as np
-
-
-@dataclass(frozen=True)
-class PoissonGLMResult:
-    intercept: float
-    coefficients: np.ndarray
-    n_iter: int
-    converged: bool
-    log_likelihood: float
-
-    def predict_rate(
-        self, x: Sequence[Sequence[float]] | Sequence[float] | np.ndarray, offset: Sequence[float] | np.ndarray | None = None
-    ) -> np.ndarray:
-        x_arr = np.asarray(x, dtype=float)
-        if x_arr.ndim == 1:
-            x_arr = x_arr[:, None]
-        eta = self.intercept + x_arr @ self.coefficients
-        if offset is not None:
-            eta = eta + np.asarray(offset, dtype=float).reshape(-1)
-        return np.exp(np.clip(eta, -20.0, 20.0))
-
-
-def fit_poisson_glm(
-    x: Sequence[Sequence[float]] | Sequence[float] | np.ndarray,
-    y: Sequence[float] | np.ndarray,
-    *,
-    offset: Sequence[float] | np.ndarray | None = None,
-    l2: float = 1e-6,
-    max_iter: int = 120,
-    tol: float = 1e-8,
-) -> PoissonGLMResult:
-    x_arr = np.asarray(x, dtype=float)
-    y_arr = np.asarray(y, dtype=float).reshape(-1)
-    if x_arr.ndim == 1:
-        x_arr = x_arr[:, None]
-    if x_arr.shape[0] != y_arr.shape[0]:
-        raise ValueError("x and y must have same row count")
-
-    if offset is None:
-        offset_arr = np.zeros_like(y_arr)
-    else:
-        offset_arr = np.asarray(offset, dtype=float).reshape(-1)
-        if offset_arr.shape[0] != y_arr.shape[0]:
-            raise ValueError("offset size mismatch")
-
-    n_samples, n_features = x_arr.shape
-    x_aug = np.column_stack([np.ones(n_samples), x_arr])
-    beta = np.zeros(n_features + 1, dtype=float)
-
-    eye = np.eye(n_features + 1)
-    eye[0, 0] = 0.0
-
-    converged = False
-    n_iter = 0
-    for n_iter in range(1, max_iter + 1):
-        eta = x_aug @ beta + offset_arr
-        lam = np.exp(np.clip(eta, -20.0, 20.0))
-
-        grad = x_aug.T @ (y_arr - lam) - l2 * (eye @ beta)
-        hess_pos = x_aug.T @ (lam[:, None] * x_aug) + l2 * eye
-        try:
-            step = np.linalg.solve(hess_pos, grad)
-        except np.linalg.LinAlgError:
-            step = np.linalg.lstsq(hess_pos, grad, rcond=None)[0]
-
-        beta_next = beta + step
-        if np.linalg.norm(beta_next - beta, ord=2) < tol:
-            beta = beta_next
-            converged = True
-            break
-        beta = beta_next
-
-    eta = x_aug @ beta + offset_arr
-    lam = np.exp(np.clip(eta, -20.0, 20.0))
-    log_likelihood = float(np.sum(y_arr * np.log(np.maximum(lam, 1e-12)) - lam))
-
-    return PoissonGLMResult(
-        intercept=float(beta[0]),
-        coefficients=beta[1:].copy(),
-        n_iter=n_iter,
-        converged=converged,
-        log_likelihood=log_likelihood,
-    )
diff --git a/python/nstat/history.py b/python/nstat/history.py
deleted file mode 100644
index efdb575..0000000
--- a/python/nstat/history.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from __future__ import annotations
-
-from dataclasses import dataclass
-
-import numpy as np
-
-from .signal import Covariate
-
-
-@dataclass
-class HistoryBasis:
-    """Spike-history basis using lagged spike-count regressors."""
-
-    lags: np.ndarray
-    name: str = "History"
-
-    def __init__(self, lags, name: str = "History") -> None:
-        arr = np.asarray(lags, dtype=int).reshape(-1)
-        if arr.size == 0:
-            raise ValueError("lags must be non-empty")
-        if np.any(arr <= 0):
-            raise ValueError("lags must be strictly positive")
-        self.lags = np.unique(arr)
-        self.name = name
-
-    def design_matrix(self, spike_indicator: np.ndarray) -> np.ndarray:
-        y = np.asarray(spike_indicator, dtype=float).reshape(-1)
-        x = np.zeros((y.shape[0], self.lags.shape[0]), dtype=float)
-        for j, lag in enumerate(self.lags):
-            x[lag:, j] = y[:-lag]
-        return x
-
-    def compute_history(self, spike_indicator: np.ndarray, time: np.ndarray) -> Covariate:
-        x = self.design_matrix(spike_indicator)
-        labels = [f"hist_lag_{int(l)}" for l in self.lags]
-        return Covariate(time, x, self.name, "time", "s", "count", labels)
-
-    # MATLAB-compatible method names.
-    def computeHistory(self, nst) -> Covariate:
-        y = np.asarray(getattr(nst, "getSigRep")().data[:, 0], dtype=float)
-        t = np.asarray(getattr(nst, "getSigRep")().time, dtype=float)
-        return self.compute_history(y, t)
-
-
-# Backward-compatible alias.
-History = HistoryBasis
-
-
-__all__ = ["HistoryBasis", "History"]
diff --git a/python/nstat/nspikeTrain.py b/python/nstat/nspikeTrain.py
deleted file mode 100644
index f6e54d8..0000000
--- a/python/nstat/nspikeTrain.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ._compat import warn_deprecated_adapter
-from .spikes import SpikeTrain
-
-
-class nspikeTrain(SpikeTrain):
-    def __init__(self, *args, **kwargs) -> None:
-        warn_deprecated_adapter("nstat.nspikeTrain.nspikeTrain", "nstat.spikes.SpikeTrain")
-        super().__init__(*args, **kwargs)
-
-
-__all__ = ["nspikeTrain"]
diff --git a/python/nstat/nstColl.py b/python/nstat/nstColl.py
deleted file mode 100644
index 1e1cb96..0000000
--- a/python/nstat/nstColl.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from __future__ import annotations
-
-from ._compat import warn_deprecated_adapter
-from .spikes import SpikeTrainCollection
-
-
-class nstColl(SpikeTrainCollection):
-    def __init__(self, *args, **kwargs) -> None:
-        warn_deprecated_adapter("nstat.nstColl.nstColl", "nstat.spikes.SpikeTrainCollection")
-        super().__init__(*args, **kwargs)
-
-
-__all__ = ["nstColl"]
diff --git a/python/nstat/nstat_install.py b/python/nstat/nstat_install.py
deleted file mode 100644
index d34dd7b..0000000
--- a/python/nstat/nstat_install.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from __future__ import annotations
-
-from pathlib import Path
-
-
-def nSTAT_Install() -> Path:
-    """Return Python package root path (MATLAB nSTAT_Install analogue)."""
-    return Path(__file__).resolve().parents[1]
diff --git a/python/nstat/paper_examples.py b/python/nstat/paper_examples.py
deleted file mode 100644
index 38d6f82..0000000
--- a/python/nstat/paper_examples.py
+++ /dev/null
@@ -1,456 +0,0 @@
-from __future__ import annotations
-
-import argparse
-import json
-from pathlib import Path
-from typing import Any
-
-import numpy as np
-from scipy.io import loadmat
-
-from .analysis import psth
-from .decoding_algorithms import DecodingAlgorithms
-from .glm import fit_poisson_glm
-from .simulation import simulate_poisson_from_rate
-
-
-Summary = dict[str, float]
-Payload = dict[str, Any]
-Results = dict[str, Summary]
-PlotPayloads = dict[str, Payload]
-
-
-def _aic_bic(log_likelihood: float, n_obs: int, n_params: int) -> tuple[float, float]:
-    aic = 2.0 * n_params - 2.0 * log_likelihood
-    bic = np.log(max(n_obs, 1)) * n_params - 2.0 * log_likelihood
-    return float(aic), float(bic)
-
-
-def _history_matrix(y: np.ndarray, lags: tuple[int, ...]) -> np.ndarray:
-    x = np.zeros((y.shape[0], len(lags)), dtype=float)
-    for j, lag in enumerate(lags):
-        x[lag:, j] = y[:-lag]
-    return x
-
-
-def _bin_mean(values: np.ndarray, samples_per_bin: int) -> np.ndarray:
-    n_bins = values.shape[0] // samples_per_bin
-    if n_bins < 1:
-        return np.asarray([], dtype=float)
-    trimmed = values[: n_bins * samples_per_bin]
-    return trimmed.reshape(n_bins, samples_per_bin).mean(axis=1)
-
-
-def _bin_sum(values: np.ndarray, samples_per_bin: int) -> np.ndarray:
-    n_bins = values.shape[0] // samples_per_bin
-    if n_bins < 1:
-        return np.asarray([], dtype=float)
-    trimmed = values[: n_bins * samples_per_bin]
-    return trimmed.reshape(n_bins, samples_per_bin).sum(axis=1)
-
-
-def run_experiment2(data_dir: Path, *, return_payload: bool = False) -> Summary | tuple[Summary, Payload]:
-    mat_path = data_dir / "Explicit Stimulus" / "Dir3" / "Neuron1" / "Stim2" / "trngdataBis.mat"
-    d = loadmat(mat_path, squeeze_me=True, struct_as_record=False)
-
-    stim_raw = np.asarray(d["t"], dtype=float).reshape(-1)
-    y = np.asarray(d["y"], dtype=float).reshape(-1)
-
-    dt = 0.001
-    stim = stim_raw / 10.0
-    stim_vel = np.gradient(stim, dt)
-    hist = _history_matrix(y, lags=(1, 2, 3, 4, 5))
-
-    x1 = np.zeros((y.shape[0], 0), dtype=float)
-    x2 = np.column_stack([stim, stim_vel])
-    x3 = np.column_stack([stim, stim_vel, hist])
-    offset = np.full(y.shape[0], np.log(dt), dtype=float)
-
-    m1 = fit_poisson_glm(x1, y, offset=offset)
-    m2 = fit_poisson_glm(x2, y, offset=offset)
-    m3 = fit_poisson_glm(x3, y, offset=offset)
-
-    aic1, bic1 = _aic_bic(m1.log_likelihood, y.shape[0], 1)
-    aic2, bic2 = _aic_bic(m2.log_likelihood, y.shape[0], 3)
-    aic3, bic3 = _aic_bic(m3.log_likelihood, y.shape[0], 8)
-
-    summary: Summary = {
-        "n_samples": float(y.shape[0]),
-        "model1_aic": aic1,
-        "model2_aic": aic2,
-        "model3_aic": aic3,
-        "model1_bic": bic1,
-        "model2_bic": bic2,
-        "model3_bic": bic3,
-    }
-    if not return_payload:
-        return summary
-
-    rate1_hz = m1.predict_rate(x1, offset=offset)
-    rate2_hz = m2.predict_rate(x2, offset=offset)
-    rate3_hz = m3.predict_rate(x3, offset=offset)
-
-    samples_per_bin = 50  # 50 ms bins.
-    t_binned = np.arange(y.shape[0] // samples_per_bin, dtype=float) * dt * samples_per_bin
-    obs_rate_hz = _bin_sum(y, samples_per_bin) / (dt * samples_per_bin)
-    stim_binned = _bin_mean(stim, samples_per_bin)
-    rate1_binned_hz = _bin_mean(rate1_hz, samples_per_bin)
-    rate2_binned_hz = _bin_mean(rate2_hz, samples_per_bin)
-    rate3_binned_hz = _bin_mean(rate3_hz, samples_per_bin)
-
-    payload: Payload = {
-        "time_binned_s": t_binned,
-        "stimulus_binned": stim_binned,
-        "obs_rate_hz": obs_rate_hz,
-        "rate1_binned_hz": rate1_binned_hz,
-        "rate2_binned_hz": rate2_binned_hz,
-        "rate3_binned_hz": rate3_binned_hz,
-        "aic": np.asarray([aic1, aic2, aic3], dtype=float),
-        "bic": np.asarray([bic1, bic2, bic3], dtype=float),
-    }
-    return summary, payload
-
-
-def run_experiment3(seed: int = 7, *, return_payload: bool = False) -> Summary | tuple[Summary, Payload]:
-    rng = np.random.default_rng(seed)
-    dt = 0.001
-    tmax = 1.0
-    time = np.arange(0.0, tmax + dt, dt)
-
-    f = 2.0
-    mu = -3.0
-    linear = np.sin(2.0 * np.pi * f * time) + mu
-    p = np.exp(linear)
-    p = p / (1.0 + p)
-    rate_hz = p / dt
-
-    trains = [simulate_poisson_from_rate(time, rate_hz, rng=rng) for _ in range(20)]
-    bin_edges = np.arange(0.0, tmax + 0.05, 0.05)
-    psth_rate_hz, counts = psth(trains, bin_edges)
-
-    summary: Summary = {
-        "num_trials": float(len(trains)),
-        "psth_peak_hz": float(np.max(psth_rate_hz)),
-        "psth_mean_hz": float(np.mean(psth_rate_hz)),
-        "total_spikes": float(np.sum(counts)),
-    }
-    if not return_payload:
-        return summary
-
-    payload: Payload = {
-        "time_s": time,
-        "true_rate_hz": rate_hz,
-        "psth_bin_centers_s": 0.5 * (bin_edges[:-1] + bin_edges[1:]),
-        "psth_rate_hz": psth_rate_hz,
-        "raster_spike_times": [np.asarray(train.spikeTimes, dtype=float) for train in trains],
-    }
-    return summary, payload
-
-
-def _spike_indicator_from_times(time: np.ndarray, spike_times: np.ndarray) -> np.ndarray:
-    y = np.zeros(time.shape[0], dtype=float)
-    idx = np.searchsorted(time, spike_times, side="left")
-    idx = idx[(idx >= 0) & (idx < time.shape[0])]
-    if idx.size:
-        y[idx] = 1.0
-    return y
-
-
-def _zernike_like_basis(x: np.ndarray, y: np.ndarray) -> np.ndarray:
-    theta = np.arctan2(y, x)
-    r = np.sqrt(x * x + y * y)
-    return np.column_stack(
-        [
-            np.ones_like(r),
-            r,
-            r**2,
-            np.cos(theta),
-            np.sin(theta),
-            r * np.cos(theta),
-            r * np.sin(theta),
-            r**2 * np.cos(2.0 * theta),
-            r**2 * np.sin(2.0 * theta),
-            r**3,
-        ]
-    )
-
-
-def run_experiment4(data_dir: Path, *, return_payload: bool = False) -> Summary | tuple[Summary, Payload]:
-    mat_path = data_dir / "Place Cells" / "PlaceCellDataAnimal1.mat"
-    d = loadmat(mat_path, squeeze_me=True, struct_as_record=False)
-
-    x = np.asarray(d["x"], dtype=float).reshape(-1)
-    y = np.asarray(d["y"], dtype=float).reshape(-1)
-    time = np.asarray(d["time"], dtype=float).reshape(-1)
-    neurons = np.asarray(d["neuron"], dtype=object).reshape(-1)
-
-    dt = float(np.median(np.diff(time)))
-    offset = np.full(time.shape[0], np.log(max(dt, 1e-12)), dtype=float)
-
-    x_gauss = np.column_stack([x, y, x * x, y * y, x * y])
-    x_zern = _zernike_like_basis(x, y)
-
-    n_eval = int(min(8, neurons.shape[0]))
-    delta_aic = []
-    delta_bic = []
-    for i in range(n_eval):
-        spike_times = np.asarray(neurons[i].spikeTimes, dtype=float).reshape(-1)
-        y_spike = _spike_indicator_from_times(time, spike_times)
-
-        m_g = fit_poisson_glm(x_gauss, y_spike, offset=offset)
-        m_z = fit_poisson_glm(x_zern, y_spike, offset=offset)
-
-        aic_g, bic_g = _aic_bic(m_g.log_likelihood, y_spike.shape[0], x_gauss.shape[1] + 1)
-        aic_z, bic_z = _aic_bic(m_z.log_likelihood, y_spike.shape[0], x_zern.shape[1] + 1)
-        delta_aic.append(aic_g - aic_z)
-        delta_bic.append(bic_g - bic_z)
-
-    summary: Summary = {
-        "num_cells_fit": float(n_eval),
-        "mean_delta_aic_gaussian_minus_zernike": float(np.mean(delta_aic)),
-        "mean_delta_bic_gaussian_minus_zernike": float(np.mean(delta_bic)),
-    }
-    if not return_payload:
-        return summary
-
-    first_cell_spikes = np.asarray(neurons[0].spikeTimes, dtype=float).reshape(-1)
-    payload: Payload = {
-        "time_s": time,
-        "x_pos": x,
-        "y_pos": y,
-        "first_cell_spike_times_s": first_cell_spikes,
-        "delta_aic": np.asarray(delta_aic, dtype=float),
-        "delta_bic": np.asarray(delta_bic, dtype=float),
-    }
-    return summary, payload
-
-
-def run_experiment5(seed: int = 11, *, return_payload: bool = False) -> Summary | tuple[Summary, Payload]:
-    rng = np.random.default_rng(seed)
-
-    dt = 0.001
-    time = np.arange(0.0, 1.0 + dt, dt)
-    stim = np.sin(2.0 * np.pi * 2.0 * time)
-
-    n_cells = 20
-    spikes = np.zeros((time.shape[0], n_cells), dtype=float)
-    for i in range(n_cells):
-        b1 = rng.normal(1.0, 0.5)
-        b0 = np.log(10.0 * dt) + rng.normal(0.0, 0.3)
-        eta = b1 * stim + b0
-        p = np.exp(eta)
-        p = p / (1.0 + p)
-        spikes[:, i] = (rng.random(time.shape[0]) < p).astype(float)
-
-    decoded = DecodingAlgorithms.linear_decode(spikes, stim)
-    rmse = float(np.sqrt(np.mean((decoded["decoded"] - stim) ** 2)))
-
-    summary: Summary = {
-        "num_cells": float(n_cells),
-        "decode_rmse": rmse,
-    }
-    if not return_payload:
-        return summary
-
-    payload: Payload = {
-        "time_s": time,
-        "stimulus": stim,
-        "decoded": np.asarray(decoded["decoded"], dtype=float),
-        "ci_low": np.asarray(decoded["ci"][:, 0], dtype=float),
-        "ci_high": np.asarray(decoded["ci"][:, 1], dtype=float),
-    }
-    return summary, payload
-
-
-def run_paper_examples(
-    repo_root: Path, *, return_plot_data: bool = False
-) -> Results | tuple[Results, PlotPayloads]:
-    data_dir = repo_root / "data"
-    if not data_dir.exists():
-        raise FileNotFoundError(f"Could not locate data directory: {data_dir}")
-
-    if not return_plot_data:
-        return {
-            "experiment2": run_experiment2(data_dir),  # type: ignore[arg-type]
-            "experiment3": run_experiment3(),
-            "experiment4": run_experiment4(data_dir),  # type: ignore[arg-type]
-            "experiment5": run_experiment5(),
-        }
-
-    exp2_summary, exp2_payload = run_experiment2(data_dir, return_payload=True)  # type: ignore[misc]
-    exp3_summary, exp3_payload = run_experiment3(return_payload=True)  # type: ignore[misc]
-    exp4_summary, exp4_payload = run_experiment4(data_dir, return_payload=True)  # type: ignore[misc]
-    exp5_summary, exp5_payload = run_experiment5(return_payload=True)  # type: ignore[misc]
-
-    results: Results = {
-        "experiment2": exp2_summary,
-        "experiment3": exp3_summary,
-        "experiment4": exp4_summary,
-        "experiment5": exp5_summary,
-    }
-    plot_payloads: PlotPayloads = {
-        "experiment2": exp2_payload,
-        "experiment3": exp3_payload,
-        "experiment4": exp4_payload,
-        "experiment5": exp5_payload,
-    }
-    return results, plot_payloads
-
-
-def _save_paper_example_plots(plot_payloads: PlotPayloads, plots_dir: Path) -> list[Path]:
-    import matplotlib
-
-    matplotlib.use("Agg")
-    import matplotlib.pyplot as plt
-
-    plots_dir.mkdir(parents=True, exist_ok=True)
-    saved_paths: list[Path] = []
-
-    # Experiment 2: explicit stimulus + model comparison.
-    e2 = plot_payloads["experiment2"]
-    fig, (ax_rate, ax_metrics) = plt.subplots(2, 1, figsize=(11, 8), constrained_layout=True)
-    ax_rate.plot(e2["time_binned_s"], e2["obs_rate_hz"], label="Observed spike rate", color="tab:blue", lw=1.5)
-    ax_rate.plot(e2["time_binned_s"], e2["rate1_binned_hz"], label="Model 1 rate", color="tab:orange", lw=1.2)
-    ax_rate.plot(e2["time_binned_s"], e2["rate2_binned_hz"], label="Model 2 rate", color="tab:green", lw=1.2)
-    ax_rate.plot(e2["time_binned_s"], e2["rate3_binned_hz"], label="Model 3 rate", color="tab:red", lw=1.2)
-    ax_rate.set_title("Experiment 2: Stimulus and GLM Rate Fits")
-    ax_rate.set_xlabel("Time (s)")
-    ax_rate.set_ylabel("Rate (Hz)")
-    ax_rate.grid(alpha=0.3)
-
-    ax_stim = ax_rate.twinx()
-    ax_stim.plot(e2["time_binned_s"], e2["stimulus_binned"], color="black", alpha=0.25, lw=1.0, label="Stimulus")
-    ax_stim.set_ylabel("Stimulus (a.u.)")
-
-    handles1, labels1 = ax_rate.get_legend_handles_labels()
-    handles2, labels2 = ax_stim.get_legend_handles_labels()
-    ax_rate.legend(handles1 + handles2, labels1 + labels2, loc="upper right", fontsize=8)
-
-    xloc = np.arange(3)
-    ax_metrics.bar(xloc - 0.175, e2["aic"], width=0.35, label="AIC", color="tab:purple")
-    ax_metrics.bar(xloc + 0.175, e2["bic"], width=0.35, label="BIC", color="tab:brown")
-    ax_metrics.set_xticks(xloc, ["Model 1", "Model 2", "Model 3"])
-    ax_metrics.set_ylabel("Information Criterion")
-    ax_metrics.set_title("Experiment 2: Model Comparison")
-    ax_metrics.grid(alpha=0.3, axis="y")
-    ax_metrics.legend()
-
-    out = plots_dir / "experiment2_stimulus_glm_comparison.png"
-    fig.savefig(out, dpi=180)
-    plt.close(fig)
-    saved_paths.append(out)
-
-    # Experiment 3: true CIF and PSTH with raster.
-    e3 = plot_payloads["experiment3"]
-    fig, (ax_rate, ax_raster) = plt.subplots(2, 1, figsize=(11, 8), constrained_layout=True)
-    ax_rate.plot(e3["time_s"], e3["true_rate_hz"], color="tab:gray", lw=1.5, label="True rate")
-    ax_rate.step(
-        e3["psth_bin_centers_s"],
-        e3["psth_rate_hz"],
-        where="mid",
-        color="tab:blue",
-        lw=2.0,
-        label="Estimated PSTH",
-    )
-    ax_rate.set_title("Experiment 3: Simulated CIF and Estimated PSTH")
-    ax_rate.set_xlabel("Time (s)")
-    ax_rate.set_ylabel("Rate (Hz)")
-    ax_rate.grid(alpha=0.3)
-    ax_rate.legend(loc="upper right")
-
-    n_show = min(10, len(e3["raster_spike_times"]))
-    for row, spikes in enumerate(e3["raster_spike_times"][:n_show], start=1):
-        if len(spikes) > 0:
-            ax_raster.vlines(spikes, row - 0.4, row + 0.4, color="black", lw=0.6)
-    ax_raster.set_ylim(0.5, n_show + 0.5)
-    ax_raster.set_title("Experiment 3: Spike Raster (first 10 trials)")
-    ax_raster.set_xlabel("Time (s)")
-    ax_raster.set_ylabel("Trial")
-    ax_raster.grid(alpha=0.2)
-
-    out = plots_dir / "experiment3_psth_and_raster.png"
-    fig.savefig(out, dpi=180)
-    plt.close(fig)
-    saved_paths.append(out)
-
-    # Experiment 4: trajectory and model deltas.
-    e4 = plot_payloads["experiment4"]
-    fig, (ax_traj, ax_delta) = plt.subplots(1, 2, figsize=(13, 5.5), constrained_layout=True)
-    ax_traj.plot(e4["x_pos"], e4["y_pos"], color="tab:blue", alpha=0.45, lw=0.8, label="Trajectory")
-    spike_x = np.interp(e4["first_cell_spike_times_s"], e4["time_s"], e4["x_pos"])
-    spike_y = np.interp(e4["first_cell_spike_times_s"], e4["time_s"], e4["y_pos"])
-    ax_traj.scatter(spike_x, spike_y, s=8, color="tab:red", alpha=0.6, label="Cell 1 spikes")
-    ax_traj.set_title("Experiment 4: Place Trajectory and Cell 1 Spikes")
-    ax_traj.set_xlabel("X position")
-    ax_traj.set_ylabel("Y position")
-    ax_traj.set_aspect("equal", adjustable="box")
-    ax_traj.grid(alpha=0.3)
-    ax_traj.legend(loc="upper right", fontsize=8)
-
-    cells = np.arange(1, len(e4["delta_aic"]) + 1)
-    ax_delta.plot(cells, e4["delta_aic"], marker="o", lw=1.5, color="tab:purple", label="Delta AIC")
-    ax_delta.plot(cells, e4["delta_bic"], marker="s", lw=1.5, color="tab:green", label="Delta BIC")
-    ax_delta.axhline(0.0, color="black", lw=1.0, alpha=0.5)
-    ax_delta.set_title("Experiment 4: Gaussian - Zernike Model Delta")
-    ax_delta.set_xlabel("Cell index")
-    ax_delta.set_ylabel("Score difference")
-    ax_delta.grid(alpha=0.3)
-    ax_delta.legend(loc="upper right")
-
-    out = plots_dir / "experiment4_placecell_model_comparison.png"
-    fig.savefig(out, dpi=180)
-    plt.close(fig)
-    saved_paths.append(out)
-
-    # Experiment 5: decode quality with confidence bounds.
-    e5 = plot_payloads["experiment5"]
-    fig, ax = plt.subplots(1, 1, figsize=(11, 4.5), constrained_layout=True)
-    ax.plot(e5["time_s"], e5["stimulus"], color="tab:blue", lw=1.8, label="True stimulus")
-    ax.plot(e5["time_s"], e5["decoded"], color="tab:orange", lw=1.5, label="Decoded stimulus")
-    ax.fill_between(e5["time_s"], e5["ci_low"], e5["ci_high"], color="tab:orange", alpha=0.2, label="95% CI")
-    ax.set_title("Experiment 5: Linear Decoding of Simulated Stimulus")
-    ax.set_xlabel("Time (s)")
-    ax.set_ylabel("Stimulus (a.u.)")
-    ax.grid(alpha=0.3)
-    ax.legend(loc="upper right")
-
-    out = plots_dir / "experiment5_stimulus_decoding.png"
-    fig.savefig(out, dpi=180)
-    plt.close(fig)
-    saved_paths.append(out)
-
-    return saved_paths
-
-
-def main() -> int:
-    parser = argparse.ArgumentParser(description="Python nSTAT paper examples equivalent")
-    parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[2])
-    parser.add_argument("--no-plots", action="store_true", help="Run metrics only without generating plots")
-    parser.add_argument("--plots-dir", type=Path, default=None, help="Directory for generated PNG plots")
-    parser.add_argument("--output-json", type=Path, default=None)
-    args = parser.parse_args()
-
-    repo_root = args.repo_root.resolve()
-    default_plots_dir = repo_root / "python" / "plots" / "nstat_paper_examples"
-    plots_dir = (args.plots_dir or default_plots_dir).resolve()
-
-    if args.no_plots:
-        results = run_paper_examples(repo_root)
-        saved_plots: list[Path] = []
-    else:
-        results, payloads = run_paper_examples(repo_root, return_plot_data=True)
-        saved_plots = _save_paper_example_plots(payloads, plots_dir)
-
-    if args.output_json is not None:
-        args.output_json.write_text(json.dumps(results, indent=2), encoding="utf-8")
-
-    print(json.dumps(results, indent=2))
-    if saved_plots:
-        print("\nGenerated plots:")
-        for path in saved_plots:
-            print(str(path))
-    return 0
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/nstat/paper_examples_full.py b/python/nstat/paper_examples_full.py
deleted file mode 100644
index 55f0cae..0000000
--- a/python/nstat/paper_examples_full.py
+++ /dev/null
@@ -1,452 +0,0 @@
-from __future__ import annotations
-
-import argparse
-import json
-import os
-from pathlib import Path
-
-import numpy as np
-from scipy.io import loadmat
-
-from .analysis import psth
-from .decoding_algorithms import DecodingAlgorithms
-from .glm import fit_poisson_glm
-from .simulation import simulate_poisson_from_rate
-
-
-def _allow_synthetic_data() -> bool:
-    return os.environ.get("NSTAT_ALLOW_SYNTHETIC_DATA", "").strip().lower() in {"1", "true", "yes", "on"}
-
-
-def _is_lfs_pointer(path: Path) -> bool:
-    try:
-        head = path.read_bytes()[:200]
-    except OSError:
-        return False
-    return head.startswith(b"version https://git-lfs.github.com/spec/v1")
-
-
-def _loadmat_checked(path: Path):
-    if path.exists() and not _is_lfs_pointer(path):
-        return loadmat(path, squeeze_me=True, struct_as_record=False)
-    if _allow_synthetic_data():
-        return None
-    if not path.exists():
-        raise FileNotFoundError(f"Missing MAT file: {path}")
-    if _is_lfs_pointer(path):
-        raise RuntimeError(
-            f"MAT file is a Git LFS pointer, not dataset content: {path}. "
-            "Fetch LFS assets or set NSTAT_ALLOW_SYNTHETIC_DATA=1 for synthetic CI fallback."
-        )
-    raise RuntimeError(f"Unable to load MAT file: {path}")
-
-
-def _aic_bic(log_likelihood: float, n_obs: int, n_params: int) -> tuple[float, float]:
-    aic = 2.0 * n_params - 2.0 * log_likelihood
-    bic = np.log(max(n_obs, 1)) * n_params - 2.0 * log_likelihood
-    return float(aic), float(bic)
-
-
-def _history_matrix(y: np.ndarray, lags: list[int]) -> np.ndarray:
-    out = np.zeros((y.shape[0], len(lags)), dtype=float)
-    for j, lag in enumerate(lags):
-        out[lag:, j] = y[:-lag]
-    return out
-
-
-def _load_mepsc_times_seconds(path: Path) -> np.ndarray:
-    arr = np.loadtxt(path, skiprows=1)
-    return np.asarray(arr[:, 1], dtype=float).reshape(-1) / 1000.0
-
-
-def _bin_spike_times(spikes: np.ndarray, t0: float, t1: float, dt: float) -> tuple[np.ndarray, np.ndarray]:
-    n_bins = int(np.floor((t1 - t0) / dt)) + 1
-    edges = t0 + np.arange(n_bins + 1, dtype=float) * dt
-    counts, _ = np.histogram(spikes, bins=edges)
-    return edges[:-1], counts.astype(float)
-
-
-def run_experiment1(data_dir: Path) -> dict[str, float]:
-    mepsc_dir = data_dir / "mEPSCs"
-    epsc2 = _load_mepsc_times_seconds(mepsc_dir / "epsc2.txt")
-    washout1 = _load_mepsc_times_seconds(mepsc_dir / "washout1.txt")
-    washout2 = _load_mepsc_times_seconds(mepsc_dir / "washout2.txt")
-
-    dt_const = 0.01
-    _, y_const = _bin_spike_times(epsc2, 0.0, float(np.max(epsc2)), dt_const)
-    off_const = np.full(y_const.shape[0], np.log(dt_const), dtype=float)
-    m_const = fit_poisson_glm(np.zeros((y_const.shape[0], 0), dtype=float), y_const, offset=off_const, max_iter=80)
-    aic_const, bic_const = _aic_bic(m_const.log_likelihood, y_const.shape[0], 1)
-
-    spikes = np.concatenate([260.0 + washout1, np.sort(washout2) + 745.0])
-    dt = 0.01
-    t, y = _bin_spike_times(spikes, 260.0, float(np.max(spikes)), dt)
-    off = np.full(y.shape[0], np.log(dt), dtype=float)
-
-    seg2 = ((t >= 495.0) & (t < 765.0)).astype(float)
-    seg3 = (t >= 765.0).astype(float)
-    x_piece = np.column_stack([seg2, seg3])
-    hist = _history_matrix(y, [1, 2, 3, 4, 5, 7, 10, 14, 20, 30])
-    x_piece_hist = np.column_stack([x_piece, hist])
-
-    m_piece = fit_poisson_glm(x_piece, y, offset=off, max_iter=100)
-    m_piece_hist = fit_poisson_glm(x_piece_hist, y, offset=off, max_iter=120)
-    aic_piece, bic_piece = _aic_bic(m_piece.log_likelihood, y.shape[0], x_piece.shape[1] + 1)
-    aic_piece_hist, bic_piece_hist = _aic_bic(m_piece_hist.log_likelihood, y.shape[0], x_piece_hist.shape[1] + 1)
-
-    return {
-        "const_condition_spikes": float(np.sum(y_const)),
-        "const_model_aic": aic_const,
-        "const_model_bic": bic_const,
-        "decreasing_condition_spikes": float(np.sum(y)),
-        "piecewise_model_aic": aic_piece,
-        "piecewise_model_bic": bic_piece,
-        "piecewise_history_model_aic": aic_piece_hist,
-        "piecewise_history_model_bic": bic_piece_hist,
-        "dt_seconds": dt,
-    }
-
-
-def run_experiment2(data_dir: Path) -> dict[str, float]:
-    path = data_dir / "Explicit Stimulus" / "Dir3" / "Neuron1" / "Stim2" / "trngdataBis.mat"
-    d = _loadmat_checked(path)
-    if d is None:
-        rng = np.random.default_rng(2002)
-        n = 5000
-        t = np.linspace(0.0, 2.0 * np.pi, n, dtype=float)
-        stim_raw = np.sin(1.8 * t) + 0.25 * np.sin(0.4 * t + 0.2)
-        p = np.clip(0.015 + 0.02 * np.maximum(stim_raw, 0.0), 1e-4, 0.35)
-        y = (rng.random(n) < p).astype(float)
-    else:
-        stim_raw = np.asarray(d["t"], dtype=float).reshape(-1)
-        y = np.asarray(d["y"], dtype=float).reshape(-1)
-
-    dt = 0.001
-    stim = stim_raw / 10.0
-    stim_vel = np.gradient(stim, dt)
-    hist = _history_matrix(y, [1, 2, 3, 4, 5])
-
-    x1 = np.zeros((y.shape[0], 0), dtype=float)
-    x2 = np.column_stack([stim, stim_vel])
-    x3 = np.column_stack([stim, stim_vel, hist])
-    offset = np.full(y.shape[0], np.log(dt), dtype=float)
-
-    m1 = fit_poisson_glm(x1, y, offset=offset)
-    m2 = fit_poisson_glm(x2, y, offset=offset)
-    m3 = fit_poisson_glm(x3, y, offset=offset)
-
-    aic1, bic1 = _aic_bic(m1.log_likelihood, y.shape[0], 1)
-    aic2, bic2 = _aic_bic(m2.log_likelihood, y.shape[0], 3)
-    aic3, bic3 = _aic_bic(m3.log_likelihood, y.shape[0], 8)
-
-    return {
-        "n_samples": float(y.shape[0]),
-        "model1_aic": aic1,
-        "model2_aic": aic2,
-        "model3_aic": aic3,
-        "model1_bic": bic1,
-        "model2_bic": bic2,
-        "model3_bic": bic3,
-    }
-
-
-def run_experiment3(seed: int = 7) -> dict[str, float]:
-    rng = np.random.default_rng(seed)
-    dt = 0.001
-    tmax = 1.0
-    time = np.arange(0.0, tmax + dt, dt)
-
-    linear = np.sin(2.0 * np.pi * 2.0 * time) - 3.0
-    p = np.exp(linear)
-    p = p / (1.0 + p)
-    rate_hz = p / dt
-
-    trains = [simulate_poisson_from_rate(time, rate_hz, rng=rng) for _ in range(20)]
-    edges = np.arange(0.0, tmax + 0.05, 0.05)
-    psth_rate_hz, counts = psth(trains, edges)
-
-    return {
-        "num_trials": float(len(trains)),
-        "psth_peak_hz": float(np.max(psth_rate_hz)),
-        "psth_mean_hz": float(np.mean(psth_rate_hz)),
-        "total_spikes": float(np.sum(counts)),
-    }
-
-
-def run_experiment3b(data_dir: Path) -> dict[str, float]:
-    path = data_dir / "SSGLMExampleData.mat"
-    d = _loadmat_checked(path)
-    if d is None:
-        rng = np.random.default_rng(3003)
-        stimulus = rng.normal(0.0, 1.0, size=(15, 250))
-        xk = stimulus + rng.normal(0.0, 0.2, size=stimulus.shape)
-        ci_half = np.abs(rng.normal(0.35, 0.08, size=stimulus.shape))
-        stim_cis = np.stack([xk - ci_half, xk + ci_half], axis=-1)
-        qhat = np.abs(rng.normal(0.12, 0.03, size=stimulus.shape[0]))
-        gammahat = np.abs(rng.normal(0.08, 0.02, size=stimulus.shape[0]))
-        logll = float(-np.mean((xk - stimulus) ** 2) * stimulus.size)
-    else:
-        stimulus = np.asarray(d["stimulus"], dtype=float)
-        xk = np.asarray(d["xK"], dtype=float)
-        stim_cis = np.asarray(d["stimCIs"], dtype=float)
-        qhat = np.asarray(d["Qhat"], dtype=float).reshape(-1)
-        gammahat = np.asarray(d["gammahat"], dtype=float).reshape(-1)
-        logll = float(np.asarray(d["logll"], dtype=float).reshape(-1)[0])
-
-    coverage = np.mean((stimulus >= stim_cis[:, :, 0]) & (stimulus <= stim_cis[:, :, 1]))
-    rmse = np.sqrt(np.mean((xk - stimulus) ** 2))
-
-    return {
-        "num_trials": float(stimulus.shape[0]),
-        "num_time_bins": float(stimulus.shape[1]),
-        "state_rmse": float(rmse),
-        "ci_coverage": float(coverage),
-        "mean_qhat": float(np.mean(qhat)),
-        "mean_gammahat": float(np.mean(gammahat)),
-        "log_likelihood": logll,
-    }
-
-
-def _spike_indicator(time: np.ndarray, spike_times: np.ndarray) -> np.ndarray:
-    y = np.zeros(time.shape[0], dtype=float)
-    idx = np.searchsorted(time, spike_times, side="left")
-    idx = idx[(idx >= 0) & (idx < time.shape[0])]
-    if idx.size > 0:
-        y[idx] = 1.0
-    return y
-
-
-def _zernike_like_basis(x: np.ndarray, y: np.ndarray) -> np.ndarray:
-    theta = np.arctan2(y, x)
-    r = np.sqrt(x * x + y * y)
-    return np.column_stack([
-        np.ones_like(r),
-        r,
-        r**2,
-        np.cos(theta),
-        np.sin(theta),
-        r * np.cos(theta),
-        r * np.sin(theta),
-        r**2 * np.cos(2.0 * theta),
-        r**2 * np.sin(2.0 * theta),
-        r**3,
-    ])
-
-
-def run_experiment4(data_dir: Path) -> dict[str, float]:
-    path = data_dir / "Place Cells" / "PlaceCellDataAnimal1.mat"
-    d = _loadmat_checked(path)
-    if d is None:
-        rng = np.random.default_rng(4004)
-        time = np.linspace(0.0, 20.0, 2400, dtype=float)
-        x = 0.8 * np.sin(0.6 * time) + 0.2 * np.sin(1.7 * time + 0.5)
-        y = 0.7 * np.cos(0.5 * time + 0.3)
-        n_cells = 8
-        neurons = []
-        for _ in range(n_cells):
-            field = np.exp(-((x - rng.uniform(-0.5, 0.5)) ** 2 + (y - rng.uniform(-0.5, 0.5)) ** 2) / 0.2)
-            p = np.clip(0.001 + 0.03 * field, 1e-6, 0.25)
-            spikes = time[rng.random(time.shape[0]) < p]
-            neurons.append(type("N", (), {"spikeTimes": spikes})())
-        neurons = np.asarray(neurons, dtype=object)
-    else:
-        x = np.asarray(d["x"], dtype=float).reshape(-1)
-        y = np.asarray(d["y"], dtype=float).reshape(-1)
-        time = np.asarray(d["time"], dtype=float).reshape(-1)
-        neurons = np.asarray(d["neuron"], dtype=object).reshape(-1)
-
-    dt = float(np.median(np.diff(time)))
-    offset = np.full(time.shape[0], np.log(max(dt, 1e-12)), dtype=float)
-    x_gauss = np.column_stack([x, y, x * x, y * y, x * y])
-    x_zern = _zernike_like_basis(x, y)
-
-    d_aic = []
-    d_bic = []
-    n_eval = int(min(8, neurons.shape[0]))
-    for i in range(n_eval):
-        spike_times = np.asarray(neurons[i].spikeTimes, dtype=float).reshape(-1)
-        y_spike = _spike_indicator(time, spike_times)
-        mg = fit_poisson_glm(x_gauss, y_spike, offset=offset)
-        mz = fit_poisson_glm(x_zern, y_spike, offset=offset)
-        aicg, bicg = _aic_bic(mg.log_likelihood, y_spike.shape[0], x_gauss.shape[1] + 1)
-        aicz, bicz = _aic_bic(mz.log_likelihood, y_spike.shape[0], x_zern.shape[1] + 1)
-        d_aic.append(aicg - aicz)
-        d_bic.append(bicg - bicz)
-
-    return {
-        "num_cells_fit": float(n_eval),
-        "mean_delta_aic_gaussian_minus_zernike": float(np.mean(d_aic)),
-        "mean_delta_bic_gaussian_minus_zernike": float(np.mean(d_bic)),
-    }
-
-
-def run_experiment5(seed: int = 11, n_cells: int = 20) -> dict[str, float]:
-    rng = np.random.default_rng(seed)
-    dt = 0.001
-    time = np.arange(0.0, 1.0 + dt, dt)
-    stim = np.sin(2.0 * np.pi * 2.0 * time)
-
-    n_cells = int(n_cells)
-    if n_cells < 1:
-        raise ValueError("n_cells must be >= 1")
-    spikes = np.zeros((time.shape[0], n_cells), dtype=float)
-    for i in range(n_cells):
-        b1 = rng.normal(1.0, 0.5)
-        b0 = np.log(10.0 * dt) + rng.normal(0.0, 0.3)
-        eta = b1 * stim + b0
-        p = np.exp(eta)
-        p = p / (1.0 + p)
-        spikes[:, i] = (rng.random(time.shape[0]) < p).astype(float)
-
-    decoded = DecodingAlgorithms.linear_decode(spikes, stim)
-    rmse = float(np.sqrt(np.mean((decoded["decoded"] - stim) ** 2)))
-    return {"num_cells": float(n_cells), "decode_rmse": rmse}
-
-
-def run_experiment5b(seed: int = 19, n_cells: int = 30) -> dict[str, float]:
-    rng = np.random.default_rng(seed)
-
-    dt = 0.01
-    time = np.arange(0.0, 20.0 + dt, dt)
-    x_true = 0.25 * np.sin(2.0 * np.pi * 0.15 * time)
-    y_true = 0.20 * np.cos(2.0 * np.pi * 0.10 * time)
-    vx = np.gradient(x_true, dt)
-    vy = np.gradient(y_true, dt)
-
-    n_cells = int(n_cells)
-    if n_cells < 1:
-        raise ValueError("n_cells must be >= 1")
-    spikes = np.zeros((time.shape[0], n_cells), dtype=float)
-    for i in range(n_cells):
-        wx = rng.normal(0.0, 1.0)
-        wy = rng.normal(0.0, 1.0)
-        b0 = -3.0 + rng.normal(0.0, 0.2)
-        eta = b0 + 3.0 * wx * vx + 3.0 * wy * vy
-        p = 1.0 / (1.0 + np.exp(-np.clip(eta, -20.0, 20.0)))
-        spikes[:, i] = (rng.random(time.shape[0]) < p).astype(float)
-
-    dx = DecodingAlgorithms.linear_decode(spikes, x_true)["decoded"]
-    dy = DecodingAlgorithms.linear_decode(spikes, y_true)["decoded"]
-    return {
-        "num_cells": float(n_cells),
-        "num_samples": float(time.shape[0]),
-        "decode_rmse_x": float(np.sqrt(np.mean((dx - x_true) ** 2))),
-        "decode_rmse_y": float(np.sqrt(np.mean((dy - y_true) ** 2))),
-    }
-
-
-def _simulate_hybrid_spikes(x: np.ndarray, mstate: np.ndarray, dt: float, n_cells: int, seed: int):
-    rng = np.random.default_rng(seed)
-    vx = x[2, :]
-    vy = x[3, :]
-    wvx = rng.normal(0.0, 1.0, size=n_cells)
-    wvy = rng.normal(0.0, 1.0, size=n_cells)
-    b1 = rng.normal(-3.8, 0.2, size=n_cells)
-    b2 = rng.normal(-2.8, 0.2, size=n_cells)
-
-    spikes = np.zeros((x.shape[1], n_cells), dtype=float)
-    for t in range(x.shape[1]):
-        base = b1 if int(mstate[t]) == 1 else b2
-        eta = base + 1.2 * wvx * vx[t] + 1.2 * wvy * vy[t]
-        lam = np.exp(np.clip(eta, -15.0, 15.0))
-        p = 1.0 - np.exp(-lam * dt)
-        spikes[t, :] = (rng.random(n_cells) < p).astype(float)
-    return spikes, wvx, wvy, b1, b2
-
-
-def _hybrid_state_filter(spikes: np.ndarray, x: np.ndarray, dt: float, p_ij: np.ndarray, wvx: np.ndarray, wvy: np.ndarray, b1: np.ndarray, b2: np.ndarray) -> np.ndarray:
-    n_t, _ = spikes.shape
-    post = np.zeros((n_t, 2), dtype=float)
-    post[0, :] = [0.5, 0.5]
-    vx = x[2, :]
-    vy = x[3, :]
-
-    for t in range(1, n_t):
-        eta1 = b1 + 1.2 * wvx * vx[t] + 1.2 * wvy * vy[t]
-        eta2 = b2 + 1.2 * wvx * vx[t] + 1.2 * wvy * vy[t]
-        p1 = np.clip(1.0 - np.exp(-np.exp(np.clip(eta1, -15.0, 15.0)) * dt), 1e-6, 1.0 - 1e-6)
-        p2 = np.clip(1.0 - np.exp(-np.exp(np.clip(eta2, -15.0, 15.0)) * dt), 1e-6, 1.0 - 1e-6)
-        k = spikes[t, :]
-        ll1 = np.sum(k * np.log(p1) + (1.0 - k) * np.log(1.0 - p1))
-        ll2 = np.sum(k * np.log(p2) + (1.0 - k) * np.log(1.0 - p2))
-        pred0 = max(post[t - 1, 0] * p_ij[0, 0] + post[t - 1, 1] * p_ij[1, 0], 1e-15)
-        pred1 = max(post[t - 1, 0] * p_ij[0, 1] + post[t - 1, 1] * p_ij[1, 1], 1e-15)
-        logs = np.array([np.log(pred0) + ll1, np.log(pred1) + ll2], dtype=float)
-        logs = logs - np.max(logs)
-        un = np.exp(logs)
-        post[t, :] = un / np.sum(un)
-    return post
-
-
-def run_experiment6(repo_root: Path, seed: int = 37) -> dict[str, float]:
-    path = repo_root / "helpfiles" / "paperHybridFilterExample.mat"
-    d = _loadmat_checked(path)
-    if d is None:
-        rng = np.random.default_rng(seed)
-        dt = 0.01
-        t = np.arange(0.0, 30.0, dt, dtype=float)
-        x_pos = 0.3 * np.sin(0.2 * t)
-        y_pos = 0.25 * np.cos(0.15 * t)
-        x_vel = np.gradient(x_pos, dt)
-        y_vel = np.gradient(y_pos, dt)
-        x = np.vstack([x_pos, y_pos, x_vel, y_vel])
-        mstate = np.where(np.sin(0.05 * t + 0.4) > 0.0, 1, 2).astype(int)
-        # Add mild stochasticity so state filter is non-trivial.
-        flip = rng.random(t.shape[0]) < 0.02
-        mstate[flip] = 3 - mstate[flip]
-        p_ij = np.array([[0.985, 0.015], [0.02, 0.98]], dtype=float)
-    else:
-        x = np.asarray(d["X"], dtype=float)
-        mstate = np.asarray(d["mstate"], dtype=int).reshape(-1)
-        p_ij = np.asarray(d["p_ij"], dtype=float)
-        dt = float(np.asarray(d["delta"], dtype=float).reshape(-1)[0])
-
-    n_cells = 24
-    spikes, wvx, wvy, b1, b2 = _simulate_hybrid_spikes(x, mstate, dt, n_cells=n_cells, seed=seed)
-    post = _hybrid_state_filter(spikes, x, dt, p_ij, wvx, wvy, b1, b2)
-    state_hat = np.argmax(post, axis=1) + 1
-    state_acc = np.mean(state_hat == mstate)
-
-    dx = DecodingAlgorithms.linear_decode(spikes, x[0, :])["decoded"]
-    dy = DecodingAlgorithms.linear_decode(spikes, x[1, :])["decoded"]
-    return {
-        "num_samples": float(x.shape[1]),
-        "num_cells": float(n_cells),
-        "state_accuracy": float(state_acc),
-        "decode_rmse_x": float(np.sqrt(np.mean((dx - x[0, :]) ** 2))),
-        "decode_rmse_y": float(np.sqrt(np.mean((dy - x[1, :]) ** 2))),
-    }
-
-
-def run_full_paper_examples(repo_root: Path) -> dict[str, dict[str, float]]:
-    data_dir = repo_root / "data"
-    if not data_dir.exists():
-        raise FileNotFoundError(f"Could not locate data directory: {data_dir}")
-
-    return {
-        "experiment1": run_experiment1(data_dir),
-        "experiment2": run_experiment2(data_dir),
-        "experiment3": run_experiment3(),
-        "experiment3b": run_experiment3b(data_dir),
-        "experiment4": run_experiment4(data_dir),
-        "experiment5": run_experiment5(),
-        "experiment5b": run_experiment5b(),
-        "experiment6": run_experiment6(repo_root),
-    }
-
-
-def main() -> int:
-    parser = argparse.ArgumentParser(description="Native Python approximation of nSTATPaperExamples.m")
-    parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[2])
-    parser.add_argument("--output-json", type=Path, default=None)
-    args = parser.parse_args()
-
-    results = run_full_paper_examples(args.repo_root.resolve())
-    if args.output_json is not None:
-        args.output_json.write_text(json.dumps(results, indent=2), encoding="utf-8")
-    print(json.dumps(results, indent=2))
-    return 0
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/nstat/signal.py b/python/nstat/signal.py
deleted file mode 100644
index 11be2dc..0000000
--- a/python/nstat/signal.py
+++ /dev/null
@@ -1,88 +0,0 @@
-from __future__ import annotations
-
-from typing import Sequence
-
-import numpy as np
-
-from .core import Covariate as _LegacyCovariate
-from .core import SignalObj as _LegacySignalObj
-
-
-class Signal(_LegacySignalObj):
-    """Canonical Pythonic signal abstraction for nSTAT."""
-
-    def copy(self) -> "Signal":
-        copied = self.copySignal()
-        return Signal(
-            copied.time,
-            copied.data,
-            copied.name,
-            copied.xlabelval,
-            copied.xunits,
-            copied.yunits,
-            copied.dataLabels,
-        )
-
-    def sub_signal(self, index: int) -> "Signal":
-        """Return one channel as a new signal.
-
-        Uses zero-based indexing in Python, unlike MATLAB's one-based indexing.
-        """
-        if index < 0 or index >= self.dimension:
-            raise IndexError("Signal channel index out of range.")
-        out = self.getSubSignal(index + 1)
-        return Signal(
-            out.time,
-            out.data,
-            out.name,
-            out.xlabelval,
-            out.xunits,
-            out.yunits,
-            out.dataLabels,
-        )
-
-    def window(self, start: float, stop: float) -> "Signal":
-        out = self.getSigInTimeWindow(start, stop)
-        return Signal(
-            out.time,
-            out.data,
-            out.name,
-            out.xlabelval,
-            out.xunits,
-            out.yunits,
-            out.dataLabels,
-        )
-
-    def as_array(self) -> np.ndarray:
-        return np.asarray(self.data, dtype=float)
-
-
-class Covariate(_LegacyCovariate):
-    """Canonical covariate type for model design matrices."""
-
-    def standardize(self) -> "Covariate":
-        data = np.asarray(self.data, dtype=float)
-        mu = np.mean(data, axis=0, keepdims=True)
-        sigma = np.std(data, axis=0, keepdims=True)
-        sigma[sigma == 0.0] = 1.0
-        z = (data - mu) / sigma
-        return Covariate(
-            self.time,
-            z,
-            self.name,
-            self.xlabelval,
-            self.xunits,
-            self.yunits,
-            self.dataLabels,
-        )
-
-    @classmethod
-    def from_values(
-        cls,
-        time: Sequence[float],
-        values: Sequence[float] | Sequence[Sequence[float]] | np.ndarray,
-        *,
-        name: str = "covariate",
-        units: str = "",
-    ) -> "Covariate":
-        return cls(time=time, values=values, name=name, units=units)
diff --git a/python/nstat/simulation.py b/python/nstat/simulation.py
deleted file mode 100644
index 863bd60..0000000
--- a/python/nstat/simulation.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from __future__ import annotations
-
-import numpy as np
-
-from .core import nspikeTrain
-
-
-# Backward-compatible alias used by earlier Python code.
-SpikeTrain = nspikeTrain
-
-
-def simulate_poisson_from_rate(
-    time: np.ndarray,
-    rate_hz: np.ndarray,
-    rng: np.random.Generator | None = None,
-) -> nspikeTrain:
-    t = np.asarray(time, dtype=float).reshape(-1)
-    r = np.asarray(rate_hz, dtype=float).reshape(-1)
-    if t.shape[0] != r.shape[0]:
-        raise ValueError("time and rate_hz length mismatch")
-    if t.shape[0] < 2:
-        return nspikeTrain(np.asarray([], dtype=float))
-
-    if rng is None:
-        rng = np.random.default_rng()
-
-    dt = np.diff(t)
-    dt = np.concatenate([dt, [dt[-1]]])
-    p = 1.0 - np.exp(-np.clip(r, 0.0, np.inf) * dt)
-    p = np.clip(p, 0.0, 1.0)
-    keep = rng.random(t.shape[0]) < p
-    return nspikeTrain(spikeTimes=t[keep])
-
-
-__all__ = ["SpikeTrain", "simulate_poisson_from_rate"]
diff --git a/python/nstat/simulators.py b/python/nstat/simulators.py
deleted file mode 100644
index fb88990..0000000
--- a/python/nstat/simulators.py
+++ /dev/null
@@ -1,73 +0,0 @@
-from __future__ import annotations
-
-from dataclasses import dataclass
-
-import numpy as np
-
-from .spikes import SpikeTrain, SpikeTrainCollection
-
-
-@dataclass
-class PointProcessSimulation:
-    time: np.ndarray
-    rate_hz: np.ndarray
-    spikes: SpikeTrain
-
-
-@dataclass
-class NetworkSimulationResult:
-    time: np.ndarray
-    latent_drive: np.ndarray
-    spikes: SpikeTrainCollection
-
-
-def simulate_point_process(time: np.ndarray, rate_hz: np.ndarray, *, seed: int | None = None) -> PointProcessSimulation:
-    t = np.asarray(time, dtype=float).reshape(-1)
-    r = np.asarray(rate_hz, dtype=float).reshape(-1)
-    if t.shape[0] != r.shape[0]:
-        raise ValueError("time and rate_hz length mismatch")
-    if t.shape[0] < 2:
-        return PointProcessSimulation(t, r, SpikeTrain(np.array([], dtype=float)))
-
-    dt = np.diff(t)
-    dt = np.concatenate([dt, [dt[-1]]])
-    p = 1.0 - np.exp(-np.clip(r, 0.0, np.inf) * dt)
-    p = np.clip(p, 0.0, 1.0)
-
-    rng = np.random.default_rng(seed)
-    keep = rng.random(t.shape[0]) < p
-    return PointProcessSimulation(t, r, SpikeTrain(t[keep]))
-
-
-def simulate_two_neuron_network(
-    duration_s: float = 2.0,
-    dt: float = 0.001,
-    base_rate_hz: float = 8.0,
-    coupling: float = 1.2,
-    seed: int | None = 13,
-) -> NetworkSimulationResult:
-    """Standalone Python replacement for Simulink-style 2-neuron network examples."""
-    if duration_s <= 0 or dt <= 0:
-        raise ValueError("duration_s and dt must be > 0")
-
-    time = np.arange(0.0, duration_s + dt, dt)
-    drive = np.sin(2.0 * np.pi * 2.0 * time)
-
-    rng = np.random.default_rng(seed)
-    spikes = np.zeros((time.shape[0], 2), dtype=float)
-    for i in range(1, time.shape[0]):
-        prev = spikes[i - 1]
-        eta1 = np.log(base_rate_hz * dt) + 1.5 * drive[i] + coupling * (prev[1] - 0.1)
-        eta2 = np.log(base_rate_hz * dt) - 1.5 * drive[i] + coupling * (prev[0] - 0.1)
-        p1 = 1.0 / (1.0 + np.exp(-np.clip(eta1, -20.0, 20.0)))
-        p2 = 1.0 / (1.0 + np.exp(-np.clip(eta2, -20.0, 20.0)))
-        spikes[i, 0] = 1.0 if rng.random() < p1 else 0.0
-        spikes[i, 1] = 1.0 if rng.random() < p2 else 0.0
-
-    t1 = time[spikes[:, 0] > 0.5]
-    t2 = time[spikes[:, 1] > 0.5]
-    coll = SpikeTrainCollection([SpikeTrain(t1, name="neuron_1"), SpikeTrain(t2, name="neuron_2")])
-    return NetworkSimulationResult(time=time, latent_drive=drive, spikes=coll)
-
-
-__all__ = ["PointProcessSimulation", "NetworkSimulationResult", "simulate_point_process", "simulate_two_neuron_network"]
diff --git a/python/nstat/spikes.py b/python/nstat/spikes.py
deleted file mode 100644
index 1670c0f..0000000
--- a/python/nstat/spikes.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from __future__ import annotations
-
-from typing import Sequence
-
-import numpy as np
-
-from .core import nspikeTrain as _LegacySpikeTrain
-from .simulation import simulate_poisson_from_rate
-from .trial import nstColl as _LegacySpikeTrainCollection
-
-
-class SpikeTrain(_LegacySpikeTrain):
-    """Canonical spike train type for point-process analyses."""
-
-    def inter_spike_intervals(self) -> np.ndarray:
-        return self.getISIs()
-
-    def to_counts(self, bin_edges: Sequence[float]) -> np.ndarray:
-        return self.to_binned_counts(bin_edges)
-
-
-class SpikeTrainCollection(_LegacySpikeTrainCollection):
-    """Collection of aligned spike trains for ensemble analyses."""
-
-    def __iter__(self):
-        for i in range(1, self.numSpikeTrains + 1):
-            yield self.getNST(i)
-
-    def to_matrix(self, bin_edges: Sequence[float]) -> np.ndarray:
-        edges = np.asarray(bin_edges, dtype=float).reshape(-1)
-        if edges.ndim != 1 or edges.size < 2:
-            raise ValueError("bin_edges must be a 1D array with at least two entries")
-        rows = [np.asarray(train.to_binned_counts(edges), dtype=float) for train in self]
-        return np.vstack(rows)
-
-
-__all__ = ["SpikeTrain", "SpikeTrainCollection", "simulate_poisson_from_rate"]
diff --git a/python/nstat/trial.py b/python/nstat/trial.py
deleted file mode 100644
index 8b33636..0000000
--- a/python/nstat/trial.py
+++ /dev/null
@@ -1,266 +0,0 @@
-from __future__ import annotations
-
-from dataclasses import dataclass
-from typing import Iterable, Sequence
-
-import numpy as np
-
-from .core import nspikeTrain
-from .events import Events
-from .signal import Covariate
-
-
-class CovariateCollection:
-    def __init__(self, covariates: Sequence[Covariate] | None = None) -> None:
-        self.covariates = list(covariates or [])
-
-    @property
-    def names(self) -> list[str]:
-        return [cov.name for cov in self.covariates]
-
-    def add(self, covariate: Covariate) -> None:
-        self.covariates.append(covariate)
-
-    def addCovariate(self, covariate: Covariate) -> None:
-        self.add(covariate)
-
-    def addToColl(self, covariate: Covariate) -> None:
-        self.add(covariate)
-
-    def get(self, name: str) -> Covariate:
-        for cov in self.covariates:
-            if cov.name == name:
-                return cov
-        raise KeyError(f"Covariate '{name}' not found")
-
-    def getCov(self, name: str) -> Covariate:
-        return self.get(name)
-
-    def dataToMatrix(self, names: Sequence[str] | None = None) -> tuple[np.ndarray, np.ndarray, list[str]]:
-        if not self.covariates:
-            raise ValueError("CovariateCollection is empty")
-        selected = self.covariates
-        if names is not None:
-            keep = set(names)
-            selected = [cov for cov in self.covariates if cov.name in keep]
-            if not selected:
-                raise ValueError("No covariates matched requested names")
-
-        base_time = selected[0].time
-        x_parts: list[np.ndarray] = []
-        labels: list[str] = []
-        for cov in selected:
-            if cov.time.shape != base_time.shape or np.max(np.abs(cov.time - base_time)) > 1e-9:
-                raise ValueError("All covariates must share the same time grid")
-            x_parts.append(np.asarray(cov.data, dtype=float))
-            labels.extend(cov.dataLabels)
-        return base_time, np.hstack(x_parts), labels
-
-
-class SpikeTrainCollection:
-    def __init__(self, trains: Sequence[nspikeTrain] | nspikeTrain) -> None:
-        if isinstance(trains, nspikeTrain):
-            trains = [trains]
-        self._trains = list(trains)
-        if len(self._trains) == 0:
-            raise ValueError("SpikeTrainCollection requires at least one spike train")
-
-        self.minTime = float(min(s.minTime for s in self._trains))
-        self.maxTime = float(max(s.maxTime for s in self._trains))
-        rates = [s.sampleRate for s in self._trains if s.sampleRate > 0]
-        self.sampleRate = float(np.median(rates)) if rates else 1000.0
-
-    @property
-    def num_spike_trains(self) -> int:
-        return len(self._trains)
-
-    @property
-    def numSpikeTrains(self) -> int:
-        return self.num_spike_trains
-
-    def __iter__(self):
-        for tr in self._trains:
-            yield tr
-
-    def get_nst(self, idx: int) -> nspikeTrain:
-        if idx < 0 or idx >= len(self._trains):
-            raise IndexError("SpikeTrainCollection index out of bounds (0-based indexing).")
-        return self._trains[idx]
-
-    def getNST(self, idx: int) -> nspikeTrain:
-        if idx < 1 or idx > len(self._trains):
-            raise IndexError("nstColl index out of bounds (1-based indexing).")
-        return self._trains[idx - 1]
-
-    def setMinTime(self, value: float) -> None:
-        self.minTime = float(value)
-
-    def setMaxTime(self, value: float) -> None:
-        self.maxTime = float(value)
-
-    def dataToMatrix(self, bin_edges: Sequence[float]) -> np.ndarray:
-        edges = np.asarray(bin_edges, dtype=float).reshape(-1)
-        if edges.ndim != 1 or edges.size < 2:
-            raise ValueError("bin_edges must be a 1D array with at least two points")
-        rows = [np.asarray(spk.to_binned_counts(edges), dtype=float) for spk in self._trains]
-        return np.vstack(rows)
-
-    def plot(self, *_, **__) -> None:
-        return None
-
-    def psth(self, binwidth: float) -> Covariate:
-        if binwidth <= 0:
-            raise ValueError("binwidth must be > 0")
-        min_time = float(self.minTime)
-        max_time = float(self.maxTime)
-        if max_time < min_time:
-            raise ValueError("maxTime must be >= minTime")
-
-        # Match MATLAB nstColl.psth edge construction:
-        #   windowTimes = minTime:binwidth:maxTime;
-        #   if ~any(windowTimes==maxTime), append maxTime
-        span = max_time - min_time
-        n_full = int(np.floor((span / binwidth) + 1e-12))
-        window_times = min_time + np.arange(n_full + 1, dtype=float) * float(binwidth)
-        if window_times.size == 0:
-            window_times = np.array([min_time, max_time], dtype=float)
-        if window_times[-1] < max_time - 1e-12:
-            window_times = np.append(window_times, max_time)
-        elif window_times[-1] > max_time + 1e-12:
-            window_times[-1] = max_time
-        if window_times.size < 2:
-            window_times = np.array([min_time, max_time], dtype=float)
-            if window_times[1] <= window_times[0]:
-                window_times[1] = window_times[0] + float(binwidth)
-
-        # MATLAB histc-like counting produces one extra terminal bin for x==max;
-        # nstColl.psth discards that final bin before normalizing.
-        psth_hist = np.zeros(window_times.size, dtype=float)
-        for spk in self._trains:
-            spikes = np.asarray(spk.spikeTimes, dtype=float).reshape(-1)
-            if spikes.size == 0:
-                continue
-            valid = np.isfinite(spikes) & (spikes >= window_times[0]) & (spikes <= window_times[-1])
-            if not np.any(valid):
-                continue
-            idx = np.searchsorted(window_times, spikes[valid], side="right") - 1
-            idx = np.clip(idx, 0, window_times.size - 1)
-            psth_hist += np.bincount(idx, minlength=window_times.size).astype(float)
-
-        rate = psth_hist[:-1] / binwidth / float(len(self._trains))
-        centers = (window_times[1:] + window_times[:-1]) * 0.5
-        return Covariate(centers, rate, "PSTH", "time", "s", "spikes/sec", ["psth"])
-
-    def psthGLM(self, binwidth: float):
-        psth_signal = self.psth(binwidth)
-        return psth_signal, None, None
-
-
-@dataclass
-class TrialConfig:
-    covMask: Sequence[Sequence[str]] | Sequence[str]
-    sampleRate: float
-    history: object | None = None
-    ensCovHist: object | None = None
-    covLag: object | None = None
-    name: str = ""
-
-    def setName(self, name: str) -> None:
-        self.name = str(name)
-
-    @property
-    def covariate_names(self) -> list[str]:
-        if not self.covMask:
-            return []
-        names: list[str] = []
-        for item in self.covMask:
-            if isinstance(item, str):
-                names.append(item)
-            else:
-                names.extend([str(v) for v in item])
-        return names
-
-
-class ConfigCollection:
-    def __init__(self, configs: Sequence[TrialConfig] | None = None) -> None:
-        self.configs: list[TrialConfig] = list(configs or [])
-
-    @property
-    def numConfigs(self) -> int:
-        return len(self.configs)
-
-    @property
-    def configArray(self) -> list[TrialConfig]:
-        return self.configs
-
-    def add_config(self, cfg: TrialConfig) -> None:
-        self.configs.append(cfg)
-
-    def addConfig(self, cfg: TrialConfig) -> None:
-        self.add_config(cfg)
-
-    def get_config(self, idx: int) -> TrialConfig:
-        if idx < 0 or idx >= len(self.configs):
-            raise IndexError("ConfigCollection index out of bounds (0-based indexing).")
-        return self.configs[idx]
-
-    def getConfig(self, idx: int) -> TrialConfig:
-        return self.configs[idx - 1]
-
-    def getConfigNames(self, index: Sequence[int] | None = None) -> list[str]:
-        if index is None:
-            index = list(range(1, self.numConfigs + 1))
-        out: list[str] = []
-        for i in index:
-            cfg = self.configs[i - 1]
-            out.append(cfg.name if cfg.name else f"Fit {i}")
-        return out
-
-
-class Trial:
-    def __init__(
-        self,
-        spike_collection: SpikeTrainCollection | None = None,
-        covariate_collection: CovariateCollection | None = None,
-        events: Events | None = None,
-        *,
-        spikeColl: SpikeTrainCollection | None = None,
-        covarColl: CovariateCollection | None = None,
-    ) -> None:
-        self.spike_collection = spike_collection if spike_collection is not None else spikeColl
-        self.covariate_collection = covariate_collection if covariate_collection is not None else covarColl
-        if self.spike_collection is None or self.covariate_collection is None:
-            raise ValueError("Trial requires both spike_collection and covariate_collection")
-        self.events = events
-
-    @property
-    def spikeColl(self) -> SpikeTrainCollection:
-        return self.spike_collection
-
-    @property
-    def covarColl(self) -> CovariateCollection:
-        return self.covariate_collection
-
-    def get_covariate_matrix(self, selected_covariates: Sequence[str] | None = None) -> tuple[np.ndarray, np.ndarray, list[str]]:
-        return self.covariate_collection.dataToMatrix(selected_covariates)
-
-    def getSpikeVector(self, bin_edges: Sequence[float], neuron_index: int = 1) -> np.ndarray:
-        return self.spike_collection.getNST(neuron_index).to_binned_counts(bin_edges)
-
-
-# Backward-compatible MATLAB-style aliases.
-CovColl = CovariateCollection
-nstColl = SpikeTrainCollection
-ConfigColl = ConfigCollection
-
-
-__all__ = [
-    "CovariateCollection",
-    "SpikeTrainCollection",
-    "TrialConfig",
-    "ConfigCollection",
-    "Trial",
-    "CovColl",
-    "nstColl",
-    "ConfigColl",
-]
diff --git a/python/pyproject.toml b/python/pyproject.toml
deleted file mode 100644
index 9356a4c..0000000
--- a/python/pyproject.toml
+++ /dev/null
@@ -1,35 +0,0 @@
-[build-system]
-requires = ["setuptools>=68", "wheel"]
-build-backend = "setuptools.build_meta"
-
-[project]
-name = "nstat-toolbox"
-version = "0.1.0"
-description = "Python port of the nSTAT toolbox"
-readme = "README.md"
-requires-python = ">=3.10"
-authors = [
-  { name = "Cajigas Lab" }
-]
-dependencies = [
-  "numpy>=1.24",
-  "scipy>=1.10",
-  "matplotlib>=3.7"
-]
-
-[project.optional-dependencies]
-dev = [
-  "pytest>=8.0",
-  "nbformat>=5.9",
-  "sphinx>=7.0",
-]
-
-[tool.setuptools.packages.find]
-where = ["."]
-include = ["nstat*"]
-
-[tool.setuptools.package-data]
-"nstat.data" = ["manifest.json"]
-
-[project.scripts]
-nstat-paper-examples = "nstat.paper_examples:main"
diff --git a/python/reports/examples_notebook_verification.json b/python/reports/examples_notebook_verification.json
deleted file mode 100644
index eb50065..0000000
--- a/python/reports/examples_notebook_verification.json
+++ /dev/null
@@ -1,458 +0,0 @@
-{
-  "summary": {
-    "total_examples": 25,
-    "python_modules_ok": 25,
-    "notebooks_ok": 25,
-    "topic_alignment_ok": 25
-  },
-  "rows": [
-    {
-      "example": "SignalObjExamples",
-      "title": "Using the SignalObj Class",
-      "matlab_target": "SignalObjExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "dimension",
-        "parity",
-        "sample_rate",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "CovariateExamples",
-      "title": "Using the Covariate Class",
-      "matlab_target": "CovariateExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "mean",
-        "parity",
-        "std",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "CovCollExamples",
-      "title": "Using the CovColl Class",
-      "matlab_target": "CovCollExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "labels",
-        "matrix_shape",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "nSpikeTrainExamples",
-      "title": "Using the nSpikeTrain Class",
-      "matlab_target": "nSpikeTrainExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "n_spikes",
-        "parity",
-        "rate_hz",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "nstCollExamples",
-      "title": "Using the nstColl Class",
-      "matlab_target": "nstCollExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "num_trains",
-        "parity",
-        "psth_points",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "EventsExamples",
-      "title": "Using the Events Class",
-      "matlab_target": "EventsExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "n_events",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "HistoryExamples",
-      "title": "Using the History Class",
-      "matlab_target": "HistoryExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "design_shape",
-        "lags",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "TrialExamples",
-      "title": "Using the Trial Class",
-      "matlab_target": "TrialExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "covariate_rows",
-        "neurons",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "TrialConfigExamples",
-      "title": "Using the TrialConfig Class",
-      "matlab_target": "TrialConfigExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "covariates",
-        "parity",
-        "sample_rate",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "ConfigCollExamples",
-      "title": "Using the ConfigColl Class",
-      "matlab_target": "ConfigCollExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "names",
-        "num_configs",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "AnalysisExamples",
-      "title": "Using the Analysis Class",
-      "matlab_target": "AnalysisExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "first_aic",
-        "num_results",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "FitResultExamples",
-      "title": "Using the FitResult Class",
-      "matlab_target": "FitResultExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "bic",
-        "coeffs",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "FitResSummaryExamples",
-      "title": "Using the FitResSummary Class",
-      "matlab_target": "FitResSummaryExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "mean_aic",
-        "mean_bic",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "PPThinning",
-      "title": "Point Process Simulation via Thinning",
-      "matlab_target": "PPThinning.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "num_realizations",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "PSTHEstimation",
-      "title": "Example Data Analysis - Simulated Data - Computing a Peri-Stimulus Time Histogram (PSTH)",
-      "matlab_target": "PSTHEstimation.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "num_realizations",
-        "parity",
-        "peak_rate",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "ValidationDataSet",
-      "title": "Example Data Analysis - Simulated Constant (Piecewise Constant) Rate Poisson",
-      "matlab_target": "ValidationDataSet.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "num_trials",
-        "parity",
-        "psth_mean_hz",
-        "psth_peak_hz",
-        "topic",
-        "total_spikes"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "mEPSCAnalysis",
-      "title": "Example Data Analysis - Miniature Excitatory Post-Synaptic Currents (mEPSCs)",
-      "matlab_target": "mEPSCAnalysis.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "const_condition_spikes",
-        "const_model_aic",
-        "const_model_bic",
-        "decreasing_condition_spikes",
-        "dt_seconds",
-        "parity",
-        "piecewise_history_model_aic",
-        "piecewise_history_model_bic",
-        "piecewise_model_aic",
-        "piecewise_model_bic",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "PPSimExample",
-      "title": "Example Data Analysis - Simulated Explicit Stimulus and History",
-      "matlab_target": "PPSimExample.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "model1_aic",
-        "model1_bic",
-        "model2_aic",
-        "model2_bic",
-        "model3_aic",
-        "model3_bic",
-        "n_samples",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "ExplicitStimulusWhiskerData",
-      "title": "Example Data Analysis - Explicit Stimulus",
-      "matlab_target": "ExplicitStimulusWhiskerData.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "model1_aic",
-        "model1_bic",
-        "model2_aic",
-        "model2_bic",
-        "model3_aic",
-        "model3_bic",
-        "n_samples",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "HippocampalPlaceCellExample",
-      "title": "Example Data Analysis - Hippocampal Place Cell Receptive Field Estimation",
-      "matlab_target": "HippocampalPlaceCellExample.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "mean_delta_aic_gaussian_minus_zernike",
-        "mean_delta_bic_gaussian_minus_zernike",
-        "num_cells_fit",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "DecodingExample",
-      "title": "Example Data Analysis - Decoding Univariate Simulated Stimuli (No History Effect)",
-      "matlab_target": "DecodingExample.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "decode_rmse",
-        "num_cells",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "DecodingExampleWithHist",
-      "title": "Example Data Analysis - Decoding Univariate Simulated Stimuli with and without History Effect",
-      "matlab_target": "DecodingExampleWithHist.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "decode_rmse_x",
-        "decode_rmse_y",
-        "num_cells",
-        "num_samples",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "StimulusDecode2D",
-      "title": "Example Data Analysis - Decoding Bivariate Simulated Stimuli",
-      "matlab_target": "StimulusDecode2D.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "decode_rmse_x",
-        "decode_rmse_y",
-        "num_cells",
-        "num_samples",
-        "parity",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "NetworkTutorial",
-      "title": "Example Data Analysis - Two Neuron Network Simulation and Estimation of Ensemble Effect",
-      "matlab_target": "NetworkTutorial.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "neuron_count",
-        "parity",
-        "psth_peak",
-        "samples",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    },
-    {
-      "example": "nSTATPaperExamples",
-      "title": "nSTAT Paper Examples",
-      "matlab_target": "nSTATPaperExamples.html",
-      "python_module_ok": true,
-      "python_module_error": "",
-      "python_output_keys": [
-        "experiments",
-        "parity",
-        "summary",
-        "topic"
-      ],
-      "notebook_ok": true,
-      "notebook_error": "",
-      "notebook_code_cells": 3,
-      "topic_alignment_ok": true
-    }
-  ]
-}
\ No newline at end of file
diff --git a/python/reports/implemented_method_coverage.json b/python/reports/implemented_method_coverage.json
deleted file mode 100644
index e1c36fb..0000000
--- a/python/reports/implemented_method_coverage.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-  "summary": {
-    "implemented_method_count": 34,
-    "smoke_covered_count": 34,
-    "missing_in_smoke_count": 0,
-    "extra_in_smoke_count": 0,
-    "implemented_class_count": 13,
-    "docs_class_covered_count": 13,
-    "missing_doc_class_count": 0
-  },
-  "missing_in_smoke": [],
-  "extra_in_smoke": [],
-  "doc_class_coverage": {
-    "Analysis": true,
-    "CIF": true,
-    "ConfidenceInterval": true,
-    "ConfigColl": true,
-    "CovColl": true,
-    "DecodingAlgorithms": true,
-    "Events": true,
-    "FitResSummary": true,
-    "FitResult": true,
-    "History": true,
-    "Trial": true,
-    "TrialConfig": true,
-    "nstColl": true
-  },
-  "missing_doc_classes": [],
-  "pass": true
-}
\ No newline at end of file
diff --git a/python/reports/matlab_smoke_input.json b/python/reports/matlab_smoke_input.json
deleted file mode 100644
index ed8f7ec..0000000
--- a/python/reports/matlab_smoke_input.json
+++ /dev/null
@@ -1,172 +0,0 @@
-[
-  {
-    "source": "helpfiles/AnalysisExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/AnalysisExamples2.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/ClassDefinitions.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/ConfigCollExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/CovCollExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/CovariateExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/DecodingExample.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/DecodingExampleWithHist.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/EventsExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/Examples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/ExplicitStimulusWhiskerData.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/FitResSummaryExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/FitResultExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/HippocampalPlaceCellExample.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/HistoryExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/HybridFilterExample.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/NetworkTutorial.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/NeuralSpikeAnalysis_top.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/PPSimExample.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/PPThinning.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/PSTHEstimation.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/SignalObjExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/StimulusDecode2D.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/TrialConfigExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/TrialExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/ValidationDataSet.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/mEPSCAnalysis.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/nSTATPaperExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/nSpikeTrainExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/nstCollExamples.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "helpfiles/temp.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD_demo.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "nSTAT_Install.m",
-    "kind": "script",
-    "function_name": ""
-  },
-  {
-    "source": "python/reports/matlab_smoke_runner.m",
-    "kind": "script",
-    "function_name": ""
-  }
-]
\ No newline at end of file
diff --git a/python/reports/matlab_smoke_runner.m b/python/reports/matlab_smoke_runner.m
deleted file mode 100644
index 643ddbf..0000000
--- a/python/reports/matlab_smoke_runner.m
+++ /dev/null
@@ -1,42 +0,0 @@
-
-repo = '/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local';
-inFile = '/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local/python/reports/matlab_smoke_input.json';
-outFile = '/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local/python/reports/matlab_smoke_output.json';
-set(0,'DefaultFigureVisible','off');
-cfg = jsondecode(fileread(inFile));
-n = numel(cfg);
-res = repmat(struct('source','','ok',false,'message',''), n, 1);
-for i = 1:n
-    src = '';
-    kind = '';
-    fn = '';
-    if isfield(cfg(i), 'source')
-        src = char(cfg(i).source);
-    end
-    if isfield(cfg(i), 'kind')
-        kind = char(cfg(i).kind);
-    end
-    if isfield(cfg(i), 'function_name')
-        fn = char(cfg(i).function_name);
-    end
-    try
-        restoredefaultpath;
-        cd(repo);
-        addpath(genpath(repo),'-begin');
-        if strcmp(kind,'script')
-            run(fullfile(repo, src));
-        else
-            feval(fn);
-        end
-        res(i).ok = true;
-        res(i).message = 'ok';
-    catch ME
-        res(i).ok = false;
-        res(i).message = [ME.identifier ' | ' ME.message];
-    end
-    res(i).source = src;
-end
-fid = fopen(outFile,'w');
-fprintf(fid,'%s',jsonencode(res));
-fclose(fid);
-exit(0);
diff --git a/python/reports/method_parity_matrix.json b/python/reports/method_parity_matrix.json
deleted file mode 100644
index 81a08b6..0000000
--- a/python/reports/method_parity_matrix.json
+++ /dev/null
@@ -1,2897 +0,0 @@
-{
-  "summary": {
-    "implemented": 34,
-    "planned": 428,
-    "intentionally_omitted": 39,
-    "total": 501
-  },
-  "implemented_methods": [
-    {
-      "matlab_class": "Analysis",
-      "matlab_method": "RunAnalysisForAllNeurons"
-    },
-    {
-      "matlab_class": "Analysis",
-      "matlab_method": "RunAnalysisForNeuron"
-    },
-    {
-      "matlab_class": "CIF",
-      "matlab_method": "simulateCIFByThinningFromLambda"
-    },
-    {
-      "matlab_class": "ConfidenceInterval",
-      "matlab_method": "setColor"
-    },
-    {
-      "matlab_class": "ConfigColl",
-      "matlab_method": "addConfig"
-    },
-    {
-      "matlab_class": "ConfigColl",
-      "matlab_method": "getConfig"
-    },
-    {
-      "matlab_class": "ConfigColl",
-      "matlab_method": "getConfigNames"
-    },
-    {
-      "matlab_class": "CovColl",
-      "matlab_method": "addToColl"
-    },
-    {
-      "matlab_class": "CovColl",
-      "matlab_method": "dataToMatrix"
-    },
-    {
-      "matlab_class": "CovColl",
-      "matlab_method": "getCov"
-    },
-    {
-      "matlab_class": "DecodingAlgorithms",
-      "matlab_method": "PPDecodeFilter"
-    },
-    {
-      "matlab_class": "DecodingAlgorithms",
-      "matlab_method": "PPDecodeFilterLinear"
-    },
-    {
-      "matlab_class": "DecodingAlgorithms",
-      "matlab_method": "kalman_filter"
-    },
-    {
-      "matlab_class": "Events",
-      "matlab_method": "fromStructure"
-    },
-    {
-      "matlab_class": "Events",
-      "matlab_method": "plot"
-    },
-    {
-      "matlab_class": "Events",
-      "matlab_method": "toStructure"
-    },
-    {
-      "matlab_class": "FitResSummary",
-      "matlab_method": "getDiffAIC"
-    },
-    {
-      "matlab_class": "FitResSummary",
-      "matlab_method": "getDiffBIC"
-    },
-    {
-      "matlab_class": "FitResSummary",
-      "matlab_method": "plotSummary"
-    },
-    {
-      "matlab_class": "FitResult",
-      "matlab_method": "KSPlot"
-    },
-    {
-      "matlab_class": "FitResult",
-      "matlab_method": "fromStructure"
-    },
-    {
-      "matlab_class": "FitResult",
-      "matlab_method": "getCoeffs"
-    },
-    {
-      "matlab_class": "FitResult",
-      "matlab_method": "getHistCoeffs"
-    },
-    {
-      "matlab_class": "FitResult",
-      "matlab_method": "mergeResults"
-    },
-    {
-      "matlab_class": "FitResult",
-      "matlab_method": "plotCoeffs"
-    },
-    {
-      "matlab_class": "FitResult",
-      "matlab_method": "plotInvGausTrans"
-    },
-    {
-      "matlab_class": "FitResult",
-      "matlab_method": "plotResidual"
-    },
-    {
-      "matlab_class": "FitResult",
-      "matlab_method": "plotResults"
-    },
-    {
-      "matlab_class": "FitResult",
-      "matlab_method": "plotSeqCorr"
-    },
-    {
-      "matlab_class": "FitResult",
-      "matlab_method": "toStructure"
-    },
-    {
-      "matlab_class": "History",
-      "matlab_method": "computeHistory"
-    },
-    {
-      "matlab_class": "Trial",
-      "matlab_method": "getSpikeVector"
-    },
-    {
-      "matlab_class": "TrialConfig",
-      "matlab_method": "setName"
-    },
-    {
-      "matlab_class": "nstColl",
-      "matlab_method": "dataToMatrix"
-    }
-  ],
-  "classes": [
-    {
-      "matlab_class": "SignalObj",
-      "matlab_source": "SignalObj.m",
-      "python_target": "python/nstat/signal.py",
-      "python_class": "Signal",
-      "python_methods": [
-        "as_array",
-        "copy",
-        "sub_signal",
-        "window"
-      ],
-      "methods": [
-        {
-          "matlab_method": "MTMspectrum",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "SignalObj",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "abs",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "alignTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "alignToMax",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "areDataLabelsEmpty",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "autocorrelation",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "cell2str",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "clearPlotProps",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "convertNamesToIndices",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "convertSigStructureToStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "convertSimpleStructureToSigStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "copySignal",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "crosscorrelation",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "ctranspose",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "dataToMatrix",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "dataToStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "derivative",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "derivativeAt",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "filter",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "filtfilt",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findGlobalPeak",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findIndFromDataMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findMaxima",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findMinima",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findNearestTimeIndex",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findNearestTimeIndices",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findPeaks",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getAvailableColor",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getData",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getIndexFromLabel",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getIndicesFromLabels",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getOrigDataSig",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getOriginalData",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getPlotProps",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSigInTimeWindow",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSubSignal",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSubSignalFromInd",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSubSignalFromNames",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSubSignalsWithinNStd",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getValueAt",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "integral",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isLabelPresent",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isMaskSet",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "ldivide",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "log",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "makeCompatible",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "max",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mean",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "median",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "merge",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "min",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "minus",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mode",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mtimes",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "normWindowedSignal",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "periodogram",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "plot",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotAllVariability",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotPropsSet",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotVariability",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plus",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "power",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "rdivide",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resample",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resampleMe",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resetMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "restoreToOriginal",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setDataLabels",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setDataMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMaskByInd",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMaskByLabels",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMaxTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMinTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setName",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setPlotProps",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setSampleRate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setUnits",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setXUnits",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setXlabel",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setYLabel",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setYUnits",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setupPlots",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "shift",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "shiftMe",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "signalFromStruct",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "spectrogram",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "sqrt",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "std",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "times",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "transpose",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "uminus",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "uplus",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "windowedSignal",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "xcorr",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "xcov",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    },
-    {
-      "matlab_class": "Covariate",
-      "matlab_source": "Covariate.m",
-      "python_target": "python/nstat/signal.py",
-      "python_class": "Covariate",
-      "python_methods": [
-        "from_values",
-        "standardize"
-      ],
-      "methods": [
-        {
-          "matlab_method": "Covariate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeMeanPlusCI",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "copySignal",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "dataToStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "filtfilt",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "fromStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSigRep",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSubSignal",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isConfIntervalSet",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "minus",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "plot",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plus",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setConfInterval",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "toStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    },
-    {
-      "matlab_class": "nspikeTrain",
-      "matlab_source": "nspikeTrain.m",
-      "python_target": "python/nstat/spikes.py",
-      "python_class": "SpikeTrain",
-      "python_methods": [
-        "inter_spike_intervals",
-        "to_counts"
-      ],
-      "methods": [
-        {
-          "matlab_method": "clearSigRep",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeRate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeStatistics",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "fromStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getFieldVal",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getISIs",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getLStatistic",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getMaxBinSizeBinary",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getMinISI",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSigRep",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSpikeTimes",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isSigRepBinary",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "nspikeTrain",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "nstCopy",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "partitionNST",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "plot",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotExponentialFit",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotISIHistogram",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotISISpectrumFunction",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotJointISIHistogram",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotProbPlot",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "resample",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "restoreToOriginal",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMER",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMaxTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMinTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setName",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setSigRep",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "toStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    },
-    {
-      "matlab_class": "nstColl",
-      "matlab_source": "nstColl.m",
-      "python_target": "python/nstat/spikes.py",
-      "python_class": "SpikeTrainCollection",
-      "python_methods": [
-        "__iter__",
-        "to_matrix"
-      ],
-      "methods": [
-        {
-          "matlab_method": "BinarySigRep",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "addNeuronNamesToEnsCovColl",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "addSingleSpikeToColl",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "addToColl",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "areNeighborsSet",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "dataToMatrix",
-          "status": "implemented",
-          "rationale": "Implemented via Pythonic rename: to_matrix."
-        },
-        {
-          "matlab_method": "enforceSampleRate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "ensureConsistancy",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "estimateVarianceAcrossTrials",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findMaxSampleRate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "fromStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "generateUnitImpulseBasis",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getEnsembleNeuronCovariates",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getFieldVal",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getFirstSpikeTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getISIs",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getIndFromMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getIndFromMaskMinusOne",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getLastSpikeTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getMaxBinSizeBinary",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getMinISIs",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getNST",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getNSTFromName",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getNSTIndicesFromName",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getNSTnameFromInd",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getNSTnames",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getNeighbors",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSpikeTimes",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getUniqueNSTnames",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isNeuronMaskSet",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isSigRepBinary",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "merge",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "nstColl",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "plot",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotExponentialFit",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotISIHistogram",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "psth",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "psthBars",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "psthGLM",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resample",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resetMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "restoreToOriginal",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMaxTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMinTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setNeighbors",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setNeuronMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setNeuronMaskFromInd",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "shiftTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "ssglm",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "toSpikeTrain",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "toStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "updateTimes",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    },
-    {
-      "matlab_class": "CovColl",
-      "matlab_source": "CovColl.m",
-      "python_target": "python/nstat/trial.py",
-      "python_class": "CovariateCollection",
-      "python_methods": [
-        "__init__",
-        "add",
-        "addCovariate",
-        "addToColl",
-        "dataToMatrix",
-        "get",
-        "getCov",
-        "names"
-      ],
-      "methods": [
-        {
-          "matlab_method": "CovColl",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "addCovCellToColl",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "addCovCollection",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "addSingleCovToColl",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "addToColl",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "containsChars",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "copy",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "covIndFromSelector",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "dataToMatrix",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "dataToMatrixFromNames",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "dataToMatrixFromSel",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "dataToStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "enforceSampleRate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findMaxTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findMinTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "flattenCovMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "fromStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "generateRemainingIndex",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "generateSelectorCell",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getAllCovLabels",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getCov",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "getCovDataMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getCovDimension",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getCovIndFromName",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getCovIndicesFromNames",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getCovLabelsFromMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getCovMaskFromSelector",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSelectorFromMasks",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isCovMaskSet",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isCovPresent",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isaSelectorCell",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "maskAwayAllExcept",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "maskAwayCov",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "maskAwayOnlyCov",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "nActCovar",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "numActCov",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "parseDataSelectorArray",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "plot",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "removeCovariate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "removeFromColl",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "removeFromCollByIndices",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resample",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resetCovShift",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resetMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "restoreToOriginal",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "restrictToTimeWindow",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setCovShift",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMasksFromSelector",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMaxTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMinTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setSampleRate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "sumDimensions",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "toStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "updateTimes",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    },
-    {
-      "matlab_class": "TrialConfig",
-      "matlab_source": "TrialConfig.m",
-      "python_target": "python/nstat/trial.py",
-      "python_class": "TrialConfig",
-      "python_methods": [
-        "covariate_names",
-        "setName"
-      ],
-      "methods": [
-        {
-          "matlab_method": "TrialConfig",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "fromStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getName",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setConfig",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setName",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "toStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    },
-    {
-      "matlab_class": "ConfigColl",
-      "matlab_source": "ConfigColl.m",
-      "python_target": "python/nstat/trial.py",
-      "python_class": "ConfigCollection",
-      "python_methods": [
-        "__init__",
-        "addConfig",
-        "add_config",
-        "configArray",
-        "getConfig",
-        "getConfigNames",
-        "get_config",
-        "numConfigs"
-      ],
-      "methods": [
-        {
-          "matlab_method": "ConfigColl",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "addConfig",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "fromStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getConfig",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "getConfigNames",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "getSubsetConfigs",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setConfig",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setConfigNames",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "toStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    },
-    {
-      "matlab_class": "Trial",
-      "matlab_source": "Trial.m",
-      "python_target": "python/nstat/trial.py",
-      "python_class": "Trial",
-      "python_methods": [
-        "__init__",
-        "covarColl",
-        "getSpikeVector",
-        "get_covariate_matrix",
-        "spikeColl"
-      ],
-      "methods": [
-        {
-          "matlab_method": "Trial",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "addCov",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findMaxSampleRate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findMaxTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findMinSampleRate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "findMinTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "flattenCovMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "flattenMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "fromStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getAllCovLabels",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getAllLabels",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getCov",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getCovLabelsFromMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getCovSelectorFromMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getDesignMatrix",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getEnsCovLabels",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getEnsCovLabelsFromMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getEnsCovMatrix",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getEnsembleNeuronCovariates",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getEvents",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getHistForNeurons",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getHistLabels",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getHistMatrices",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getLabelsFromMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getNeuron",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getNeuronIndFromMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getNeuronIndFromName",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getNeuronNames",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getNeuronNeighbors",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getNumHist",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getNumUniqueNeurons",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSpikeVector",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "getTrialPartition",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getUniqueNeuronNames",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isCovMaskSet",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isEnsCovHistSet",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isHistSet",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isMaskSet",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isNeuronMaskSet",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isSampleRateConsistent",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "makeConsistentSampleRate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "makeConsistentTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "plot",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotCovariates",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotRaster",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "removeCov",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resample",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resampleEnsColl",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resetCovMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resetEnsCovMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resetHistory",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resetNeuronMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "restoreToOriginal",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setCovMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setEnsCovHist",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setEnsCovMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setHistory",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMaxTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setMinTime",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setNeighbors",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setNeuronMask",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setSampleRate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setTrialEvents",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setTrialPartition",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setTrialTimesFor",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "shiftCovariates",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "toStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "updateTimePartitions",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    },
-    {
-      "matlab_class": "History",
-      "matlab_source": "History.m",
-      "python_target": "python/nstat/history.py",
-      "python_class": "HistoryBasis",
-      "python_methods": [
-        "__init__",
-        "computeHistory",
-        "compute_history",
-        "design_matrix"
-      ],
-      "methods": [
-        {
-          "matlab_method": "History",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeHistory",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "computeNSTHistoryWindow",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "fromStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "plot",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "setWindow",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "toFilter",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "toStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    },
-    {
-      "matlab_class": "Events",
-      "matlab_source": "Events.m",
-      "python_target": "python/nstat/events.py",
-      "python_class": "Events",
-      "python_methods": [
-        "__init__",
-        "fromStructure",
-        "plot",
-        "toStructure"
-      ],
-      "methods": [
-        {
-          "matlab_method": "Events",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "dsxy2figxy",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "fromStructure",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "plot",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "toStructure",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        }
-      ]
-    },
-    {
-      "matlab_class": "ConfidenceInterval",
-      "matlab_source": "ConfidenceInterval.m",
-      "python_target": "python/nstat/confidence_interval.py",
-      "python_class": "ConfidenceInterval",
-      "python_methods": [
-        "__init__",
-        "lower",
-        "setColor",
-        "upper"
-      ],
-      "methods": [
-        {
-          "matlab_method": "ConfidenceInterval",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "fromStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "plot",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "setColor",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "setValue",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    },
-    {
-      "matlab_class": "CIF",
-      "matlab_source": "CIF.m",
-      "python_target": "python/nstat/cif.py",
-      "python_class": "CIFModel",
-      "python_methods": [
-        "__post_init__",
-        "from_linear_terms",
-        "simulate",
-        "to_covariate"
-      ],
-      "methods": [
-        {
-          "matlab_method": "CIF",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "CIFCopy",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "evalFunctionWithVectorArgs",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "evalGradient",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "evalGradientLDGamma",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "evalGradientLog",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "evalGradientLogLDGamma",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "evalJacobian",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "evalJacobianLDGamma",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "evalJacobianLog",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "evalJacobianLogLDGamma",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "evalLDGamma",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "evalLambdaDelta",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "evalLogLDGamma",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isSymBeta",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "resolveSimulinkModelName",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setHistory",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setSpikeTrain",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "simulateCIF",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "simulateCIFByThinning",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "simulateCIFByThinningFromLambda",
-          "status": "implemented",
-          "rationale": "Implemented via Pythonic rename: simulate."
-        }
-      ]
-    },
-    {
-      "matlab_class": "FitResult",
-      "matlab_source": "FitResult.m",
-      "python_target": "python/nstat/fit.py",
-      "python_class": "FitResult",
-      "python_methods": [
-        "KSPlot",
-        "__init__",
-        "fromStructure",
-        "getCoeffs",
-        "getHistCoeffs",
-        "lambdaCov",
-        "lambdaObj",
-        "lambdaSignal",
-        "lambda_",
-        "lambda_data",
-        "lambda_model",
-        "lambda_obj",
-        "lambda_rate",
-        "lambda_result",
-        "lambda_sig",
-        "lambda_time",
-        "lambda_values",
-        "mergeResults",
-        "plotCoeffs",
-        "plotInvGausTrans",
-        "plotResidual",
-        "plotResults",
-        "plotSeqCorr",
-        "toStructure"
-      ],
-      "methods": [
-        {
-          "matlab_method": "CellArrayToStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "FitResult",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "KSPlot",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "addParamsToFit",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computePlotParams",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeValLambda",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "evalLambda",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "fromStructure",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "getCoeffIndex",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getCoeffs",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "getHistCoeffs",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "getHistIndex",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getParam",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getPlotParams",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSubsetFitResult",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getUniqueLabels",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "isValDataPresent",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mapCovLabelsToUniqueLabels",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mergeResults",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "plotCoeffs",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "plotCoeffsWithoutHistory",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotHistCoeffs",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotInvGausTrans",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "plotResidual",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "plotResults",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "plotSeqCorr",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "plotValidation",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "setFitResidual",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setInvGausStats",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setKSStats",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "setNeuronName",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "toStructure",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "xticklabel_rotate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    },
-    {
-      "matlab_class": "FitResSummary",
-      "matlab_source": "FitResSummary.m",
-      "python_target": "python/nstat/fit.py",
-      "python_class": "FitSummary",
-      "python_methods": [
-        "__init__",
-        "getDiffAIC",
-        "getDiffBIC",
-        "plotSummary"
-      ],
-      "methods": [
-        {
-          "matlab_method": "FitResSummary",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "binCoeffs",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "boxPlot",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeDiffMat",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "fromStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getCoeffIndex",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getCoeffs",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getDiffAIC",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "getDiffBIC",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "getDifflogLL",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getHistCoeffs",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getHistIndex",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getSigCoeffs",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "getUniqueLabels",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mapCovLabelsToUniqueLabels",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "plot2dCoeffSummary",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plot3dCoeffSummary",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotAIC",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotAllCoeffs",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotBIC",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotCoeffsWithoutHistory",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotHistCoeffs",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotIC",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotKSSummary",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotResidualSummary",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotSummary",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "plotlogLL",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "setCoeffRange",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "toStructure",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "xticklabel_rotate",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    },
-    {
-      "matlab_class": "Analysis",
-      "matlab_source": "Analysis.m",
-      "python_target": "python/nstat/analysis.py",
-      "python_class": "Analysis",
-      "python_methods": [
-        "RunAnalysisForAllNeurons",
-        "RunAnalysisForNeuron",
-        "psth",
-        "run_analysis_for_all_neurons",
-        "run_analysis_for_neuron"
-      ],
-      "methods": [
-        {
-          "matlab_method": "GLMFit",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "KSPlot",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "RunAnalysisForAllNeurons",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "RunAnalysisForNeuron",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "bnlrCG",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "compHistEnsCoeff",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "compHistEnsCoeffForAll",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeFitResidual",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeGrangerCausalityMatrix",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeHistLag",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeHistLagForAll",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeInvGausTrans",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeKSStats",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeNeighbors",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "fdr_bh",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "flatMaskCellToMat",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "ksdiscrete",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "plotCoeffs",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotFitResidual",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotInvGausTrans",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "plotSeqCorr",
-          "status": "intentionally_omitted",
-          "rationale": "Visualization helper is handled in notebooks/docs instead of core API."
-        },
-        {
-          "matlab_method": "spikeTrigAvg",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    },
-    {
-      "matlab_class": "DecodingAlgorithms",
-      "matlab_source": "DecodingAlgorithms.m",
-      "python_target": "python/nstat/decoding_algorithms.py",
-      "python_class": "DecodingAlgorithms",
-      "python_methods": [
-        "kalman_filter",
-        "linear_decode"
-      ],
-      "methods": [
-        {
-          "matlab_method": "ComputeStimulusCIs",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "KF_ComputeParamStandardErrors",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "KF_EM",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "KF_EMCreateConstraints",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "KF_EStep",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "KF_MStep",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PPDecodeFilter",
-          "status": "implemented",
-          "rationale": "Implemented via Pythonic rename: kalman_filter."
-        },
-        {
-          "matlab_method": "PPDecodeFilterLinear",
-          "status": "implemented",
-          "rationale": "Implemented via Pythonic rename: kalman_filter."
-        },
-        {
-          "matlab_method": "PPDecode_predict",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PPDecode_update",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PPDecode_updateLinear",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PPHybridFilter",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PPHybridFilterLinear",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PPSS_EM",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PPSS_EMFB",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PPSS_EStep",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PPSS_MStep",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PP_ComputeParamStandardErrors",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PP_EM",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PP_EMCreateConstraints",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PP_EStep",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PP_MStep",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "PP_fixedIntervalSmoother",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeSpikeRateCIs",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "computeSpikeRateDiffCIs",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "estimateInfoMat",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "kalman_filter",
-          "status": "implemented",
-          "rationale": "Method name exists in canonical Python class."
-        },
-        {
-          "matlab_method": "kalman_fixedIntervalSmoother",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "kalman_predict",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "kalman_smoother",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "kalman_smootherFromFiltered",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "kalman_update",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mPPCODecodeLinear",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mPPCODecode_predict",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mPPCODecode_update",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mPPCO_ComputeParamStandardErrors",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mPPCO_EM",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mPPCO_EMCreateConstraints",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mPPCO_EStep",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mPPCO_MStep",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "mPPCO_fixedIntervalSmoother",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "prepareEMResults",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "ukf",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "ukf_sigmas",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        },
-        {
-          "matlab_method": "ukf_ut",
-          "status": "planned",
-          "rationale": "Not yet implemented in canonical class; tracked for incremental parity completion."
-        }
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/python/reports/mfile_parity_report.json b/python/reports/mfile_parity_report.json
deleted file mode 100644
index 4912ffd..0000000
--- a/python/reports/mfile_parity_report.json
+++ /dev/null
@@ -1,593 +0,0 @@
-{
-  "summary": {
-    "total_m_files": 58,
-    "python_ok": 58,
-    "python_runnable_ok": 33,
-    "matlab_runnable_ok": 24,
-    "runnable_parity_pass": 24,
-    "interface_only": 25,
-    "runnable_total": 33
-  },
-  "rows": [
-    {
-      "source": "Analysis.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/Analysis.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "CIF.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/CIF.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "ConfidenceInterval.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/ConfidenceInterval.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "ConfigColl.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/ConfigColl.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "CovColl.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/CovColl.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "Covariate.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/Covariate.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "DecodingAlgorithms.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/DecodingAlgorithms.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "Events.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/Events.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "FitResSummary.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/FitResSummary.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "FitResult.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/FitResult.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "History.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/History.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "SignalObj.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/SignalObj.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "Trial.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/Trial.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "TrialConfig.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/TrialConfig.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "data/Explicit Stimulus/GenCovMat.m",
-      "kind": "function_args",
-      "python_target": "python/matlab_port/data/Explicit Stimulus/GenCovMat.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "helpfiles/AnalysisExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/AnalysisExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/AnalysisExamples2.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/AnalysisExamples2.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": false,
-      "matlab_message": "Analyzing Configuration #1: Neuron #1CODEX_SMOKE_FAIL\nstats:glmfit:InputSizeMismatchX | Number of observations in X and Y must match.",
-      "parity": "fail"
-    },
-    {
-      "source": "helpfiles/ClassDefinitions.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/ClassDefinitions.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/ConfigCollExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/ConfigCollExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/CovCollExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/CovCollExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/CovariateExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/CovariateExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/DecodingExample.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/DecodingExample.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": false,
-      "matlab_message": "Analyzing Configuration #1: Neuron #1,2,3,4,5,6,7,8,9,10\nAnalyzing Configuration #2: Neuron #1,2,3,4,5,6,7,8,9,10\nCODEX_SMOKE_FAIL\nMATLAB:innerdim | Incorrect dimensions for matrix multiplication. Check that the number of columns in the first matrix matches the number of rows in the second matrix. To operate on each element of the matrix individually, use TIMES (.*) for elementwise multiplication.",
-      "parity": "fail"
-    },
-    {
-      "source": "helpfiles/DecodingExampleWithHist.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/DecodingExampleWithHist.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": false,
-      "matlab_message": "matlab_timeout",
-      "parity": "fail"
-    },
-    {
-      "source": "helpfiles/EventsExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/EventsExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/Examples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/Examples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/ExplicitStimulusWhiskerData.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/ExplicitStimulusWhiskerData.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/FitResSummaryExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/FitResSummaryExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/FitResult.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/helpfiles/FitResult.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "helpfiles/FitResultExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/FitResultExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/HippocampalPlaceCellExample.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/HippocampalPlaceCellExample.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": false,
-      "matlab_message": "CODEX_SMOKE_FAIL\nMATLAB:pmaxsize | Requested array exceeds the maximum possible variable size.",
-      "parity": "fail"
-    },
-    {
-      "source": "helpfiles/HistoryExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/HistoryExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/HybridFilterExample.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/HybridFilterExample.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/NetworkTutorial.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/NetworkTutorial.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/NeuralSpikeAnalysis_top.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/NeuralSpikeAnalysis_top.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/PPSimExample.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/PPSimExample.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/PPThinning.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/PPThinning.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/PSTHEstimation.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/PSTHEstimation.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/SignalObjExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/SignalObjExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": false,
-      "matlab_message": "ans =\n        1001           1\nName successfully set \nCODEX_SMOKE_FAIL\nMATLAB:sizeDimensionsMustMatch | Arrays have incompatible sizes for this operation.",
-      "parity": "fail"
-    },
-    {
-      "source": "helpfiles/StimulusDecode2D.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/StimulusDecode2D.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": false,
-      "matlab_message": "CODEX_SMOKE_FAIL\nsymbolic:sym:sym:ArgumentMustBeVarnameOrNumber | Each character vector or string in input arguments must specify a variable or a number. To evaluate a character vector or a string representing symbolic expression, use 'str2sym'.",
-      "parity": "fail"
-    },
-    {
-      "source": "helpfiles/TrialConfigExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/TrialConfigExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/TrialExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/TrialExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/ValidationDataSet.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/ValidationDataSet.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": false,
-      "matlab_message": "lambda =\n    10\nmu =\n   -4.5951\nAnalyzing Configuration #1: Neuron #1CODEX_SMOKE_FAIL\nstats:glmfit:InputSizeMismatchX | Number of observations in X and Y must match.",
-      "parity": "fail"
-    },
-    {
-      "source": "helpfiles/mEPSCAnalysis.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/mEPSCAnalysis.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/nSTATPaperExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/nSTATPaperExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": false,
-      "matlab_message": "matlab_timeout",
-      "parity": "fail"
-    },
-    {
-      "source": "helpfiles/nSpikeTrainExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/nSpikeTrainExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/nstCollExamples.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/nstCollExamples.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "helpfiles/temp.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/helpfiles/temp.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD.m",
-      "kind": "function_args",
-      "python_target": "python/matlab_port/libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD_demo.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/libraries/NearestSymmetricPositiveDefinite/NearestSymmetricPositiveDefinite/nearestSPD_demo.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": false,
-      "matlab_message": "  Columns 15 through 21\n    0.3714    0.3922    0.6373    0.7147    0.8857    1.2443    1.3638\n  Columns 22 through 25\n    1.4439    1.5006    1.8153   13.0417\np =\n     3\nCODEX_SMOKE_FAIL\nMATLAB:UndefinedFunction | Undefined function 'nearest_posdef' for input arguments of type 'double'.",
-      "parity": "fail"
-    },
-    {
-      "source": "libraries/fixPSlinestyle.m",
-      "kind": "function_args",
-      "python_target": "python/matlab_port/libraries/fixPSlinestyle.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "libraries/rotateXLabels/rotateXLabels.m",
-      "kind": "function_args",
-      "python_target": "python/matlab_port/libraries/rotateXLabels/rotateXLabels.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "libraries/xticklabel_rotate.m",
-      "kind": "function_args",
-      "python_target": "python/matlab_port/libraries/xticklabel_rotate.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "libraries/zernike/zernfun.m",
-      "kind": "function_args",
-      "python_target": "python/matlab_port/libraries/zernike/zernfun.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "libraries/zernike/zernfun2.m",
-      "kind": "function_args",
-      "python_target": "python/matlab_port/libraries/zernike/zernfun2.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "libraries/zernike/zernpol.m",
-      "kind": "function_args",
-      "python_target": "python/matlab_port/libraries/zernike/zernpol.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "nSTAT_Install.m",
-      "kind": "script",
-      "python_target": "python/matlab_port/nSTAT_Install.py",
-      "python_ok": true,
-      "python_message": "run_ok",
-      "matlab_ok": true,
-      "matlab_message": "ok",
-      "parity": "pass"
-    },
-    {
-      "source": "nspikeTrain.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/nspikeTrain.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    },
-    {
-      "source": "nstColl.m",
-      "kind": "classdef",
-      "python_target": "python/matlab_port/nstColl.py",
-      "python_ok": true,
-      "python_message": "interface_checked",
-      "matlab_ok": null,
-      "matlab_message": "not_run",
-      "parity": "interface_only"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/python/reports/offline_standalone_verification.json b/python/reports/offline_standalone_verification.json
deleted file mode 100644
index 3b84256..0000000
--- a/python/reports/offline_standalone_verification.json
+++ /dev/null
@@ -1,69 +0,0 @@
-{
-  "full_notebooks": false,
-  "steps": [
-    {
-      "cmd": [
-        "/Library/Developer/CommandLineTools/usr/bin/python3",
-        "-m",
-        "pip",
-        "install",
-        "--no-deps",
-        "./python",
-        "--target",
-        "/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/nstat_offline_site_cwn3d943/site"
-      ],
-      "cwd": ".",
-      "returncode": 0,
-      "stdout": "Processing ./python\n  Installing build dependencies: started\n  Installing build dependencies: finished with status 'done'\n  Getting requirements to build wheel: started\n  Getting requirements to build wheel: finished with status 'done'\n    Preparing wheel metadata: started\n    Preparing wheel metadata: finished with status 'done'\nBuilding wheels for collected packages: UNKNOWN\n  Building wheel for UNKNOWN (PEP 517): started\n  Building wheel for UNKNOWN (PEP 517): finished with status 'done'\n  Created wheel for UNKNOWN: filename=UNKNOWN-0.0.0-py3-none-any.whl size=964 sha256=c90108beae5d6e47c6c92f0b9352de391932056a5d74b69c2720b860d3fce19c\n  Stored in directory: /private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-ephem-wheel-cache-83_7ve9i/wheels/81/67/1b/46c5955793e14fd89065ce3e49006c6b43d36c440a1343a2c9\nSuccessfully built UNKNOWN\nInstalling collected packages: UNKNOWN\nSuccessfully installed UNKNOWN-0.0.0\n",
-      "stderr": "  DEPRECATION: A future pip version will change local packages to be built in-place without first copying to a temporary directory. We recommend you use --use-feature=in-tree-build to test your packages with this new behavior before it becomes the default.\n   pip 21.3 will remove support for this functionality. You can find discussion regarding this at https://github.com/pypa/pip/issues/7555.\n  WARNING: Value for prefixed-purelib does not match. Please report this to \n  distutils: /private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-build-env-ff_yyb7u/normal/lib/python3.9/site-packages\n  sysconfig: /Library/Python/3.9/site-packages\n  WARNING: Value for prefixed-platlib does not match. Please report this to \n  distutils: /private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-build-env-ff_yyb7u/normal/lib/python3.9/site-packages\n  sysconfig: /Library/Python/3.9/site-packages\n  WARNING: Additional context:\n  user = False\n  home = None\n  root = None\n  prefix = '/private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-build-env-ff_yyb7u/normal'\n  WARNING: Value for prefixed-purelib does not match. Please report this to \n  distutils: /private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-build-env-ff_yyb7u/overlay/lib/python3.9/site-packages\n  sysconfig: /Library/Python/3.9/site-packages\n  WARNING: Value for prefixed-platlib does not match. Please report this to \n  distutils: /private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-build-env-ff_yyb7u/overlay/lib/python3.9/site-packages\n  sysconfig: /Library/Python/3.9/site-packages\n  WARNING: Additional context:\n  user = False\n  home = None\n  root = None\n  prefix = '/private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-build-env-ff_yyb7u/overlay'\nWARNING: You are using pip version 21.2.4; however, version 26.0.1 is available.\nYou should consider upgrading via the '/Library/Developer/CommandLineTools/usr/bin/python3 -m pip install --upgrade pip' command.\n",
-      "ok": true
-    },
-    {
-      "cmd": [
-        "/Library/Developer/CommandLineTools/usr/bin/python3",
-        "-c",
-        "import json, pathlib, nstat; checks=nstat.verify_checksums(); print(json.dumps({'dataset_count': len(nstat.list_datasets()), 'checksum_all_true': all(checks.values()), 'nstat_path': str(pathlib.Path(nstat.__file__).resolve())}))"
-      ],
-      "cwd": ".",
-      "returncode": 1,
-      "stdout": "",
-      "stderr": "Traceback (most recent call last):\n  File \"\", line 1, in \nModuleNotFoundError: No module named 'nstat'\n",
-      "ok": false
-    },
-    {
-      "cmd": [
-        "/Library/Developer/CommandLineTools/usr/bin/python3",
-        "-c",
-        "import numpy as np; from nstat.signal import Signal; t=np.linspace(0.0,1.0,100); sig=Signal(t, np.column_stack([np.sin(t), np.cos(t)]), name='offline_check'); print(sig.dimension)"
-      ],
-      "cwd": ".",
-      "returncode": 1,
-      "stdout": "",
-      "stderr": "Traceback (most recent call last):\n  File \"\", line 1, in \nModuleNotFoundError: No module named 'nstat'\n",
-      "ok": false
-    },
-    {
-      "cmd": [
-        "/Library/Developer/CommandLineTools/usr/bin/python3",
-        "-c",
-        "import json, pathlib, nstat; checks=nstat.verify_checksums(); print(json.dumps({'dataset_count': len(nstat.list_datasets()), 'checksum_all_true': all(checks.values()), 'nstat_path': str(pathlib.Path(nstat.__file__).resolve())}))"
-      ],
-      "cwd": ".",
-      "returncode": 0,
-      "stdout": "{\"dataset_count\": 7, \"checksum_all_true\": true, \"nstat_path\": \"/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local/python/nstat/__init__.py\"}\n",
-      "stderr": "",
-      "ok": true
-    }
-  ],
-  "runtime_matlab_scan": {
-    "ok": true,
-    "hits": []
-  },
-  "target_install_ok": true,
-  "installed_runtime_ok": false,
-  "source_fallback_ok": true,
-  "notebook_checks_ok": true,
-  "install_mode": "source_fallback",
-  "pass_strict_target_install": false,
-  "pass": true
-}
\ No newline at end of file
diff --git a/python/reports/offline_standalone_verification_no_matlab_path.json b/python/reports/offline_standalone_verification_no_matlab_path.json
deleted file mode 100644
index 9b212a5..0000000
--- a/python/reports/offline_standalone_verification_no_matlab_path.json
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "full_notebooks": false,
-  "steps": [
-    {
-      "cmd": [
-        "/Library/Developer/CommandLineTools/usr/bin/python3",
-        "-m",
-        "pip",
-        "install",
-        "--no-deps",
-        "./python",
-        "--target",
-        "/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/nstat_offline_site_cwn3d943/site"
-      ],
-      "cwd": ".",
-      "returncode": 0,
-      "stdout": "Processing ./python\n  Installing build dependencies: started\n  Installing build dependencies: finished with status 'done'\n  Getting requirements to build wheel: started\n  Getting requirements to build wheel: finished with status 'done'\n    Preparing wheel metadata: started\n    Preparing wheel metadata: finished with status 'done'\nBuilding wheels for collected packages: UNKNOWN\n  Building wheel for UNKNOWN (PEP 517): started\n  Building wheel for UNKNOWN (PEP 517): finished with status 'done'\n  Created wheel for UNKNOWN: filename=UNKNOWN-0.0.0-py3-none-any.whl size=964 sha256=c90108beae5d6e47c6c92f0b9352de391932056a5d74b69c2720b860d3fce19c\n  Stored in directory: /private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-ephem-wheel-cache-83_7ve9i/wheels/81/67/1b/46c5955793e14fd89065ce3e49006c6b43d36c440a1343a2c9\nSuccessfully built UNKNOWN\nInstalling collected packages: UNKNOWN\nSuccessfully installed UNKNOWN-0.0.0\n",
-      "stderr": "  DEPRECATION: A future pip version will change local packages to be built in-place without first copying to a temporary directory. We recommend you use --use-feature=in-tree-build to test your packages with this new behavior before it becomes the default.\n   pip 21.3 will remove support for this functionality. You can find discussion regarding this at https://github.com/pypa/pip/issues/7555.\n  WARNING: Value for prefixed-purelib does not match. Please report this to \n  distutils: /private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-build-env-ff_yyb7u/normal/lib/python3.9/site-packages\n  sysconfig: /Library/Python/3.9/site-packages\n  WARNING: Value for prefixed-platlib does not match. Please report this to \n  distutils: /private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-build-env-ff_yyb7u/normal/lib/python3.9/site-packages\n  sysconfig: /Library/Python/3.9/site-packages\n  WARNING: Additional context:\n  user = False\n  home = None\n  root = None\n  prefix = '/private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-build-env-ff_yyb7u/normal'\n  WARNING: Value for prefixed-purelib does not match. Please report this to \n  distutils: /private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-build-env-ff_yyb7u/overlay/lib/python3.9/site-packages\n  sysconfig: /Library/Python/3.9/site-packages\n  WARNING: Value for prefixed-platlib does not match. Please report this to \n  distutils: /private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-build-env-ff_yyb7u/overlay/lib/python3.9/site-packages\n  sysconfig: /Library/Python/3.9/site-packages\n  WARNING: Additional context:\n  user = False\n  home = None\n  root = None\n  prefix = '/private/var/folders/tg/z6dfb8b13wg_h4f3v8whzpgh0000gn/T/pip-build-env-ff_yyb7u/overlay'\nWARNING: You are using pip version 21.2.4; however, version 26.0.1 is available.\nYou should consider upgrading via the '/Library/Developer/CommandLineTools/usr/bin/python3 -m pip install --upgrade pip' command.\n",
-      "ok": true
-    },
-    {
-      "cmd": [
-        "/Library/Developer/CommandLineTools/usr/bin/python3",
-        "-c",
-        "import json, pathlib, nstat; checks=nstat.verify_checksums(); print(json.dumps({'dataset_count': len(nstat.list_datasets()), 'checksum_all_true': all(checks.values()), 'nstat_path': str(pathlib.Path(nstat.__file__).resolve())}))"
-      ],
-      "cwd": ".",
-      "returncode": 1,
-      "stdout": "",
-      "stderr": "Traceback (most recent call last):\n  File \"\", line 1, in \nModuleNotFoundError: No module named 'nstat'\n",
-      "ok": false
-    },
-    {
-      "cmd": [
-        "/Library/Developer/CommandLineTools/usr/bin/python3",
-        "-c",
-        "import numpy as np; from nstat.signal import Signal; t=np.linspace(0.0,1.0,100); sig=Signal(t, np.column_stack([np.sin(t), np.cos(t)]), name='offline_check'); print(sig.dimension)"
-      ],
-      "cwd": ".",
-      "returncode": 1,
-      "stdout": "",
-      "stderr": "Traceback (most recent call last):\n  File \"\", line 1, in \nModuleNotFoundError: No module named 'nstat'\n",
-      "ok": false
-    },
-    {
-      "cmd": [
-        "/Library/Developer/CommandLineTools/usr/bin/python3",
-        "-c",
-        "import json, pathlib, nstat; checks=nstat.verify_checksums(); print(json.dumps({'dataset_count': len(nstat.list_datasets()), 'checksum_all_true': all(checks.values()), 'nstat_path': str(pathlib.Path(nstat.__file__).resolve())}))"
-      ],
-      "cwd": ".",
-      "returncode": 0,
-      "stdout": "{\"dataset_count\": 7, \"checksum_all_true\": true, \"nstat_path\": \"/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local/python/nstat/__init__.py\"}\n",
-      "stderr": "",
-      "ok": true
-    }
-  ],
-  "runtime_matlab_scan": {
-    "ok": true,
-    "hits": []
-  },
-  "target_install_ok": true,
-  "installed_runtime_ok": false,
-  "source_fallback_ok": true,
-  "notebook_checks_ok": true,
-  "install_mode": "source_fallback",
-  "pass_strict_target_install": false,
-  "pass": true,
-  "run_context": {
-    "path": "/usr/bin:/bin:/usr/sbin:/sbin",
-    "matlab_on_path": false
-  }
-}
\ No newline at end of file
diff --git a/python/reports/port_baseline_snapshot.json b/python/reports/port_baseline_snapshot.json
deleted file mode 100644
index 2236711..0000000
--- a/python/reports/port_baseline_snapshot.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
-  "generated_at_utc": "2026-02-25T13:51:32.513233+00:00",
-  "reports": {
-    "mfile_parity": {
-      "path": "python/reports/mfile_parity_report.json",
-      "exists": true,
-      "sha256": "14550105d488cd5f91d47dbbb1bd6ad96e34e2dd74d102d85b3f9b0c392a1e7a",
-      "size_bytes": 19449,
-      "summary": {
-        "total_m_files": 58,
-        "python_ok": 58,
-        "python_runnable_ok": 33,
-        "matlab_runnable_ok": 24,
-        "runnable_parity_pass": 24,
-        "interface_only": 25,
-        "runnable_total": 33
-      }
-    },
-    "examples_notebooks": {
-      "path": "python/reports/examples_notebook_verification.json",
-      "exists": true,
-      "sha256": "e7823adb5d5634a3ed345679dd4134f87b7c18246a27b1ef12d24fdeee5602ca",
-      "size_bytes": 12417,
-      "summary": {
-        "total_examples": 25,
-        "python_modules_ok": 25,
-        "notebooks_ok": 25,
-        "topic_alignment_ok": 25
-      }
-    },
-    "matlab_smoke_input": {
-      "path": "python/reports/matlab_smoke_input.json",
-      "exists": true,
-      "sha256": "3e5ffe693d440c094c529163ca403f791cc5f4c4210d379a220652cc487eb713",
-      "size_bytes": 3510
-    }
-  }
-}
\ No newline at end of file
diff --git a/python/reports/python_vs_matlab_similarity_baseline.json b/python/reports/python_vs_matlab_similarity_baseline.json
deleted file mode 100644
index eaab6d2..0000000
--- a/python/reports/python_vs_matlab_similarity_baseline.json
+++ /dev/null
@@ -1,1464 +0,0 @@
-{
-  "frozen_at_utc": "2026-02-25T13:25:15.196500+00:00",
-  "source_report": "python/reports/python_vs_matlab_similarity_report.json",
-  "report": {
-    "class_similarity": {
-      "python": {
-        "nspike_getISIs": [
-          0.1,
-          0.2
-        ],
-        "nspike_rate": 3.0,
-        "nstcoll_psth_len": 5,
-        "nstcoll_psth_mean": 3.0,
-        "covcoll_shape": [
-          11,
-          2
-        ],
-        "history_num_columns": 2,
-        "trial_sample_rate": 10.0,
-        "trial_minmax": [
-          0.0,
-          1.0
-        ],
-        "cif_num_realizations": 3
-      },
-      "matlab": {
-        "nspike_getISIs": [
-          0.1,
-          0.2
-        ],
-        "nspike_rate": 3,
-        "nstcoll_psth_len": 5,
-        "nstcoll_psth_mean": 3,
-        "covcoll_shape": [
-          11,
-          2
-        ],
-        "history_num_columns": 2,
-        "trial_sample_rate": 10,
-        "trial_minmax": [
-          0,
-          1
-        ],
-        "cif_num_realizations": 3
-      },
-      "comparisons": [
-        {
-          "metric": "nspike_getISIs",
-          "python": [
-            0.1,
-            0.2
-          ],
-          "matlab": [
-            0.1,
-            0.2
-          ],
-          "pass": true
-        },
-        {
-          "metric": "nspike_rate",
-          "python": 3.0,
-          "matlab": 3.0,
-          "abs_diff": 0.0,
-          "pass": true
-        },
-        {
-          "metric": "nstcoll_psth_len",
-          "python": 5.0,
-          "matlab": 5.0,
-          "abs_diff": 0.0,
-          "pass": true
-        },
-        {
-          "metric": "nstcoll_psth_mean",
-          "python": 3.0,
-          "matlab": 3.0,
-          "abs_diff": 0.0,
-          "pass": true
-        },
-        {
-          "metric": "covcoll_shape",
-          "python": [
-            11.0,
-            2.0
-          ],
-          "matlab": [
-            11.0,
-            2.0
-          ],
-          "pass": true
-        },
-        {
-          "metric": "history_num_columns",
-          "python": 2.0,
-          "matlab": 2.0,
-          "abs_diff": 0.0,
-          "pass": true
-        },
-        {
-          "metric": "trial_sample_rate",
-          "python": 10.0,
-          "matlab": 10.0,
-          "abs_diff": 0.0,
-          "pass": true
-        },
-        {
-          "metric": "trial_minmax",
-          "python": [
-            0.0,
-            1.0
-          ],
-          "matlab": [
-            0.0,
-            1.0
-          ],
-          "pass": true
-        },
-        {
-          "metric": "cif_num_realizations",
-          "python": 3.0,
-          "matlab": 3.0,
-          "abs_diff": 0.0,
-          "pass": true
-        }
-      ],
-      "summary": {
-        "passed": 9,
-        "total": 9,
-        "similarity_score": 1.0
-      }
-    },
-    "helpfile_similarity": {
-      "summary": {
-        "total_topics": 25,
-        "both_ok": 25,
-        "python_ok": 25,
-        "matlab_ok": 25,
-        "scalar_overlap_topics": 25,
-        "scalar_overlap_pass_topics": 25,
-        "avg_similarity_score": 1.0
-      },
-      "rows": [
-        {
-          "topic": "SignalObjExamples",
-          "title": "Using the SignalObj Class",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "dimension",
-            "parity",
-            "sample_rate",
-            "topic"
-          ],
-          "python_scalar_count": 6,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 16,
-          "matlab_var_count": 19,
-          "matlab_scalar_count": 6,
-          "matlab_script_used": "helpfiles/SignalObjExamples.m [shadow_safe_copy]",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 12.859008073806763,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "sample_rate",
-                "matlab_key": "sampleRate",
-                "python": 5000.00000000055,
-                "matlab": 5000.0,
-                "abs_diff": 5.502442945726216e-10,
-                "pass": true
-              },
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 16.0,
-                "matlab": 16.0,
-                "abs_diff": 0.0,
-                "pass": true
-              },
-              {
-                "python_key": "parity.sample_rate_hz",
-                "matlab_key": "parity_sample_rate_hz",
-                "python": 5000.00000000055,
-                "matlab": 5000.0,
-                "abs_diff": 5.502442945726216e-10,
-                "pass": true
-              },
-              {
-                "python_key": "sample_rate_hz",
-                "matlab_key": "sample_rate_hz",
-                "python": 5000.00000000055,
-                "matlab": 5000.0,
-                "abs_diff": 5.502442945726216e-10,
-                "pass": true
-              }
-            ],
-            "overlap_count": 4,
-            "overlap_passed": 4
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "CovariateExamples",
-          "title": "Using the Covariate Class",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "mean",
-            "parity",
-            "std",
-            "topic"
-          ],
-          "python_scalar_count": 4,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 2,
-          "matlab_var_count": 16,
-          "matlab_scalar_count": 2,
-          "matlab_script_used": "helpfiles/CovariateExamples.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 10.382561206817627,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 2.0,
-                "matlab": 2.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "CovCollExamples",
-          "title": "Using the CovColl Class",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "labels",
-            "matrix_shape",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 2,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 2,
-          "matlab_var_count": 7,
-          "matlab_scalar_count": 2,
-          "matlab_script_used": "helpfiles/CovCollExamples.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 10.461670160293579,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 2.0,
-                "matlab": 2.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "nSpikeTrainExamples",
-          "title": "Using the nSpikeTrain Class",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "n_spikes",
-            "parity",
-            "rate_hz",
-            "topic"
-          ],
-          "python_scalar_count": 4,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 4,
-          "matlab_var_count": 6,
-          "matlab_scalar_count": 2,
-          "matlab_script_used": "helpfiles/nSpikeTrainExamples.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 30.248419046401978,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 4.0,
-                "matlab": 4.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "nstCollExamples",
-          "title": "Using the nstColl Class",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "num_trains",
-            "parity",
-            "psth_points",
-            "topic"
-          ],
-          "python_scalar_count": 4,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 3,
-          "matlab_var_count": 8,
-          "matlab_scalar_count": 2,
-          "matlab_script_used": "helpfiles/nstCollExamples.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 10.848538875579834,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 3.0,
-                "matlab": 3.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "EventsExamples",
-          "title": "Using the Events Class",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "n_events",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 3,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 3,
-          "matlab_var_count": 8,
-          "matlab_scalar_count": 2,
-          "matlab_script_used": "helpfiles/EventsExamples.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 9.690116167068481,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 3.0,
-                "matlab": 3.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "HistoryExamples",
-          "title": "Using the History Class",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "design_shape",
-            "lags",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 2,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 3,
-          "matlab_var_count": 12,
-          "matlab_scalar_count": 3,
-          "matlab_script_used": "helpfiles/HistoryExamples.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 30.391725063323975,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 3.0,
-                "matlab": 3.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "TrialExamples",
-          "title": "Using the Trial Class",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "covariate_rows",
-            "neurons",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 4,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 6,
-          "matlab_var_count": 16,
-          "matlab_scalar_count": 3,
-          "matlab_script_used": "helpfiles/TrialExamples.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 13.00658917427063,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 6.0,
-                "matlab": 6.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "TrialConfigExamples",
-          "title": "Using the TrialConfig Class",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "covariates",
-            "parity",
-            "sample_rate",
-            "topic"
-          ],
-          "python_scalar_count": 3,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 0,
-          "matlab_var_count": 6,
-          "matlab_scalar_count": 2,
-          "matlab_script_used": "helpfiles/TrialConfigExamples.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 7.838926076889038,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 0.0,
-                "matlab": 0.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "ConfigCollExamples",
-          "title": "Using the ConfigColl Class",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "names",
-            "num_configs",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 3,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 0,
-          "matlab_var_count": 6,
-          "matlab_scalar_count": 2,
-          "matlab_script_used": "helpfiles/ConfigCollExamples.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 8.061589002609253,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 0.0,
-                "matlab": 0.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "AnalysisExamples",
-          "title": "Using the Analysis Class",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "first_aic",
-            "num_results",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 4,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 4,
-          "matlab_var_count": 40,
-          "matlab_scalar_count": 21,
-          "matlab_script_used": "helpfiles/AnalysisExamples.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 10.786340713500977,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 4.0,
-                "matlab": 4.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "FitResultExamples",
-          "title": "Using the FitResult Class",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "bic",
-            "coeffs",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 3,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 0,
-          "matlab_var_count": 3,
-          "matlab_scalar_count": 2,
-          "matlab_script_used": "helpfiles/FitResultExamples.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 8.028863906860352,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 0.0,
-                "matlab": 0.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "FitResSummaryExamples",
-          "title": "Using the FitResSummary Class",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "mean_aic",
-            "mean_bic",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 2,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 0,
-          "matlab_var_count": 3,
-          "matlab_scalar_count": 2,
-          "matlab_script_used": "helpfiles/FitResSummaryExamples.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 8.054399967193604,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 0.0,
-                "matlab": 0.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "PPThinning",
-          "title": "Point Process Simulation via Thinning",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "num_realizations",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 4,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 3,
-          "matlab_var_count": 25,
-          "matlab_scalar_count": 10,
-          "matlab_script_used": "helpfiles/PPThinning.m",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 20.007148027420044,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "num_realizations",
-                "matlab_key": "num_realizations",
-                "python": 20.0,
-                "matlab": 20.0,
-                "abs_diff": 0.0,
-                "pass": true
-              },
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 3.0,
-                "matlab": 3.0,
-                "abs_diff": 0.0,
-                "pass": true
-              },
-              {
-                "python_key": "parity.num_realizations",
-                "matlab_key": "parity_num_realizations",
-                "python": 20.0,
-                "matlab": 20.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 3,
-            "overlap_passed": 3
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "PSTHEstimation",
-          "title": "Example Data Analysis - Simulated Data - Computing a Peri-Stimulus Time Histogram (PSTH)",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "num_realizations",
-            "parity",
-            "peak_rate",
-            "topic"
-          ],
-          "python_scalar_count": 5,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 2,
-          "matlab_var_count": 23,
-          "matlab_scalar_count": 13,
-          "matlab_script_used": "helpfiles/PSTHEstimation.m",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 19.249541997909546,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "num_realizations",
-                "matlab_key": "numRealizations",
-                "python": 20.0,
-                "matlab": 20.0,
-                "abs_diff": 0.0,
-                "pass": true
-              },
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 2.0,
-                "matlab": 2.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 2,
-            "overlap_passed": 2
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "ValidationDataSet",
-          "title": "Example Data Analysis - Simulated Constant (Piecewise Constant) Rate Poisson",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "num_trials",
-            "parity",
-            "psth_mean_hz",
-            "psth_peak_hz",
-            "topic",
-            "total_spikes"
-          ],
-          "python_scalar_count": 6,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 8,
-          "matlab_var_count": 41,
-          "matlab_scalar_count": 21,
-          "matlab_script_used": "helpfiles/ValidationDataSet.m",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 26.339579105377197,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 8.0,
-                "matlab": 8.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "mEPSCAnalysis",
-          "title": "Example Data Analysis - Miniature Excitatory Post-Synaptic Currents (mEPSCs)",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "const_condition_spikes",
-            "const_model_aic",
-            "const_model_bic",
-            "decreasing_condition_spikes",
-            "dt_seconds",
-            "parity",
-            "piecewise_history_model_aic",
-            "piecewise_history_model_bic",
-            "piecewise_model_aic",
-            "piecewise_model_bic",
-            "topic"
-          ],
-          "python_scalar_count": 11,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 4,
-          "matlab_var_count": 31,
-          "matlab_scalar_count": 8,
-          "matlab_script_used": "helpfiles/mEPSCAnalysis.m",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 51.48474884033203,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 4.0,
-                "matlab": 4.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "PPSimExample",
-          "title": "Example Data Analysis - Simulated Explicit Stimulus and History",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "model1_aic",
-            "model1_bic",
-            "model2_aic",
-            "model2_bic",
-            "model3_aic",
-            "model3_bic",
-            "n_samples",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 9,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 3,
-          "matlab_var_count": 33,
-          "matlab_scalar_count": 10,
-          "matlab_script_used": "helpfiles/PPSimExample.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 44.576667070388794,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 3.0,
-                "matlab": 3.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "ExplicitStimulusWhiskerData",
-          "title": "Example Data Analysis - Explicit Stimulus",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "model1_aic",
-            "model1_bic",
-            "model2_aic",
-            "model2_bic",
-            "model3_aic",
-            "model3_bic",
-            "n_samples",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 9,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 8,
-          "matlab_var_count": 46,
-          "matlab_scalar_count": 22,
-          "matlab_script_used": "helpfiles/ExplicitStimulusWhiskerData.m",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 27.45716691017151,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 8.0,
-                "matlab": 8.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "HippocampalPlaceCellExample",
-          "title": "Example Data Analysis - Hippocampal Place Cell Receptive Field Estimation",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "mean_delta_aic_gaussian_minus_zernike",
-            "mean_delta_bic_gaussian_minus_zernike",
-            "num_cells_fit",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 5,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 9,
-          "matlab_var_count": 47,
-          "matlab_scalar_count": 11,
-          "matlab_script_used": "helpfiles/HippocampalPlaceCellExample.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 42.86396503448486,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 9.0,
-                "matlab": 9.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "DecodingExample",
-          "title": "Example Data Analysis - Decoding Univariate Simulated Stimuli (No History Effect)",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "decode_rmse",
-            "num_cells",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 5,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 5,
-          "matlab_var_count": 43,
-          "matlab_scalar_count": 11,
-          "matlab_script_used": "helpfiles/DecodingExample.mlx",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 20.125417232513428,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 5.0,
-                "matlab": 5.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "DecodingExampleWithHist",
-          "title": "Example Data Analysis - Decoding Univariate Simulated Stimuli with and without History Effect",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "decode_rmse_x",
-            "decode_rmse_y",
-            "num_cells",
-            "num_samples",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 7,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 2,
-          "matlab_var_count": 46,
-          "matlab_scalar_count": 16,
-          "matlab_script_used": "helpfiles/DecodingExampleWithHist.m [shadow_safe_copy]",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 129.56473088264465,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 2.0,
-                "matlab": 2.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "StimulusDecode2D",
-          "title": "Example Data Analysis - Decoding Bivariate Simulated Stimuli",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "decode_rmse_x",
-            "decode_rmse_y",
-            "num_cells",
-            "num_samples",
-            "parity",
-            "topic"
-          ],
-          "python_scalar_count": 9,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 4,
-          "matlab_var_count": 47,
-          "matlab_scalar_count": 13,
-          "matlab_script_used": "helpfiles/StimulusDecode2D.m",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 15.194157123565674,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "num_cells",
-                "matlab_key": "num_cells",
-                "python": 80.0,
-                "matlab": 80.0,
-                "abs_diff": 0.0,
-                "pass": true
-              },
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 4.0,
-                "matlab": 4.0,
-                "abs_diff": 0.0,
-                "pass": true
-              },
-              {
-                "python_key": "parity.num_cells",
-                "matlab_key": "parity_num_cells",
-                "python": 80.0,
-                "matlab": 80.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 3,
-            "overlap_passed": 3
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "NetworkTutorial",
-          "title": "Example Data Analysis - Two Neuron Network Simulation and Estimation of Ensemble Effect",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "neuron_count",
-            "parity",
-            "psth_peak",
-            "samples",
-            "topic"
-          ],
-          "python_scalar_count": 5,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 4,
-          "matlab_var_count": 54,
-          "matlab_scalar_count": 13,
-          "matlab_script_used": "helpfiles/NetworkTutorial.m",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 33.98667907714844,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 4.0,
-                "matlab": 4.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 1,
-            "overlap_passed": 1
-          },
-          "similarity_score": 1.0
-        },
-        {
-          "topic": "nSTATPaperExamples",
-          "title": "nSTAT Paper Examples",
-          "python_ok": true,
-          "python_error": "",
-          "python_output_keys": [
-            "experiments",
-            "parity",
-            "summary",
-            "topic"
-          ],
-          "python_scalar_count": 30,
-          "matlab_ok": true,
-          "matlab_error": "",
-          "matlab_error_report": "",
-          "matlab_fallback_error": "",
-          "matlab_fallback_error_report": "",
-          "matlab_figures": 1,
-          "matlab_var_count": 79,
-          "matlab_scalar_count": 14,
-          "matlab_script_used": "helpfiles/nSTATPaperExamples.m [shadow_safe_copy]",
-          "matlab_fallback_script_used": "",
-          "matlab_runtime_s": 162.30554699897766,
-          "scalar_overlap": {
-            "overlaps": [
-              {
-                "python_key": "num_cells",
-                "matlab_key": "num_cells",
-                "python": 40.0,
-                "matlab": 40.0,
-                "abs_diff": 0.0,
-                "pass": true
-              },
-              {
-                "python_key": "figs",
-                "matlab_key": "figs",
-                "python": 1.0,
-                "matlab": 1.0,
-                "abs_diff": 0.0,
-                "pass": true
-              },
-              {
-                "python_key": "parity.num_cells",
-                "matlab_key": "parity_num_cells",
-                "python": 40.0,
-                "matlab": 40.0,
-                "abs_diff": 0.0,
-                "pass": true
-              }
-            ],
-            "overlap_count": 3,
-            "overlap_passed": 3
-          },
-          "similarity_score": 1.0
-        }
-      ]
-    },
-    "parity_contract": {
-      "pass": true,
-      "failures": [],
-      "rows": [
-        {
-          "topic": "SignalObjExamples",
-          "required_keys": [
-            "sample_rate_hz"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "CovariateExamples",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "CovCollExamples",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "nSpikeTrainExamples",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "nstCollExamples",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "EventsExamples",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "HistoryExamples",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "TrialExamples",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "TrialConfigExamples",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "ConfigCollExamples",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "AnalysisExamples",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "FitResultExamples",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "FitResSummaryExamples",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "PPThinning",
-          "required_keys": [
-            "num_realizations"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "PSTHEstimation",
-          "required_keys": [
-            "num_realizations"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "ValidationDataSet",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "mEPSCAnalysis",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "PPSimExample",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "ExplicitStimulusWhiskerData",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "HippocampalPlaceCellExample",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "DecodingExample",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "DecodingExampleWithHist",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "StimulusDecode2D",
-          "required_keys": [
-            "num_cells"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "NetworkTutorial",
-          "required_keys": [
-            "figs"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        },
-        {
-          "topic": "nSTATPaperExamples",
-          "required_keys": [
-            "num_cells"
-          ],
-          "missing_keys": [],
-          "failing_keys": [],
-          "status": "pass"
-        }
-      ]
-    },
-    "regression_gate": {
-      "pass": true,
-      "failures": [],
-      "matlab_failed_topics": [],
-      "known_allowlist": [],
-      "unexpected_failures": [],
-      "known_allowlist_not_currently_failing": [],
-      "parity_contract_pass": true
-    }
-  }
-}
\ No newline at end of file
diff --git a/python/reports/python_vs_matlab_similarity_report.json b/python/reports/python_vs_matlab_similarity_report.json
deleted file mode 100644
index 2b3d5ba..0000000
--- a/python/reports/python_vs_matlab_similarity_report.json
+++ /dev/null
@@ -1,1460 +0,0 @@
-{
-  "class_similarity": {
-    "python": {
-      "nspike_getISIs": [
-        0.1,
-        0.2
-      ],
-      "nspike_rate": 3.0,
-      "nstcoll_psth_len": 5,
-      "nstcoll_psth_mean": 3.0,
-      "covcoll_shape": [
-        11,
-        2
-      ],
-      "history_num_columns": 2,
-      "trial_sample_rate": 10.0,
-      "trial_minmax": [
-        0.0,
-        1.0
-      ],
-      "cif_num_realizations": 3
-    },
-    "matlab": {
-      "nspike_getISIs": [
-        0.1,
-        0.2
-      ],
-      "nspike_rate": 3,
-      "nstcoll_psth_len": 5,
-      "nstcoll_psth_mean": 3,
-      "covcoll_shape": [
-        11,
-        2
-      ],
-      "history_num_columns": 2,
-      "trial_sample_rate": 10,
-      "trial_minmax": [
-        0,
-        1
-      ],
-      "cif_num_realizations": 3
-    },
-    "comparisons": [
-      {
-        "metric": "nspike_getISIs",
-        "python": [
-          0.1,
-          0.2
-        ],
-        "matlab": [
-          0.1,
-          0.2
-        ],
-        "pass": true
-      },
-      {
-        "metric": "nspike_rate",
-        "python": 3.0,
-        "matlab": 3.0,
-        "abs_diff": 0.0,
-        "pass": true
-      },
-      {
-        "metric": "nstcoll_psth_len",
-        "python": 5.0,
-        "matlab": 5.0,
-        "abs_diff": 0.0,
-        "pass": true
-      },
-      {
-        "metric": "nstcoll_psth_mean",
-        "python": 3.0,
-        "matlab": 3.0,
-        "abs_diff": 0.0,
-        "pass": true
-      },
-      {
-        "metric": "covcoll_shape",
-        "python": [
-          11.0,
-          2.0
-        ],
-        "matlab": [
-          11.0,
-          2.0
-        ],
-        "pass": true
-      },
-      {
-        "metric": "history_num_columns",
-        "python": 2.0,
-        "matlab": 2.0,
-        "abs_diff": 0.0,
-        "pass": true
-      },
-      {
-        "metric": "trial_sample_rate",
-        "python": 10.0,
-        "matlab": 10.0,
-        "abs_diff": 0.0,
-        "pass": true
-      },
-      {
-        "metric": "trial_minmax",
-        "python": [
-          0.0,
-          1.0
-        ],
-        "matlab": [
-          0.0,
-          1.0
-        ],
-        "pass": true
-      },
-      {
-        "metric": "cif_num_realizations",
-        "python": 3.0,
-        "matlab": 3.0,
-        "abs_diff": 0.0,
-        "pass": true
-      }
-    ],
-    "summary": {
-      "passed": 9,
-      "total": 9,
-      "similarity_score": 1.0
-    }
-  },
-  "helpfile_similarity": {
-    "summary": {
-      "total_topics": 25,
-      "both_ok": 25,
-      "python_ok": 25,
-      "matlab_ok": 25,
-      "scalar_overlap_topics": 25,
-      "scalar_overlap_pass_topics": 25,
-      "avg_similarity_score": 1.0
-    },
-    "rows": [
-      {
-        "topic": "SignalObjExamples",
-        "title": "Using the SignalObj Class",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "dimension",
-          "parity",
-          "sample_rate",
-          "topic"
-        ],
-        "python_scalar_count": 6,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 16,
-        "matlab_var_count": 19,
-        "matlab_scalar_count": 6,
-        "matlab_script_used": "helpfiles/SignalObjExamples.m [shadow_safe_copy]",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 12.859008073806763,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "sample_rate",
-              "matlab_key": "sampleRate",
-              "python": 5000.00000000055,
-              "matlab": 5000.0,
-              "abs_diff": 5.502442945726216e-10,
-              "pass": true
-            },
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 16.0,
-              "matlab": 16.0,
-              "abs_diff": 0.0,
-              "pass": true
-            },
-            {
-              "python_key": "parity.sample_rate_hz",
-              "matlab_key": "parity_sample_rate_hz",
-              "python": 5000.00000000055,
-              "matlab": 5000.0,
-              "abs_diff": 5.502442945726216e-10,
-              "pass": true
-            },
-            {
-              "python_key": "sample_rate_hz",
-              "matlab_key": "sample_rate_hz",
-              "python": 5000.00000000055,
-              "matlab": 5000.0,
-              "abs_diff": 5.502442945726216e-10,
-              "pass": true
-            }
-          ],
-          "overlap_count": 4,
-          "overlap_passed": 4
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "CovariateExamples",
-        "title": "Using the Covariate Class",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "mean",
-          "parity",
-          "std",
-          "topic"
-        ],
-        "python_scalar_count": 4,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 2,
-        "matlab_var_count": 16,
-        "matlab_scalar_count": 2,
-        "matlab_script_used": "helpfiles/CovariateExamples.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 10.382561206817627,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 2.0,
-              "matlab": 2.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "CovCollExamples",
-        "title": "Using the CovColl Class",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "labels",
-          "matrix_shape",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 2,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 2,
-        "matlab_var_count": 7,
-        "matlab_scalar_count": 2,
-        "matlab_script_used": "helpfiles/CovCollExamples.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 10.461670160293579,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 2.0,
-              "matlab": 2.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "nSpikeTrainExamples",
-        "title": "Using the nSpikeTrain Class",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "n_spikes",
-          "parity",
-          "rate_hz",
-          "topic"
-        ],
-        "python_scalar_count": 4,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 4,
-        "matlab_var_count": 6,
-        "matlab_scalar_count": 2,
-        "matlab_script_used": "helpfiles/nSpikeTrainExamples.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 30.248419046401978,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 4.0,
-              "matlab": 4.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "nstCollExamples",
-        "title": "Using the nstColl Class",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "num_trains",
-          "parity",
-          "psth_points",
-          "topic"
-        ],
-        "python_scalar_count": 4,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 3,
-        "matlab_var_count": 8,
-        "matlab_scalar_count": 2,
-        "matlab_script_used": "helpfiles/nstCollExamples.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 10.848538875579834,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 3.0,
-              "matlab": 3.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "EventsExamples",
-        "title": "Using the Events Class",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "n_events",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 3,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 3,
-        "matlab_var_count": 8,
-        "matlab_scalar_count": 2,
-        "matlab_script_used": "helpfiles/EventsExamples.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 9.690116167068481,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 3.0,
-              "matlab": 3.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "HistoryExamples",
-        "title": "Using the History Class",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "design_shape",
-          "lags",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 2,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 3,
-        "matlab_var_count": 12,
-        "matlab_scalar_count": 3,
-        "matlab_script_used": "helpfiles/HistoryExamples.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 30.391725063323975,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 3.0,
-              "matlab": 3.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "TrialExamples",
-        "title": "Using the Trial Class",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "covariate_rows",
-          "neurons",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 4,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 6,
-        "matlab_var_count": 16,
-        "matlab_scalar_count": 3,
-        "matlab_script_used": "helpfiles/TrialExamples.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 13.00658917427063,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 6.0,
-              "matlab": 6.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "TrialConfigExamples",
-        "title": "Using the TrialConfig Class",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "covariates",
-          "parity",
-          "sample_rate",
-          "topic"
-        ],
-        "python_scalar_count": 3,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 0,
-        "matlab_var_count": 6,
-        "matlab_scalar_count": 2,
-        "matlab_script_used": "helpfiles/TrialConfigExamples.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 7.838926076889038,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 0.0,
-              "matlab": 0.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "ConfigCollExamples",
-        "title": "Using the ConfigColl Class",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "names",
-          "num_configs",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 3,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 0,
-        "matlab_var_count": 6,
-        "matlab_scalar_count": 2,
-        "matlab_script_used": "helpfiles/ConfigCollExamples.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 8.061589002609253,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 0.0,
-              "matlab": 0.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "AnalysisExamples",
-        "title": "Using the Analysis Class",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "first_aic",
-          "num_results",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 4,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 4,
-        "matlab_var_count": 40,
-        "matlab_scalar_count": 21,
-        "matlab_script_used": "helpfiles/AnalysisExamples.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 10.786340713500977,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 4.0,
-              "matlab": 4.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "FitResultExamples",
-        "title": "Using the FitResult Class",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "bic",
-          "coeffs",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 3,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 0,
-        "matlab_var_count": 3,
-        "matlab_scalar_count": 2,
-        "matlab_script_used": "helpfiles/FitResultExamples.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 8.028863906860352,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 0.0,
-              "matlab": 0.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "FitResSummaryExamples",
-        "title": "Using the FitResSummary Class",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "mean_aic",
-          "mean_bic",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 2,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 0,
-        "matlab_var_count": 3,
-        "matlab_scalar_count": 2,
-        "matlab_script_used": "helpfiles/FitResSummaryExamples.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 8.054399967193604,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 0.0,
-              "matlab": 0.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "PPThinning",
-        "title": "Point Process Simulation via Thinning",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "num_realizations",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 4,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 3,
-        "matlab_var_count": 25,
-        "matlab_scalar_count": 10,
-        "matlab_script_used": "helpfiles/PPThinning.m",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 20.007148027420044,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "num_realizations",
-              "matlab_key": "num_realizations",
-              "python": 20.0,
-              "matlab": 20.0,
-              "abs_diff": 0.0,
-              "pass": true
-            },
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 3.0,
-              "matlab": 3.0,
-              "abs_diff": 0.0,
-              "pass": true
-            },
-            {
-              "python_key": "parity.num_realizations",
-              "matlab_key": "parity_num_realizations",
-              "python": 20.0,
-              "matlab": 20.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 3,
-          "overlap_passed": 3
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "PSTHEstimation",
-        "title": "Example Data Analysis - Simulated Data - Computing a Peri-Stimulus Time Histogram (PSTH)",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "num_realizations",
-          "parity",
-          "peak_rate",
-          "topic"
-        ],
-        "python_scalar_count": 5,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 2,
-        "matlab_var_count": 23,
-        "matlab_scalar_count": 13,
-        "matlab_script_used": "helpfiles/PSTHEstimation.m",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 19.249541997909546,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "num_realizations",
-              "matlab_key": "numRealizations",
-              "python": 20.0,
-              "matlab": 20.0,
-              "abs_diff": 0.0,
-              "pass": true
-            },
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 2.0,
-              "matlab": 2.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 2,
-          "overlap_passed": 2
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "ValidationDataSet",
-        "title": "Example Data Analysis - Simulated Constant (Piecewise Constant) Rate Poisson",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "num_trials",
-          "parity",
-          "psth_mean_hz",
-          "psth_peak_hz",
-          "topic",
-          "total_spikes"
-        ],
-        "python_scalar_count": 6,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 8,
-        "matlab_var_count": 41,
-        "matlab_scalar_count": 21,
-        "matlab_script_used": "helpfiles/ValidationDataSet.m",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 26.339579105377197,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 8.0,
-              "matlab": 8.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "mEPSCAnalysis",
-        "title": "Example Data Analysis - Miniature Excitatory Post-Synaptic Currents (mEPSCs)",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "const_condition_spikes",
-          "const_model_aic",
-          "const_model_bic",
-          "decreasing_condition_spikes",
-          "dt_seconds",
-          "parity",
-          "piecewise_history_model_aic",
-          "piecewise_history_model_bic",
-          "piecewise_model_aic",
-          "piecewise_model_bic",
-          "topic"
-        ],
-        "python_scalar_count": 11,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 4,
-        "matlab_var_count": 31,
-        "matlab_scalar_count": 8,
-        "matlab_script_used": "helpfiles/mEPSCAnalysis.m",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 51.48474884033203,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 4.0,
-              "matlab": 4.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "PPSimExample",
-        "title": "Example Data Analysis - Simulated Explicit Stimulus and History",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "model1_aic",
-          "model1_bic",
-          "model2_aic",
-          "model2_bic",
-          "model3_aic",
-          "model3_bic",
-          "n_samples",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 9,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 3,
-        "matlab_var_count": 33,
-        "matlab_scalar_count": 10,
-        "matlab_script_used": "helpfiles/PPSimExample.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 44.576667070388794,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 3.0,
-              "matlab": 3.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "ExplicitStimulusWhiskerData",
-        "title": "Example Data Analysis - Explicit Stimulus",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "model1_aic",
-          "model1_bic",
-          "model2_aic",
-          "model2_bic",
-          "model3_aic",
-          "model3_bic",
-          "n_samples",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 9,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 8,
-        "matlab_var_count": 46,
-        "matlab_scalar_count": 22,
-        "matlab_script_used": "helpfiles/ExplicitStimulusWhiskerData.m",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 27.45716691017151,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 8.0,
-              "matlab": 8.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "HippocampalPlaceCellExample",
-        "title": "Example Data Analysis - Hippocampal Place Cell Receptive Field Estimation",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "mean_delta_aic_gaussian_minus_zernike",
-          "mean_delta_bic_gaussian_minus_zernike",
-          "num_cells_fit",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 5,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 9,
-        "matlab_var_count": 47,
-        "matlab_scalar_count": 11,
-        "matlab_script_used": "helpfiles/HippocampalPlaceCellExample.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 42.86396503448486,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 9.0,
-              "matlab": 9.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "DecodingExample",
-        "title": "Example Data Analysis - Decoding Univariate Simulated Stimuli (No History Effect)",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "decode_rmse",
-          "num_cells",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 5,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 5,
-        "matlab_var_count": 43,
-        "matlab_scalar_count": 11,
-        "matlab_script_used": "helpfiles/DecodingExample.mlx",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 20.125417232513428,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 5.0,
-              "matlab": 5.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "DecodingExampleWithHist",
-        "title": "Example Data Analysis - Decoding Univariate Simulated Stimuli with and without History Effect",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "decode_rmse_x",
-          "decode_rmse_y",
-          "num_cells",
-          "num_samples",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 7,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 2,
-        "matlab_var_count": 46,
-        "matlab_scalar_count": 16,
-        "matlab_script_used": "helpfiles/DecodingExampleWithHist.m [shadow_safe_copy]",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 129.56473088264465,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 2.0,
-              "matlab": 2.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "StimulusDecode2D",
-        "title": "Example Data Analysis - Decoding Bivariate Simulated Stimuli",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "decode_rmse_x",
-          "decode_rmse_y",
-          "num_cells",
-          "num_samples",
-          "parity",
-          "topic"
-        ],
-        "python_scalar_count": 9,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 4,
-        "matlab_var_count": 47,
-        "matlab_scalar_count": 13,
-        "matlab_script_used": "helpfiles/StimulusDecode2D.m",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 15.194157123565674,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "num_cells",
-              "matlab_key": "num_cells",
-              "python": 80.0,
-              "matlab": 80.0,
-              "abs_diff": 0.0,
-              "pass": true
-            },
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 4.0,
-              "matlab": 4.0,
-              "abs_diff": 0.0,
-              "pass": true
-            },
-            {
-              "python_key": "parity.num_cells",
-              "matlab_key": "parity_num_cells",
-              "python": 80.0,
-              "matlab": 80.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 3,
-          "overlap_passed": 3
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "NetworkTutorial",
-        "title": "Example Data Analysis - Two Neuron Network Simulation and Estimation of Ensemble Effect",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "neuron_count",
-          "parity",
-          "psth_peak",
-          "samples",
-          "topic"
-        ],
-        "python_scalar_count": 5,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 4,
-        "matlab_var_count": 54,
-        "matlab_scalar_count": 13,
-        "matlab_script_used": "helpfiles/NetworkTutorial.m",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 33.98667907714844,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 4.0,
-              "matlab": 4.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 1,
-          "overlap_passed": 1
-        },
-        "similarity_score": 1.0
-      },
-      {
-        "topic": "nSTATPaperExamples",
-        "title": "nSTAT Paper Examples",
-        "python_ok": true,
-        "python_error": "",
-        "python_output_keys": [
-          "experiments",
-          "parity",
-          "summary",
-          "topic"
-        ],
-        "python_scalar_count": 30,
-        "matlab_ok": true,
-        "matlab_error": "",
-        "matlab_error_report": "",
-        "matlab_fallback_error": "",
-        "matlab_fallback_error_report": "",
-        "matlab_figures": 1,
-        "matlab_var_count": 79,
-        "matlab_scalar_count": 14,
-        "matlab_script_used": "helpfiles/nSTATPaperExamples.m [shadow_safe_copy]",
-        "matlab_fallback_script_used": "",
-        "matlab_runtime_s": 162.30554699897766,
-        "scalar_overlap": {
-          "overlaps": [
-            {
-              "python_key": "num_cells",
-              "matlab_key": "num_cells",
-              "python": 40.0,
-              "matlab": 40.0,
-              "abs_diff": 0.0,
-              "pass": true
-            },
-            {
-              "python_key": "figs",
-              "matlab_key": "figs",
-              "python": 1.0,
-              "matlab": 1.0,
-              "abs_diff": 0.0,
-              "pass": true
-            },
-            {
-              "python_key": "parity.num_cells",
-              "matlab_key": "parity_num_cells",
-              "python": 40.0,
-              "matlab": 40.0,
-              "abs_diff": 0.0,
-              "pass": true
-            }
-          ],
-          "overlap_count": 3,
-          "overlap_passed": 3
-        },
-        "similarity_score": 1.0
-      }
-    ]
-  },
-  "parity_contract": {
-    "pass": true,
-    "failures": [],
-    "rows": [
-      {
-        "topic": "SignalObjExamples",
-        "required_keys": [
-          "sample_rate_hz"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "CovariateExamples",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "CovCollExamples",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "nSpikeTrainExamples",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "nstCollExamples",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "EventsExamples",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "HistoryExamples",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "TrialExamples",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "TrialConfigExamples",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "ConfigCollExamples",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "AnalysisExamples",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "FitResultExamples",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "FitResSummaryExamples",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "PPThinning",
-        "required_keys": [
-          "num_realizations"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "PSTHEstimation",
-        "required_keys": [
-          "num_realizations"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "ValidationDataSet",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "mEPSCAnalysis",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "PPSimExample",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "ExplicitStimulusWhiskerData",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "HippocampalPlaceCellExample",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "DecodingExample",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "DecodingExampleWithHist",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "StimulusDecode2D",
-        "required_keys": [
-          "num_cells"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "NetworkTutorial",
-        "required_keys": [
-          "figs"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      },
-      {
-        "topic": "nSTATPaperExamples",
-        "required_keys": [
-          "num_cells"
-        ],
-        "missing_keys": [],
-        "failing_keys": [],
-        "status": "pass"
-      }
-    ]
-  },
-  "regression_gate": {
-    "pass": true,
-    "failures": [],
-    "matlab_failed_topics": [],
-    "known_allowlist": [],
-    "unexpected_failures": [],
-    "known_allowlist_not_currently_failing": [],
-    "parity_contract_pass": true
-  }
-}
\ No newline at end of file
diff --git a/python/tests/conftest.py b/python/tests/conftest.py
deleted file mode 100644
index 72703ef..0000000
--- a/python/tests/conftest.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from __future__ import annotations
-
-import sys
-from pathlib import Path
-
-import pytest
-
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-PY_ROOT = REPO_ROOT / "python"
-if str(PY_ROOT) not in sys.path:
-    sys.path.insert(0, str(PY_ROOT))
-
-
-@pytest.fixture(scope="session")
-def repo_root() -> Path:
-    return REPO_ROOT
diff --git a/python/tests/test_analysis_pipeline.py b/python/tests/test_analysis_pipeline.py
deleted file mode 100644
index 1da1fb7..0000000
--- a/python/tests/test_analysis_pipeline.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from __future__ import annotations
-
-import numpy as np
-
-from nstat import Analysis, CIFModel, ConfigCollection, Covariate, CovariateCollection, FitSummary, Trial, TrialConfig
-
-
-def test_trial_analysis_pipeline() -> None:
-    t = np.arange(0.0, 1.0, 0.001)
-    stim = np.sin(2 * np.pi * 2 * t)
-    cov = Covariate(t, stim, "stim", "time", "s", "a.u.", ["stim"])
-
-    model = CIFModel(t, 10.0 + 5.0 * np.maximum(stim, 0.0), name="lambda")
-    spikes = model.simulate(num_realizations=3, seed=2)
-
-    trial = Trial(spike_collection=spikes, covariate_collection=CovariateCollection([cov]))
-    cfgs = ConfigCollection([TrialConfig(covMask=["stim"], sampleRate=1000.0, name="stim_model")])
-
-    fits = Analysis.run_analysis_for_all_neurons(trial, cfgs)
-    assert len(fits) == 3
-    assert fits[0].AIC.shape == (1,)
-
-    summary = FitSummary(fits)
-    assert summary.numNeurons == 3
-    assert summary.AIC.shape == (1,)
diff --git a/python/tests/test_api_surface.py b/python/tests/test_api_surface.py
deleted file mode 100644
index 1aef5dc..0000000
--- a/python/tests/test_api_surface.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from __future__ import annotations
-
-import warnings
-
-import nstat
-
-
-def test_canonical_api_imports() -> None:
-    assert nstat.Signal is not None
-    assert nstat.SpikeTrain is not None
-    assert nstat.SpikeTrainCollection is not None
-    assert nstat.Trial is not None
-    assert nstat.Analysis is not None
-    assert nstat.FitResult is not None
-    assert nstat.FitSummary is not None
-    assert nstat.CIFModel is not None
-    assert nstat.DecoderSuite is not None
-
-
-def test_compatibility_adapters_emit_deprecation() -> None:
-    with warnings.catch_warnings(record=True) as w:
-        warnings.simplefilter("always")
-        from nstat.SignalObj import SignalObj
-
-        _ = SignalObj([0.0, 1.0], [1.0, 2.0])
-        assert any("deprecated" in str(item.message).lower() for item in w)
diff --git a/python/tests/test_datasets.py b/python/tests/test_datasets.py
deleted file mode 100644
index 869f448..0000000
--- a/python/tests/test_datasets.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from __future__ import annotations
-
-import os
-
-import nstat
-
-
-def test_dataset_manifest_and_checksums() -> None:
-    names = nstat.list_datasets()
-    assert names
-
-    check = nstat.verify_checksums()
-    assert set(check.keys()) == set(names)
-    assert all(isinstance(v, bool) for v in check.values())
-    allow_synthetic = os.environ.get("NSTAT_ALLOW_SYNTHETIC_DATA", "").strip().lower() in {"1", "true", "yes", "on"}
-    if not allow_synthetic:
-        assert all(check.values())
-
-
-def test_get_dataset_path() -> None:
-    name = nstat.list_datasets()[0]
-    path = nstat.get_dataset_path(name)
-    assert path.exists()
diff --git a/python/tests/test_docs.py b/python/tests/test_docs.py
deleted file mode 100644
index 0b9b6aa..0000000
--- a/python/tests/test_docs.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from __future__ import annotations
-
-import os
-import shutil
-import subprocess
-
-import pytest
-
-
-def test_sphinx_build(repo_root) -> None:
-    if os.environ.get("NSTAT_CI_LIGHT") == "1":
-        pytest.skip("Sphinx build already validated in dedicated CI workflow step")
-    if shutil.which("sphinx-build") is None:
-        pytest.skip("sphinx-build not available in environment")
-
-    cp = subprocess.run(
-        ["sphinx-build", "-b", "html", "python/docs", "python/docs/_build/html"],
-        cwd=str(repo_root),
-        capture_output=True,
-        text=True,
-        check=False,
-    )
-    assert cp.returncode == 0, cp.stdout + "\n" + cp.stderr
diff --git a/python/tests/test_help_topics.py b/python/tests/test_help_topics.py
deleted file mode 100644
index 426171e..0000000
--- a/python/tests/test_help_topics.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from __future__ import annotations
-
-import os
-
-import pytest
-
-from examples.help_topics._common import run_topic
-
-TOPICS = [
-    "SignalObjExamples",
-    "CovariateExamples",
-    "CovCollExamples",
-    "nSpikeTrainExamples",
-    "nstCollExamples",
-    "EventsExamples",
-    "HistoryExamples",
-    "TrialExamples",
-    "TrialConfigExamples",
-    "ConfigCollExamples",
-    "AnalysisExamples",
-    "FitResultExamples",
-    "FitResSummaryExamples",
-    "PPThinning",
-    "PSTHEstimation",
-    "ValidationDataSet",
-    "mEPSCAnalysis",
-    "PPSimExample",
-    "ExplicitStimulusWhiskerData",
-    "HippocampalPlaceCellExample",
-    "DecodingExample",
-    "DecodingExampleWithHist",
-    "StimulusDecode2D",
-    "NetworkTutorial",
-    "nSTATPaperExamples",
-]
-
-
-def test_help_topics_all_run(repo_root) -> None:
-    if os.environ.get("NSTAT_CI_LIGHT") == "1":
-        pytest.skip("Help-topic execution already validated in dedicated CI workflow step")
-    for topic in TOPICS:
-        out = run_topic(topic, repo_root)
-        assert isinstance(out, dict)
-        assert out.get("topic") == topic
diff --git a/python/tests/test_implemented_method_coverage_report.py b/python/tests/test_implemented_method_coverage_report.py
deleted file mode 100644
index 8a3f9c9..0000000
--- a/python/tests/test_implemented_method_coverage_report.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from __future__ import annotations
-
-import json
-import os
-import subprocess
-import sys
-
-import pytest
-
-
-def test_implemented_method_coverage_report(repo_root) -> None:
-    if os.environ.get("NSTAT_CI_LIGHT") == "1":
-        pytest.skip("Method coverage report already generated in dedicated CI workflow step")
-    cp = subprocess.run(
-        [sys.executable, "python/tools/generate_implemented_method_coverage.py"],
-        cwd=str(repo_root),
-        capture_output=True,
-        text=True,
-        check=True,
-    )
-    payload = json.loads(cp.stdout)
-    assert payload["pass"] is True
-    assert payload["summary"]["missing_in_smoke_count"] == 0
-    assert payload["summary"]["missing_doc_class_count"] == 0
diff --git a/python/tests/test_implemented_method_smoke.py b/python/tests/test_implemented_method_smoke.py
deleted file mode 100644
index 8474707..0000000
--- a/python/tests/test_implemented_method_smoke.py
+++ /dev/null
@@ -1,175 +0,0 @@
-from __future__ import annotations
-
-import json
-import os
-from pathlib import Path
-
-import numpy as np
-import pytest
-
-from nstat import Analysis, CIFModel, ConfigCollection, Covariate, CovariateCollection, SpikeTrain, SpikeTrainCollection, Trial, TrialConfig
-from nstat.DecodingAlgorithms import DecodingAlgorithms
-from nstat.ConfidenceInterval import ConfidenceInterval
-from nstat.cif import CIF
-from nstat.events import Events
-from nstat.fit import FitResSummary, FitResult
-from nstat.history import History
-from nstat.trial import CovColl
-
-
-COVERED_IMPLEMENTED_METHODS = {
-    ("nstColl", "dataToMatrix"),
-    ("CovColl", "addToColl"),
-    ("CovColl", "dataToMatrix"),
-    ("CovColl", "getCov"),
-    ("TrialConfig", "setName"),
-    ("ConfigColl", "addConfig"),
-    ("ConfigColl", "getConfig"),
-    ("ConfigColl", "getConfigNames"),
-    ("Trial", "getSpikeVector"),
-    ("History", "computeHistory"),
-    ("Events", "fromStructure"),
-    ("Events", "plot"),
-    ("Events", "toStructure"),
-    ("ConfidenceInterval", "setColor"),
-    ("CIF", "simulateCIFByThinningFromLambda"),
-    ("FitResult", "KSPlot"),
-    ("FitResult", "fromStructure"),
-    ("FitResult", "getCoeffs"),
-    ("FitResult", "getHistCoeffs"),
-    ("FitResult", "mergeResults"),
-    ("FitResult", "plotCoeffs"),
-    ("FitResult", "plotInvGausTrans"),
-    ("FitResult", "plotResidual"),
-    ("FitResult", "plotResults"),
-    ("FitResult", "plotSeqCorr"),
-    ("FitResult", "toStructure"),
-    ("FitResSummary", "getDiffAIC"),
-    ("FitResSummary", "getDiffBIC"),
-    ("FitResSummary", "plotSummary"),
-    ("Analysis", "RunAnalysisForAllNeurons"),
-    ("Analysis", "RunAnalysisForNeuron"),
-    ("DecodingAlgorithms", "PPDecodeFilter"),
-    ("DecodingAlgorithms", "PPDecodeFilterLinear"),
-    ("DecodingAlgorithms", "kalman_filter"),
-}
-
-
-def _build_trial_and_fits() -> tuple[Trial, ConfigCollection, list[FitResult]]:
-    t = np.arange(0.0, 1.0, 0.01)
-    cov = Covariate(t, np.sin(2.0 * np.pi * t), "stim", "time", "s", "a.u.", ["stim"])
-    cov_coll = CovariateCollection([cov])
-
-    st1 = SpikeTrain(np.array([0.1, 0.2, 0.5]), name="n1", binwidth=0.01, minTime=0.0, maxTime=1.0)
-    st2 = SpikeTrain(np.array([0.15, 0.35, 0.75]), name="n2", binwidth=0.01, minTime=0.0, maxTime=1.0)
-    spikes = SpikeTrainCollection([st1, st2])
-    trial = Trial(spike_collection=spikes, covariate_collection=cov_coll)
-
-    cfg = TrialConfig(covMask=["stim"], sampleRate=100.0, name="cfg1")
-    cfgs = ConfigCollection([cfg])
-    fits = Analysis.run_analysis_for_all_neurons(trial, cfgs)
-    return trial, cfgs, fits
-
-
-def _implemented_from_matrix(repo_root: Path) -> set[tuple[str, str]]:
-    matrix_path = repo_root / "python" / "reports" / "method_parity_matrix.json"
-    payload = json.loads(matrix_path.read_text(encoding="utf-8"))
-    out: set[tuple[str, str]] = set()
-    for cls in payload.get("classes", []):
-        matlab_class = str(cls.get("matlab_class", ""))
-        for row in cls.get("methods", []):
-            if row.get("status") == "implemented":
-                out.add((matlab_class, str(row.get("matlab_method", ""))))
-    return out
-
-
-def test_implemented_method_set_matches_matrix(repo_root) -> None:
-    implemented = _implemented_from_matrix(repo_root)
-    assert implemented == COVERED_IMPLEMENTED_METHODS
-
-
-def test_implemented_methods_smoke_execute() -> None:
-    if os.environ.get("NSTAT_CI_LIGHT") == "1":
-        pytest.skip("Heavy method execution smoke is skipped in CI-light mode")
-    trial, cfgs, fits = _build_trial_and_fits()
-
-    # nstColl / Trial / CovColl / TrialConfig / ConfigColl / History
-    edges = np.linspace(0.0, 1.0, 11)
-    _ = trial.spike_collection.dataToMatrix(edges)
-    cov_coll = CovColl()
-    cov = Covariate(np.linspace(0.0, 1.0, 11), np.linspace(0.0, 1.0, 11), "c1", "time", "s", "a.u.", ["c1"])
-    cov_coll.addToColl(cov)
-    _, x, _ = cov_coll.dataToMatrix()
-    assert x.shape[0] == 11
-    assert cov_coll.getCov("c1").name == "c1"
-
-    cfg = TrialConfig(covMask=["stim"], sampleRate=100.0)
-    cfg.setName("renamed")
-    assert cfg.name == "renamed"
-
-    cfg_coll = ConfigCollection([])
-    cfg_coll.addConfig(cfg)
-    assert cfg_coll.getConfig(1).name == "renamed"
-    assert cfg_coll.getConfigNames() == ["renamed"]
-    _ = trial.getSpikeVector(edges, neuron_index=1)
-
-    hist = History([1, 2])
-    hist_cov = hist.computeHistory(trial.spike_collection.getNST(1))
-    assert hist_cov.dimension == 2
-
-    # Events / ConfidenceInterval
-    ev = Events([0.1, 0.2], labels=["a", "b"])
-    ev_struct = ev.toStructure()
-    ev2 = Events.fromStructure(ev_struct)
-    ev2.plot()
-    assert ev2.labels == ["a", "b"]
-
-    ci = ConfidenceInterval([0.0, 1.0], [[0.1, 0.2], [0.2, 0.3]])
-    ci.setColor("g")
-    assert ci.color == "g"
-
-    # CIF
-    lam = Covariate(np.linspace(0.0, 1.0, 101), np.full(101, 5.0), "lam", "time", "s", "Hz", ["lam"])
-    sim = CIF.simulateCIFByThinningFromLambda(lam, numRealizations=2)
-    assert sim.numSpikeTrains == 2
-
-    # FitResult + FitResSummary + Analysis MATLAB aliases
-    fit = fits[0]
-    _ = fit.getCoeffs()
-    _ = fit.getHistCoeffs()
-    fit.plotResults()
-    fit.KSPlot()
-    fit.plotResidual()
-    fit.plotInvGausTrans()
-    fit.plotSeqCorr()
-    fit.plotCoeffs()
-
-    merged = fit.mergeResults(fit)
-    assert merged.numResults == fit.numResults * 2
-
-    struct_payload = fit.toStructure()
-    fit_roundtrip = FitResult.fromStructure(struct_payload)
-    assert fit_roundtrip.numResults == fit.numResults
-
-    summary = FitResSummary(fits)
-    _ = summary.getDiffAIC()
-    _ = summary.getDiffBIC()
-    summary.plotSummary()
-
-    fit_single = Analysis.RunAnalysisForNeuron(trial, 1, cfgs)
-    fit_all = Analysis.RunAnalysisForAllNeurons(trial, cfgs)
-    assert fit_single.numResults >= 1
-    assert len(fit_all) == trial.spike_collection.num_spike_trains
-
-    # DecodingAlgorithms
-    y = np.zeros((8, 1))
-    a = np.eye(1)
-    h = np.eye(1)
-    q = np.eye(1) * 0.01
-    r = np.eye(1) * 0.1
-    x0 = np.zeros(1)
-    p0 = np.eye(1)
-    out0 = DecodingAlgorithms.kalman_filter(y, a, h, q, r, x0, p0)
-    out1 = DecodingAlgorithms.PPDecodeFilter(y, a, h, q, r, x0, p0)
-    out2 = DecodingAlgorithms.PPDecodeFilterLinear(y, a, h, q, r, x0, p0)
-    assert out0["state"].shape == out1["state"].shape == out2["state"].shape
diff --git a/python/tests/test_notebooks.py b/python/tests/test_notebooks.py
deleted file mode 100644
index f1bb376..0000000
--- a/python/tests/test_notebooks.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from __future__ import annotations
-
-import json
-import os
-import subprocess
-import sys
-
-import pytest
-
-
-def test_generated_notebooks_execute(repo_root) -> None:
-    if os.environ.get("NSTAT_CI_LIGHT") == "1":
-        pytest.skip("Notebook validation already executed in dedicated CI workflow step")
-    cp = subprocess.run(
-        [sys.executable, "python/tools/verify_examples_notebooks.py"],
-        cwd=str(repo_root),
-        capture_output=True,
-        text=True,
-        check=True,
-    )
-    report = json.loads(cp.stdout)
-    assert report["total_examples"] == 25
-    assert report["python_modules_ok"] == 25
-    assert report["notebooks_ok"] == 25
-    assert report["topic_alignment_ok"] == 25
diff --git a/python/tests/test_reports.py b/python/tests/test_reports.py
deleted file mode 100644
index a2b4c81..0000000
--- a/python/tests/test_reports.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from __future__ import annotations
-
-import json
-import os
-import subprocess
-import sys
-from pathlib import Path
-
-import pytest
-
-
-def test_phase0_reports_generation(repo_root) -> None:
-    if os.environ.get("NSTAT_CI_LIGHT") == "1":
-        pytest.skip("Report generation already executed in dedicated CI workflow step")
-    subprocess.run([sys.executable, "python/tools/freeze_port_baseline.py"], cwd=str(repo_root), check=True)
-    subprocess.run([sys.executable, "python/tools/generate_method_parity_matrix.py"], cwd=str(repo_root), check=True)
-
-    snapshot = json.loads((repo_root / "python/reports/port_baseline_snapshot.json").read_text(encoding="utf-8"))
-    matrix = json.loads((repo_root / "python/reports/method_parity_matrix.json").read_text(encoding="utf-8"))
-
-    assert "reports" in snapshot
-    assert matrix["summary"]["total"] > 0
-    assert len(matrix["classes"]) >= 10
diff --git a/python/tools/debug_parity_blocks.py b/python/tools/debug_parity_blocks.py
deleted file mode 100644
index f3f45fd..0000000
--- a/python/tools/debug_parity_blocks.py
+++ /dev/null
@@ -1,310 +0,0 @@
-from __future__ import annotations
-
-import argparse
-import json
-import os
-import subprocess
-import sys
-import time
-from collections import Counter
-from collections import deque
-from pathlib import Path
-from typing import Any
-
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-VERIFY_SCRIPT = REPO_ROOT / "python" / "tools" / "verify_python_vs_matlab_similarity.py"
-REPORT_DIR = REPO_ROOT / "python" / "reports"
-DEFAULT_OUTPUT = REPORT_DIR / "parity_block_benchmark_report.json"
-
-BLOCKS: list[tuple[str, list[str]]] = [
-    (
-        "core_smoke",
-        ["TrialConfigExamples", "ConfigCollExamples", "FitResultExamples", "FitResSummaryExamples"],
-    ),
-    (
-        "timeout_front",
-        [
-            "SignalObjExamples",
-            "CovariateExamples",
-            "CovCollExamples",
-            "nSpikeTrainExamples",
-            "nstCollExamples",
-            "EventsExamples",
-            "HistoryExamples",
-            "TrialExamples",
-            "AnalysisExamples",
-        ],
-    ),
-    (
-        "graphics_mid",
-        [
-            "PPThinning",
-            "PSTHEstimation",
-            "ValidationDataSet",
-            "mEPSCAnalysis",
-            "PPSimExample",
-            "ExplicitStimulusWhiskerData",
-            "HippocampalPlaceCellExample",
-            "DecodingExample",
-        ],
-    ),
-    (
-        "heavy_tail",
-        ["DecodingExampleWithHist", "StimulusDecode2D", "NetworkTutorial", "nSTATPaperExamples"],
-    ),
-    ("full_suite", []),
-]
-
-
-def _classify_matlab_failure(err: str) -> str:
-    e = (err or "").strip()
-    if not e:
-        return "unknown"
-    if e == "matlab_timeout":
-        return "timeout"
-    if "libmwhandle_graphics" in e or "MATLAB is exiting because of fatal error" in e:
-        return "graphics_crash"
-    if "matlab_json_missing" in e:
-        return "json_missing"
-    return "other_error"
-
-
-def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
-    parser = argparse.ArgumentParser(
-        description="Run staged local parity blocks to isolate slow/failing MATLAB help topics."
-    )
-    parser.add_argument(
-        "--blocks",
-        nargs="+",
-        default=[name for name, _ in BLOCKS],
-        help="Block names to run. Defaults to all blocks.",
-    )
-    parser.add_argument(
-        "--matlab-extra-args",
-        default="-maca64 -nodisplay -noFigureWindows -softwareopengl",
-        help="Value for NSTAT_MATLAB_EXTRA_ARGS.",
-    )
-    parser.add_argument(
-        "--force-m-help-scripts",
-        action="store_true",
-        default=True,
-        help="Set NSTAT_FORCE_M_HELP_SCRIPTS=1 (default on).",
-    )
-    parser.add_argument(
-        "--no-force-m-help-scripts",
-        action="store_true",
-        help="Disable NSTAT_FORCE_M_HELP_SCRIPTS for this run.",
-    )
-    parser.add_argument(
-        "--default-topic-timeout",
-        type=int,
-        default=120,
-        help="Default per-topic timeout seconds passed to verify script.",
-    )
-    parser.add_argument(
-        "--set-actions-runner-svc",
-        action="store_true",
-        help="Set ACTIONS_RUNNER_SVC=1 for closer parity with self-hosted runner service mode.",
-    )
-    parser.add_argument(
-        "--output",
-        default=str(DEFAULT_OUTPUT.relative_to(REPO_ROOT)),
-        help="Output JSON report path (repo-relative or absolute).",
-    )
-    parser.add_argument(
-        "--no-stream-verify-output",
-        action="store_true",
-        help="Disable passthrough of verify-script output while each block is running.",
-    )
-    return parser.parse_args(argv)
-
-
-def _resolve_output(path_arg: str) -> Path:
-    out = Path(path_arg)
-    if not out.is_absolute():
-        out = REPO_ROOT / out
-    out.parent.mkdir(parents=True, exist_ok=True)
-    return out
-
-
-def _block_map() -> dict[str, list[str]]:
-    return {name: topics for name, topics in BLOCKS}
-
-
-def _run_block(
-    block_name: str,
-    topics: list[str],
-    matlab_extra_args: str,
-    force_m_help_scripts: bool,
-    default_topic_timeout: int,
-    set_actions_runner_svc: bool,
-    stream_verify_output: bool,
-) -> dict[str, Any]:
-    block_report = REPORT_DIR / f"parity_block_{block_name}.json"
-    cmd = [
-        sys.executable,
-        str(VERIFY_SCRIPT),
-        "--default-topic-timeout",
-        str(default_topic_timeout),
-        "--report-path",
-        str(block_report.relative_to(REPO_ROOT)),
-    ]
-    if topics:
-        cmd.extend(["--topics", *topics])
-
-    env = os.environ.copy()
-    env["NSTAT_MATLAB_EXTRA_ARGS"] = matlab_extra_args
-    env["NSTAT_FORCE_M_HELP_SCRIPTS"] = "1" if force_m_help_scripts else "0"
-    if set_actions_runner_svc:
-        env["ACTIONS_RUNNER_SVC"] = "1"
-
-    t0 = time.time()
-    stdout_tail = ""
-    stderr_tail = ""
-    if stream_verify_output:
-        stream_tail: deque[str] = deque(maxlen=40)
-        proc = subprocess.Popen(
-            cmd,
-            cwd=str(REPO_ROOT),
-            env=env,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.STDOUT,
-            text=True,
-            bufsize=1,
-        )
-        if proc.stdout is not None:
-            for line in proc.stdout:
-                line = line.rstrip("\n")
-                print(f"[verify:{block_name}] {line}", flush=True)
-                stream_tail.append(line)
-        return_code = int(proc.wait())
-        stdout_tail = "\n".join(stream_tail)
-    else:
-        cp = subprocess.run(
-            cmd,
-            cwd=str(REPO_ROOT),
-            env=env,
-            capture_output=True,
-            text=True,
-            check=False,
-        )
-        return_code = int(cp.returncode)
-        stdout_tail = "\n".join((cp.stdout or "").splitlines()[-20:])
-        stderr_tail = "\n".join((cp.stderr or "").splitlines()[-20:])
-    wall_s = float(time.time() - t0)
-
-    report_payload: dict[str, Any] | None = None
-    if block_report.exists():
-        report_payload = json.loads(block_report.read_text(encoding="utf-8"))
-
-    result: dict[str, Any] = {
-        "block": block_name,
-        "topics_requested": topics,
-        "command": cmd,
-        "return_code": return_code,
-        "wall_runtime_s": wall_s,
-        "report_path": str(block_report.relative_to(REPO_ROOT)),
-        "stdout_tail": stdout_tail,
-        "stderr_tail": stderr_tail,
-    }
-    if report_payload is None:
-        result["report_missing"] = True
-        return result
-
-    help_rows = report_payload.get("helpfile_similarity", {}).get("rows", [])
-    failed = [r for r in help_rows if not bool(r.get("matlab_ok"))]
-    failure_counter = Counter(_classify_matlab_failure(str(r.get("matlab_error", ""))) for r in failed)
-    slowest = sorted(
-        [
-            {
-                "topic": str(r.get("topic", "")),
-                "runtime_s": float(r.get("matlab_runtime_s") or 0.0),
-                "timeout_s": int(r.get("matlab_timeout_s") or 0),
-                "matlab_ok": bool(r.get("matlab_ok")),
-            }
-            for r in help_rows
-        ],
-        key=lambda x: x["runtime_s"],
-        reverse=True,
-    )[:5]
-
-    result["summary"] = {
-        "class_similarity": report_payload.get("class_similarity", {}).get("summary", {}),
-        "help_similarity": report_payload.get("helpfile_similarity", {}).get("summary", {}),
-        "parity_contract_pass": bool(report_payload.get("parity_contract", {}).get("pass", False)),
-        "regression_gate_pass": bool(report_payload.get("regression_gate", {}).get("pass", False)),
-        "matlab_failure_types": dict(failure_counter),
-        "matlab_failed_topics": [str(r.get("topic", "")) for r in failed],
-        "slowest_topics": slowest,
-    }
-    return result
-
-
-def main(argv: list[str] | None = None) -> int:
-    args = _parse_args(argv)
-    block_topics = _block_map()
-    unknown = [b for b in args.blocks if b not in block_topics]
-    if unknown:
-        print(f"unknown block(s): {unknown}", file=sys.stderr)
-        return 2
-    if args.default_topic_timeout <= 0:
-        print("--default-topic-timeout must be positive", file=sys.stderr)
-        return 2
-
-    force_m_help_scripts = bool(args.force_m_help_scripts and not args.no_force_m_help_scripts)
-    out_path = _resolve_output(args.output)
-    started = time.time()
-    results: list[dict[str, Any]] = []
-
-    for block_name in args.blocks:
-        topics = block_topics[block_name]
-        print(f"[block] {block_name} ({'all topics' if not topics else len(topics)})", flush=True)
-        res = _run_block(
-            block_name=block_name,
-            topics=topics,
-            matlab_extra_args=args.matlab_extra_args,
-            force_m_help_scripts=force_m_help_scripts,
-            default_topic_timeout=args.default_topic_timeout,
-            set_actions_runner_svc=args.set_actions_runner_svc,
-            stream_verify_output=not bool(args.no_stream_verify_output),
-        )
-        results.append(res)
-        summary = res.get("summary", {})
-        help_summary = summary.get("help_similarity", {})
-        print(
-            json.dumps(
-                {
-                    "block": block_name,
-                    "return_code": res.get("return_code"),
-                    "wall_runtime_s": round(float(res.get("wall_runtime_s", 0.0)), 3),
-                    "help_matlab_ok": help_summary.get("matlab_ok"),
-                    "help_total": help_summary.get("total_topics"),
-                    "failure_types": summary.get("matlab_failure_types", {}),
-                }
-            ),
-            flush=True,
-        )
-
-    payload = {
-        "generated_at_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
-        "repo_root": str(REPO_ROOT),
-        "matlab_extra_args": args.matlab_extra_args,
-        "force_m_help_scripts": force_m_help_scripts,
-        "default_topic_timeout": int(args.default_topic_timeout),
-        "set_actions_runner_svc": bool(args.set_actions_runner_svc),
-        "blocks_requested": args.blocks,
-        "total_wall_runtime_s": float(time.time() - started),
-        "results": results,
-    }
-    out_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
-    try:
-        out_print = str(out_path.relative_to(REPO_ROOT))
-    except ValueError:
-        out_print = str(out_path)
-    print(json.dumps({"report": out_print, "blocks": len(results)}, indent=2))
-    return 0
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/tools/freeze_port_baseline.py b/python/tools/freeze_port_baseline.py
deleted file mode 100644
index 676d131..0000000
--- a/python/tools/freeze_port_baseline.py
+++ /dev/null
@@ -1,70 +0,0 @@
-from __future__ import annotations
-
-import hashlib
-import json
-from datetime import datetime, timezone
-from pathlib import Path
-from typing import Any
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-REPORT_DIR = REPO_ROOT / "python" / "reports"
-
-SOURCE_REPORTS = {
-    "mfile_parity": "mfile_parity_report.json",
-    "examples_notebooks": "examples_notebook_verification.json",
-    "matlab_smoke_input": "matlab_smoke_input.json",
-}
-
-
-def _sha256(path: Path) -> str:
-    h = hashlib.sha256()
-    with path.open("rb") as f:
-        for chunk in iter(lambda: f.read(1024 * 1024), b""):
-            h.update(chunk)
-    return h.hexdigest()
-
-
-def _read_json(path: Path) -> Any:
-    return json.loads(path.read_text(encoding="utf-8"))
-
-
-def build_snapshot() -> dict[str, Any]:
-    payload: dict[str, Any] = {
-        "generated_at_utc": datetime.now(timezone.utc).isoformat(),
-        "reports": {},
-    }
-
-    for key, filename in SOURCE_REPORTS.items():
-        report_path = REPORT_DIR / filename
-        if not report_path.exists():
-            payload["reports"][key] = {
-                "path": str(report_path.relative_to(REPO_ROOT)),
-                "exists": False,
-            }
-            continue
-
-        data = _read_json(report_path)
-        entry: dict[str, Any] = {
-            "path": str(report_path.relative_to(REPO_ROOT)),
-            "exists": True,
-            "sha256": _sha256(report_path),
-            "size_bytes": report_path.stat().st_size,
-        }
-        if isinstance(data, dict) and "summary" in data:
-            entry["summary"] = data["summary"]
-        payload["reports"][key] = entry
-
-    return payload
-
-
-def main() -> int:
-    REPORT_DIR.mkdir(parents=True, exist_ok=True)
-    out = REPORT_DIR / "port_baseline_snapshot.json"
-    snapshot = build_snapshot()
-    out.write_text(json.dumps(snapshot, indent=2), encoding="utf-8")
-    print(json.dumps({"report": str(out.relative_to(REPO_ROOT))}, indent=2))
-    return 0
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/tools/freeze_similarity_baseline.py b/python/tools/freeze_similarity_baseline.py
deleted file mode 100644
index bb15f48..0000000
--- a/python/tools/freeze_similarity_baseline.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from __future__ import annotations
-
-import json
-from datetime import datetime, timezone
-from pathlib import Path
-
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-REPORT_PATH = REPO_ROOT / "python" / "reports" / "python_vs_matlab_similarity_report.json"
-BASELINE_PATH = REPO_ROOT / "python" / "reports" / "python_vs_matlab_similarity_baseline.json"
-
-
-def main() -> int:
-    if not REPORT_PATH.exists():
-        raise FileNotFoundError(f"Missing report: {REPORT_PATH}")
-
-    payload = json.loads(REPORT_PATH.read_text(encoding="utf-8"))
-    baseline = {
-        "frozen_at_utc": datetime.now(timezone.utc).isoformat(),
-        "source_report": str(REPORT_PATH.relative_to(REPO_ROOT)),
-        "report": payload,
-    }
-    BASELINE_PATH.write_text(json.dumps(baseline, indent=2), encoding="utf-8")
-    print(f"wrote {BASELINE_PATH.relative_to(REPO_ROOT)}")
-    return 0
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/tools/generate_example_notebooks.py b/python/tools/generate_example_notebooks.py
deleted file mode 100644
index d784ef0..0000000
--- a/python/tools/generate_example_notebooks.py
+++ /dev/null
@@ -1,146 +0,0 @@
-from __future__ import annotations
-
-import json
-import xml.etree.ElementTree as ET
-from pathlib import Path
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-TOC_PATH = REPO_ROOT / "helpfiles" / "helptoc.xml"
-NB_ROOT = REPO_ROOT / "python" / "notebooks" / "helpfiles"
-SRC_ROOT = REPO_ROOT / "python" / "examples" / "help_topics"
-
-
-def _example_topics() -> list[tuple[str, str]]:
-    tree = ET.parse(TOC_PATH)
-    root = tree.getroot()
-    examples = None
-    for item in root.iter("tocitem"):
-        if item.attrib.get("id") == "nstat_examples":
-            examples = item
-            break
-    if examples is None:
-        raise RuntimeError("Could not find examples section in helptoc.xml")
-
-    topics: list[tuple[str, str]] = []
-    for item in examples.findall("tocitem"):
-        title = " ".join("".join(item.itertext()).split())
-        target = item.attrib.get("target", "")
-        if not target:
-            continue
-        topics.append((title, target))
-    return topics
-
-
-def _build_notebook(title: str, stem: str, matlab_target: str) -> dict:
-    code_setup = (
-        "from pathlib import Path\n"
-        "import sys\n"
-        "import json\n\n"
-        "def find_repo_root(start: Path) -> Path:\n"
-        "    cur = start.resolve()\n"
-        "    for p in [cur, *cur.parents]:\n"
-        "        if (p / '.git').exists() and (p / 'python').exists() and (p / 'helpfiles').exists():\n"
-        "            return p\n"
-        "    raise RuntimeError('Could not find nSTAT repo root from notebook cwd')\n\n"
-        "repo_root = find_repo_root(Path.cwd())\n"
-        "py_root = repo_root / 'python'\n"
-        "if str(py_root) not in sys.path:\n"
-        "    sys.path.insert(0, str(py_root))\n"
-        "print('repo_root =', repo_root)\n"
-    )
-
-    code_run = (
-        f"from examples.help_topics.{stem} import run\n"
-        "out = run(repo_root=repo_root)\n"
-        "print(json.dumps(out, indent=2, default=str))\n"
-    )
-
-    code_check = (
-        "assert isinstance(out, dict)\n"
-        "assert 'topic' in out\n"
-        "print('Notebook execution check: PASS')\n"
-    )
-
-    return {
-        "cells": [
-            {
-                "cell_type": "markdown",
-                "metadata": {},
-                "source": [
-                    f"# {title}\\n",
-                    "\\n",
-                    "Executable Python notebook generated from source help-topic scripts.\\n",
-                    f"MATLAB help target: `{matlab_target}`\\n",
-                ],
-            },
-            {
-                "cell_type": "code",
-                "execution_count": None,
-                "metadata": {},
-                "outputs": [],
-                "source": code_setup.splitlines(keepends=True),
-            },
-            {
-                "cell_type": "code",
-                "execution_count": None,
-                "metadata": {},
-                "outputs": [],
-                "source": code_run.splitlines(keepends=True),
-            },
-            {
-                "cell_type": "code",
-                "execution_count": None,
-                "metadata": {},
-                "outputs": [],
-                "source": code_check.splitlines(keepends=True),
-            },
-        ],
-        "metadata": {
-            "kernelspec": {
-                "display_name": "Python 3",
-                "language": "python",
-                "name": "python3",
-            },
-            "language_info": {
-                "name": "python",
-                "version": "3",
-            },
-        },
-        "nbformat": 4,
-        "nbformat_minor": 5,
-    }
-
-
-def main() -> int:
-    NB_ROOT.mkdir(parents=True, exist_ok=True)
-    topics = _example_topics()
-
-    generated = 0
-    missing_sources: list[str] = []
-    for title, target in topics:
-        stem = Path(target).stem
-        source_mod = SRC_ROOT / f"{stem}.py"
-        if not source_mod.exists():
-            missing_sources.append(stem)
-            continue
-
-        nb = _build_notebook(title, stem, target)
-        out = NB_ROOT / f"{stem}.ipynb"
-        out.write_text(json.dumps(nb, indent=2), encoding="utf-8")
-        generated += 1
-
-    report = {
-        "total_topics": len(topics),
-        "generated": generated,
-        "missing_sources": missing_sources,
-        "output_dir": str(NB_ROOT.relative_to(REPO_ROOT)),
-    }
-    print(json.dumps(report, indent=2))
-
-    if missing_sources:
-        return 1
-    return 0
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/tools/generate_help_topic_docs.py b/python/tools/generate_help_topic_docs.py
deleted file mode 100644
index 3f8aa90..0000000
--- a/python/tools/generate_help_topic_docs.py
+++ /dev/null
@@ -1,206 +0,0 @@
-from __future__ import annotations
-
-import re
-import xml.etree.ElementTree as ET
-from pathlib import Path
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-TOC_PATH = REPO_ROOT / "helpfiles" / "helptoc.xml"
-DOCS_ROOT = REPO_ROOT / "python" / "docs"
-TOPICS_DIR = DOCS_ROOT / "topics"
-
-CLASS_API_MAP = {
-    "SignalObj": "nstat.signal.Signal",
-    "Covariate": "nstat.signal.Covariate",
-    "CovColl": "nstat.trial.CovariateCollection",
-    "nSpikeTrain": "nstat.spikes.SpikeTrain",
-    "nspikeTrain": "nstat.spikes.SpikeTrain",
-    "nstColl": "nstat.spikes.SpikeTrainCollection",
-    "Events": "nstat.events.Events",
-    "History": "nstat.history.HistoryBasis",
-    "Trial": "nstat.trial.Trial",
-    "TrialConfig": "nstat.trial.TrialConfig",
-    "ConfigColl": "nstat.trial.ConfigCollection",
-    "Analysis": "nstat.analysis.Analysis",
-    "FitResult": "nstat.fit.FitResult",
-    "FitResSummary": "nstat.fit.FitSummary",
-    "PPThinning": "nstat.cif.CIFModel.simulate",
-    "PSTHEstimation": "nstat.spikes.SpikeTrainCollection.psth",
-    "DecodingExample": "nstat.decoding.DecoderSuite",
-    "DecodingExampleWithHist": "nstat.decoding.DecoderSuite",
-    "StimulusDecode2D": "nstat.decoding.DecoderSuite",
-}
-
-
-def _slugify(value: str) -> str:
-    value = value.strip().lower()
-    value = re.sub(r"[^a-z0-9]+", "_", value)
-    return value.strip("_") or "topic"
-
-
-def _iter_topics(root: ET.Element):
-    for item in root.iter("tocitem"):
-        target = item.attrib.get("target", "").strip()
-        title = " ".join("".join(item.itertext()).split())
-        if not target:
-            continue
-        yield title, target
-
-
-def _mapping_for_target(target: str) -> tuple[str, str]:
-    stem = Path(target).stem
-    base = stem[:-8] if stem.endswith("Examples") else stem
-    matlab_api = base
-    python_api = CLASS_API_MAP.get(base, "nstat (canonical module by topic)")
-    return matlab_api, python_api
-
-
-def _topic_body(title: str, target: str) -> str:
-    is_example = "example" in target.lower() or target.lower().endswith("examples.html")
-    notebook_name = Path(target).stem
-    matlab_api, python_api = _mapping_for_target(target)
-    lines = [
-        title,
-        "=" * len(title),
-        "",
-        f"MATLAB help target: ``{target}``",
-        "",
-        "Concept",
-        "-------",
-        "This page mirrors the corresponding MATLAB help topic and documents the Python standalone equivalent.",
-        "",
-        "API Mapping (MATLAB -> Python)",
-        "------------------------------",
-        ".. list-table::",
-        "   :header-rows: 1",
-        "",
-        "   * - MATLAB API",
-        "     - Python API",
-        f"   * - ``{matlab_api}``",
-        f"     - ``{python_api}``",
-        "",
-        "Migration Callout",
-        "-----------------",
-        "- MATLAB-style compatibility adapters remain importable for one major cycle and emit ``DeprecationWarning``.",
-        "- Prefer canonical Python modules under ``nstat`` for new code.",
-        "",
-        "Python Usage",
-        "------------",
-        ".. code-block:: python",
-        "",
-        "   import nstat",
-        "   print(nstat.__all__[:5])",
-        "",
-        "Data Requirements",
-        "-----------------",
-        "Use ``nstat.datasets.list_datasets()`` and ``nstat.datasets.get_dataset_path(...)`` to access bundled datasets.",
-        "",
-        "Expected Outputs",
-        "----------------",
-        "This topic should execute without MATLAB and produce deterministic summary metrics where applicable.",
-        "",
-        "Known Differences",
-        "-----------------",
-        "- Some legacy plotting helpers are represented via notebooks/docs instead of full method parity.",
-        "- Numerical outputs may vary if random seeds, bin widths, or sample rates differ from MATLAB defaults.",
-        "",
-    ]
-
-    if is_example:
-        lines.extend(
-            [
-                "Notebook",
-                "--------",
-                f"A generated executable notebook is available at ``python/notebooks/helpfiles/{notebook_name}.ipynb``.",
-                "",
-            ]
-        )
-
-    return "\n".join(lines)
-
-
-def generate_docs() -> list[Path]:
-    if not TOC_PATH.exists():
-        raise FileNotFoundError(f"Missing TOC file: {TOC_PATH}")
-
-    tree = ET.parse(TOC_PATH)
-    toc = tree.getroot()
-
-    TOPICS_DIR.mkdir(parents=True, exist_ok=True)
-    created: list[Path] = []
-    topic_entries: list[tuple[str, str, Path]] = []
-    seen_slugs: set[str] = set()
-
-    for title, target in _iter_topics(toc):
-        if target.startswith("http://") or target.startswith("https://"):
-            continue
-        slug = _slugify(Path(target).stem)
-        if slug in seen_slugs:
-            continue
-        seen_slugs.add(slug)
-        out = TOPICS_DIR / f"{slug}.rst"
-        out.write_text(_topic_body(title, target), encoding="utf-8")
-        created.append(out)
-        topic_entries.append((title, target, out))
-
-    topic_index_lines = [
-        "Help Topics",
-        "===========",
-        "",
-        ".. toctree::",
-        "   :maxdepth: 1",
-        "",
-    ]
-    for _, _, out in topic_entries:
-        topic_index_lines.append(f"   topics/{out.stem}")
-
-    (DOCS_ROOT / "help_topics.rst").write_text("\n".join(topic_index_lines) + "\n", encoding="utf-8")
-
-    api_lines = [
-        "API Reference",
-        "============",
-        "",
-        ".. code-block:: python",
-        "",
-        "   import nstat",
-        "   print(nstat.__all__)",
-        "",
-        "Canonical modules include:",
-        "",
-        "- ``nstat.signal``",
-        "- ``nstat.spikes``",
-        "- ``nstat.trial``",
-        "- ``nstat.analysis``",
-        "- ``nstat.fit``",
-        "- ``nstat.cif``",
-        "- ``nstat.decoding``",
-        "- ``nstat.datasets``",
-    ]
-    (DOCS_ROOT / "api.rst").write_text("\n".join(api_lines) + "\n", encoding="utf-8")
-
-    index_lines = [
-        "nSTAT Python Documentation",
-        "==========================",
-        "",
-        "Standalone Python port of nSTAT with MATLAB-help topic coverage and executable notebooks.",
-        "",
-        ".. toctree::",
-        "   :maxdepth: 2",
-        "",
-        "   api",
-        "   help_topics",
-    ]
-    (DOCS_ROOT / "index.rst").write_text("\n".join(index_lines) + "\n", encoding="utf-8")
-
-    return created
-
-
-def main() -> int:
-    created = generate_docs()
-    print(f"generated_topics={len(created)}")
-    print(f"docs_root={DOCS_ROOT}")
-    return 0
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/tools/generate_implemented_method_coverage.py b/python/tools/generate_implemented_method_coverage.py
deleted file mode 100644
index c72164f..0000000
--- a/python/tools/generate_implemented_method_coverage.py
+++ /dev/null
@@ -1,106 +0,0 @@
-from __future__ import annotations
-
-import ast
-import json
-import subprocess
-from pathlib import Path
-from typing import Any
-
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-MATRIX_PATH = REPO_ROOT / "python" / "reports" / "method_parity_matrix.json"
-SMOKE_TEST_PATH = REPO_ROOT / "python" / "tests" / "test_implemented_method_smoke.py"
-DOCS_TOPICS_DIR = REPO_ROOT / "python" / "docs" / "topics"
-OUT_PATH = REPO_ROOT / "python" / "reports" / "implemented_method_coverage.json"
-
-DOC_CLASS_TOKENS = {
-    "SignalObj": ["signalobj", "classdefinitions"],
-    "Covariate": ["covariate", "classdefinitions"],
-    "nspikeTrain": ["nspiketrain", "classdefinitions"],
-    "nstColl": ["nstcoll", "classdefinitions"],
-    "CovColl": ["covcoll", "classdefinitions"],
-    "TrialConfig": ["trialconfig", "classdefinitions"],
-    "ConfigColl": ["configcoll", "classdefinitions"],
-    "Trial": ["trial", "classdefinitions"],
-    "History": ["history", "classdefinitions"],
-    "Events": ["events", "classdefinitions"],
-    "ConfidenceInterval": ["classdefinitions"],
-    "CIF": ["cif", "ppthinning", "classdefinitions"],
-    "FitResult": ["fitresult", "classdefinitions"],
-    "FitResSummary": ["fitressummary", "classdefinitions"],
-    "Analysis": ["analysis", "classdefinitions"],
-    "DecodingAlgorithms": ["decoding", "classdefinitions"],
-}
-
-
-def _implemented_methods(matrix: dict[str, Any]) -> set[tuple[str, str]]:
-    out: set[tuple[str, str]] = set()
-    for cls in matrix.get("classes", []):
-        matlab_class = str(cls.get("matlab_class", ""))
-        for method in cls.get("methods", []):
-            if method.get("status") == "implemented":
-                out.add((matlab_class, str(method.get("matlab_method", ""))))
-    return out
-
-
-def _load_smoke_set() -> set[tuple[str, str]]:
-    src = SMOKE_TEST_PATH.read_text(encoding="utf-8")
-    tree = ast.parse(src)
-    for node in tree.body:
-        if isinstance(node, ast.Assign):
-            for target in node.targets:
-                if isinstance(target, ast.Name) and target.id == "COVERED_IMPLEMENTED_METHODS":
-                    value = ast.literal_eval(node.value)
-                    return {(str(a), str(b)) for (a, b) in value}
-    raise RuntimeError("Could not find COVERED_IMPLEMENTED_METHODS in smoke test file")
-
-
-def _doc_class_coverage(classes: set[str]) -> dict[str, bool]:
-    topic_names = [p.stem.lower() for p in DOCS_TOPICS_DIR.glob("*.rst")]
-    out: dict[str, bool] = {}
-    for c in sorted(classes):
-        tokens = DOC_CLASS_TOKENS.get(c, [c.lower()])
-        out[c] = any(any(tok in stem for tok in tokens) for stem in topic_names)
-    return out
-
-
-def main() -> int:
-    if not MATRIX_PATH.exists():
-        subprocess.run(["python3", "python/tools/generate_method_parity_matrix.py"], cwd=str(REPO_ROOT), check=True)
-
-    matrix = json.loads(MATRIX_PATH.read_text(encoding="utf-8"))
-    implemented = _implemented_methods(matrix)
-    smoke = _load_smoke_set()
-
-    missing_in_smoke = sorted(implemented - smoke)
-    extra_in_smoke = sorted(smoke - implemented)
-
-    classes = {c for c, _ in implemented}
-    doc_cov = _doc_class_coverage(classes)
-    missing_doc_classes = sorted([c for c, ok in doc_cov.items() if not ok])
-
-    report = {
-        "summary": {
-            "implemented_method_count": len(implemented),
-            "smoke_covered_count": len(smoke),
-            "missing_in_smoke_count": len(missing_in_smoke),
-            "extra_in_smoke_count": len(extra_in_smoke),
-            "implemented_class_count": len(classes),
-            "docs_class_covered_count": sum(1 for v in doc_cov.values() if v),
-            "missing_doc_class_count": len(missing_doc_classes),
-        },
-        "missing_in_smoke": [[c, m] for (c, m) in missing_in_smoke],
-        "extra_in_smoke": [[c, m] for (c, m) in extra_in_smoke],
-        "doc_class_coverage": doc_cov,
-        "missing_doc_classes": missing_doc_classes,
-        "pass": (len(missing_in_smoke) == 0 and len(extra_in_smoke) == 0 and len(missing_doc_classes) == 0),
-    }
-
-    OUT_PATH.parent.mkdir(parents=True, exist_ok=True)
-    OUT_PATH.write_text(json.dumps(report, indent=2), encoding="utf-8")
-    print(json.dumps({"report": str(OUT_PATH.relative_to(REPO_ROOT)), "summary": report["summary"], "pass": report["pass"]}, indent=2))
-    return 0 if report["pass"] else 1
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/tools/generate_method_parity_matrix.py b/python/tools/generate_method_parity_matrix.py
deleted file mode 100644
index 032f152..0000000
--- a/python/tools/generate_method_parity_matrix.py
+++ /dev/null
@@ -1,134 +0,0 @@
-from __future__ import annotations
-
-import ast
-import json
-import re
-from pathlib import Path
-from typing import Any
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-REPORT_DIR = REPO_ROOT / "python" / "reports"
-
-CLASS_MAPPING = {
-    "SignalObj": ("SignalObj.m", "python/nstat/signal.py", "Signal"),
-    "Covariate": ("Covariate.m", "python/nstat/signal.py", "Covariate"),
-    "nspikeTrain": ("nspikeTrain.m", "python/nstat/spikes.py", "SpikeTrain"),
-    "nstColl": ("nstColl.m", "python/nstat/spikes.py", "SpikeTrainCollection"),
-    "CovColl": ("CovColl.m", "python/nstat/trial.py", "CovariateCollection"),
-    "TrialConfig": ("TrialConfig.m", "python/nstat/trial.py", "TrialConfig"),
-    "ConfigColl": ("ConfigColl.m", "python/nstat/trial.py", "ConfigCollection"),
-    "Trial": ("Trial.m", "python/nstat/trial.py", "Trial"),
-    "History": ("History.m", "python/nstat/history.py", "HistoryBasis"),
-    "Events": ("Events.m", "python/nstat/events.py", "Events"),
-    "ConfidenceInterval": ("ConfidenceInterval.m", "python/nstat/confidence_interval.py", "ConfidenceInterval"),
-    "CIF": ("CIF.m", "python/nstat/cif.py", "CIFModel"),
-    "FitResult": ("FitResult.m", "python/nstat/fit.py", "FitResult"),
-    "FitResSummary": ("FitResSummary.m", "python/nstat/fit.py", "FitSummary"),
-    "Analysis": ("Analysis.m", "python/nstat/analysis.py", "Analysis"),
-    "DecodingAlgorithms": ("DecodingAlgorithms.m", "python/nstat/decoding_algorithms.py", "DecodingAlgorithms"),
-}
-
-IMPLEMENTED_ALIAS_MAP = {
-    "runanalysisforallneurons": "run_analysis_for_all_neurons",
-    "runanalysisforneuron": "run_analysis_for_neuron",
-    "simulatecifbythinningfromlambda": "simulate",
-    "ppdecodefilter": "kalman_filter",
-    "ppdecodefilterlinear": "kalman_filter",
-    "getconfig": "get_config",
-    "addconfig": "add_config",
-    "getnst": "get_nst",
-    "getcov": "get",
-    "datatomatrix": "to_matrix",
-}
-
-
-def _normalize(name: str) -> str:
-    return re.sub(r"[^a-z0-9]", "", name.lower())
-
-
-def _matlab_methods(path: Path) -> list[str]:
-    text = path.read_text(encoding="utf-8", errors="ignore")
-    methods: list[str] = []
-    for m in re.finditer(r"^\s*function\s+(?:\[[^\]]*\]\s*=\s*|\w+\s*=\s*)?(\w+)\s*(?:\(|$)", text, flags=re.M):
-        methods.append(m.group(1))
-    return sorted(set(methods))
-
-
-def _python_methods(path: Path, class_name: str) -> list[str]:
-    src = path.read_text(encoding="utf-8", errors="ignore")
-    tree = ast.parse(src)
-    for node in tree.body:
-        if isinstance(node, ast.ClassDef) and node.name == class_name:
-            return sorted({n.name for n in node.body if isinstance(n, ast.FunctionDef)})
-    return []
-
-
-def _status_for_method(m_method: str, py_methods_norm: set[str]) -> tuple[str, str]:
-    m_norm = _normalize(m_method)
-    mapped = IMPLEMENTED_ALIAS_MAP.get(m_norm)
-
-    if m_norm in py_methods_norm:
-        return "implemented", "Method name exists in canonical Python class."
-    if mapped is not None and _normalize(mapped) in py_methods_norm:
-        return "implemented", f"Implemented via Pythonic rename: {mapped}."
-
-    if m_norm.startswith("plot") or "histogram" in m_norm or m_norm in {"dsxy2figxy"}:
-        return "intentionally_omitted", "Visualization helper is handled in notebooks/docs instead of core API."
-
-    return "planned", "Not yet implemented in canonical class; tracked for incremental parity completion."
-
-
-def build_matrix() -> dict[str, Any]:
-    rows: list[dict[str, Any]] = []
-    implemented_methods: list[dict[str, str]] = []
-    summary = {"implemented": 0, "planned": 0, "intentionally_omitted": 0, "total": 0}
-
-    for matlab_class, (m_rel, py_rel, py_class) in CLASS_MAPPING.items():
-        m_path = REPO_ROOT / m_rel
-        py_path = REPO_ROOT / py_rel
-
-        m_methods = _matlab_methods(m_path) if m_path.exists() else []
-        py_methods = _python_methods(py_path, py_class) if py_path.exists() else []
-        py_norm = {_normalize(x) for x in py_methods}
-
-        method_rows = []
-        for method in m_methods:
-            status, rationale = _status_for_method(method, py_norm)
-            method_rows.append(
-                {
-                    "matlab_method": method,
-                    "status": status,
-                    "rationale": rationale,
-                }
-            )
-            if status == "implemented":
-                implemented_methods.append({"matlab_class": matlab_class, "matlab_method": method})
-            summary[status] += 1
-            summary["total"] += 1
-
-        rows.append(
-            {
-                "matlab_class": matlab_class,
-                "matlab_source": m_rel,
-                "python_target": py_rel,
-                "python_class": py_class,
-                "python_methods": py_methods,
-                "methods": method_rows,
-            }
-        )
-
-    implemented_methods = sorted(implemented_methods, key=lambda r: (r["matlab_class"], r["matlab_method"]))
-    return {"summary": summary, "implemented_methods": implemented_methods, "classes": rows}
-
-
-def main() -> int:
-    REPORT_DIR.mkdir(parents=True, exist_ok=True)
-    out = REPORT_DIR / "method_parity_matrix.json"
-    matrix = build_matrix()
-    out.write_text(json.dumps(matrix, indent=2), encoding="utf-8")
-    print(json.dumps({"report": str(out.relative_to(REPO_ROOT)), "summary": matrix["summary"]}, indent=2))
-    return 0
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/tools/generate_translations_and_notebooks.py b/python/tools/generate_translations_and_notebooks.py
deleted file mode 100644
index 8cd61a6..0000000
--- a/python/tools/generate_translations_and_notebooks.py
+++ /dev/null
@@ -1,89 +0,0 @@
-from __future__ import annotations
-
-import json
-import re
-from pathlib import Path
-
-from generate_example_notebooks import main as generate_notebooks
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-PORT_ROOT = REPO_ROOT / "python" / "matlab_port"
-
-
-def _classify_m_file(path: Path) -> str:
-    text = path.read_text(encoding="utf-8", errors="ignore")
-    for line in text.splitlines():
-        s = line.strip()
-        if not s or s.startswith("%"):
-            continue
-        if s.startswith("classdef"):
-            return "classdef"
-        if s.startswith("function"):
-            return "function"
-        break
-    return "script"
-
-
-def _iter_matlab_sources() -> list[Path]:
-    files: list[Path] = []
-    for p in REPO_ROOT.rglob("*.m"):
-        rel = p.relative_to(REPO_ROOT)
-        if rel.parts and rel.parts[0] == "python":
-            continue
-        files.append(p)
-    return sorted(files)
-
-
-def _target_for_source(src: Path) -> str:
-    rel = src.relative_to(REPO_ROOT)
-    return str((PORT_ROOT / rel.parent / f"{src.stem}.py").relative_to(REPO_ROOT))
-
-
-def build_translation_map() -> dict[str, object]:
-    entries = []
-    counts: dict[str, int] = {}
-    for src in _iter_matlab_sources():
-        kind = _classify_m_file(src)
-        counts[kind] = counts.get(kind, 0) + 1
-        entries.append(
-            {
-                "source": str(src.relative_to(REPO_ROOT)),
-                "target": _target_for_source(src),
-                "kind": kind,
-                "status": "archived_reference",
-                "note": "matlab_port is kept as historical scaffold; canonical implementation lives in python/nstat",
-            }
-        )
-
-    return {
-        "repo_root": str(REPO_ROOT),
-        "output_root": str(PORT_ROOT),
-        "counts": {"total": len(entries), "by_kind": counts},
-        "entries": entries,
-    }
-
-
-def main() -> int:
-    PORT_ROOT.mkdir(parents=True, exist_ok=True)
-
-    mapping = build_translation_map()
-    map_path = PORT_ROOT / "TRANSLATION_MAP.json"
-    map_path.write_text(json.dumps(mapping, indent=2), encoding="utf-8")
-
-    nb_rc = generate_notebooks()
-
-    print(
-        json.dumps(
-            {
-                "translation_map": str(map_path.relative_to(REPO_ROOT)),
-                "matlab_sources": mapping["counts"]["total"],
-                "notebook_generation_rc": nb_rc,
-            },
-            indent=2,
-        )
-    )
-    return int(nb_rc)
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/tools/run_parity_ladder.sh b/python/tools/run_parity_ladder.sh
deleted file mode 100755
index 25b7143..0000000
--- a/python/tools/run_parity_ladder.sh
+++ /dev/null
@@ -1,380 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
-PYTHON_BIN="${PYTHON_BIN:-python3}"
-MATLAB_EXTRA_ARGS="${NSTAT_MATLAB_EXTRA_ARGS:--maca64 -nodisplay -noFigureWindows -softwareopengl}"
-MATLAB_BIN="${NSTAT_MATLAB_BIN:-/Applications/MATLAB_R2025b.app/bin/matlab}"
-SET_ACTIONS_RUNNER_SVC="${NSTAT_SET_ACTIONS_RUNNER_SVC:-1}"
-RUNTIME_MULTIPLIER="${NSTAT_PARITY_RUNTIME_MULTIPLIER:-2.5}"
-RETRY_TIMEOUT_BLOCKS="${NSTAT_PARITY_RETRY_TIMEOUT_BLOCKS:-0}"
-TIMEOUT_RETRY_BLOCKS="${NSTAT_PARITY_TIMEOUT_RETRY_BLOCKS:-timeout_front}"
-RETRY_RECOVERABLE_BLOCKS="${NSTAT_PARITY_RETRY_RECOVERABLE_BLOCKS:-1}"
-RECOVERABLE_RETRY_BLOCKS="${NSTAT_PARITY_RECOVERABLE_RETRY_BLOCKS:-graphics_mid,heavy_tail,full_suite}"
-RETRY_SUMMARY_PATH="${NSTAT_PARITY_RETRY_SUMMARY_PATH:-python/reports/parity_retry_summary.json}"
-
-DEFAULT_BLOCKS=(core_smoke timeout_front graphics_mid heavy_tail full_suite)
-if [[ $# -gt 0 ]]; then
-  BLOCKS=("$@")
-else
-  BLOCKS=("${DEFAULT_BLOCKS[@]}")
-fi
-
-baseline_runtime_sum_s() {
-  case "$1" in
-    core_smoke) echo 47 ;;
-    timeout_front) echo 122 ;;
-    graphics_mid) echo 291 ;;
-    heavy_tail) echo 385 ;;
-    full_suite) echo 826 ;;
-    *) return 1 ;;
-  esac
-}
-
-block_retry_enabled() {
-  local block="$1"
-  [[ "${RETRY_TIMEOUT_BLOCKS}" == "1" ]] || return 1
-  local token
-  for token in ${TIMEOUT_RETRY_BLOCKS//,/ }; do
-    [[ "${token}" == "${block}" ]] && return 0
-  done
-  return 1
-}
-
-block_recoverable_retry_enabled() {
-  local block="$1"
-  [[ "${RETRY_RECOVERABLE_BLOCKS}" == "1" ]] || return 1
-  local token
-  for token in ${RECOVERABLE_RETRY_BLOCKS//,/ }; do
-    [[ "${token}" == "${block}" ]] && return 0
-  done
-  return 1
-}
-
-is_timeout_only_regression() {
-  local report_path="$1"
-  "${PYTHON_BIN}" - "${report_path}" <<'PY'
-import json
-import sys
-from pathlib import Path
-
-path = Path(sys.argv[1])
-if not path.exists():
-    raise SystemExit(1)
-payload = json.loads(path.read_text(encoding="utf-8"))
-rows = payload.get("helpfile_similarity", {}).get("rows", [])
-if not rows:
-    raise SystemExit(1)
-failed = [r for r in rows if not bool(r.get("matlab_ok"))]
-if not failed or len(failed) != len(rows):
-    raise SystemExit(1)
-if not all(str(r.get("matlab_error", "")).strip() == "matlab_timeout" for r in failed):
-    raise SystemExit(1)
-topics = [str(r.get("topic", "")) for r in failed]
-print(f"[ladder] timeout-only regression detected across {len(topics)} topic(s): {topics}")
-raise SystemExit(0)
-PY
-}
-
-warmup_matlab() {
-  if [[ ! -x "${MATLAB_BIN}" ]]; then
-    echo "[ladder] matlab warmup skipped; binary not executable: ${MATLAB_BIN}"
-    return 0
-  fi
-  echo "[ladder] running matlab warmup before retry"
-  "${MATLAB_BIN}" ${MATLAB_EXTRA_ARGS} -batch "disp(version); exit" >/dev/null 2>&1 || true
-}
-
-resolve_path() {
-  local p="$1"
-  if [[ "${p}" = /* ]]; then
-    printf "%s" "${p}"
-  else
-    printf "%s/%s" "${REPO_ROOT}" "${p}"
-  fi
-}
-
-timeout_only_topics_csv() {
-  local report_path="$1"
-  "${PYTHON_BIN}" - "${report_path}" <<'PY'
-import json
-import sys
-from pathlib import Path
-
-path = Path(sys.argv[1])
-if not path.exists():
-    raise SystemExit(1)
-payload = json.loads(path.read_text(encoding="utf-8"))
-rows = payload.get("helpfile_similarity", {}).get("rows", [])
-if not rows:
-    raise SystemExit(1)
-failed = [r for r in rows if not bool(r.get("matlab_ok"))]
-if not failed or len(failed) != len(rows):
-    raise SystemExit(1)
-if not all(str(r.get("matlab_error", "")).strip() == "matlab_timeout" for r in failed):
-    raise SystemExit(1)
-topics = [str(r.get("topic", "")).strip() for r in failed if str(r.get("topic", "")).strip()]
-print(",".join(topics))
-raise SystemExit(0)
-PY
-}
-
-retryable_failure_topics_csv() {
-  local report_path="$1"
-  "${PYTHON_BIN}" - "${report_path}" <<'PY'
-import json
-import sys
-from pathlib import Path
-
-path = Path(sys.argv[1])
-if not path.exists():
-    raise SystemExit(1)
-payload = json.loads(path.read_text(encoding="utf-8"))
-rows = payload.get("helpfile_similarity", {}).get("rows", [])
-if not rows:
-    raise SystemExit(1)
-failed = [r for r in rows if not bool(r.get("matlab_ok"))]
-if not failed:
-    raise SystemExit(1)
-
-markers = (
-    "matlab_timeout",
-    "matlab is exiting because of fatal error",
-    "fatal error",
-    "mathworkscrashreporter",
-    "crash report has been saved",
-    "libmwhandle_graphics",
-)
-
-def retryable(err: str) -> bool:
-    e = (err or "").strip().lower()
-    if e == "matlab_timeout":
-        return True
-    return any(m in e for m in markers)
-
-if not all(retryable(str(r.get("matlab_error", ""))) for r in failed):
-    raise SystemExit(1)
-
-topics = [str(r.get("topic", "")).strip() for r in failed if str(r.get("topic", "")).strip()]
-if not topics:
-    raise SystemExit(1)
-print(",".join(topics))
-raise SystemExit(0)
-PY
-}
-
-init_retry_summary() {
-  "${PYTHON_BIN}" - "${RETRY_SUMMARY_ABS}" "${RETRY_TIMEOUT_BLOCKS}" "${TIMEOUT_RETRY_BLOCKS}" "${RETRY_RECOVERABLE_BLOCKS}" "${RECOVERABLE_RETRY_BLOCKS}" <<'PY'
-import json
-import sys
-from datetime import datetime, timezone
-from pathlib import Path
-
-path = Path(sys.argv[1])
-path.parent.mkdir(parents=True, exist_ok=True)
-payload = {
-    "generated_at_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
-    "retry_timeout_blocks_enabled": sys.argv[2] == "1",
-    "timeout_retry_blocks": [b for b in sys.argv[3].replace(",", " ").split() if b],
-    "retry_recoverable_blocks_enabled": sys.argv[4] == "1",
-    "recoverable_retry_blocks": [b for b in sys.argv[5].replace(",", " ").split() if b],
-    "events": [],
-}
-path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
-PY
-}
-
-append_retry_summary_event() {
-  local kind="$1"
-  local block="$2"
-  local attempt="$3"
-  local max_attempts="$4"
-  local status="$5"
-  local return_code="$6"
-  local reason="$7"
-  local timeout_topics_csv="$8"
-  "${PYTHON_BIN}" - "${RETRY_SUMMARY_ABS}" "${kind}" "${block}" "${attempt}" "${max_attempts}" "${status}" "${return_code}" "${reason}" "${timeout_topics_csv}" <<'PY'
-import json
-import sys
-from datetime import datetime, timezone
-from pathlib import Path
-
-path = Path(sys.argv[1])
-if path.exists():
-    payload = json.loads(path.read_text(encoding="utf-8"))
-else:
-    payload = {"generated_at_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "events": []}
-events = payload.setdefault("events", [])
-topics_raw = sys.argv[9].strip()
-event = {
-    "ts_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
-    "kind": sys.argv[2],
-    "block": sys.argv[3],
-    "attempt": int(sys.argv[4]),
-    "max_attempts": int(sys.argv[5]),
-    "status": sys.argv[6],
-    "return_code": int(sys.argv[7]),
-    "reason": sys.argv[8],
-    "timeout_topics": [t for t in topics_raw.split(",") if t] if topics_raw else [],
-}
-events.append(event)
-path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
-PY
-}
-
-cd "${REPO_ROOT}"
-RETRY_SUMMARY_ABS="$(resolve_path "${RETRY_SUMMARY_PATH}")"
-init_retry_summary
-
-echo "[ladder] repo: ${REPO_ROOT}"
-echo "[ladder] python: ${PYTHON_BIN}"
-echo "[ladder] matlab args: ${MATLAB_EXTRA_ARGS}"
-echo "[ladder] blocks: ${BLOCKS[*]}"
-echo "[ladder] runtime multiplier: ${RUNTIME_MULTIPLIER} (<=0 disables runtime regression checks)"
-echo "[ladder] retry timeout-only blocks: ${RETRY_TIMEOUT_BLOCKS} (blocks: ${TIMEOUT_RETRY_BLOCKS})"
-echo "[ladder] retry recoverable-failure blocks: ${RETRY_RECOVERABLE_BLOCKS} (blocks: ${RECOVERABLE_RETRY_BLOCKS})"
-echo "[ladder] retry summary path: ${RETRY_SUMMARY_PATH}"
-
-for block in "${BLOCKS[@]}"; do
-  if ! baseline_s="$(baseline_runtime_sum_s "${block}")"; then
-    echo "[ladder] unknown block: ${block}" >&2
-    exit 2
-  fi
-
-  echo "[ladder] running block: ${block}"
-  report_path="${REPO_ROOT}/python/reports/parity_block_${block}.json"
-  max_attempts=1
-  if block_retry_enabled "${block}" || block_recoverable_retry_enabled "${block}"; then
-    max_attempts=2
-  fi
-  attempt=1
-  while true; do
-    cmd=(
-      "${PYTHON_BIN}"
-      "${REPO_ROOT}/python/tools/debug_parity_blocks.py"
-      --blocks "${block}"
-      --matlab-extra-args "${MATLAB_EXTRA_ARGS}"
-      --output "python/reports/parity_block_benchmark_report_ladder_${block}.json"
-    )
-    if [[ "${SET_ACTIONS_RUNNER_SVC}" == "1" ]]; then
-      cmd+=(--set-actions-runner-svc)
-    fi
-
-    "${cmd[@]}"
-
-    if [[ ! -f "${report_path}" ]]; then
-      echo "[ladder] missing report: ${report_path}" >&2
-      exit 3
-    fi
-
-    if "${PYTHON_BIN}" - "${report_path}" "${block}" "${baseline_s}" "${RUNTIME_MULTIPLIER}" <<'PY'
-import json
-import sys
-from pathlib import Path
-
-report = Path(sys.argv[1])
-block = sys.argv[2]
-baseline = float(sys.argv[3])
-mult = float(sys.argv[4])
-
-payload = json.loads(report.read_text(encoding="utf-8"))
-summary = payload.get("helpfile_similarity", {}).get("summary", {})
-rows = payload.get("helpfile_similarity", {}).get("rows", [])
-
-
-def i(name: str) -> int:
-    try:
-        return int(summary.get(name, 0) or 0)
-    except Exception:
-        return 0
-
-
-total = i("total_topics")
-python_ok = i("python_ok")
-matlab_ok = i("matlab_ok")
-both_ok = i("both_ok")
-scalar_ok = i("scalar_overlap_pass_topics")
-parity_pass = bool(payload.get("parity_contract", {}).get("pass", False))
-regression_pass = bool(payload.get("regression_gate", {}).get("pass", False))
-matlab_failed = [str(r.get("topic", "")) for r in rows if not bool(r.get("matlab_ok"))]
-runtime_sum = sum(float(r.get("matlab_runtime_s") or 0.0) for r in rows)
-
-print(
-    f"[ladder] block={block} total={total} python_ok={python_ok} matlab_ok={matlab_ok} "
-    f"both_ok={both_ok} scalar_ok={scalar_ok} parity_pass={parity_pass} "
-    f"regression_pass={regression_pass} runtime_sum_s={runtime_sum:.2f}"
-)
-
-regression_reasons = []
-if total <= 0:
-    regression_reasons.append("no topics were executed")
-if python_ok != total:
-    regression_reasons.append(f"python_ok={python_ok}/{total}")
-if matlab_ok != total:
-    regression_reasons.append(f"matlab_ok={matlab_ok}/{total}")
-if both_ok != total:
-    regression_reasons.append(f"both_ok={both_ok}/{total}")
-if scalar_ok != total:
-    regression_reasons.append(f"scalar_overlap_pass_topics={scalar_ok}/{total}")
-if not parity_pass:
-    regression_reasons.append("parity_contract=fail")
-if not regression_pass:
-    regression_reasons.append("regression_gate=fail")
-
-if regression_reasons:
-    print(f"[ladder] regression in {block}: {'; '.join(regression_reasons)}", file=sys.stderr)
-    if matlab_failed:
-        print(f"[ladder] failing matlab topics: {matlab_failed}", file=sys.stderr)
-    sys.exit(10)
-
-if mult > 0:
-    threshold = baseline * mult
-    if runtime_sum > threshold:
-        print(
-            f"[ladder] runtime regression in {block}: runtime_sum_s={runtime_sum:.2f} > "
-            f"threshold_s={threshold:.2f} (baseline={baseline:.2f}, mult={mult:.2f})",
-            file=sys.stderr,
-        )
-        sys.exit(11)
-
-print(f"[ladder] block passed: {block}")
-PY
-    then
-      append_retry_summary_event "block_result" "${block}" "${attempt}" "${max_attempts}" "pass" "0" "ok" ""
-      break
-    fi
-
-    rc=$?
-    if [[ "${rc}" -eq 10 ]] && [[ "${attempt}" -lt "${max_attempts}" ]] && timeout_topics_csv="$(timeout_only_topics_csv "${report_path}")"; then
-      is_timeout_only_regression "${report_path}" >/dev/null
-      echo "[ladder] retrying block ${block} after timeout-only regression (attempt ${attempt}/${max_attempts}); topics=${timeout_topics_csv}"
-      append_retry_summary_event "retry_scheduled" "${block}" "${attempt}" "${max_attempts}" "retry" "${rc}" "timeout_only_regression" "${timeout_topics_csv}"
-      warmup_matlab
-      attempt=$((attempt + 1))
-      continue
-    fi
-    if [[ "${rc}" -eq 10 ]] && [[ "${attempt}" -lt "${max_attempts}" ]] && retry_topics_csv="$(retryable_failure_topics_csv "${report_path}")"; then
-      echo "[ladder] retrying block ${block} after recoverable MATLAB failures (attempt ${attempt}/${max_attempts}); topics=${retry_topics_csv}"
-      append_retry_summary_event "retry_scheduled" "${block}" "${attempt}" "${max_attempts}" "retry" "${rc}" "recoverable_matlab_failures" "${retry_topics_csv}"
-      warmup_matlab
-      attempt=$((attempt + 1))
-      continue
-    fi
-    reason="block_failure"
-    if [[ "${rc}" -eq 10 ]]; then
-      reason="regression_gate_failure"
-    elif [[ "${rc}" -eq 11 ]]; then
-      reason="runtime_regression"
-    fi
-    timeout_topics_csv=""
-    if timeout_topics_tmp="$(timeout_only_topics_csv "${report_path}")"; then
-      timeout_topics_csv="${timeout_topics_tmp}"
-    fi
-    append_retry_summary_event "block_result" "${block}" "${attempt}" "${max_attempts}" "fail" "${rc}" "${reason}" "${timeout_topics_csv}"
-    exit "${rc}"
-  done
-
-done
-
-echo "[ladder] all requested blocks passed"
diff --git a/python/tools/run_parity_preflight.sh b/python/tools/run_parity_preflight.sh
deleted file mode 100755
index 6ce6fca..0000000
--- a/python/tools/run_parity_preflight.sh
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
-PYTHON_BIN="${PYTHON_BIN:-python3}"
-MATLAB_EXTRA_ARGS="${NSTAT_MATLAB_EXTRA_ARGS:--maca64 -nodisplay -noFigureWindows -softwareopengl}"
-STAGE_A_BLOCKS_RAW="${NSTAT_PARITY_PREFLIGHT_STAGEA_BLOCKS:-core_smoke timeout_front}"
-STAGE_B_TOPICS_RAW="${NSTAT_PARITY_PREFLIGHT_STAGEB_TOPICS:-PPThinning,ValidationDataSet,DecodingExample,StimulusDecode2D}"
-STAGE_B_REPORT_PATH="${NSTAT_PARITY_PREFLIGHT_STAGEB_REPORT:-python/reports/parity_preflight_stageb_selected.json}"
-
-stage_a_tokens="${STAGE_A_BLOCKS_RAW//,/ }"
-read -r -a STAGE_A_BLOCKS <<< "${stage_a_tokens}"
-if [[ "${#STAGE_A_BLOCKS[@]}" -eq 0 ]]; then
-  echo "[preflight] no Stage A blocks resolved from NSTAT_PARITY_PREFLIGHT_STAGEA_BLOCKS='${STAGE_A_BLOCKS_RAW}'" >&2
-  exit 2
-fi
-
-stage_b_tokens="${STAGE_B_TOPICS_RAW//,/ }"
-read -r -a STAGE_B_TOPICS <<< "${stage_b_tokens}"
-if [[ "${#STAGE_B_TOPICS[@]}" -eq 0 ]]; then
-  echo "[preflight] no Stage B topics resolved from NSTAT_PARITY_PREFLIGHT_STAGEB_TOPICS='${STAGE_B_TOPICS_RAW}'" >&2
-  exit 2
-fi
-
-cd "${REPO_ROOT}"
-export NSTAT_MATLAB_EXTRA_ARGS="${MATLAB_EXTRA_ARGS}"
-export NSTAT_FORCE_M_HELP_SCRIPTS="${NSTAT_FORCE_M_HELP_SCRIPTS:-1}"
-if [[ "${NSTAT_SET_ACTIONS_RUNNER_SVC:-1}" == "1" ]]; then
-  export ACTIONS_RUNNER_SVC=1
-fi
-
-echo "[preflight] repo: ${REPO_ROOT}"
-echo "[preflight] python: ${PYTHON_BIN}"
-echo "[preflight] matlab args: ${NSTAT_MATLAB_EXTRA_ARGS}"
-echo "[preflight] stage A blocks: ${STAGE_A_BLOCKS[*]}"
-echo "[preflight] stage B selected topics: ${STAGE_B_TOPICS[*]}"
-echo "[preflight] stage B report: ${STAGE_B_REPORT_PATH}"
-
-python/tools/run_parity_ladder.sh "${STAGE_A_BLOCKS[@]}"
-
-"${PYTHON_BIN}" python/tools/verify_python_vs_matlab_similarity.py \
-  --enforce-gate \
-  --report-path "${STAGE_B_REPORT_PATH}" \
-  --topics "${STAGE_B_TOPICS[@]}"
-
-"${PYTHON_BIN}" python/tools/summarize_parity_report.py "${STAGE_B_REPORT_PATH}" || true
-
-echo "[preflight] complete"
diff --git a/python/tools/summarize_parity_report.py b/python/tools/summarize_parity_report.py
deleted file mode 100644
index 46a8afe..0000000
--- a/python/tools/summarize_parity_report.py
+++ /dev/null
@@ -1,134 +0,0 @@
-from __future__ import annotations
-
-import argparse
-import json
-import statistics
-from collections import Counter
-from pathlib import Path
-from typing import Any
-
-
-def _classify_failure(err: str) -> str:
-    e = (err or "").strip()
-    if not e:
-        return "unknown"
-    if e == "matlab_timeout":
-        return "timeout"
-    if "libmwhandle_graphics" in e or "MATLAB is exiting because of fatal error" in e:
-        return "graphics_crash"
-    if "matlab_json_missing" in e:
-        return "json_missing"
-    return "other_error"
-
-
-def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
-    parser = argparse.ArgumentParser(description="Summarize nSTAT MATLAB/Python parity report runtime and failures.")
-    parser.add_argument("report", help="Path to python_vs_matlab_similarity_report.json style file.")
-    parser.add_argument("--top", type=int, default=10, help="Number of slowest topics to print.")
-    parser.add_argument("--json", action="store_true", help="Emit machine-readable summary JSON.")
-    return parser.parse_args(argv)
-
-
-def _load(path: Path) -> dict[str, Any]:
-    return json.loads(path.read_text(encoding="utf-8"))
-
-
-def _summarize_rows(rows: list[dict[str, Any]], top: int) -> dict[str, Any]:
-    runtimes = [float(r.get("matlab_runtime_s") or 0.0) for r in rows]
-    failures = [r for r in rows if not bool(r.get("matlab_ok"))]
-    failure_types = Counter(_classify_failure(str(r.get("matlab_error", ""))) for r in failures)
-    sorted_rows = sorted(rows, key=lambda r: float(r.get("matlab_runtime_s") or 0.0), reverse=True)
-    slowest = [
-        {
-            "topic": str(r.get("topic", "")),
-            "runtime_s": float(r.get("matlab_runtime_s") or 0.0),
-            "timeout_s": int(r.get("matlab_timeout_s") or 0),
-            "matlab_ok": bool(r.get("matlab_ok")),
-            "matlab_error": str(r.get("matlab_error", "")),
-        }
-        for r in sorted_rows[: max(1, top)]
-    ]
-    near_timeout = []
-    for r in rows:
-        timeout_s = int(r.get("matlab_timeout_s") or 0)
-        runtime_s = float(r.get("matlab_runtime_s") or 0.0)
-        if timeout_s > 0 and runtime_s / timeout_s >= 0.8:
-            near_timeout.append(
-                {
-                    "topic": str(r.get("topic", "")),
-                    "runtime_s": runtime_s,
-                    "timeout_s": timeout_s,
-                    "pct_timeout": round((runtime_s / timeout_s) * 100.0, 2),
-                    "matlab_error": str(r.get("matlab_error", "")),
-                }
-            )
-    near_timeout.sort(key=lambda x: float(x["pct_timeout"]), reverse=True)
-    p50 = statistics.median(runtimes) if runtimes else 0.0
-    p90 = sorted(runtimes)[int(0.9 * (len(runtimes) - 1))] if runtimes else 0.0
-    return {
-        "topic_count": len(rows),
-        "matlab_failures": len(failures),
-        "failure_types": dict(failure_types),
-        "runtime_s": {
-            "sum": round(sum(runtimes), 3),
-            "p50": round(float(p50), 3),
-            "p90": round(float(p90), 3),
-            "max": round(max(runtimes) if runtimes else 0.0, 3),
-        },
-        "slowest_topics": slowest,
-        "near_timeout_topics": near_timeout,
-    }
-
-
-def main(argv: list[str] | None = None) -> int:
-    args = _parse_args(argv)
-    path = Path(args.report).expanduser().resolve()
-    payload = _load(path)
-
-    if "helpfile_similarity" not in payload:
-        raise SystemExit(f"Unsupported report schema: {path}")
-
-    summary = payload.get("helpfile_similarity", {}).get("summary", {})
-    rows = payload.get("helpfile_similarity", {}).get("rows", [])
-    details = _summarize_rows(rows, top=args.top)
-    out = {
-        "report_path": str(path),
-        "help_summary": summary,
-        "details": details,
-    }
-
-    if args.json:
-        print(json.dumps(out, indent=2))
-        return 0
-
-    print(f"report: {path}")
-    print(f"help summary: {summary}")
-    print(
-        "runtime seconds: "
-        f"sum={details['runtime_s']['sum']} p50={details['runtime_s']['p50']} "
-        f"p90={details['runtime_s']['p90']} max={details['runtime_s']['max']}"
-    )
-    print(f"matlab failures: {details['matlab_failures']} ({details['failure_types']})")
-
-    print("slowest topics:")
-    for row in details["slowest_topics"]:
-        print(
-            f"  - {row['topic']}: runtime={row['runtime_s']:.2f}s "
-            f"timeout={row['timeout_s']} ok={row['matlab_ok']}"
-        )
-
-    if details["near_timeout_topics"]:
-        print("near-timeout topics (>=80% of timeout):")
-        for row in details["near_timeout_topics"]:
-            print(
-                f"  - {row['topic']}: {row['runtime_s']:.2f}/{row['timeout_s']}s "
-                f"({row['pct_timeout']:.1f}%)"
-            )
-    else:
-        print("near-timeout topics (>=80% of timeout): none")
-
-    return 0
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/tools/verify_examples_notebooks.py b/python/tools/verify_examples_notebooks.py
deleted file mode 100644
index 65a2374..0000000
--- a/python/tools/verify_examples_notebooks.py
+++ /dev/null
@@ -1,134 +0,0 @@
-from __future__ import annotations
-
-import contextlib
-import importlib
-import io
-import json
-import sys
-import traceback
-import xml.etree.ElementTree as ET
-from pathlib import Path
-from typing import Any
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-NOTEBOOK_ROOT = REPO_ROOT / "python" / "notebooks" / "helpfiles"
-SRC_ROOT = REPO_ROOT / "python" / "examples" / "help_topics"
-REPORT_DIR = REPO_ROOT / "python" / "reports"
-TOC_PATH = REPO_ROOT / "helpfiles" / "helptoc.xml"
-PY_ROOT = REPO_ROOT / "python"
-if str(PY_ROOT) not in sys.path:
-    sys.path.insert(0, str(PY_ROOT))
-
-
-def example_topics() -> list[tuple[str, str]]:
-    tree = ET.parse(TOC_PATH)
-    root = tree.getroot()
-    examples = None
-    for item in root.iter("tocitem"):
-        if item.attrib.get("id") == "nstat_examples":
-            examples = item
-            break
-    if examples is None:
-        raise RuntimeError("Unable to locate examples node in helptoc.xml")
-
-    out: list[tuple[str, str]] = []
-    for item in examples.findall("tocitem"):
-        title = " ".join("".join(item.itertext()).split())
-        target = item.attrib.get("target", "")
-        if target:
-            out.append((title, target))
-    return out
-
-
-def run_python_module(stem: str) -> dict[str, Any]:
-    try:
-        mod = importlib.import_module(f"examples.help_topics.{stem}")
-    except Exception as exc:  # noqa: BLE001
-        return {"ok": False, "error": f"import_error: {exc}"}
-
-    if not hasattr(mod, "run"):
-        return {"ok": False, "error": "missing run()"}
-    try:
-        out = mod.run(repo_root=REPO_ROOT)
-    except Exception as exc:  # noqa: BLE001
-        return {
-            "ok": False,
-            "error": "".join(traceback.format_exception_only(type(exc), exc)).strip(),
-        }
-    return {"ok": True, "output": out}
-
-
-def execute_notebook(path: Path) -> dict[str, Any]:
-    if not path.exists():
-        return {"ok": False, "error": "notebook_missing", "stdout": "", "code_cells": 0}
-    data = json.loads(path.read_text(encoding="utf-8"))
-    ns: dict[str, Any] = {}
-    buf = io.StringIO()
-    code_cells = 0
-    try:
-        for idx, cell in enumerate(data.get("cells", []), start=1):
-            if cell.get("cell_type") != "code":
-                continue
-            code_cells += 1
-            code = "".join(cell.get("source", []))
-            with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(buf):
-                exec(compile(code, f"{path}:{idx}", "exec"), ns, ns)
-    except Exception as exc:  # noqa: BLE001
-        return {
-            "ok": False,
-            "error": "".join(traceback.format_exception_only(type(exc), exc)).strip(),
-            "stdout": buf.getvalue(),
-            "code_cells": code_cells,
-        }
-    return {"ok": True, "error": "", "stdout": buf.getvalue(), "code_cells": code_cells}
-
-
-def main() -> int:
-    topics = example_topics()
-    rows: list[dict[str, Any]] = []
-    summary = {
-        "total_examples": len(topics),
-        "python_modules_ok": 0,
-        "notebooks_ok": 0,
-        "topic_alignment_ok": 0,
-    }
-
-    for title, target in topics:
-        stem = Path(target).stem
-        py = run_python_module(stem)
-        nb = execute_notebook(NOTEBOOK_ROOT / f"{stem}.ipynb")
-
-        if py["ok"]:
-            summary["python_modules_ok"] += 1
-        if nb["ok"]:
-            summary["notebooks_ok"] += 1
-
-        topic_ok = bool(py.get("ok") and isinstance(py.get("output"), dict) and py["output"].get("topic") == stem)
-        if topic_ok:
-            summary["topic_alignment_ok"] += 1
-
-        rows.append(
-            {
-                "example": stem,
-                "title": title,
-                "matlab_target": target,
-                "python_module_ok": py["ok"],
-                "python_module_error": py.get("error", ""),
-                "python_output_keys": sorted(list(py.get("output", {}).keys())) if py["ok"] and isinstance(py.get("output"), dict) else [],
-                "notebook_ok": nb["ok"],
-                "notebook_error": nb.get("error", ""),
-                "notebook_code_cells": nb.get("code_cells"),
-                "topic_alignment_ok": topic_ok,
-            }
-        )
-
-    REPORT_DIR.mkdir(parents=True, exist_ok=True)
-    out = REPORT_DIR / "examples_notebook_verification.json"
-    out.write_text(json.dumps({"summary": summary, "rows": rows}, indent=2), encoding="utf-8")
-
-    print(json.dumps({"report": str(out.relative_to(REPO_ROOT)), **summary}, indent=2))
-    return 0
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/tools/verify_help_docs_coverage.py b/python/tools/verify_help_docs_coverage.py
deleted file mode 100644
index ad42e2a..0000000
--- a/python/tools/verify_help_docs_coverage.py
+++ /dev/null
@@ -1,76 +0,0 @@
-from __future__ import annotations
-
-import json
-import re
-import sys
-import xml.etree.ElementTree as ET
-from pathlib import Path
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-TOC_PATH = REPO_ROOT / "helpfiles" / "helptoc.xml"
-DOCS_ROOT = REPO_ROOT / "python" / "docs"
-TOPICS_DIR = DOCS_ROOT / "topics"
-HELP_TOPICS_INDEX = DOCS_ROOT / "help_topics.rst"
-
-
-def _slugify(value: str) -> str:
-    value = value.strip().lower()
-    value = re.sub(r"[^a-z0-9]+", "_", value)
-    return value.strip("_") or "topic"
-
-
-def _expected_topic_slugs() -> list[str]:
-    tree = ET.parse(TOC_PATH)
-    root = tree.getroot()
-    seen: set[str] = set()
-    slugs: list[str] = []
-    for item in root.iter("tocitem"):
-        target = item.attrib.get("target", "").strip()
-        if not target:
-            continue
-        if target.startswith("http://") or target.startswith("https://"):
-            continue
-        slug = _slugify(Path(target).stem)
-        if slug in seen:
-            continue
-        seen.add(slug)
-        slugs.append(slug)
-    return slugs
-
-
-def _index_entries() -> set[str]:
-    if not HELP_TOPICS_INDEX.exists():
-        return set()
-    entries: set[str] = set()
-    for line in HELP_TOPICS_INDEX.read_text(encoding="utf-8").splitlines():
-        line = line.strip()
-        if not line.startswith("topics/"):
-            continue
-        entries.add(line.split("/", 1)[1])
-    return entries
-
-
-def main() -> int:
-    if not TOC_PATH.exists():
-        print(json.dumps({"error": f"missing TOC: {TOC_PATH}"}, indent=2))
-        return 2
-
-    expected = _expected_topic_slugs()
-    index = _index_entries()
-
-    missing_topic_files = [slug for slug in expected if not (TOPICS_DIR / f"{slug}.rst").exists()]
-    missing_index_entries = [slug for slug in expected if slug not in index]
-
-    payload = {
-        "toc_topics": len(expected),
-        "missing_topic_files": missing_topic_files,
-        "missing_index_entries": missing_index_entries,
-        "pass": not missing_topic_files and not missing_index_entries,
-    }
-    print(json.dumps(payload, indent=2))
-
-    return 0 if payload["pass"] else 1
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/tools/verify_mfile_parity.py b/python/tools/verify_mfile_parity.py
deleted file mode 100644
index 41067fd..0000000
--- a/python/tools/verify_mfile_parity.py
+++ /dev/null
@@ -1,233 +0,0 @@
-from __future__ import annotations
-
-import importlib
-import json
-import re
-import subprocess
-import sys
-from dataclasses import dataclass
-from pathlib import Path
-from typing import Any
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-PORT_ROOT = REPO_ROOT / "python" / "matlab_port"
-REPORT_DIR = REPO_ROOT / "python" / "reports"
-MATLAB_BIN = Path("/Applications/MATLAB_R2025b.app/bin/matlab")
-
-
-@dataclass
-class Entry:
-    source: str
-    kind: str
-    function_name: str | None
-    target: str
-
-
-def first_code_line(text: str) -> str:
-    for line in text.splitlines():
-        s = line.strip()
-        if not s or s.startswith("%"):
-            continue
-        return s
-    return ""
-
-
-def classify_m_file(path: Path) -> tuple[str, str | None]:
-    text = path.read_text(encoding="utf-8", errors="ignore")
-    first = first_code_line(text)
-    if first.startswith("classdef "):
-        return "classdef", None
-
-    if first.startswith("function"):
-        fn = None
-        args = ""
-        patterns = [
-            r"^function\s+\[[^\]]*\]\s*=\s*(\w+)\s*\(([^)]*)\)",
-            r"^function\s+\w+\s*=\s*(\w+)\s*\(([^)]*)\)",
-            r"^function\s+(\w+)\s*\(([^)]*)\)",
-            r"^function\s+\[[^\]]*\]\s*=\s*(\w+)\s*$",
-            r"^function\s+\w+\s*=\s*(\w+)\s*$",
-            r"^function\s+(\w+)\s*$",
-        ]
-        for p in patterns:
-            m = re.match(p, first)
-            if m:
-                fn = m.group(1)
-                if m.lastindex and m.lastindex >= 2:
-                    args = m.group(2)
-                break
-        if fn is None:
-            fn = path.stem
-        nargs = 0 if args.strip() == "" else len([x for x in args.split(",") if x.strip()])
-        return ("function_no_args" if nargs == 0 else "function_args"), fn
-
-    return "script", None
-
-
-def python_module_name(target_rel: str) -> str:
-    rel = Path(target_rel)
-    if rel.parts and rel.parts[0] == "python":
-        rel = Path(*rel.parts[1:])
-    return str(rel.with_suffix("")).replace("/", ".")
-
-
-def run_python_entry(module_name: str, kind: str, function_name: str | None) -> tuple[bool, str]:
-    try:
-        mod = importlib.import_module(module_name)
-    except Exception as e:  # noqa: BLE001
-        return False, f"import_error: {e}"
-
-    if kind in {"classdef", "function_args"}:
-        return True, "interface_checked"
-
-    try:
-        if hasattr(mod, "run"):
-            _ = mod.run(repo_root=REPO_ROOT)
-            return True, "run_ok"
-        if kind == "function_no_args" and function_name and hasattr(mod, function_name):
-            _ = getattr(mod, function_name)()
-            return True, "function_ok"
-        if hasattr(mod, "main"):
-            _ = mod.main()
-            return True, "main_ok"
-        return False, "no_runnable_entrypoint"
-    except Exception as e:  # noqa: BLE001
-        return False, f"runtime_error: {e}"
-
-
-def run_matlab_smoke(entries: list[Entry]) -> dict[str, dict[str, Any]]:
-    results: dict[str, dict[str, Any]] = {}
-    runnable = [e for e in entries if e.kind in {"script", "function_no_args"}]
-
-    if not runnable:
-        return results
-
-    if not MATLAB_BIN.exists():
-        for e in runnable:
-            results[e.source] = {"ok": False, "message": "matlab_not_found"}
-        return results
-
-    repo_q = str(REPO_ROOT).replace("'", "''")
-    for e in runnable:
-        src_q = e.source.replace("'", "''")
-        fn_q = (e.function_name or "").replace("'", "''")
-        if e.kind == "script":
-            run_expr = f"run(fullfile(repo,'{src_q}'));"
-        else:
-            run_expr = f"feval('{fn_q}');"
-
-        cmd = (
-            "restoredefaultpath; "
-            f"repo='{repo_q}'; "
-            "cd(repo); addpath(genpath(repo),'-begin'); set(0,'DefaultFigureVisible','off'); "
-            "try; "
-            + run_expr
-            + " disp('CODEX_SMOKE_OK'); "
-            "catch ME; disp('CODEX_SMOKE_FAIL'); disp([ME.identifier ' | ' ME.message]); "
-            "end; exit(0);"
-        )
-        try:
-            cp = subprocess.run(
-                [str(MATLAB_BIN), "-batch", cmd],
-                cwd=str(REPO_ROOT),
-                capture_output=True,
-                text=True,
-                timeout=120,
-                check=False,
-            )
-        except subprocess.TimeoutExpired:
-            results[e.source] = {"ok": False, "message": "matlab_timeout"}
-            continue
-
-        out = (cp.stdout or "") + "\n" + (cp.stderr or "")
-        if "CODEX_SMOKE_OK" in out:
-            results[e.source] = {"ok": True, "message": "ok"}
-        else:
-            # Keep tail concise but preserve exact MATLAB error lines.
-            tail = "\n".join([ln for ln in out.splitlines() if ln.strip()][-8:])
-            results[e.source] = {"ok": False, "message": tail or "matlab_failed_without_message"}
-    return results
-
-
-def main() -> int:
-    entries: list[Entry] = []
-    for m in sorted(REPO_ROOT.rglob("*.m")):
-        if PORT_ROOT in m.parents:
-            continue
-        rel_path = m.relative_to(REPO_ROOT)
-        if rel_path.parts and rel_path.parts[0] == "python":
-            continue
-        rel = str(m.relative_to(REPO_ROOT))
-        kind, fn = classify_m_file(m)
-        tgt = PORT_ROOT.joinpath(*m.relative_to(REPO_ROOT).parts[:-1], f"{m.stem}.py")
-        entries.append(Entry(source=rel, kind=kind, function_name=fn, target=str(tgt.relative_to(REPO_ROOT))))
-
-    py_root = REPO_ROOT / "python"
-    if str(py_root) not in sys.path:
-        sys.path.insert(0, str(py_root))
-
-    matlab_results = run_matlab_smoke(entries)
-
-    rows: list[dict[str, Any]] = []
-    summary = {
-        "total_m_files": len(entries),
-        "python_ok": 0,
-        "python_runnable_ok": 0,
-        "matlab_runnable_ok": 0,
-        "runnable_parity_pass": 0,
-        "interface_only": 0,
-        "runnable_total": 0,
-    }
-
-    for e in entries:
-        tgt_exists = (REPO_ROOT / e.target).exists()
-        py_ok = False
-        py_msg = "target_missing"
-        if tgt_exists:
-            py_ok, py_msg = run_python_entry(python_module_name(e.target), e.kind, e.function_name)
-
-        if py_ok:
-            summary["python_ok"] += 1
-
-        matlab_ok = None
-        matlab_msg = "not_run"
-        parity = "interface_only"
-
-        if e.kind in {"script", "function_no_args"}:
-            summary["runnable_total"] += 1
-            mr = matlab_results.get(e.source, {"ok": False, "message": "missing_matlab_result"})
-            matlab_ok = bool(mr["ok"])
-            matlab_msg = str(mr["message"])
-            if py_ok:
-                summary["python_runnable_ok"] += 1
-            if matlab_ok:
-                summary["matlab_runnable_ok"] += 1
-            parity = "pass" if (py_ok and matlab_ok) else "fail"
-            if parity == "pass":
-                summary["runnable_parity_pass"] += 1
-        else:
-            summary["interface_only"] += 1
-
-        rows.append(
-            {
-                "source": e.source,
-                "kind": e.kind,
-                "python_target": e.target,
-                "python_ok": py_ok,
-                "python_message": py_msg,
-                "matlab_ok": matlab_ok,
-                "matlab_message": matlab_msg,
-                "parity": parity,
-            }
-        )
-
-    REPORT_DIR.mkdir(parents=True, exist_ok=True)
-    out = REPORT_DIR / "mfile_parity_report.json"
-    out.write_text(json.dumps({"summary": summary, "rows": rows}, indent=2), encoding="utf-8")
-
-    print(json.dumps({"report": str(out.relative_to(REPO_ROOT)), **summary}, indent=2))
-    return 0
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/tools/verify_offline_standalone.py b/python/tools/verify_offline_standalone.py
deleted file mode 100644
index 4e37487..0000000
--- a/python/tools/verify_offline_standalone.py
+++ /dev/null
@@ -1,191 +0,0 @@
-from __future__ import annotations
-
-import argparse
-import json
-import os
-import re
-import subprocess
-import sys
-import tempfile
-from pathlib import Path
-from typing import Any
-
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-REPORT_PATH = REPO_ROOT / "python" / "reports" / "offline_standalone_verification.json"
-
-
-def _run(cmd: list[str], *, cwd: Path, env: dict[str, str]) -> dict[str, Any]:
-    cp = subprocess.run(cmd, cwd=str(cwd), env=env, capture_output=True, text=True, check=False)
-    return {
-        "cmd": cmd,
-        "cwd": str(cwd.relative_to(REPO_ROOT)),
-        "returncode": cp.returncode,
-        "stdout": cp.stdout[-4000:],
-        "stderr": cp.stderr[-4000:],
-        "ok": cp.returncode == 0,
-    }
-
-
-def _sanitize_path(path_value: str) -> str:
-    parts = [p for p in path_value.split(os.pathsep) if p and "matlab" not in p.lower()]
-    return os.pathsep.join(parts)
-
-
-def _runtime_matlab_dependency_scan() -> dict[str, Any]:
-    py_root = REPO_ROOT / "python" / "nstat"
-    bad_hits: list[str] = []
-    pattern = re.compile(r"(/Applications/MATLAB|subprocess\..*matlab|MATLAB_BIN)", re.IGNORECASE)
-    for p in py_root.rglob("*.py"):
-        text = p.read_text(encoding="utf-8", errors="ignore")
-        if pattern.search(text):
-            bad_hits.append(str(p.relative_to(REPO_ROOT)))
-    return {"ok": len(bad_hits) == 0, "hits": bad_hits}
-
-
-def verify(full_notebooks: bool = False) -> dict[str, Any]:
-    REPORT_PATH.parent.mkdir(parents=True, exist_ok=True)
-    report: dict[str, Any] = {"full_notebooks": bool(full_notebooks)}
-
-    with tempfile.TemporaryDirectory(prefix="nstat_offline_site_") as td:
-        site_dir = Path(td) / "site"
-        site_dir.mkdir(parents=True, exist_ok=True)
-        py = Path(sys.executable)
-        env = dict(os.environ)
-        env["PATH"] = _sanitize_path(env.get("PATH", ""))
-        env["PYTHONPATH"] = str(site_dir)
-
-        install_env = {**env, "PYTHONPATH": str(site_dir)}
-        source_env = {**env, "PYTHONPATH": str(REPO_ROOT / "python")}
-        steps = []
-        steps.append(
-            _run(
-                [
-                    str(py),
-                    "-m",
-                    "pip",
-                    "install",
-                    "--no-deps",
-                    "./python",
-                    "--target",
-                    str(site_dir),
-                ],
-                cwd=REPO_ROOT,
-                env=install_env,
-            )
-        )
-        steps.append(
-            _run(
-                [
-                    str(py),
-                    "-c",
-                    (
-                        "import json, pathlib, nstat; "
-                        "checks=nstat.verify_checksums(); "
-                        "print(json.dumps({'dataset_count': len(nstat.list_datasets()), "
-                        "'checksum_all_true': all(checks.values()), "
-                        "'nstat_path': str(pathlib.Path(nstat.__file__).resolve())}))"
-                    ),
-                ],
-                cwd=REPO_ROOT,
-                env=install_env,
-            )
-        )
-        steps.append(
-            _run(
-                [
-                    str(py),
-                    "-c",
-                    (
-                        "import numpy as np; "
-                        "from nstat.signal import Signal; "
-                        "t=np.linspace(0.0,1.0,100); "
-                        "sig=Signal(t, np.column_stack([np.sin(t), np.cos(t)]), name='offline_check'); "
-                        "print(sig.dimension)"
-                    ),
-                ],
-                cwd=REPO_ROOT,
-                env=install_env,
-            )
-        )
-        # Source-path fallback check keeps CI resilient if pip --target behavior changes.
-        steps.append(
-            _run(
-                [
-                    str(py),
-                    "-c",
-                    (
-                        "import json, pathlib, nstat; "
-                        "checks=nstat.verify_checksums(); "
-                        "print(json.dumps({'dataset_count': len(nstat.list_datasets()), "
-                        "'checksum_all_true': all(checks.values()), "
-                        "'nstat_path': str(pathlib.Path(nstat.__file__).resolve())}))"
-                    ),
-                ],
-                cwd=REPO_ROOT,
-                env=source_env,
-            )
-        )
-        if full_notebooks:
-            steps.append(
-                _run(
-                    [str(py), "python/tools/verify_examples_notebooks.py"],
-                    cwd=REPO_ROOT,
-                    env=source_env,
-                )
-            )
-
-        report["steps"] = steps
-
-    report["runtime_matlab_scan"] = _runtime_matlab_dependency_scan()
-    report["target_install_ok"] = bool(report["steps"][0]["ok"])
-    report["installed_runtime_ok"] = bool(report["steps"][1]["ok"] and report["steps"][2]["ok"])
-    report["source_fallback_ok"] = bool(report["steps"][3]["ok"])
-    report["notebook_checks_ok"] = bool((not full_notebooks) or report["steps"][-1]["ok"])
-    report["install_mode"] = (
-        "target_install"
-        if (report["target_install_ok"] and report["installed_runtime_ok"])
-        else ("source_fallback" if report["source_fallback_ok"] else "failed")
-    )
-    report["pass_strict_target_install"] = bool(
-        report["target_install_ok"]
-        and report["installed_runtime_ok"]
-        and report["notebook_checks_ok"]
-        and report["runtime_matlab_scan"]["ok"]
-    )
-    report["pass"] = bool(
-        report["notebook_checks_ok"]
-        and report["runtime_matlab_scan"]["ok"]
-        and (report["pass_strict_target_install"] or report["source_fallback_ok"])
-    )
-    REPORT_PATH.write_text(json.dumps(report, indent=2), encoding="utf-8")
-    return report
-
-
-def main() -> int:
-    parser = argparse.ArgumentParser(description="Verify standalone offline Python nSTAT usage from source checkout.")
-    parser.add_argument("--full-notebooks", action="store_true", help="Also execute all generated notebooks.")
-    parser.add_argument(
-        "--require-target-install",
-        action="store_true",
-        help="Fail unless pip --target install succeeds (no source fallback mode).",
-    )
-    args = parser.parse_args()
-
-    report = verify(full_notebooks=args.full_notebooks)
-    effective_pass = bool(report["pass_strict_target_install"] if args.require_target_install else report["pass"])
-    printable = {
-        "report": str(REPORT_PATH.relative_to(REPO_ROOT)),
-        "pass": effective_pass,
-        "steps_ok": [step["ok"] for step in report["steps"]],
-        "runtime_matlab_scan_ok": report["runtime_matlab_scan"]["ok"],
-        "target_install_ok": report["target_install_ok"],
-        "install_mode": report["install_mode"],
-        "pass_strict_target_install": report["pass_strict_target_install"],
-    }
-    print(json.dumps(printable, indent=2))
-    return 0 if effective_pass else 1
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())
diff --git a/python/tools/verify_python_vs_matlab_similarity.py b/python/tools/verify_python_vs_matlab_similarity.py
deleted file mode 100644
index 912c5c4..0000000
--- a/python/tools/verify_python_vs_matlab_similarity.py
+++ /dev/null
@@ -1,1170 +0,0 @@
-from __future__ import annotations
-
-import argparse
-import importlib
-import json
-import math
-import os
-import re
-import shutil
-import signal
-import subprocess
-import sys
-import tempfile
-import time
-import traceback
-import xml.etree.ElementTree as ET
-from pathlib import Path
-from typing import Any
-
-REPO_ROOT = Path(__file__).resolve().parents[2]
-MATLAB_BIN = Path("/Applications/MATLAB_R2025b.app/bin/matlab")
-MATLAB_EXTRA_ARGS = [arg for arg in os.environ.get("NSTAT_MATLAB_EXTRA_ARGS", "").split() if arg]
-FORCE_M_HELP_SCRIPTS = os.environ.get("NSTAT_FORCE_M_HELP_SCRIPTS", "").strip().lower() in {"1", "true", "yes", "on"}
-TOC_PATH = REPO_ROOT / "helpfiles" / "helptoc.xml"
-PY_ROOT = REPO_ROOT / "python"
-if str(PY_ROOT) not in sys.path:
-    sys.path.insert(0, str(PY_ROOT))
-
-EXPECTED_CLASS_TOTAL = 9
-HELP_PYTHON_REQUIRED_OK = 25
-HELP_MATLAB_MIN_OK = 25
-SCALAR_OVERLAP_PASS_MIN_TOPICS = 25
-KNOWN_MATLAB_HELP_FAILURES: set[str] = set()
-PARITY_CONTRACT: dict[str, list[str]] = {
-    "SignalObjExamples": ["sample_rate_hz"],
-    "CovariateExamples": ["figs"],
-    "CovCollExamples": ["figs"],
-    "nSpikeTrainExamples": ["figs"],
-    "nstCollExamples": ["figs"],
-    "EventsExamples": ["figs"],
-    "HistoryExamples": ["figs"],
-    "TrialExamples": ["figs"],
-    "TrialConfigExamples": ["figs"],
-    "ConfigCollExamples": ["figs"],
-    "AnalysisExamples": ["figs"],
-    "FitResultExamples": ["figs"],
-    "FitResSummaryExamples": ["figs"],
-    "PPThinning": ["num_realizations"],
-    "PSTHEstimation": ["num_realizations"],
-    "ValidationDataSet": ["figs"],
-    "mEPSCAnalysis": ["figs"],
-    "PPSimExample": ["figs"],
-    "ExplicitStimulusWhiskerData": ["figs"],
-    "HippocampalPlaceCellExample": ["figs"],
-    "DecodingExample": ["figs"],
-    "DecodingExampleWithHist": ["figs"],
-    "StimulusDecode2D": ["num_cells"],
-    "NetworkTutorial": ["figs"],
-    "nSTATPaperExamples": ["num_cells"],
-}
-FORCE_M_SCRIPT_TOPICS: set[str] = {
-    "SignalObjExamples",
-    "PPThinning",
-    "PSTHEstimation",
-    "StimulusDecode2D",
-    "nSTATPaperExamples",
-}
-DEFAULT_HELP_TOPIC_TIMEOUT_S = 120
-try:
-    DEFAULT_MATLAB_MAX_ATTEMPTS = max(1, int(os.environ.get("NSTAT_MATLAB_TOPIC_MAX_ATTEMPTS", "1")))
-except ValueError:
-    DEFAULT_MATLAB_MAX_ATTEMPTS = 1
-CRASH_ERROR_MARKERS = (
-    "matlab is exiting because of fatal error",
-    "fatal error",
-    "mathworkscrashreporter",
-    "crash report has been saved",
-    "libmwhandle_graphics",
-)
-DEFAULT_TOPIC_TIMEOUT_OVERRIDES: dict[str, int] = {
-    "SignalObjExamples": 180,
-    "CovariateExamples": 180,
-    "CovCollExamples": 180,
-    "nSpikeTrainExamples": 180,
-    "nstCollExamples": 180,
-    "EventsExamples": 180,
-    "HistoryExamples": 180,
-    "TrialExamples": 180,
-    "AnalysisExamples": 180,
-    "DecodingExampleWithHist": 360,
-    "StimulusDecode2D": 180,
-    "nSTATPaperExamples": 240,
-}
-
-
-def _matlab_batch_command(batch_cmd: str) -> list[str]:
-    return [str(MATLAB_BIN), *MATLAB_EXTRA_ARGS, "-batch", batch_cmd]
-
-
-def _env_truthy(name: str, default: bool = False) -> bool:
-    raw = os.environ.get(name)
-    if raw is None:
-        return default
-    return raw.strip().lower() in {"1", "true", "yes", "on"}
-
-
-def _runner_service_mode() -> bool:
-    return _env_truthy("ACTIONS_RUNNER_SVC")
-
-
-def _matlab_cleanup_allowed() -> bool:
-    # Safety default: only cleanup MATLAB processes automatically on runner hosts,
-    # unless explicitly enabled for local investigations.
-    return _runner_service_mode() or _env_truthy("NSTAT_MATLAB_ALLOW_LOCAL_PROCESS_CLEANUP")
-
-
-def _force_topic_isolation_enabled() -> bool:
-    return _env_truthy("NSTAT_MATLAB_FORCE_TOPIC_ISOLATION")
-
-
-def _hard_cleanup_on_failure_enabled() -> bool:
-    return _env_truthy("NSTAT_MATLAB_HARD_CLEANUP_ON_FAILURE")
-
-
-def _output_has_crash_marker(text: str) -> bool:
-    lowered = str(text or "").lower()
-    return any(marker in lowered for marker in CRASH_ERROR_MARKERS)
-
-
-def _matlab_process_snapshot(max_lines: int = 40) -> str:
-    try:
-        cp = subprocess.run(
-            ["ps", "-axo", "pid,ppid,etime,pcpu,pmem,command"],
-            capture_output=True,
-            text=True,
-            check=False,
-        )
-    except Exception:
-        return ""
-    if cp.returncode != 0:
-        return ""
-    patterns = (
-        "MATLAB_R2025b.app/bin/maca64/MATLAB",
-        "MATLABWindow.app/Contents/MacOS/MATLABWindow",
-        "matlabwindowhelper.app/Contents/MacOS/matlabwindowhelper",
-        "MathWorksCrashReporter",
-    )
-    lines = [ln for ln in (cp.stdout or "").splitlines() if any(p in ln for p in patterns)]
-    return "\n".join(lines[-max_lines:])
-
-
-def _cleanup_runner_matlab_processes(hard: bool = False) -> bool:
-    if not _matlab_cleanup_allowed():
-        return False
-    kill_patterns = [
-        "/Applications/MATLAB_R2025b.app/bin/maca64/MATLAB",
-        "/Applications/MATLAB_R2025b.app/bin/matlab",
-        "MATLABWindow.app/Contents/MacOS/MATLABWindow",
-        "matlabwindowhelper.app/Contents/MacOS/matlabwindowhelper",
-        "MathWorksCrashReporter",
-    ]
-    signals = ["-TERM", "-KILL"] if hard else ["-TERM"]
-    for sig in signals:
-        for pat in kill_patterns:
-            try:
-                subprocess.run(["pkill", sig, "-f", pat], capture_output=True, text=True, check=False)
-            except Exception:
-                pass
-        time.sleep(0.4 if sig == "-TERM" else 0.2)
-    return True
-
-
-def _cleanup_runner_matlab_processes_with_snapshots(hard: bool = False) -> dict[str, Any]:
-    snapshot_before = _matlab_process_snapshot()
-    applied = _cleanup_runner_matlab_processes(hard=hard)
-    snapshot_after = _matlab_process_snapshot() if applied else snapshot_before
-    return {
-        "failure_process_snapshot_before_cleanup": snapshot_before,
-        "failure_process_snapshot_after_cleanup": snapshot_after,
-        "runner_service_cleanup": bool(applied),
-    }
-
-
-def _matlab_warmup(timeout_s: int = 90) -> None:
-    if not MATLAB_BIN.exists():
-        return
-    try:
-        _run_matlab_batch_logged("disp(version); exit", timeout_s=timeout_s)
-    except Exception:
-        return
-
-
-def _is_retryable_matlab_failure(payload: dict[str, Any]) -> bool:
-    if bool(payload.get("ok")):
-        return False
-    error = str(payload.get("error", "")).strip()
-    if error == "matlab_timeout":
-        return True
-    combined = " ".join(
-        [
-            error,
-            str(payload.get("error_report", "")),
-            str(payload.get("fallback_error", "")),
-            str(payload.get("fallback_error_report", "")),
-        ]
-    ).lower()
-    return any(marker in combined for marker in CRASH_ERROR_MARKERS)
-
-
-def _kill_process_group(pid: int) -> None:
-    try:
-        os.killpg(pid, signal.SIGKILL)
-        return
-    except Exception:
-        pass
-    try:
-        os.kill(pid, signal.SIGKILL)
-    except Exception:
-        pass
-
-
-def _run_matlab_batch_logged(batch_cmd: str, timeout_s: int) -> dict[str, Any]:
-    cmd = _matlab_batch_command(batch_cmd)
-    with tempfile.NamedTemporaryFile(prefix="nstat_matlab_", suffix=".log", delete=False) as tf:
-        log_path = Path(tf.name)
-
-    timed_out = False
-    returncode: int | None = None
-    t0 = time.time()
-    try:
-        with log_path.open("w", encoding="utf-8", errors="replace") as fh:
-            proc = subprocess.Popen(
-                cmd,
-                cwd=str(REPO_ROOT),
-                stdout=fh,
-                stderr=subprocess.STDOUT,
-                text=True,
-                start_new_session=True,
-            )
-            try:
-                returncode = int(proc.wait(timeout=timeout_s))
-            except subprocess.TimeoutExpired:
-                timed_out = True
-                _kill_process_group(proc.pid)
-                try:
-                    proc.wait(timeout=5)
-                except Exception:
-                    pass
-        out = log_path.read_text(encoding="utf-8", errors="ignore")
-    finally:
-        try:
-            log_path.unlink()
-        except OSError:
-            pass
-
-    return {
-        "timed_out": timed_out,
-        "returncode": returncode,
-        "runtime_s": float(time.time() - t0),
-        "output": out,
-    }
-
-
-def _normalize_key(s: str) -> str:
-    return re.sub(r"[^a-z0-9]", "", s.lower())
-
-
-def _is_number(x: Any) -> bool:
-    return isinstance(x, (int, float)) and math.isfinite(float(x))
-
-
-def _flatten_numeric_scalars(obj: Any, prefix: str = "", out: dict[str, float] | None = None, depth: int = 0) -> dict[str, float]:
-    """Collect finite numeric scalars from nested dict payloads."""
-    if out is None:
-        out = {}
-    if depth > 8:
-        return out
-
-    if isinstance(obj, dict):
-        for k, v in obj.items():
-            key = str(k)
-            next_prefix = f"{prefix}.{key}" if prefix else key
-            _flatten_numeric_scalars(v, next_prefix, out, depth + 1)
-        return out
-
-    if _is_number(obj):
-        val = float(obj)
-        if prefix:
-            out[prefix] = val
-            leaf = prefix.split(".")[-1]
-            out.setdefault(leaf, val)
-    return out
-
-
-def _python_class_checks() -> dict[str, Any]:
-    import numpy as np
-
-    from nstat import CIFModel, Covariate, CovariateCollection, SpikeTrain, SpikeTrainCollection, Trial
-    from nstat.history import HistoryBasis
-
-    nst = SpikeTrain(np.array([0.1, 0.2, 0.4]), name="n1", binwidth=0.1, minTime=0.0, maxTime=1.0)
-    isis = nst.getISIs().tolist()
-    rate = float(nst.firing_rate_hz)
-
-    n1 = SpikeTrain(np.array([0.1, 0.2, 0.4]), name="a", binwidth=0.1, minTime=0.0, maxTime=1.0)
-    n2 = SpikeTrain(np.array([0.15, 0.25, 0.45]), name="b", binwidth=0.1, minTime=0.0, maxTime=1.0)
-    coll = SpikeTrainCollection([n1, n2])
-    psth = coll.psth(0.2)
-
-    t = np.arange(0.0, 1.0 + 1e-12, 0.1)
-    c1 = Covariate(t, np.sin(t), "c1", "time", "s", "", ["c1"])
-    c2 = Covariate(t, np.cos(t), "c2", "time", "s", "", ["c2"])
-    cc = CovariateCollection([c1, c2])
-    _, x, _ = cc.dataToMatrix()
-
-    h = HistoryBasis([1, 2])
-    hmat = h.design_matrix(n1.getSigRep(0.1, 0.0, 1.0).data[:, 0])
-
-    trial = Trial(spike_collection=coll, covariate_collection=cc)
-
-    lam = Covariate(t, np.ones_like(t) * 5.0, "lam", "time", "s", "Hz", ["lam"])
-    sim = CIFModel(lam.time, lam.data[:, 0], "lam").simulate(num_realizations=3, seed=0)
-
-    return {
-        "nspike_getISIs": isis,
-        "nspike_rate": rate,
-        "nstcoll_psth_len": int(psth.data.shape[0]),
-        "nstcoll_psth_mean": float(np.mean(psth.data)),
-        "covcoll_shape": [int(x.shape[0]), int(x.shape[1])],
-        "history_num_columns": int(hmat.shape[1]),
-        "trial_sample_rate": float(trial.spike_collection.sampleRate),
-        "trial_minmax": [float(trial.spike_collection.minTime), float(trial.spike_collection.maxTime)],
-        "cif_num_realizations": int(sim.numSpikeTrains),
-    }
-
-
-def _matlab_class_checks(timeout_s: int = 180) -> dict[str, Any]:
-    if not MATLAB_BIN.exists():
-        return {"ok": False, "error": "matlab_not_found"}
-    if _force_topic_isolation_enabled():
-        _cleanup_runner_matlab_processes(hard=True)
-
-    repo_q = str(REPO_ROOT).replace("'", "''")
-    cmd = (
-        "restoredefaultpath; "
-        f"repo='{repo_q}'; "
-        "cd(repo); addpath(genpath(repo),'-begin'); set(0,'DefaultFigureVisible','off'); "
-        "try; "
-        "R=struct(); "
-        "nst=nspikeTrain([0.1 0.2 0.4],'n1',0.1,0,1); "
-        "R.nspike_getISIs=nst.getISIs(); "
-        "R.nspike_rate=nst.avgFiringRate; "
-        "n1=nspikeTrain([0.1 0.2 0.4],'a',0.1,0,1); "
-        "n2=nspikeTrain([0.15 0.25 0.45],'b',0.1,0,1); "
-        "coll=nstColl({n1,n2}); "
-        "psth=coll.psth(0.2); "
-        "R.nstcoll_psth_len=length(psth.data); "
-        "R.nstcoll_psth_mean=mean(psth.data); "
-        "t=(0:0.1:1)'; "
-        "c1=Covariate(t,sin(t),'c1','time','s','','c1'); "
-        "c2=Covariate(t,cos(t),'c2','time','s','','c2'); "
-        "cc=CovColl({c1,c2}); "
-        "X=cc.dataToMatrix(); "
-        "R.covcoll_shape=size(X); "
-        "h=History([0 0.1 0.2],0,1); "
-        "hc=h.computeHistory(n1); "
-        "hcov=hc.getCov(1); "
-        "R.history_num_columns=hcov.dimension; "
-        "tr=Trial(coll,cc); "
-        "R.trial_sample_rate=tr.sampleRate; "
-        "R.trial_minmax=[tr.minTime tr.maxTime]; "
-        "lam=Covariate(t,ones(size(t))*5,'lam','time','s','Hz','lam'); "
-        "sim=CIF.simulateCIFByThinningFromLambda(lam,3); "
-        "R.cif_num_realizations=sim.numSpikeTrains; "
-        "disp(['CODEX_JSON:' jsonencode(R)]); "
-        "catch ME; "
-        "disp('CODEX_JSON_ERROR'); disp([ME.identifier ' | ' ME.message]); "
-        "end; exit(0);"
-    )
-
-    try:
-        run = _run_matlab_batch_logged(cmd, timeout_s)
-    except Exception as exc:  # noqa: BLE001
-        return {"ok": False, "error": f"matlab_subprocess_error: {exc}"}
-
-    if bool(run.get("timed_out", False)):
-        cleanup_meta = {}
-        if _hard_cleanup_on_failure_enabled() or _force_topic_isolation_enabled():
-            cleanup_meta = _cleanup_runner_matlab_processes_with_snapshots(hard=True)
-        return {
-            "ok": False,
-            "error": "matlab_timeout",
-            "runtime_s": float(timeout_s),
-            "timeout_process_snapshot_before_cleanup": cleanup_meta.get("failure_process_snapshot_before_cleanup", ""),
-            "timeout_process_snapshot_after_cleanup": cleanup_meta.get("failure_process_snapshot_after_cleanup", ""),
-            "runner_service_cleanup": bool(cleanup_meta.get("runner_service_cleanup", False)),
-            "cleanup_reason": "matlab_timeout",
-        }
-
-    out = str(run.get("output", ""))
-    m = re.search(r"CODEX_JSON:(\{.*\})", out, flags=re.S)
-    if not m:
-        tail = "\n".join([ln for ln in out.splitlines() if ln.strip()][-12:])
-        payload = {"ok": False, "error": tail or "matlab_json_missing"}
-        if _output_has_crash_marker(out) and (_hard_cleanup_on_failure_enabled() or _force_topic_isolation_enabled()):
-            payload.update(_cleanup_runner_matlab_processes_with_snapshots(hard=True))
-            payload["cleanup_reason"] = "matlab_crash_no_json"
-        return payload
-
-    try:
-        payload = json.loads(m.group(1))
-    except json.JSONDecodeError as exc:
-        return {"ok": False, "error": f"json_decode_error: {exc}"}
-
-    return {"ok": True, "payload": payload}
-
-
-def _compare_class_results(py: dict[str, Any], ml: dict[str, Any]) -> dict[str, Any]:
-    comparisons = []
-
-    def cmp_scalar(name: str, atol: float = 1e-6, rtol: float = 1e-4):
-        pv = float(py[name])
-        mv = float(ml[name])
-        diff = abs(pv - mv)
-        tol = atol + rtol * abs(mv)
-        ok = diff <= tol
-        comparisons.append({"metric": name, "python": pv, "matlab": mv, "abs_diff": diff, "pass": ok})
-
-    def cmp_list(name: str, atol: float = 1e-6):
-        pa = [float(x) for x in py[name]]
-        ma = [float(x) for x in ml[name]]
-        ok = len(pa) == len(ma) and all(abs(a - b) <= atol for a, b in zip(pa, ma))
-        comparisons.append({"metric": name, "python": pa, "matlab": ma, "pass": ok})
-
-    cmp_list("nspike_getISIs", atol=1e-9)
-    cmp_scalar("nspike_rate", atol=1e-9, rtol=1e-9)
-    cmp_scalar("nstcoll_psth_len", atol=0.0, rtol=0.0)
-    cmp_scalar("nstcoll_psth_mean", atol=1e-9, rtol=1e-6)
-    cmp_list("covcoll_shape", atol=0.0)
-    cmp_scalar("history_num_columns", atol=0.0, rtol=0.0)
-    cmp_scalar("trial_sample_rate", atol=1e-9, rtol=1e-9)
-    cmp_list("trial_minmax", atol=1e-9)
-    cmp_scalar("cif_num_realizations", atol=0.0, rtol=0.0)
-
-    passed = sum(1 for c in comparisons if c["pass"])
-    total = len(comparisons)
-    return {
-        "comparisons": comparisons,
-        "summary": {
-            "passed": passed,
-            "total": total,
-            "similarity_score": float(passed / total if total else 0.0),
-        },
-    }
-
-
-def _example_topics() -> list[tuple[str, str]]:
-    tree = ET.parse(TOC_PATH)
-    root = tree.getroot()
-    examples = None
-    for item in root.iter("tocitem"):
-        if item.attrib.get("id") == "nstat_examples":
-            examples = item
-            break
-    if examples is None:
-        raise RuntimeError("Unable to locate examples node in helptoc.xml")
-
-    out: list[tuple[str, str]] = []
-    for item in examples.findall("tocitem"):
-        title = " ".join("".join(item.itertext()).split())
-        target = item.attrib.get("target", "")
-        if target:
-            out.append((title, target))
-    return out
-
-
-def _parse_topics_arg(topics_arg: list[str] | None) -> set[str] | None:
-    if not topics_arg:
-        return None
-    topics: set[str] = set()
-    for raw in topics_arg:
-        for part in raw.split(","):
-            stem = part.strip()
-            if stem:
-                topics.add(stem)
-    return topics or None
-
-
-def _parse_topic_timeout_overrides(specs: list[str]) -> dict[str, int]:
-    out: dict[str, int] = {}
-    for spec in specs:
-        key, sep, value = spec.partition("=")
-        topic = key.strip()
-        raw_seconds = value.strip()
-        if sep != "=" or not topic or not raw_seconds:
-            raise ValueError(f"invalid --topic-timeout '{spec}'; expected TOPIC=SECONDS")
-        try:
-            seconds = int(raw_seconds)
-        except ValueError as exc:
-            raise ValueError(f"invalid timeout value in '{spec}': {raw_seconds}") from exc
-        if seconds <= 0:
-            raise ValueError(f"timeout must be positive in '{spec}'")
-        out[topic] = seconds
-    return out
-
-
-def _resolve_topics(requested_topics: set[str] | None) -> list[tuple[str, str]]:
-    topics = _example_topics()
-    if requested_topics is None:
-        return topics
-
-    available = {Path(target).stem for _, target in topics}
-    missing = sorted(requested_topics - available)
-    if missing:
-        raise ValueError(f"unknown topic(s): {missing}")
-
-    return [(title, target) for title, target in topics if Path(target).stem in requested_topics]
-
-
-def _run_python_topic(stem: str) -> dict[str, Any]:
-    try:
-        mod = importlib.import_module(f"examples.help_topics.{stem}")
-        out = mod.run(repo_root=REPO_ROOT)
-        if not isinstance(out, dict):
-            return {"ok": False, "error": "non_dict_output", "output": out}
-
-        scalar_map = _flatten_numeric_scalars(out)
-        return {"ok": True, "output": out, "scalar_map": scalar_map}
-    except Exception as exc:  # noqa: BLE001
-        return {
-            "ok": False,
-            "error": "".join(traceback.format_exception_only(type(exc), exc)).strip(),
-        }
-
-
-def _run_matlab_help_script(script_rel: str, timeout_s: int = 240) -> dict[str, Any]:
-    if not MATLAB_BIN.exists():
-        return {"ok": False, "error": "matlab_not_found"}
-
-    def run_script_path(path: Path, timeout: int, source_label: str | None = None) -> dict[str, Any]:
-        repo_q = str(REPO_ROOT).replace("'", "''")
-        path_q = str(path).replace("'", "''")
-        script_used = source_label or str(path.relative_to(REPO_ROOT))
-        isolation_enabled = _force_topic_isolation_enabled()
-        hard_cleanup_enabled = _hard_cleanup_on_failure_enabled() or isolation_enabled
-        cmd = (
-            "restoredefaultpath; "
-            f"repo='{repo_q}'; "
-            "cd(repo); addpath(genpath(repo),'-begin'); set(0,'DefaultFigureVisible','off'); close all force; "
-            "try; "
-            f"run('{path_q}'); "
-            "figs=numel(findall(0,'Type','figure')); "
-            "vars=whos; "
-            "scalars=struct(); "
-            "for ii=1:numel(vars); "
-            "vn=vars(ii).name; "
-            "if(strcmp(vn,'P')||strcmp(vn,'ME')||strcmp(vn,'ans')); continue; end; "
-            "try; vv=eval(vn); "
-            "if (isnumeric(vv)&&isscalar(vv)&&isfinite(vv)); "
-            "scalars.(vn)=double(vv); "
-            "elseif (islogical(vv)&&isscalar(vv)); "
-            "scalars.(vn)=double(vv); "
-            "elseif (isstruct(vv)&&isscalar(vv)); "
-            "fn=fieldnames(vv); "
-            "for jj=1:numel(fn); "
-            "f=fn{jj}; sv=vv.(f); "
-            "if (isnumeric(sv)&&isscalar(sv)&&isfinite(sv)); "
-            "if ~isfield(scalars,f); scalars.(f)=double(sv); end; "
-            "scalars.([vn '_' f])=double(sv); "
-            "end; "
-            "end; "
-            "end; "
-            "catch; end; "
-            "end; "
-            "P=struct('ok',logical(1),'figures',figs,'var_count',numel(vars),'scalars',scalars); "
-            "disp(['CODEX_JSON:' jsonencode(P)]); "
-            "catch ME; "
-            "errId=ME.identifier; errMsg=ME.message; "
-            "try; errRep=getReport(ME,'extended','hyperlinks','off'); catch; errRep=''; end; "
-            "P=struct('ok',logical(0),'error',[errId ' | ' errMsg],'error_report',errRep); "
-            "disp(['CODEX_JSON:' jsonencode(P)]); "
-            "end; exit(0);"
-        )
-
-        # Optional strict isolation: clear any pre-existing MATLAB helper/process state
-        # before launching each topic attempt.
-        if isolation_enabled:
-            _cleanup_runner_matlab_processes(hard=True)
-
-        t0 = time.time()
-        try:
-            run = _run_matlab_batch_logged(cmd, timeout)
-        except Exception as exc:  # noqa: BLE001
-            cleanup_meta = {}
-            if hard_cleanup_enabled:
-                cleanup_meta = _cleanup_runner_matlab_processes_with_snapshots(hard=True)
-            return {
-                "ok": False,
-                "error": f"matlab_subprocess_error: {exc}",
-                "runtime_s": float(time.time() - t0),
-                "script_used": script_used,
-                "topic_isolation_enabled": isolation_enabled,
-                "cleanup_reason": "matlab_subprocess_error",
-                **cleanup_meta,
-            }
-
-        if bool(run.get("timed_out", False)):
-            cleanup_meta = {}
-            if hard_cleanup_enabled:
-                cleanup_meta = _cleanup_runner_matlab_processes_with_snapshots(hard=True)
-            return {
-                "ok": False,
-                "error": "matlab_timeout",
-                "runtime_s": float(timeout),
-                "script_used": script_used,
-                "topic_isolation_enabled": isolation_enabled,
-                "timeout_process_snapshot_before_cleanup": cleanup_meta.get("failure_process_snapshot_before_cleanup", ""),
-                "timeout_process_snapshot_after_cleanup": cleanup_meta.get("failure_process_snapshot_after_cleanup", ""),
-                "runner_service_cleanup": bool(cleanup_meta.get("runner_service_cleanup", False)),
-                "cleanup_reason": "matlab_timeout",
-                **cleanup_meta,
-            }
-
-        runtime = float(run.get("runtime_s", time.time() - t0))
-        out = str(run.get("output", ""))
-        m = re.search(r"CODEX_JSON:(\{.*\})", out, flags=re.S)
-        if not m:
-            tail = "\n".join([ln for ln in out.splitlines() if ln.strip()][-10:])
-            payload: dict[str, Any] = {
-                "ok": False,
-                "error": tail or "matlab_json_missing",
-                "runtime_s": runtime,
-                "script_used": script_used,
-                "topic_isolation_enabled": isolation_enabled,
-            }
-            if _output_has_crash_marker(out) and hard_cleanup_enabled:
-                payload.update(_cleanup_runner_matlab_processes_with_snapshots(hard=True))
-                payload["cleanup_reason"] = "matlab_crash_no_json"
-            return payload
-
-        try:
-            payload = json.loads(m.group(1))
-        except json.JSONDecodeError as exc:
-            error_payload: dict[str, Any] = {
-                "ok": False,
-                "error": f"json_decode_error: {exc}",
-                "runtime_s": runtime,
-                "script_used": script_used,
-                "topic_isolation_enabled": isolation_enabled,
-            }
-            if _output_has_crash_marker(out) and hard_cleanup_enabled:
-                error_payload.update(_cleanup_runner_matlab_processes_with_snapshots(hard=True))
-                error_payload["cleanup_reason"] = "json_decode_after_crash_output"
-            return error_payload
-
-        payload["runtime_s"] = runtime
-        payload["script_used"] = script_used
-        payload["topic_isolation_enabled"] = isolation_enabled
-        if (
-            not bool(payload.get("ok"))
-            and hard_cleanup_enabled
-            and _is_retryable_matlab_failure(payload)
-            and str(payload.get("error", "")).strip() != "matlab_timeout"
-        ):
-            payload.update(_cleanup_runner_matlab_processes_with_snapshots(hard=True))
-            payload["cleanup_reason"] = "retryable_matlab_failure"
-        return payload
-
-    def run_with_shadow_safe_copy(path: Path, timeout: int) -> dict[str, Any]:
-        # If a same-stem .mlx exists, direct run() of .m is shadowed in MATLAB.
-        # Execute a temporary copy with a unique name to preserve .m behavior.
-        if path.suffix.lower() == ".m" and path.with_suffix(".mlx").exists():
-            with tempfile.TemporaryDirectory(prefix="nstat_verify_") as temp_dir:
-                temp_script = Path(temp_dir) / f"codex_{path.stem}_shadowsafe.m"
-                shutil.copy2(path, temp_script)
-                out = run_script_path(temp_script, timeout, f"{path.relative_to(REPO_ROOT)} [shadow_safe_copy]")
-            return out
-        return run_script_path(path, timeout, str(path.relative_to(REPO_ROOT)))
-
-    script_abs = REPO_ROOT / script_rel
-    if not script_abs.exists():
-        return {"ok": False, "error": f"missing_script: {script_rel}"}
-
-    primary = run_with_shadow_safe_copy(script_abs, timeout_s)
-    if primary.get("ok"):
-        return primary
-
-    # If .mlx fails and peer .m exists, try .m as fallback to recover from
-    # live-script execution issues and timeout-heavy topics.
-    if script_abs.suffix.lower() == ".mlx":
-        m_peer = script_abs.with_suffix(".m")
-        if m_peer.exists():
-            fallback_timeout = max(timeout_s, 180)
-            fallback = run_with_shadow_safe_copy(m_peer, fallback_timeout)
-            fallback["fallback_from"] = str(script_abs.relative_to(REPO_ROOT))
-            if fallback.get("ok"):
-                return fallback
-            combined = dict(primary)
-            combined["fallback_script_used"] = fallback.get("script_used")
-            combined["fallback_error"] = fallback.get("error", "")
-            combined["fallback_error_report"] = fallback.get("error_report", "")
-            combined["error"] = f"{primary.get('error', '')} || fallback_error: {fallback.get('error', '')}"
-            if not combined.get("cleanup_reason"):
-                combined["cleanup_reason"] = fallback.get("cleanup_reason", "")
-            if not combined.get("failure_process_snapshot_before_cleanup"):
-                combined["failure_process_snapshot_before_cleanup"] = fallback.get(
-                    "failure_process_snapshot_before_cleanup", ""
-                )
-            if not combined.get("failure_process_snapshot_after_cleanup"):
-                combined["failure_process_snapshot_after_cleanup"] = fallback.get(
-                    "failure_process_snapshot_after_cleanup", ""
-                )
-            combined["runner_service_cleanup"] = bool(
-                combined.get("runner_service_cleanup", False) or fallback.get("runner_service_cleanup", False)
-            )
-            combined["topic_isolation_enabled"] = bool(
-                combined.get("topic_isolation_enabled", False) or fallback.get("topic_isolation_enabled", False)
-            )
-            return combined
-
-    return primary
-
-
-def _compare_topic_scalars(py_scalars: dict[str, float], ml_scalars: dict[str, float]) -> dict[str, Any]:
-    ml_norm = {_normalize_key(k): (k, float(v)) for k, v in ml_scalars.items() if _is_number(v)}
-    overlaps = []
-    for pk, pv in py_scalars.items():
-        nk = _normalize_key(pk)
-        if nk in ml_norm:
-            mk, mv = ml_norm[nk]
-            diff = abs(float(pv) - float(mv))
-            tol = 1e-6 + 1e-3 * abs(mv)
-            overlaps.append(
-                {
-                    "python_key": pk,
-                    "matlab_key": mk,
-                    "python": float(pv),
-                    "matlab": float(mv),
-                    "abs_diff": diff,
-                    "pass": diff <= tol,
-                }
-            )
-    passed = sum(1 for o in overlaps if o["pass"])
-    return {
-        "overlaps": overlaps,
-        "overlap_count": len(overlaps),
-        "overlap_passed": passed,
-    }
-
-
-def _help_similarity(
-    topics: list[tuple[str, str]],
-    default_timeout_s: int = DEFAULT_HELP_TOPIC_TIMEOUT_S,
-    topic_timeout_overrides: dict[str, int] | None = None,
-    matlab_max_attempts: int = DEFAULT_MATLAB_MAX_ATTEMPTS,
-) -> dict[str, Any]:
-    rows: list[dict[str, Any]] = []
-
-    summary = {
-        "total_topics": len(topics),
-        "both_ok": 0,
-        "python_ok": 0,
-        "matlab_ok": 0,
-        "scalar_overlap_topics": 0,
-        "scalar_overlap_pass_topics": 0,
-        "avg_similarity_score": 0.0,
-    }
-
-    scores: list[float] = []
-    topic_timeouts = dict(DEFAULT_TOPIC_TIMEOUT_OVERRIDES)
-    if topic_timeout_overrides:
-        topic_timeouts.update(topic_timeout_overrides)
-    for idx, (title, target) in enumerate(topics, start=1):
-        stem = Path(target).stem
-        m_rel = f"helpfiles/{stem}.m"
-        mlx_rel = f"helpfiles/{stem}.mlx"
-        if (FORCE_M_HELP_SCRIPTS or stem in FORCE_M_SCRIPT_TOPICS) and (REPO_ROOT / m_rel).exists():
-            script_rel = m_rel
-        elif (REPO_ROOT / mlx_rel).exists():
-            script_rel = mlx_rel
-        elif (REPO_ROOT / m_rel).exists():
-            script_rel = m_rel
-        else:
-            script_rel = m_rel
-
-        print(f"[help {idx}/{len(topics)}] {stem}", flush=True)
-
-        py = _run_python_topic(stem)
-        timeout_s = topic_timeouts.get(stem, default_timeout_s)
-        ml_attempt_history: list[dict[str, Any]] = []
-        ml = _run_matlab_help_script(script_rel, timeout_s=timeout_s)
-        ml_attempt_history.append(
-            {
-                "attempt": 1,
-                "ok": bool(ml.get("ok")),
-                "error": str(ml.get("error", "")),
-                "runtime_s": float(ml.get("runtime_s") or 0.0),
-                "script_used": str(ml.get("script_used", script_rel)),
-                "cleanup_reason": str(ml.get("cleanup_reason", "")),
-                "runner_service_cleanup": bool(ml.get("runner_service_cleanup", False)),
-                "topic_isolation_enabled": bool(ml.get("topic_isolation_enabled", False)),
-            }
-        )
-        attempt = 1
-        while (
-            attempt < matlab_max_attempts
-            and not bool(ml.get("ok"))
-            and _is_retryable_matlab_failure(ml)
-        ):
-            next_attempt = attempt + 1
-            print(
-                f"[help retry {next_attempt}/{matlab_max_attempts}] {stem} "
-                f"after retryable MATLAB failure: {ml.get('error', '')}",
-                flush=True,
-            )
-            _matlab_warmup()
-            ml = _run_matlab_help_script(script_rel, timeout_s=timeout_s)
-            ml_attempt_history.append(
-                {
-                    "attempt": next_attempt,
-                    "ok": bool(ml.get("ok")),
-                    "error": str(ml.get("error", "")),
-                    "runtime_s": float(ml.get("runtime_s") or 0.0),
-                    "script_used": str(ml.get("script_used", script_rel)),
-                    "cleanup_reason": str(ml.get("cleanup_reason", "")),
-                    "runner_service_cleanup": bool(ml.get("runner_service_cleanup", False)),
-                    "topic_isolation_enabled": bool(ml.get("topic_isolation_enabled", False)),
-                }
-            )
-            attempt = next_attempt
-
-        if py.get("ok"):
-            summary["python_ok"] += 1
-        if ml.get("ok"):
-            summary["matlab_ok"] += 1
-
-        scalar_cmp = {"overlaps": [], "overlap_count": 0, "overlap_passed": 0}
-        if py.get("ok") and ml.get("ok"):
-            scalar_cmp = _compare_topic_scalars(py.get("scalar_map", {}), ml.get("scalars", {}))
-
-        both_ok = bool(py.get("ok") and ml.get("ok"))
-        if both_ok:
-            summary["both_ok"] += 1
-
-        if scalar_cmp["overlap_count"] > 0:
-            summary["scalar_overlap_topics"] += 1
-            if scalar_cmp["overlap_passed"] == scalar_cmp["overlap_count"]:
-                summary["scalar_overlap_pass_topics"] += 1
-
-        if not both_ok:
-            score = 0.0
-        elif scalar_cmp["overlap_count"] == 0:
-            score = 0.7
-        else:
-            score = 0.7 + 0.3 * (scalar_cmp["overlap_passed"] / scalar_cmp["overlap_count"])
-        scores.append(score)
-
-        rows.append(
-            {
-                "topic": stem,
-                "title": title,
-                "python_ok": bool(py.get("ok")),
-                "python_error": py.get("error", ""),
-                "python_output_keys": sorted(list(py.get("output", {}).keys())) if py.get("ok") else [],
-                "python_scalar_count": len(py.get("scalar_map", {})) if py.get("ok") else 0,
-                "matlab_ok": bool(ml.get("ok")),
-                "matlab_error": ml.get("error", ""),
-                "matlab_error_report": ml.get("error_report", ""),
-                "matlab_fallback_error": ml.get("fallback_error", ""),
-                "matlab_fallback_error_report": ml.get("fallback_error_report", ""),
-                "matlab_figures": ml.get("figures"),
-                "matlab_var_count": ml.get("var_count"),
-                "matlab_scalar_count": len(ml.get("scalars", {})) if isinstance(ml.get("scalars"), dict) else 0,
-                "matlab_script_used": ml.get("script_used", script_rel),
-                "matlab_fallback_script_used": ml.get("fallback_script_used", ""),
-                "matlab_runtime_s": ml.get("runtime_s"),
-                "matlab_timeout_s": timeout_s,
-                "matlab_attempts": len(ml_attempt_history),
-                "matlab_retry_applied": len(ml_attempt_history) > 1,
-                "matlab_attempt_history": ml_attempt_history,
-                "matlab_timeout_snapshot_before_cleanup": ml.get("timeout_process_snapshot_before_cleanup", ""),
-                "matlab_timeout_snapshot_after_cleanup": ml.get("timeout_process_snapshot_after_cleanup", ""),
-                "matlab_failure_snapshot_before_cleanup": ml.get("failure_process_snapshot_before_cleanup", ""),
-                "matlab_failure_snapshot_after_cleanup": ml.get("failure_process_snapshot_after_cleanup", ""),
-                "matlab_runner_service_cleanup": bool(ml.get("runner_service_cleanup", False)),
-                "matlab_cleanup_reason": ml.get("cleanup_reason", ""),
-                "matlab_topic_isolation_enabled": bool(ml.get("topic_isolation_enabled", False)),
-                "scalar_overlap": scalar_cmp,
-                "similarity_score": score,
-            }
-        )
-
-    summary["avg_similarity_score"] = float(sum(scores) / len(scores) if scores else 0.0)
-    return {"summary": summary, "rows": rows}
-
-
-def _evaluate_parity_contract(help_rows: list[dict[str, Any]], topics_filter: set[str] | None = None) -> dict[str, Any]:
-    by_topic = {str(r.get("topic", "")): r for r in help_rows}
-    rows: list[dict[str, Any]] = []
-    failures: list[str] = []
-    if topics_filter is None:
-        contract_items = list(PARITY_CONTRACT.items())
-    else:
-        contract_items = [(topic, required_keys) for topic, required_keys in PARITY_CONTRACT.items() if topic in topics_filter]
-        missing_contract_entries = sorted(topics_filter - set(PARITY_CONTRACT))
-        for topic in missing_contract_entries:
-            failures.append(f"{topic}: missing parity contract entry")
-            rows.append({"topic": topic, "required_keys": [], "status": "missing_contract"})
-
-    for topic, required_keys in contract_items:
-        row = by_topic.get(topic)
-        if row is None:
-            failures.append(f"{topic}: missing topic row")
-            rows.append({"topic": topic, "required_keys": required_keys, "status": "missing_topic"})
-            continue
-
-        if not (bool(row.get("python_ok")) and bool(row.get("matlab_ok"))):
-            failures.append(f"{topic}: python_ok={row.get('python_ok')} matlab_ok={row.get('matlab_ok')}")
-            rows.append(
-                {
-                    "topic": topic,
-                    "required_keys": required_keys,
-                    "status": "topic_not_ok",
-                    "python_ok": bool(row.get("python_ok")),
-                    "matlab_ok": bool(row.get("matlab_ok")),
-                }
-            )
-            continue
-
-        overlaps = row.get("scalar_overlap", {}).get("overlaps", [])
-        normalized = {}
-        for ov in overlaps:
-            pkey = str(ov.get("python_key", ""))
-            mkey = str(ov.get("matlab_key", ""))
-            normalized[_normalize_key(pkey)] = ov
-            normalized[_normalize_key(mkey)] = ov
-
-        missing: list[str] = []
-        failing: list[str] = []
-        for key in required_keys:
-            nk = _normalize_key(key)
-            ov = normalized.get(nk)
-            if ov is None:
-                missing.append(key)
-                continue
-            if not bool(ov.get("pass")):
-                failing.append(key)
-
-        status = "pass" if not missing and not failing else "fail"
-        if status == "fail":
-            failures.append(f"{topic}: missing={missing} failing={failing}")
-        rows.append(
-            {
-                "topic": topic,
-                "required_keys": required_keys,
-                "missing_keys": missing,
-                "failing_keys": failing,
-                "status": status,
-            }
-        )
-
-    return {
-        "pass": len(failures) == 0,
-        "failures": failures,
-        "rows": rows,
-    }
-
-
-def _evaluate_regression_gate(report: dict[str, Any]) -> dict[str, Any]:
-    topic_selection = report.get("topic_selection", {})
-    class_summary = report.get("class_similarity", {}).get("summary", {})
-    help_summary = report.get("helpfile_similarity", {}).get("summary", {})
-    help_rows = report.get("helpfile_similarity", {}).get("rows", [])
-    parity_contract = report.get("parity_contract", {})
-
-    failures: list[str] = []
-    full_suite = bool(topic_selection.get("full_suite", True))
-    selected_topics = int(topic_selection.get("total_topics", help_summary.get("total_topics", 0)))
-    python_required = HELP_PYTHON_REQUIRED_OK if full_suite else selected_topics
-    matlab_required = HELP_MATLAB_MIN_OK if full_suite else selected_topics
-    scalar_required = SCALAR_OVERLAP_PASS_MIN_TOPICS if full_suite else selected_topics
-
-    class_passed = int(class_summary.get("passed", 0))
-    class_total = int(class_summary.get("total", 0))
-    if class_total < EXPECTED_CLASS_TOTAL or class_passed != class_total:
-        failures.append(
-            f"class gate failed: expected {EXPECTED_CLASS_TOTAL}/{EXPECTED_CLASS_TOTAL}, got {class_passed}/{class_total}"
-        )
-
-    python_ok = int(help_summary.get("python_ok", 0))
-    total_topics = int(help_summary.get("total_topics", 0))
-    if python_ok < python_required or python_ok != total_topics:
-        if full_suite:
-            failures.append(f"python help gate failed: expected all topics ok, got {python_ok}/{total_topics}")
-        else:
-            failures.append(
-                f"python help gate failed for selected topics: expected {python_required}/{selected_topics}, "
-                f"got {python_ok}/{total_topics}"
-            )
-
-    matlab_ok = int(help_summary.get("matlab_ok", 0))
-    if matlab_ok < matlab_required:
-        if full_suite:
-            failures.append(f"matlab help gate failed: minimum {HELP_MATLAB_MIN_OK}, got {matlab_ok}")
-        else:
-            failures.append(
-                f"matlab help gate failed for selected topics: minimum {matlab_required}, got {matlab_ok}"
-            )
-
-    scalar_overlap_pass_topics = int(help_summary.get("scalar_overlap_pass_topics", 0))
-    if scalar_overlap_pass_topics < scalar_required:
-        if full_suite:
-            failures.append(
-                f"scalar overlap gate failed: minimum {SCALAR_OVERLAP_PASS_MIN_TOPICS}, got {scalar_overlap_pass_topics}"
-            )
-        else:
-            failures.append(
-                f"scalar overlap gate failed for selected topics: minimum {scalar_required}, got {scalar_overlap_pass_topics}"
-            )
-
-    matlab_failed_topics = sorted([str(r.get("topic", "")) for r in help_rows if not bool(r.get("matlab_ok"))])
-    unexpected_failures = sorted(set(matlab_failed_topics) - KNOWN_MATLAB_HELP_FAILURES)
-    if unexpected_failures:
-        failures.append(f"unexpected matlab topic failures: {unexpected_failures}")
-
-    if not bool(parity_contract.get("pass", False)):
-        failures.append(f"parity contract failed: {parity_contract.get('failures', [])}")
-
-    known_missing = sorted(KNOWN_MATLAB_HELP_FAILURES - set(matlab_failed_topics))
-    return {
-        "pass": len(failures) == 0,
-        "failures": failures,
-        "matlab_failed_topics": matlab_failed_topics,
-        "known_allowlist": sorted(KNOWN_MATLAB_HELP_FAILURES),
-        "unexpected_failures": unexpected_failures,
-        "known_allowlist_not_currently_failing": known_missing,
-        "parity_contract_pass": bool(parity_contract.get("pass", False)),
-    }
-
-
-def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
-    parser = argparse.ArgumentParser(description="Verify MATLAB/Python output similarity for nSTAT.")
-    parser.add_argument(
-        "--enforce-gate",
-        action="store_true",
-        help="Return non-zero exit code if regression gate fails.",
-    )
-    parser.add_argument(
-        "--topics",
-        nargs="+",
-        default=None,
-        help="Optional help-topic stems to run (space/comma separated). Default is all topics.",
-    )
-    parser.add_argument(
-        "--default-topic-timeout",
-        type=int,
-        default=DEFAULT_HELP_TOPIC_TIMEOUT_S,
-        help=f"Default MATLAB timeout per topic in seconds (default: {DEFAULT_HELP_TOPIC_TIMEOUT_S}).",
-    )
-    parser.add_argument(
-        "--topic-timeout",
-        action="append",
-        default=[],
-        help="Override per-topic MATLAB timeout using TOPIC=SECONDS (repeatable).",
-    )
-    parser.add_argument(
-        "--matlab-max-attempts",
-        type=int,
-        default=DEFAULT_MATLAB_MAX_ATTEMPTS,
-        help=(
-            "Maximum MATLAB attempts per help topic for retryable failures "
-            f"(default: {DEFAULT_MATLAB_MAX_ATTEMPTS})."
-        ),
-    )
-    parser.add_argument(
-        "--report-path",
-        default="python/reports/python_vs_matlab_similarity_report.json",
-        help="Output report path (absolute or repo-relative).",
-    )
-    return parser.parse_args(argv)
-
-
-def main(argv: list[str] | None = None) -> int:
-    args = _parse_args(argv)
-    report: dict[str, Any] = {}
-    if args.default_topic_timeout <= 0:
-        print("--default-topic-timeout must be positive", file=sys.stderr)
-        return 2
-    if args.matlab_max_attempts <= 0:
-        print("--matlab-max-attempts must be positive", file=sys.stderr)
-        return 2
-    try:
-        requested_topics = _parse_topics_arg(args.topics)
-        topics = _resolve_topics(requested_topics)
-        topic_timeout_overrides = _parse_topic_timeout_overrides(args.topic_timeout)
-    except ValueError as exc:
-        print(str(exc), file=sys.stderr)
-        return 2
-
-    selected_topic_stems = [Path(target).stem for _, target in topics]
-    full_suite = requested_topics is None
-    report["topic_selection"] = {
-        "full_suite": full_suite,
-        "requested_topics": sorted(requested_topics) if requested_topics else [],
-        "selected_topics": selected_topic_stems,
-        "total_topics": len(selected_topic_stems),
-        "default_timeout_s": args.default_topic_timeout,
-        "topic_timeout_overrides": topic_timeout_overrides,
-        "force_m_help_scripts": FORCE_M_HELP_SCRIPTS,
-        "matlab_max_attempts": args.matlab_max_attempts,
-        "runner_service_mode": _runner_service_mode(),
-        "matlab_cleanup_allowed": _matlab_cleanup_allowed(),
-        "matlab_force_topic_isolation": _force_topic_isolation_enabled(),
-        "matlab_hard_cleanup_on_failure": _hard_cleanup_on_failure_enabled(),
-    }
-
-    print("[class] running Python/MATLAB class checks", flush=True)
-    py_cls = _python_class_checks()
-    ml_cls = _matlab_class_checks()
-    if ml_cls.get("ok"):
-        class_cmp = _compare_class_results(py_cls, ml_cls["payload"])
-        report["class_similarity"] = {
-            "python": py_cls,
-            "matlab": ml_cls["payload"],
-            **class_cmp,
-        }
-    else:
-        report["class_similarity"] = {
-            "python": py_cls,
-            "matlab_error": ml_cls.get("error", "matlab_unavailable"),
-            "summary": {"passed": 0, "total": 0, "similarity_score": 0.0},
-            "comparisons": [],
-        }
-
-    report["helpfile_similarity"] = _help_similarity(
-        topics=topics,
-        default_timeout_s=args.default_topic_timeout,
-        topic_timeout_overrides=topic_timeout_overrides,
-        matlab_max_attempts=args.matlab_max_attempts,
-    )
-    contract_topics = None if full_suite else set(selected_topic_stems)
-    report["parity_contract"] = _evaluate_parity_contract(report["helpfile_similarity"]["rows"], topics_filter=contract_topics)
-    report["regression_gate"] = _evaluate_regression_gate(report)
-
-    out = Path(args.report_path)
-    if not out.is_absolute():
-        out = REPO_ROOT / out
-    out.parent.mkdir(parents=True, exist_ok=True)
-    out.write_text(json.dumps(report, indent=2), encoding="utf-8")
-    try:
-        out_print = str(out.relative_to(REPO_ROOT))
-    except ValueError:
-        out_print = str(out)
-
-    printable = {
-        "report": out_print,
-        "topic_selection": report["topic_selection"],
-        "class_similarity": report["class_similarity"]["summary"],
-        "helpfile_similarity": report["helpfile_similarity"]["summary"],
-        "parity_contract": report["parity_contract"],
-        "regression_gate": report["regression_gate"],
-    }
-    print(json.dumps(printable, indent=2))
-    if args.enforce_gate and not report["regression_gate"]["pass"]:
-        return 1
-    return 0
-
-
-if __name__ == "__main__":
-    raise SystemExit(main())