neuralign 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- neuralign-0.1.0/.github/scripts/check_pypi_release.py +68 -0
- neuralign-0.1.0/.github/workflows/ci.yml +80 -0
- neuralign-0.1.0/.github/workflows/pages.yml +47 -0
- neuralign-0.1.0/.github/workflows/publish.yml +89 -0
- neuralign-0.1.0/CITATION.cff +36 -0
- neuralign-0.1.0/LICENSE +21 -0
- neuralign-0.1.0/PKG-INFO +74 -0
- neuralign-0.1.0/README.md +45 -0
- neuralign-0.1.0/docs/api.html +133 -0
- neuralign-0.1.0/docs/api.md +74 -0
- neuralign-0.1.0/docs/assets/neuralign-mark.png +0 -0
- neuralign-0.1.0/docs/assets/neuralign-mark.svg +217 -0
- neuralign-0.1.0/docs/assets/neuralign-overview.pdf +0 -0
- neuralign-0.1.0/docs/assets/neuralign-overview.png +0 -0
- neuralign-0.1.0/docs/assets/quickstart.png +0 -0
- neuralign-0.1.0/docs/assets/quickstart.py +45 -0
- neuralign-0.1.0/docs/build.py +51 -0
- neuralign-0.1.0/docs/getting-started.html +24 -0
- neuralign-0.1.0/docs/getting-started.md +31 -0
- neuralign-0.1.0/docs/index.html +31 -0
- neuralign-0.1.0/docs/index.md +45 -0
- neuralign-0.1.0/docs/model-and-outputs.html +91 -0
- neuralign-0.1.0/docs/model-and-outputs.md +44 -0
- neuralign-0.1.0/docs/reproducibility.html +15 -0
- neuralign-0.1.0/docs/reproducibility.md +17 -0
- neuralign-0.1.0/docs/style.css +24 -0
- neuralign-0.1.0/examples/quickstart.png +0 -0
- neuralign-0.1.0/examples/quickstart.py +45 -0
- neuralign-0.1.0/pyproject.toml +57 -0
- neuralign-0.1.0/src/neuralign/__init__.py +10 -0
- neuralign-0.1.0/src/neuralign/adapt.py +439 -0
- neuralign-0.1.0/src/neuralign/callbacks.py +90 -0
- neuralign-0.1.0/src/neuralign/configs.py +52 -0
- neuralign-0.1.0/src/neuralign/gauges.py +67 -0
- neuralign-0.1.0/src/neuralign/initializers.py +106 -0
- neuralign-0.1.0/src/neuralign/losses.py +402 -0
- neuralign-0.1.0/src/neuralign/model.py +418 -0
- neuralign-0.1.0/src/neuralign/neuralign_factors.py +345 -0
- neuralign-0.1.0/src/neuralign/ops.py +54 -0
- neuralign-0.1.0/src/neuralign/simulation.py +53 -0
- neuralign-0.1.0/src/neuralign/trainer.py +81 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Decide whether a verified version is new, without hiding partial uploads."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import tomllib
|
|
7
|
+
from urllib.error import HTTPError
|
|
8
|
+
from urllib.parse import quote
|
|
9
|
+
from urllib.request import Request, urlopen
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def needs_publishing(name, version, filenames):
|
|
13
|
+
"""Return False only for an existing, complete, non-yanked release.
|
|
14
|
+
|
|
15
|
+
PyPI files cannot be replaced. Ordinary main updates keep their version and
|
|
16
|
+
skip publishing; incomplete releases require an explicit maintainer decision.
|
|
17
|
+
Network failures and unexpected API responses must fail the workflow.
|
|
18
|
+
"""
|
|
19
|
+
url = f"https://pypi.org/pypi/{quote(name, safe='')}/{quote(version, safe='')}/json"
|
|
20
|
+
request = Request(
|
|
21
|
+
url,
|
|
22
|
+
headers={"Accept": "application/json", "User-Agent": "neuralign-release-check"},
|
|
23
|
+
)
|
|
24
|
+
try:
|
|
25
|
+
with urlopen(request, timeout=30) as response:
|
|
26
|
+
release = json.load(response)
|
|
27
|
+
except HTTPError as error:
|
|
28
|
+
if error.code == 404:
|
|
29
|
+
return True
|
|
30
|
+
raise
|
|
31
|
+
|
|
32
|
+
available = {
|
|
33
|
+
item["filename"] for item in release["urls"] if not item.get("yanked", False)
|
|
34
|
+
}
|
|
35
|
+
missing = set(filenames) - available
|
|
36
|
+
if missing:
|
|
37
|
+
raise RuntimeError(
|
|
38
|
+
f"PyPI already contains {name} {version}, but its release is incomplete "
|
|
39
|
+
f"or yanked (missing: {', '.join(sorted(missing))}). Recover the release "
|
|
40
|
+
"deliberately or bump the version; automatic publishing will not skip "
|
|
41
|
+
"individual files."
|
|
42
|
+
)
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def main():
|
|
47
|
+
project = tomllib.loads(Path("pyproject.toml").read_text())["project"]
|
|
48
|
+
name, version = project["name"], project["version"]
|
|
49
|
+
wheels = list(Path("dist").glob("*.whl"))
|
|
50
|
+
sources = list(Path("dist").glob("*.tar.gz"))
|
|
51
|
+
if len(wheels) != 1 or len(sources) != 1:
|
|
52
|
+
raise RuntimeError("Expected exactly one verified wheel and source distribution")
|
|
53
|
+
|
|
54
|
+
publish = needs_publishing(name, version, [path.name for path in wheels + sources])
|
|
55
|
+
outputs = f"version={version}\npublish={str(publish).lower()}\n"
|
|
56
|
+
if output_file := os.environ.get("GITHUB_OUTPUT"):
|
|
57
|
+
with open(output_file, "a", encoding="utf-8") as stream:
|
|
58
|
+
stream.write(outputs)
|
|
59
|
+
status = "will be published" if publish else "is already published; skipping upload"
|
|
60
|
+
message = f"{name} {version} {status}."
|
|
61
|
+
print(message)
|
|
62
|
+
if summary_file := os.environ.get("GITHUB_STEP_SUMMARY"):
|
|
63
|
+
with open(summary_file, "a", encoding="utf-8") as stream:
|
|
64
|
+
stream.write(message + "\n")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
main()
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, dev]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, dev]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
15
|
+
cancel-in-progress: true
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
package:
|
|
19
|
+
name: Python ${{ matrix.python }} / ${{ matrix.dependencies }}
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
strategy:
|
|
22
|
+
fail-fast: false
|
|
23
|
+
matrix:
|
|
24
|
+
include:
|
|
25
|
+
- python: "3.10"
|
|
26
|
+
dependencies: minimum
|
|
27
|
+
torch: torch==2.9.1
|
|
28
|
+
numpy: numpy==1.26.0
|
|
29
|
+
- python: "3.11"
|
|
30
|
+
dependencies: latest
|
|
31
|
+
torch: torch>=2.9.1
|
|
32
|
+
numpy: numpy>=1.26
|
|
33
|
+
- python: "3.12"
|
|
34
|
+
dependencies: latest
|
|
35
|
+
torch: torch>=2.9.1
|
|
36
|
+
numpy: numpy>=1.26
|
|
37
|
+
- python: "3.13"
|
|
38
|
+
dependencies: latest
|
|
39
|
+
torch: torch>=2.9.1
|
|
40
|
+
numpy: numpy>=1.26
|
|
41
|
+
- python: "3.14"
|
|
42
|
+
dependencies: latest
|
|
43
|
+
torch: torch>=2.9.1
|
|
44
|
+
numpy: numpy>=1.26
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v7
|
|
47
|
+
- uses: actions/setup-python@v7
|
|
48
|
+
with:
|
|
49
|
+
python-version: ${{ matrix.python }}
|
|
50
|
+
cache: pip
|
|
51
|
+
cache-dependency-path: pyproject.toml
|
|
52
|
+
- name: Build and validate distributions
|
|
53
|
+
run: |
|
|
54
|
+
python -m pip install build twine
|
|
55
|
+
python -m build
|
|
56
|
+
python -m twine check --strict dist/*
|
|
57
|
+
- name: Install the wheel with CPU dependencies
|
|
58
|
+
env:
|
|
59
|
+
TORCH_REQUIREMENT: ${{ matrix.torch }}
|
|
60
|
+
NUMPY_REQUIREMENT: ${{ matrix.numpy }}
|
|
61
|
+
run: |
|
|
62
|
+
python -m pip install --index-url https://download.pytorch.org/whl/cpu "$TORCH_REQUIREMENT"
|
|
63
|
+
python -m pip install "$NUMPY_REQUIREMENT" dist/*.whl
|
|
64
|
+
python -m pip check
|
|
65
|
+
- name: Test the installed package
|
|
66
|
+
run: python -I -m unittest discover -s tests -v
|
|
67
|
+
|
|
68
|
+
docs:
|
|
69
|
+
name: Documentation
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@v7
|
|
73
|
+
- uses: actions/setup-python@v7
|
|
74
|
+
with:
|
|
75
|
+
python-version: "3.12"
|
|
76
|
+
- name: Build documentation and check generated files
|
|
77
|
+
run: |
|
|
78
|
+
python -m pip install "markdown-it-py>=3.0"
|
|
79
|
+
python docs/build.py
|
|
80
|
+
git diff --exit-code -- docs
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Deploy docs to GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: pages
|
|
15
|
+
cancel-in-progress: false
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
deploy:
|
|
19
|
+
environment:
|
|
20
|
+
name: github-pages
|
|
21
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- name: Checkout
|
|
25
|
+
uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Set up Python
|
|
28
|
+
uses: actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.12"
|
|
31
|
+
|
|
32
|
+
- name: Build documentation
|
|
33
|
+
run: |
|
|
34
|
+
python -m pip install "markdown-it-py>=3.0"
|
|
35
|
+
python docs/build.py
|
|
36
|
+
|
|
37
|
+
- name: Setup Pages
|
|
38
|
+
uses: actions/configure-pages@v5
|
|
39
|
+
|
|
40
|
+
- name: Upload artifact
|
|
41
|
+
uses: actions/upload-pages-artifact@v3
|
|
42
|
+
with:
|
|
43
|
+
path: docs
|
|
44
|
+
|
|
45
|
+
- name: Deploy to GitHub Pages
|
|
46
|
+
id: deployment
|
|
47
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
name: Publish package to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
|
|
9
|
+
# A rerun or a second main update must not race an upload already in progress.
|
|
10
|
+
concurrency:
|
|
11
|
+
group: pypi-publish
|
|
12
|
+
cancel-in-progress: false
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
name: Build and verify distributions
|
|
17
|
+
if: github.ref == 'refs/heads/main'
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 20
|
|
20
|
+
outputs:
|
|
21
|
+
publish: ${{ steps.release.outputs.publish }}
|
|
22
|
+
version: ${{ steps.release.outputs.version }}
|
|
23
|
+
steps:
|
|
24
|
+
- name: Checkout
|
|
25
|
+
uses: actions/checkout@v7
|
|
26
|
+
with:
|
|
27
|
+
persist-credentials: false
|
|
28
|
+
|
|
29
|
+
- name: Set up Python
|
|
30
|
+
uses: actions/setup-python@v7
|
|
31
|
+
with:
|
|
32
|
+
python-version: "3.12"
|
|
33
|
+
|
|
34
|
+
- name: Build wheel and source distribution
|
|
35
|
+
run: |
|
|
36
|
+
python -m pip install --upgrade build twine
|
|
37
|
+
python -m build
|
|
38
|
+
python -m twine check --strict dist/*
|
|
39
|
+
|
|
40
|
+
- name: Install CPU PyTorch
|
|
41
|
+
run: python -m pip install --index-url https://download.pytorch.org/whl/cpu "torch>=2.9.1"
|
|
42
|
+
|
|
43
|
+
- name: Test installed wheel
|
|
44
|
+
run: |
|
|
45
|
+
python -m pip install dist/*.whl
|
|
46
|
+
python -m pip check
|
|
47
|
+
python -I -m unittest discover -s tests -v
|
|
48
|
+
|
|
49
|
+
- name: Test installed source distribution
|
|
50
|
+
run: |
|
|
51
|
+
python -m pip uninstall --yes neuralign
|
|
52
|
+
python -m pip install dist/*.tar.gz
|
|
53
|
+
python -m pip check
|
|
54
|
+
python -I -m unittest discover -s tests -v
|
|
55
|
+
|
|
56
|
+
- name: Check whether this version needs publishing
|
|
57
|
+
id: release
|
|
58
|
+
run: python .github/scripts/check_pypi_release.py
|
|
59
|
+
|
|
60
|
+
- name: Upload verified distributions
|
|
61
|
+
uses: actions/upload-artifact@v7
|
|
62
|
+
with:
|
|
63
|
+
name: python-distributions
|
|
64
|
+
path: dist/*
|
|
65
|
+
if-no-files-found: error
|
|
66
|
+
retention-days: 14
|
|
67
|
+
|
|
68
|
+
publish:
|
|
69
|
+
name: Publish new version
|
|
70
|
+
needs: build
|
|
71
|
+
if: github.ref == 'refs/heads/main' && needs.build.outputs.publish == 'true'
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
timeout-minutes: 10
|
|
74
|
+
environment:
|
|
75
|
+
name: pypi
|
|
76
|
+
url: https://pypi.org/project/neuralign/${{ needs.build.outputs.version }}/
|
|
77
|
+
permissions:
|
|
78
|
+
id-token: write
|
|
79
|
+
steps:
|
|
80
|
+
- name: Download verified distributions
|
|
81
|
+
uses: actions/download-artifact@v8
|
|
82
|
+
with:
|
|
83
|
+
name: python-distributions
|
|
84
|
+
path: dist/
|
|
85
|
+
|
|
86
|
+
# No checkout or package execution in the job with publishing permission.
|
|
87
|
+
# Configure this workflow and the pypi environment as a Trusted Publisher.
|
|
88
|
+
- name: Publish distributions with Trusted Publishing
|
|
89
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use Neuralign, please cite this software and the associated manuscript."
|
|
3
|
+
title: "Neuralign"
|
|
4
|
+
type: software
|
|
5
|
+
license: MIT
|
|
6
|
+
version: 0.1.0
|
|
7
|
+
repository-code: "https://github.com/MiklosKralik/neuralign"
|
|
8
|
+
authors:
|
|
9
|
+
- family-names: Králik
|
|
10
|
+
given-names: Miklós
|
|
11
|
+
- family-names: Tinelli
|
|
12
|
+
given-names: Sasha
|
|
13
|
+
- family-names: Kozloski
|
|
14
|
+
given-names: James
|
|
15
|
+
- family-names: Ponte Costa
|
|
16
|
+
given-names: Rui
|
|
17
|
+
- family-names: Sharott
|
|
18
|
+
given-names: Andrew
|
|
19
|
+
- family-names: Peach
|
|
20
|
+
given-names: Robert L.
|
|
21
|
+
preferred-citation:
|
|
22
|
+
type: unpublished
|
|
23
|
+
title: "Identifying aligned tensor decompositions across heterogeneous neural datasets"
|
|
24
|
+
authors:
|
|
25
|
+
- family-names: Králik
|
|
26
|
+
given-names: Miklós
|
|
27
|
+
- family-names: Tinelli
|
|
28
|
+
given-names: Sasha
|
|
29
|
+
- family-names: Kozloski
|
|
30
|
+
given-names: James
|
|
31
|
+
- family-names: Ponte Costa
|
|
32
|
+
given-names: Rui
|
|
33
|
+
- family-names: Sharott
|
|
34
|
+
given-names: Andrew
|
|
35
|
+
- family-names: Peach
|
|
36
|
+
given-names: Robert L.
|
neuralign-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Neuralign contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
neuralign-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neuralign
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Joint tensor decomposition and alignment of trial-resolved neural recordings
|
|
5
|
+
Keywords: computational neuroscience,tensor decomposition,neural population dynamics,representation alignment
|
|
6
|
+
Author: Miklós Králik, Sasha Tinelli, James Kozloski, Rui Ponte Costa, Andrew Sharott, Robert L. Peach
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Requires-Dist: numpy>=1.26
|
|
17
|
+
Requires-Dist: opt-einsum>=3.4.0
|
|
18
|
+
Requires-Dist: torch>=2.9.1
|
|
19
|
+
Requires-Dist: tqdm>=4.67.1
|
|
20
|
+
Requires-Dist: markdown-it-py>=3.0 ; extra == 'docs'
|
|
21
|
+
Requires-Dist: matplotlib>=3.8 ; extra == 'examples'
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Project-URL: Documentation, https://mikloskralik.github.io/neuralign/
|
|
24
|
+
Project-URL: Repository, https://github.com/MiklosKralik/neuralign
|
|
25
|
+
Project-URL: Issues, https://github.com/MiklosKralik/neuralign/issues
|
|
26
|
+
Provides-Extra: docs
|
|
27
|
+
Provides-Extra: examples
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# <img src="https://raw.githubusercontent.com/MiklosKralik/neuralign/main/docs/assets/neuralign-mark.png" width="36" alt="" /> Neuralign
|
|
31
|
+
|
|
32
|
+
Find shared activity patterns across neural recordings, with a factor for each neuron and each trial.
|
|
33
|
+
|
|
34
|
+
[](https://raw.githubusercontent.com/MiklosKralik/neuralign/main/docs/assets/neuralign-overview.pdf)
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
Python 3.10 or later. From PyPI, after the first release:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python -m pip install neuralign
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or directly from GitHub:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python -m pip install git+https://github.com/MiklosKralik/neuralign.git
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quickstart
|
|
51
|
+
|
|
52
|
+
This example generates its own data:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from neuralign import NeuralignModel
|
|
56
|
+
from neuralign.simulation import simulate_sessions
|
|
57
|
+
|
|
58
|
+
sessions, _ = simulate_sessions(seed=42)
|
|
59
|
+
factors = NeuralignModel(sessions, rank=2, max_iter=500, seed=42).fit("cpu")
|
|
60
|
+
|
|
61
|
+
time_factors = factors.V # time × components
|
|
62
|
+
neuron_factors = factors[0].neuron_factors # neurons × components
|
|
63
|
+
trial_factors = factors[0].trial_factors # trials × components
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
For your own recordings, pass a list of arrays in **neurons × time × trials** order. Sessions may have different neuron and trial counts, but must share time bins.
|
|
67
|
+
|
|
68
|
+
[Documentation](https://mikloskralik.github.io/neuralign/) · [Use your recordings](https://mikloskralik.github.io/neuralign/getting-started.html) · [Plotting example](https://github.com/MiklosKralik/neuralign/blob/main/examples/quickstart.py)
|
|
69
|
+
|
|
70
|
+
## Paper
|
|
71
|
+
|
|
72
|
+
Neuralign is described in *Identifying aligned tensor decompositions across heterogeneous neural datasets*.
|
|
73
|
+
|
|
74
|
+
Please cite the software and manuscript when using Neuralign in research. [Citation](https://github.com/MiklosKralik/neuralign/blob/main/CITATION.cff) · [MIT License](https://github.com/MiklosKralik/neuralign/blob/main/LICENSE).
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# <img src="https://raw.githubusercontent.com/MiklosKralik/neuralign/main/docs/assets/neuralign-mark.png" width="36" alt="" /> Neuralign
|
|
2
|
+
|
|
3
|
+
Find shared activity patterns across neural recordings, with a factor for each neuron and each trial.
|
|
4
|
+
|
|
5
|
+
[](https://raw.githubusercontent.com/MiklosKralik/neuralign/main/docs/assets/neuralign-overview.pdf)
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Python 3.10 or later. From PyPI, after the first release:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
python -m pip install neuralign
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or directly from GitHub:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
python -m pip install git+https://github.com/MiklosKralik/neuralign.git
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quickstart
|
|
22
|
+
|
|
23
|
+
This example generates its own data:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from neuralign import NeuralignModel
|
|
27
|
+
from neuralign.simulation import simulate_sessions
|
|
28
|
+
|
|
29
|
+
sessions, _ = simulate_sessions(seed=42)
|
|
30
|
+
factors = NeuralignModel(sessions, rank=2, max_iter=500, seed=42).fit("cpu")
|
|
31
|
+
|
|
32
|
+
time_factors = factors.V # time × components
|
|
33
|
+
neuron_factors = factors[0].neuron_factors # neurons × components
|
|
34
|
+
trial_factors = factors[0].trial_factors # trials × components
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
For your own recordings, pass a list of arrays in **neurons × time × trials** order. Sessions may have different neuron and trial counts, but must share time bins.
|
|
38
|
+
|
|
39
|
+
[Documentation](https://mikloskralik.github.io/neuralign/) · [Use your recordings](https://mikloskralik.github.io/neuralign/getting-started.html) · [Plotting example](https://github.com/MiklosKralik/neuralign/blob/main/examples/quickstart.py)
|
|
40
|
+
|
|
41
|
+
## Paper
|
|
42
|
+
|
|
43
|
+
Neuralign is described in *Identifying aligned tensor decompositions across heterogeneous neural datasets*.
|
|
44
|
+
|
|
45
|
+
Please cite the software and manuscript when using Neuralign in research. [Citation](https://github.com/MiklosKralik/neuralign/blob/main/CITATION.cff) · [MIT License](https://github.com/MiklosKralik/neuralign/blob/main/LICENSE).
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
3
|
+
<title>API · Neuralign</title><link rel="icon" href="assets/neuralign-mark.svg" type="image/svg+xml"><link rel="stylesheet" href="style.css"></head>
|
|
4
|
+
<body><nav aria-label="Main navigation"><a href="index.html" >Neuralign</a> <a href="getting-started.html" >Your recordings</a> <a href="model-and-outputs.html" >Outputs</a> <a href="api.html" aria-current=page>API</a> <a href="reproducibility.html" >Save a fit</a></nav><main><h1>API reference</h1>
|
|
5
|
+
<pre><code class="language-python">from neuralign import NeuralignConfig, NeuralignModel, NeuralignFactors
|
|
6
|
+
</code></pre>
|
|
7
|
+
<h2>Fitting settings</h2>
|
|
8
|
+
<p>Pass settings through <code>NeuralignConfig</code> or directly to <code>NeuralignModel</code>.</p>
|
|
9
|
+
<table>
|
|
10
|
+
<thead>
|
|
11
|
+
<tr>
|
|
12
|
+
<th>Setting</th>
|
|
13
|
+
<th>Default</th>
|
|
14
|
+
<th>Purpose</th>
|
|
15
|
+
</tr>
|
|
16
|
+
</thead>
|
|
17
|
+
<tbody>
|
|
18
|
+
<tr>
|
|
19
|
+
<td><code>rank</code></td>
|
|
20
|
+
<td><code>4</code></td>
|
|
21
|
+
<td>Number of components</td>
|
|
22
|
+
</tr>
|
|
23
|
+
<tr>
|
|
24
|
+
<td><code>max_iter</code></td>
|
|
25
|
+
<td><code>2000</code></td>
|
|
26
|
+
<td>Maximum optimisation steps</td>
|
|
27
|
+
</tr>
|
|
28
|
+
<tr>
|
|
29
|
+
<td><code>lr</code></td>
|
|
30
|
+
<td><code>0.01</code></td>
|
|
31
|
+
<td>Learning rate</td>
|
|
32
|
+
</tr>
|
|
33
|
+
<tr>
|
|
34
|
+
<td><code>patience</code></td>
|
|
35
|
+
<td><code>100</code></td>
|
|
36
|
+
<td>Early-stopping patience</td>
|
|
37
|
+
</tr>
|
|
38
|
+
<tr>
|
|
39
|
+
<td><code>tol</code></td>
|
|
40
|
+
<td><code>1e-6</code></td>
|
|
41
|
+
<td>Early-stopping improvement threshold</td>
|
|
42
|
+
</tr>
|
|
43
|
+
<tr>
|
|
44
|
+
<td><code>seed</code></td>
|
|
45
|
+
<td><code>None</code></td>
|
|
46
|
+
<td>Random seed</td>
|
|
47
|
+
</tr>
|
|
48
|
+
<tr>
|
|
49
|
+
<td><code>progress</code></td>
|
|
50
|
+
<td><code>True</code></td>
|
|
51
|
+
<td>Display fitting progress</td>
|
|
52
|
+
</tr>
|
|
53
|
+
<tr>
|
|
54
|
+
<td><code>lambda_tensor_reconstruction</code></td>
|
|
55
|
+
<td><code>1.0</code></td>
|
|
56
|
+
<td>Reconstruction-loss weight</td>
|
|
57
|
+
</tr>
|
|
58
|
+
<tr>
|
|
59
|
+
<td><code>lambda_procrustes</code></td>
|
|
60
|
+
<td><code>1.0</code></td>
|
|
61
|
+
<td>Alignment-loss weight</td>
|
|
62
|
+
</tr>
|
|
63
|
+
<tr>
|
|
64
|
+
<td><code>non_negative</code></td>
|
|
65
|
+
<td><code>False</code></td>
|
|
66
|
+
<td>Nonnegative factors</td>
|
|
67
|
+
</tr>
|
|
68
|
+
<tr>
|
|
69
|
+
<td><code>loss_type</code></td>
|
|
70
|
+
<td><code>"frobenius"</code></td>
|
|
71
|
+
<td>Also <code>"poisson"</code> or <code>"poisson_log"</code></td>
|
|
72
|
+
</tr>
|
|
73
|
+
</tbody>
|
|
74
|
+
</table>
|
|
75
|
+
<p>The default Frobenius loss fits continuous activity. Count losses require nonnegative counts: <code>"poisson"</code> models rates and <code>"poisson_log"</code> models log-rates. For log-Poisson fits, exponentiate <code>reconstructed_tensor()</code> to obtain rates.</p>
|
|
76
|
+
<h2>NeuralignModel</h2>
|
|
77
|
+
<pre><code class="language-python">NeuralignModel(tensor_sequence, trial_labels=None, trial_mask=None,
|
|
78
|
+
config=None, **settings)
|
|
79
|
+
model.fit(device="cpu")
|
|
80
|
+
</code></pre>
|
|
81
|
+
<p><code>fit</code> returns <code>NeuralignFactors</code>. Inputs use neurons × time × trials and share a time axis.</p>
|
|
82
|
+
<p><code>trial_labels</code> provides one label sequence per session. Alignment uses trial means without labels and condition means with labels.</p>
|
|
83
|
+
<p><code>trial_mask</code> provides one Boolean array per session. <strong>True excludes a trial from alignment only:</strong> it still contributes to reconstruction. Each session needs an unmasked trial.</p>
|
|
84
|
+
<h2>NeuralignFactors</h2>
|
|
85
|
+
<table>
|
|
86
|
+
<thead>
|
|
87
|
+
<tr>
|
|
88
|
+
<th>Method</th>
|
|
89
|
+
<th>Behaviour</th>
|
|
90
|
+
</tr>
|
|
91
|
+
</thead>
|
|
92
|
+
<tbody>
|
|
93
|
+
<tr>
|
|
94
|
+
<td><code>len(factors)</code></td>
|
|
95
|
+
<td>Number of sessions</td>
|
|
96
|
+
</tr>
|
|
97
|
+
<tr>
|
|
98
|
+
<td><code>factors[i]</code> or <code>factors.session(i)</code></td>
|
|
99
|
+
<td>Session factors</td>
|
|
100
|
+
</tr>
|
|
101
|
+
<tr>
|
|
102
|
+
<td><code>factors.to(device)</code></td>
|
|
103
|
+
<td>Move tensors to a device</td>
|
|
104
|
+
</tr>
|
|
105
|
+
<tr>
|
|
106
|
+
<td><code>factors.save(path)</code></td>
|
|
107
|
+
<td>Save factors, configuration and metadata</td>
|
|
108
|
+
</tr>
|
|
109
|
+
<tr>
|
|
110
|
+
<td><code>NeuralignFactors.load(path, map_location="cpu")</code></td>
|
|
111
|
+
<td>Load a saved fit</td>
|
|
112
|
+
</tr>
|
|
113
|
+
<tr>
|
|
114
|
+
<td><code>factors.partial_fit(...)</code></td>
|
|
115
|
+
<td>Add sessions with shared factors frozen</td>
|
|
116
|
+
</tr>
|
|
117
|
+
</tbody>
|
|
118
|
+
</table>
|
|
119
|
+
<p><code>partial_fit(new_session, device="cpu", **settings)</code> returns a copy with new sessions appended. It fits their session factors while freezing <code>w</code>, <code>V</code>, <code>H</code> and condition prototypes. New sessions must share the fitted time axis. Recognised labels anchor alignment; sessions without recognised labels use trial means. Supervised adaptation applies a small condition-centroid penalty; <code>lambda_condition_centroid=0.0</code> disables it.</p>
|
|
120
|
+
<h2>Session outputs</h2>
|
|
121
|
+
<p>Access a session with <code>session = factors[i]</code>. See <a href="model-and-outputs.html">model outputs</a> for its factor properties and shapes.</p>
|
|
122
|
+
<pre><code class="language-python">session.reconstructed_tensor(components=None, *, normalized=False)
|
|
123
|
+
session.reconstructed_latent_tensor(components=None, *, normalized=False)
|
|
124
|
+
session.latent_trajectories(layout="ktr", *, components=None, normalized=False)
|
|
125
|
+
session.projected_trajectories(Z, layout="ktr")
|
|
126
|
+
</code></pre>
|
|
127
|
+
<p>For reconstruction methods, <code>components</code> accepts an index or a list of indices; <code>None</code> includes all components. <code>normalized=True</code> uses unit global weights.</p>
|
|
128
|
+
<h2>Simulation</h2>
|
|
129
|
+
<pre><code class="language-python">from neuralign.simulation import simulate_sessions
|
|
130
|
+
sessions, truth = simulate_sessions(n_sessions=4, n_time=64, noise_std=0.15, seed=42)
|
|
131
|
+
</code></pre>
|
|
132
|
+
<p>Generates rank-two recordings with different neuron/trial counts. <code>noise_std</code> is relative to the clean signal's standard deviation; <code>truth</code> contains the generating factors.</p>
|
|
133
|
+
</main></body></html>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# API reference
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
from neuralign import NeuralignConfig, NeuralignModel, NeuralignFactors
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
## Fitting settings
|
|
8
|
+
|
|
9
|
+
Pass settings through `NeuralignConfig` or directly to `NeuralignModel`.
|
|
10
|
+
|
|
11
|
+
| Setting | Default | Purpose |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `rank` | `4` | Number of components |
|
|
14
|
+
| `max_iter` | `2000` | Maximum optimisation steps |
|
|
15
|
+
| `lr` | `0.01` | Learning rate |
|
|
16
|
+
| `patience` | `100` | Early-stopping patience |
|
|
17
|
+
| `tol` | `1e-6` | Early-stopping improvement threshold |
|
|
18
|
+
| `seed` | `None` | Random seed |
|
|
19
|
+
| `progress` | `True` | Display fitting progress |
|
|
20
|
+
| `lambda_tensor_reconstruction` | `1.0` | Reconstruction-loss weight |
|
|
21
|
+
| `lambda_procrustes` | `1.0` | Alignment-loss weight |
|
|
22
|
+
| `non_negative` | `False` | Nonnegative factors |
|
|
23
|
+
| `loss_type` | `"frobenius"` | Also `"poisson"` or `"poisson_log"` |
|
|
24
|
+
|
|
25
|
+
The default Frobenius loss fits continuous activity. Count losses require nonnegative counts: `"poisson"` models rates and `"poisson_log"` models log-rates. For log-Poisson fits, exponentiate `reconstructed_tensor()` to obtain rates.
|
|
26
|
+
|
|
27
|
+
## NeuralignModel
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
NeuralignModel(tensor_sequence, trial_labels=None, trial_mask=None,
|
|
31
|
+
config=None, **settings)
|
|
32
|
+
model.fit(device="cpu")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`fit` returns `NeuralignFactors`. Inputs use neurons × time × trials and share a time axis.
|
|
36
|
+
|
|
37
|
+
`trial_labels` provides one label sequence per session. Alignment uses trial means without labels and condition means with labels.
|
|
38
|
+
|
|
39
|
+
`trial_mask` provides one Boolean array per session. **True excludes a trial from alignment only:** it still contributes to reconstruction. Each session needs an unmasked trial.
|
|
40
|
+
|
|
41
|
+
## NeuralignFactors
|
|
42
|
+
|
|
43
|
+
| Method | Behaviour |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `len(factors)` | Number of sessions |
|
|
46
|
+
| `factors[i]` or `factors.session(i)` | Session factors |
|
|
47
|
+
| `factors.to(device)` | Move tensors to a device |
|
|
48
|
+
| `factors.save(path)` | Save factors, configuration and metadata |
|
|
49
|
+
| `NeuralignFactors.load(path, map_location="cpu")` | Load a saved fit |
|
|
50
|
+
| `factors.partial_fit(...)` | Add sessions with shared factors frozen |
|
|
51
|
+
|
|
52
|
+
`partial_fit(new_session, device="cpu", **settings)` returns a copy with new sessions appended. It fits their session factors while freezing `w`, `V`, `H` and condition prototypes. New sessions must share the fitted time axis. Recognised labels anchor alignment; sessions without recognised labels use trial means. Supervised adaptation applies a small condition-centroid penalty; `lambda_condition_centroid=0.0` disables it.
|
|
53
|
+
|
|
54
|
+
## Session outputs
|
|
55
|
+
|
|
56
|
+
Access a session with `session = factors[i]`. See [model outputs](model-and-outputs.md) for its factor properties and shapes.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
session.reconstructed_tensor(components=None, *, normalized=False)
|
|
60
|
+
session.reconstructed_latent_tensor(components=None, *, normalized=False)
|
|
61
|
+
session.latent_trajectories(layout="ktr", *, components=None, normalized=False)
|
|
62
|
+
session.projected_trajectories(Z, layout="ktr")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For reconstruction methods, `components` accepts an index or a list of indices; `None` includes all components. `normalized=True` uses unit global weights.
|
|
66
|
+
|
|
67
|
+
## Simulation
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from neuralign.simulation import simulate_sessions
|
|
71
|
+
sessions, truth = simulate_sessions(n_sessions=4, n_time=64, noise_std=0.15, seed=42)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Generates rank-two recordings with different neuron/trial counts. `noise_std` is relative to the clean signal's standard deviation; `truth` contains the generating factors.
|
|
Binary file
|