Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
lfs: true
lfs: false

- uses: actions/setup-python@v5
with:
Expand All @@ -36,6 +36,9 @@ jobs:
- name: Unit tests
run: pytest

- name: Class parity tests
run: pytest -q tests/test_*_matlab_parity.py

- name: Verify no MATLAB dependency
run: python tools/compliance/check_no_matlab_dependency.py

Expand Down Expand Up @@ -81,7 +84,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
lfs: true
lfs: false

- uses: actions/setup-python@v5
with:
Expand Down Expand Up @@ -111,7 +114,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
lfs: true
lfs: false

- uses: actions/setup-python@v5
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/data-mirror-refresh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
lfs: true
lfs: false

- uses: actions/setup-python@v5
with:
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/full-parity-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
lfs: true
lfs: false

- uses: actions/setup-python@v5
with:
Expand All @@ -34,6 +34,10 @@ jobs:
run: |
python tools/parity/prepare_validation_images.py

- name: Run class-level MATLAB parity tests
run: |
pytest -q tests/test_*_matlab_parity.py

- name: Run full parity and notebook gates
run: |
python tools/parity/build_parity_snapshot.py \
Expand All @@ -60,7 +64,10 @@ jobs:
--notebook-group all \
--timeout 900 \
--skip-command-tests \
--parity-mode gate
--parity-mode gate \
--enforce-unique-images \
--min-unique-images-per-topic 1 \
--max-cross-topic-reuse-ratio 1.0

- name: Enforce visual validation gate
run: |
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/parity-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
lfs: true
lfs: false

- uses: actions/setup-python@v5
with:
Expand All @@ -33,6 +33,10 @@ jobs:
run: |
python tools/parity/prepare_validation_images.py

- name: Run class-level MATLAB parity tests
run: |
pytest -q tests/test_*_matlab_parity.py

- name: Build parity snapshot and enforce gates
run: |
python tools/parity/build_parity_snapshot.py \
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/release-rc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
lfs: true
lfs: false
fetch-depth: 0

- uses: actions/setup-python@v5
Expand Down Expand Up @@ -77,7 +77,10 @@ jobs:
--timeout 900 \
--skip-command-tests \
--parity-mode gate \
--example-output-spec parity/example_output_spec.yml
--example-output-spec parity/example_output_spec.yml \
--enforce-unique-images \
--min-unique-images-per-topic 1 \
--max-cross-topic-reuse-ratio 1.0

- name: Resolve latest validation PDF
id: pdf
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/release-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
lfs: true
lfs: false
fetch-depth: 0

- uses: actions/setup-python@v5
Expand Down Expand Up @@ -94,7 +94,10 @@ jobs:
--timeout 900 \
--skip-command-tests \
--parity-mode gate \
--example-output-spec parity/example_output_spec.yml
--example-output-spec parity/example_output_spec.yml \
--enforce-unique-images \
--min-unique-images-per-topic 1 \
--max-cross-topic-reuse-ratio 1.0

- name: Resolve latest validation PDF
id: pdf
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/validation-pdf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
lfs: true
lfs: false

- uses: actions/setup-python@v5
with:
Expand Down Expand Up @@ -64,7 +64,10 @@ jobs:
--notebook-group all \
--timeout 900 \
--skip-command-tests \
--parity-mode gate
--parity-mode gate \
--enforce-unique-images \
--min-unique-images-per-topic 1 \
--max-cross-topic-reuse-ratio 1.0

- name: Enforce visual validation gate
run: |
Expand Down
101 changes: 101 additions & 0 deletions PORTING_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Porting Notes

This file tracks MATLAB-to-Python parity constraints, known deviations, and fixture regeneration steps.

