Skip to content

Commit 902f653

Browse files
committed
Merge branch 'main' into dev
2 parents b417724 + fb4387d commit 902f653

21 files changed

Lines changed: 4860 additions & 1532 deletions
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI
2+
3+
on: push
4+
5+
jobs:
6+
build:
7+
name: Build distribution 📦
8+
runs-on: ubuntu-latest
9+
10+
steps:
11+
- uses: actions/checkout@v4
12+
- name: Set up Python
13+
uses: actions/setup-python@v4
14+
with:
15+
python-version: "3.x"
16+
- name: Install pypa/build
17+
run: >-
18+
python3 -m
19+
pip install
20+
build
21+
--user
22+
- name: Build a binary wheel and a source tarball
23+
run: python3 -m build
24+
- name: Store the distribution packages
25+
uses: actions/upload-artifact@v4
26+
with:
27+
name: python-package-distributions
28+
path: dist/
29+
30+
publish-to-pypi:
31+
name: >-
32+
Publish Python 🐍 distribution 📦 to PyPI
33+
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
34+
needs:
35+
- build
36+
runs-on: ubuntu-latest
37+
environment:
38+
name: pypi
39+
url: https://pypi.org/p/PSID # Replace <package-name> with your PyPI project name
40+
permissions:
41+
id-token: write # IMPORTANT: mandatory for trusted publishing
42+
43+
steps:
44+
- name: Download all the dists
45+
uses: actions/download-artifact@v4
46+
with:
47+
name: python-package-distributions
48+
path: dist/
49+
- name: Publish distribution 📦 to PyPI
50+
uses: pypa/gh-action-pypi-publish@release/v1
51+
52+
github-release:
53+
name: >-
54+
Sign the Python 🐍 distribution 📦 with Sigstore
55+
and upload them to GitHub Release
56+
needs:
57+
- publish-to-pypi
58+
runs-on: ubuntu-latest
59+
60+
permissions:
61+
contents: write # IMPORTANT: mandatory for making GitHub Releases
62+
id-token: write # IMPORTANT: mandatory for sigstore
63+
64+
steps:
65+
- name: Download all the dists
66+
uses: actions/download-artifact@v4
67+
with:
68+
name: python-package-distributions
69+
path: dist/
70+
- name: Sign the dists with Sigstore
71+
uses: sigstore/gh-action-sigstore-python@v3.0.0
72+
with:
73+
inputs: >-
74+
./dist/*.tar.gz
75+
./dist/*.whl
76+
- name: Create GitHub Release
77+
env:
78+
GITHUB_TOKEN: ${{ github.token }}
79+
run: >-
80+
gh release create
81+
'${{ github.ref_name }}'
82+
--repo '${{ github.repository }}'
83+
--notes ""
84+
- name: Upload artifact signatures to GitHub Release
85+
env:
86+
GITHUB_TOKEN: ${{ github.token }}
87+
# Upload to GitHub Release using the `gh` CLI.
88+
# `dist/` contains the built packages, and the
89+
# sigstore-produced signatures and certificates.
90+
run: >-
91+
gh release upload
92+
'${{ github.ref_name }}' dist/**
93+
--repo '${{ github.repository }}'
94+
95+
publish-to-testpypi:
96+
name: Publish Python 🐍 distribution 📦 to TestPyPI
97+
needs:
98+
- build
99+
runs-on: ubuntu-latest
100+
if: false
101+
102+
environment:
103+
name: testpypi
104+
url: https://test.pypi.org/p/PSID
105+
106+
permissions:
107+
id-token: write # IMPORTANT: mandatory for trusted publishing
108+
109+
steps:
110+
- name: Download all the dists
111+
uses: actions/download-artifact@v4
112+
with:
113+
name: python-package-distributions
114+
path: dist/
115+
- name: Publish distribution 📦 to TestPyPI
116+
uses: pypa/gh-action-pypi-publish@release/v1
117+
with:
118+
repository-url: https://test.pypi.org/legacy/

ChangeLog.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# Changes
22
Versioning follows [semver](https://semver.org/).
33

4-
- v1.2.0:
4+
- v1.3.0:
55
- Add support for using different horizons for neural/behavior data.
6+
- v1.2.6:
7+
- Fixes minor error in variable init for trial-based ISID.
8+
- v1.2.5:
9+
- Fixes minor `numpy.eye` error that was thrown for unstable learned models.
10+
- v1.2.0:
11+
- Adds version with support for external input (i.e., IPSID).
612
- v1.1.0:
713
- Automatically does the necessary mean-removal preprocessing for input neural/behavior data. Automatically adds back the learned means to predicted signals.
814
- v1.0.6:

README.md

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
- [PSID: Preferential subspace identification <br/> [Python implementation]](#psid-preferential-subspace-identification--python-implementation)
2-
- [Publication](#publication)
1+
- [(I)PSID: (Input) Preferential subspace identification \[Python implementation\]](#ipsid-input-preferential-subspace-identification--python-implementation)
2+
- [Publications](#publications)
3+
- [PSID](#psid)
4+
- [IPSID](#ipsid)
35
- [Usage guide](#usage-guide)
46
- [Installation](#installation)
57
- [Initialization](#initialization)
@@ -10,16 +12,21 @@
1012
- [How to pick the state dimensions nx and n1?](#how-to-pick-the-state-dimensions-nx-and-n1)
1113
- [How to pick the horizon `i`?](#how-to-pick-the-horizon-i)
1214
- [Usage examples](#usage-examples)
15+
- [PSID](#psid-1)
16+
- [IPSID](#ipsid-1)
1317
- [Change Log](#change-log)
1418
- [Licence](#licence)
1519

16-
# PSID: Preferential subspace identification <br/> [Python implementation]
20+
# (I)PSID: (Input) Preferential subspace identification <br/> [Python implementation]
1721

1822
For MATLAB implementation see http://github.com/ShanechiLab/PSID
1923

2024
Given signals y_t (e.g. neural signals) and z_t (e.g behavior), PSID learns a dynamic model for y_t while prioritizing the dynamics that are relevant to z_t.
2125

22-
# Publication
26+
IPSID is an extension of PSID that also supports taking a third signal u_t (e.g., task instructions) that is simultaneously measured with y_t. In the learned dynamical model, u_t plays the role of input to the latent states.
27+
28+
# Publications
29+
## PSID
2330
For the derivation of PSID and results in real neural data see the paper below.
2431

2532
Omid G. Sani, Hamidreza Abbaspourazad, Yan T. Wong, Bijan Pesaran, Maryam M. Shanechi. *Modeling behaviorally relevant neural dynamics enabled by preferential subspace identification*. Nature Neuroscience, 24, 140–149 (2021). https://doi.org/10.1038/s41593-020-00733-0
@@ -31,6 +38,11 @@ Original preprint: https://doi.org/10.1101/808154
3138
You can also find a summary of the paper in the following Twitter thread:
3239
https://twitter.com/MaryamShanechi/status/1325835609345122304
3340

41+
## IPSID
42+
For the derivation of IPSID and results in real neural data see the paper below.
43+
44+
Parsa Vahidi*, Omid G. Sani*, Maryam M. Shanechi. *Modeling and dissociation of intrinsic and input-driven neural population dynamics underlying behavior*. PNAS (2024). https://doi.org/10.1073/pnas.2212887121
45+
3446

3547
# Usage guide
3648
## Installation
@@ -47,12 +59,19 @@ import PSID
4759
```
4860

4961
## Main learning function
50-
The main function for the Python implementation is [source/PSID/PSID.py](https://github.com/ShanechiLab/PyPSID/blob/main/source/PSID/PSID.py) -> function PSID. A complete usage guide is available in the function. The following shows an example case:
62+
The main functions for the Python implementation are the follwing:
63+
- For PSID: [source/PSID/PSID.py](https://github.com/ShanechiLab/PyPSID/blob/main/source/PSID/PSID.py) -> the function called PSID
64+
- For IPSID [source/PSID/IPSID.py](https://github.com/ShanechiLab/PyPSID/blob/main/source/PSID/IPSID.py) -> the function called IPSID
65+
66+
A complete usage guide is available in as comments in each function. The following shows example use cases:
5167
```
52-
idSys = PSID.PSID(y, z, nx, n1, i);
68+
idSys = PSID.PSID(y, z, nx, n1, i)
69+
# Or, if modeling effect of input u is also of interest
70+
idSys = PSID.IPSID(y, z, u, nx, n1, i)
5371
```
5472
Inputs:
5573
- y and z are time x dimension matrices with neural (e.g. LFP signal powers or spike counts) and behavioral data (e.g. joint angles, hand position, etc), respectively.
74+
- IPSID also takes u as an input, which is a time x dimension matrix, containing the measured input data.
5675
- nx is the total number of latent states to be identified.
5776
- n1 is the number of states that are going to be dedicated to behaviorally relevant dynamics.
5877
- i is the subspace horizon used for modeling.
@@ -61,30 +80,34 @@ Output:
6180
- idSys: an LSSM object containing all model parameters (A, Cy, Cz, etc). For a full list see the code.
6281

6382
## Extracting latent states using learned model
64-
Once a model is learned using PSID, you can apply the model to new data (i.e. run the associated Kalman filter) as follows:
83+
Once a model is learned using (I)PSID, you can apply the model to new data (i.e. run the associated Kalman filter) as follows:
6584
```
6685
zPred, yPred, xPred = idSys.predict(y)
86+
# Or, for IPSID:
87+
zPred, yPred, xPred = idSys.predict(y, u)
6788
```
6889
Input:
6990
- y: neural activity time series (time x dimension)
91+
- [For IPSID] u: input time series (time x dimension)
7092

7193
Outputs:
7294
- zPred: one-step ahead prediction of behavior (if any)
7395
- yPred: one-step ahead prediction of neural activity
7496
- xPred: Extracted latent state
7597

7698
## Required preprocessing
77-
A required preprocessing when using PSID is to remove the mean of neural/behavior signals and if needed, add them back to predictions after learning the model. Starting from version 1.1.0, Python and MATLAB PSID libraries automatically do this by default so that users won't need to worry about it. Please update to the latest version if you are using an older version.
99+
- Repeated data dimensions (e.g., two identical neurons) can cause issues for the learning. Remove repeated data dimensions as a preprocessing and repeat predictions as needed to reproduce prediction of repeated data dimensions.
100+
- A required preprocessing when using (I)PSID is to remove the mean of neural/behavior/input signals and if needed, add them back to neural/behavior predictions after learning the model. Starting from version 1.1.0, Python (I)PSID and MATLAB PSID libraries automatically do this by default so that users won't need to worry about it. Please update to the latest version if you are using an older version.
78101

79102
## Choosing the hyperparameters
80103
### How to pick the state dimensions nx and n1?
81-
nx determines the total dimension of the latent state and n1 determines how many of those dimensions will be prioritizing the inclusion of behaviorally relevant neural dynamics (i.e. will be extracted using stage 1 of PSID). So the values that you would select for these hyperparameters depend on the goal of modeling and on the data. Some examples use cases are:
104+
nx determines the total dimension of the latent state and n1 determines how many of those dimensions will be prioritizing the inclusion of behaviorally relevant neural dynamics (i.e. will be extracted using stage 1 of (I)PSID). So the values that you would select for these hyperparameters depend on the goal of modeling and on the data. Some examples use cases are:
82105

83106
If you want to perform dimension reduction, nx will be your desired target dimension. For example, to reduce dimension to 2 to plot low-dimensional visualizations of neural activity, you would use nx=2. Now if you want to reduce dimension while preserving as much behaviorally relevant neural dynamics as possible, you would use n1=nx.
84107
If you want to find the best fit to data overall, you can perform a grid search over values of nx and n1 and pick the value that achieves the best performance metric in the training data. For example, you could pick the nx and n1 pair that achieves the best cross-validated behavior decoding in an inner-cross-validation within the training data.
85108

86109
### How to pick the horizon `i`?
87-
The horizon `i` does not affect the model structure and only affects the intermediate linear algebra operations that PSID performs during the learning of the model. Nevertheless, different values of `i` may have different model learning performance. `i` needs to be at least 2, but also also determines the maximum n1 and nx that can be used per:
110+
The horizon `i` does not affect the model structure and only affects the intermediate linear algebra operations that (I)PSID performs during the learning of the model. Nevertheless, different values of `i` may have different model learning performance. `i` needs to be at least 2, but also also determines the maximum n1 and nx that can be used per:
88111

89112
```
90113
n1 <= nz * i
@@ -96,18 +119,27 @@ So if you have a low dimensional y_k or z_k (small ny or nz), you typically woul
96119
For more information, see the notebook(s) referenced in the next section.
97120

98121
# Usage examples
122+
## PSID
99123
Example code for running PSID is provided in
100124
[source/example/PSID_example.py](https://github.com/ShanechiLab/PyPSID/blob/main/source/PSID/example/PSID_example.py)
101-
This script performs PSID model identification and visualizes the learned eigenvalues similar to in Supplementary Fig 1.
125+
This script performs PSID model identification and visualizes the learned eigenvalues similar to in Supplementary Fig 1 in (Sani et al, 2021).
102126

103127
The following notebook also contains some examples along with more descriptions:
104128
[source/example/PSID_tutorial.ipynb](https://github.com/ShanechiLab/PyPSID/blob/main/source/PSID/example/PSID_tutorial.ipynb)
105129

130+
## IPSID
131+
Example code for running IPSID is provided in
132+
[source/example/IPSID_example.py](https://github.com/ShanechiLab/PyPSID/blob/main/source/PSID/example/IPSID_example.py)
133+
This script performs IPSID model identification and visualizes the learned eigenvalues similar to in Fig. 2A in (Vahidi, Sani, et al, 2024).
134+
135+
The following notebook also contains some examples along with more descriptions:
136+
[source/example/IPSID_tutorial.ipynb](https://github.com/ShanechiLab/PyPSID/blob/main/source/PSID/example/IPSID_tutorial.ipynb)
137+
106138
# Change Log
107-
You can see the change log in in [ChangeLog.md](https://github.com/ShanechiLab/PyPSID/blob/main/ChangeLog.md)
139+
You can see the change log in [ChangeLog.md](https://github.com/ShanechiLab/PyPSID/blob/main/ChangeLog.md)
108140

109141
# Licence
110142
Copyright (c) 2020 University of Southern California
111143
See full notice in [LICENSE.md](https://github.com/ShanechiLab/PyPSID/blob/main/LICENSE.md)
112-
Omid G. Sani and Maryam M. Shanechi
144+
Omid G. Sani, Parsa Vahidi and Maryam M. Shanechi
113145
Shanechi Lab, University of Southern California

pyproject.toml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[build-system]
2+
requires = ["setuptools"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "PSID"
7+
version = "1.2.6"
8+
authors = [
9+
{name = "Omid Sani", email = "omidsani@gmail.com"},
10+
]
11+
description = "Python implementation for preferential subspace identification (PSID)"
12+
requires-python = ">=3.10"
13+
classifiers = [
14+
"Programming Language :: Python :: 3",
15+
"Operating System :: OS Independent",
16+
]
17+
dependencies = [
18+
"numpy",
19+
"scipy",
20+
"scikit-learn",
21+
"matplotlib",
22+
"h5py"
23+
]
24+
dynamic = ["readme"]
25+
26+
[tool.setuptools.dynamic]
27+
readme = {file = ["README.md"], content-type = "text/markdown"}
28+
29+
[tool.setuptools.packages.find]
30+
where = ["source"] # list of folders that contain the packages (["."] by default)

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
numpy
22
scipy
3+
scikit-learn # sklearn
34
matplotlib
5+
h5py
46

57
# Dev tools
68
scikit-learn # Used for tests

setup.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Release tutorial:
2+
# https://packaging.python.org/tutorials/packaging-projects/
3+
# Old version:
4+
# pip install setuptools wheel twine
5+
# python setup.py sdist bdist_wheel
6+
# New version:
7+
# python -m build
8+
# Then run the following to upload to PyPI
9+
# python -m twine upload --repository testpypi dist/*
10+
# pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple PSID --upgrade
11+
# python -m twine upload --repository pypi dist/*
12+
13+
14+
import setuptools, os
15+
16+
# Get the directory of the setup.py file
17+
dir_path = os.path.dirname(os.path.realpath(__file__))
18+
base_dir = os.path.join(dir_path)
19+
20+
# requirements_file_path = os.path.join(base_dir, 'requirements.txt')
21+
# with open(requirements_file_path, "r", encoding="utf-8") as fh:
22+
# requirements = fh.read().split('\n')
23+
24+
readme_file_path = os.path.join(base_dir, "README.md")
25+
with open(readme_file_path, "r", encoding="utf-8") as fh:
26+
long_description = fh.read()
27+
28+
setuptools.setup(
29+
name="PSID",
30+
version="1.2.5",
31+
author="Omid Sani",
32+
author_email="omidsani@gmail.com",
33+
description="Python implementation for preferential subspace identification (PSID)",
34+
long_description=long_description,
35+
long_description_content_type="text/markdown",
36+
url="https://github.com/ShanechiLab/PyPSID",
37+
packages=setuptools.find_packages(where="source"),
38+
package_dir={"": "source"},
39+
package_data={"PSID": ["*.mat"]},
40+
classifiers=[
41+
"Programming Language :: Python :: 3",
42+
"Operating System :: OS Independent",
43+
],
44+
python_requires=">=3.6",
45+
# install_requires=requirements,
46+
)

0 commit comments

Comments
 (0)