## Current scope
- Completed full parity loop for `Events`:
- Python implementation updates (`src/nstat/events.py`)
- MATLAB compatibility wrapper updates (`src/nstat/compat/matlab/__init__.py`, `Events` section)
- MATLAB fixture generator (`matlab/fixture_gen/Events_fixtures.m`)
- Fixture artifact (`tests/fixtures/Events/basic.mat`)
- Python parity tests (`tests/test_events_matlab_parity.py`)
- Python demo (`examples/events_demo.py`)
- Completed full parity loop for `History`:
- Python implementation updates (`src/nstat/history.py`)
- MATLAB compatibility wrapper updates (`src/nstat/compat/matlab/__init__.py`, `History` section)
- MATLAB fixture generator (`matlab/fixture_gen/History_fixtures.m`)
- Fixture artifact (`tests/fixtures/History/basic.mat`)
- Python parity tests (`tests/test_history_matlab_parity.py`)
- Python demo (`examples/history_demo.py`)
- Completed full parity loop for `ConfidenceInterval`:
- Python implementation updates (`src/nstat/confidence.py`)
- MATLAB compatibility wrapper updates (`src/nstat/compat/matlab/__init__.py`, `ConfidenceInterval` section)
- MATLAB fixture generator (`matlab/fixture_gen/ConfidenceInterval_fixtures.m`)
- Fixture artifact (`tests/fixtures/ConfidenceInterval/basic.mat`)
- Python parity tests (`tests/test_confidence_matlab_parity.py`)
- Python demo (`examples/confidence_interval_demo.py`)
- Completed full parity loop for `SignalObj`:
- MATLAB compatibility wrapper updates (`src/nstat/compat/matlab/__init__.py`, `SignalObj` section)
- MATLAB fixture generator (`matlab/fixture_gen/SignalObj_fixtures.m`)
- Fixture artifact (`tests/fixtures/SignalObj/basic.mat`)
- Python parity tests (`tests/test_signalobj_matlab_parity.py`)
- Python demo (`examples/signalobj_demo.py`)
- Completed full parity loop for `Covariate`:
- MATLAB compatibility wrapper updates (`src/nstat/compat/matlab/__init__.py`, `Covariate` section)
- MATLAB fixture generator (`matlab/fixture_gen/Covariate_fixtures.m`)
- Fixture artifact (`tests/fixtures/Covariate/basic.mat`)
- Python parity tests (`tests/test_covariate_matlab_parity.py`)
- Python demo (`examples/covariate_demo.py`)
- Completed full parity loop for `TrialConfig`:
- MATLAB compatibility wrapper updates (`src/nstat/compat/matlab/__init__.py`, `TrialConfig` and `ConfigColl` sections)
- MATLAB fixture generator (`matlab/fixture_gen/TrialConfig_fixtures.m`)
- Fixture artifact (`tests/fixtures/TrialConfig/basic.mat`)
- Python parity tests (`tests/test_trialconfig_matlab_parity.py`)
- Python demo (`examples/trialconfig_demo.py`)
- Completed full parity loop for `ConfigColl`:
- MATLAB compatibility wrapper updates (`src/nstat/compat/matlab/__init__.py`, `ConfigColl` section)
- MATLAB fixture generator (`matlab/fixture_gen/ConfigColl_fixtures.m`)
- Fixture artifact (`tests/fixtures/ConfigColl/basic.mat`)
- Python parity tests (`tests/test_configcoll_matlab_parity.py`)
- Python demo (`examples/configcoll_demo.py`)
- Completed full parity loop for `FitResult`:
- MATLAB compatibility wrapper updates (`src/nstat/compat/matlab/__init__.py`, `FitResult` section)
- MATLAB fixture generator (`matlab/fixture_gen/FitResult_fixtures.m`)
- Fixture artifact (`tests/fixtures/FitResult/basic.mat`)
- Python parity tests (`tests/test_fitresult_matlab_parity.py`)
- Python demo (`examples/fitresult_demo.py`)
- Completed full parity loop for `FitResSummary`:
- MATLAB compatibility wrapper updates (`src/nstat/compat/matlab/__init__.py`, `FitResSummary` section)
- MATLAB fixture generator (`matlab/fixture_gen/FitResSummary_fixtures.m`)
- Fixture artifact (`tests/fixtures/FitResSummary/basic.mat`)
- Python parity tests (`tests/test_fitressummary_matlab_parity.py`)
- Python demo (`examples/fitressummary_demo.py`)
- Completed full parity loop for `DecodingAlgorithms.computeSpikeRateCIs` (MATLAB full signature overload):
- MATLAB compatibility wrapper updates (`src/nstat/compat/matlab/__init__.py`, `DecodingAlgorithms` section)
- MATLAB fixture generator (`matlab/fixture_gen/DecodingAlgorithms_fixtures.m`)
- Fixture artifact (`tests/fixtures/DecodingAlgorithms/basic.mat`)
- Python parity tests (`tests/test_decodingalgorithms_matlab_parity.py`)
- Python demo (`examples/decoding_demo.py`)

## Intentional deviations
- MATLAB indexing is 1-based; Python indexing is 0-based. This does not change `Events` numeric output, but affects user-facing index expectations in general.
- `nstat.events.Events` keeps a Pythonic `subset(start_s, end_s)` helper even though MATLAB `Events` does not define `subset`; this is additive and does not alter MATLAB compatibility wrapper behavior.
- `SignalObj.findNearestTimeIndex` and `findNearestTimeIndices` in Python compatibility currently return 0-based indices; parity assertions convert to MATLAB's 1-based convention when comparing fixtures.
- MATLAB `TrialConfig.fromStructure` currently shifts argument positions (`ensCovMask`/`covLag`) due a six-argument constructor call in `TrialConfig.m`; Python compatibility preserves this behavior for strict parity.

## Tolerances
- `Events` parity checks use exact shape matching and `np.testing.assert_allclose(..., rtol=0.0, atol=1e-12)` for floating-point vectors.
- `Covariate.filtfilt` parity checks currently use `atol=2e-3` due MATLAB/Scipy edge-handling differences at short sequence boundaries.

## Regenerate MATLAB fixtures
From repo root (`nSTAT-python`), run:

```bash
matlab -batch "addpath('/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local'); addpath('matlab/fixture_gen'); Events_fixtures('tests/fixtures/Events/basic.mat');"
matlab -batch "addpath('/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local'); addpath('matlab/fixture_gen'); TrialConfig_fixtures('tests/fixtures/TrialConfig/basic.mat');"
matlab -batch "addpath('/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local'); addpath('matlab/fixture_gen'); ConfigColl_fixtures('tests/fixtures/ConfigColl/basic.mat');"
matlab -batch "addpath('/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local'); addpath('matlab/fixture_gen'); FitResult_fixtures('tests/fixtures/FitResult/basic.mat');"
matlab -batch "addpath('/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local'); addpath('matlab/fixture_gen'); FitResSummary_fixtures('tests/fixtures/FitResSummary/basic.mat');"
matlab -batch "addpath('/Users/iahncajigas/Library/CloudStorage/Dropbox/Research/Matlab/nSTAT_currentRelease_Local'); addpath('matlab/fixture_gen'); DecodingAlgorithms_fixtures('tests/fixtures/DecodingAlgorithms/basic.mat');"
```

## Run parity checks

```bash
pytest -q tests/test_events_matlab_parity.py
pytest -q tests/test_trialconfig_matlab_parity.py
pytest -q tests/test_configcoll_matlab_parity.py
pytest -q tests/test_fitresult_matlab_parity.py
pytest -q tests/test_fitressummary_matlab_parity.py
pytest -q tests/test_decodingalgorithms_matlab_parity.py
```
14 changes: 13 additions & 1 deletion docs/help/parity_dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,19 @@ artifacts in the `parity/` directory.
| Required topics checked | 30 |
| Topics passed | 31 |
| Topics failed | 0 |
| Metrics checked | 180 |
| Metrics checked | 306 |
| Metrics failed | 0 |

## Line-by-line review
| Metric | Value |
|---|---:|
| Topics reviewed | 30 |
| Aligned topics | 0 |
| Partially aligned topics | 2 |
| Needs review topics | 24 |
| Missing artifact topics | 0 |
| Average line alignment ratio | 0.089 |

## Frozen MATLAB data snapshot
| Metric | Value |
|---|---|
Expand All @@ -62,6 +72,8 @@ artifacts in the `parity/` directory.
- [parity_gap_report.json](https://github.com/cajigaslab/nSTAT-python/blob/main/parity/parity_gap_report.json)
- [function_example_alignment_report.json](https://github.com/cajigaslab/nSTAT-python/blob/main/parity/function_example_alignment_report.json)
- [numeric_drift_report.json](https://github.com/cajigaslab/nSTAT-python/blob/main/parity/numeric_drift_report.json)
- [line_by_line_review_report.json](https://github.com/cajigaslab/nSTAT-python/blob/main/parity/line_by_line_review_report.json)
- [line_by_line_review.md](https://github.com/cajigaslab/nSTAT-python/blob/main/parity/line_by_line_review.md)
- [example_output_spec.yml](https://github.com/cajigaslab/nSTAT-python/blob/main/parity/example_output_spec.yml)
- [method_closure_sprint.md](https://github.com/cajigaslab/nSTAT-python/blob/main/parity/method_closure_sprint.md)
- [Full validation report PDF](../assets/reports/nstat_python_validation_report_full_latest.pdf)
40 changes: 40 additions & 0 deletions examples/analysis_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Analysis demo aligned to MATLAB GLM workflow with deterministic arrays."""

from __future__ import annotations

import numpy as np

from nstat.compat.matlab import Analysis


def main() -> None:
X = np.array(
[
[-1.00, 0.20],
[-0.50, -0.10],
[0.00, 0.00],
[0.30, 0.80],
[0.70, -0.60],
[1.10, 0.40],
[1.60, -1.20],
[2.00, 0.90],
],
dtype=float,
)
y = np.array([0, 1, 0, 2, 1, 3, 2, 4], dtype=float)

fit = Analysis.fitGLM(X, y, fitType="poisson", dt=0.1)
resid = Analysis.computeFitResidual(y, X, fit, dt=0.1)
transformed = Analysis.computeInvGausTrans(y, X, fit, dt=0.1)
ks = Analysis.computeKSStats(transformed)

print("Fit intercept:", float(fit.intercept))
print("Fit coefficients:", np.asarray(fit.coefficients, dtype=float).tolist())
print("Log-likelihood:", float(fit.log_likelihood))
print("Residual shape:", np.asarray(resid).shape)
print("Transformed events:", np.asarray(transformed).shape)
print("KS stats:", ks)


if __name__ == "__main__":
main()
36 changes: 36 additions & 0 deletions examples/cif_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""CIF demo aligned to MATLAB CIFExamples core derivatives workflow."""

from __future__ import annotations

import numpy as np

from nstat.compat.matlab import CIF


def main() -> None:
beta = np.array([0.4, -0.25], dtype=float)
stim = np.array(
[
[-1.00, 0.20],
[-0.25, -0.50],
[0.00, 0.00],
[0.50, 0.70],
[1.20, -1.00],
],
dtype=float,
)

cif = CIF(coefficients=beta, intercept=0.0, link="poisson")
lam_delta = cif.evalLambdaDelta(stim)
grad = cif.evalGradient(stim)
jac = cif.evalJacobian(stim)

print("CIF link:", cif.link)
print("Stimulus shape:", stim.shape)
print("Lambda*delta shape:", np.asarray(lam_delta).shape)
print("Gradient shape:", np.asarray(grad).shape)
print("Jacobian shape:", np.asarray(jac).shape)


if __name__ == "__main__":
main()
Loading
Loading