tensorsig 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.
- tensorsig-0.1.0/LICENSE +21 -0
- tensorsig-0.1.0/PKG-INFO +147 -0
- tensorsig-0.1.0/README.md +121 -0
- tensorsig-0.1.0/pyproject.toml +75 -0
- tensorsig-0.1.0/pyproject.toml.orig +66 -0
- tensorsig-0.1.0/src/tensorsig/__init__.py +24 -0
- tensorsig-0.1.0/src/tensorsig/algebra/__init__.py +17 -0
- tensorsig-0.1.0/src/tensorsig/algebra/bch_coefficients.py +135 -0
- tensorsig-0.1.0/src/tensorsig/algebra/eulerian.py +68 -0
- tensorsig-0.1.0/src/tensorsig/algebra/hall.py +16 -0
- tensorsig-0.1.0/src/tensorsig/algebra/hall_bch.py +386 -0
- tensorsig-0.1.0/src/tensorsig/algebra/hall_projection.py +361 -0
- tensorsig-0.1.0/src/tensorsig/algebra/lyndon.py +16 -0
- tensorsig-0.1.0/src/tensorsig/algebra/lyndon_words.py +114 -0
- tensorsig-0.1.0/src/tensorsig/augmentations/__init__.py +260 -0
- tensorsig-0.1.0/src/tensorsig/classical/__init__.py +18 -0
- tensorsig-0.1.0/src/tensorsig/classical/log_signature.py +527 -0
- tensorsig-0.1.0/src/tensorsig/classical/signature.py +430 -0
- tensorsig-0.1.0/src/tensorsig/classical/signatures.py +15 -0
- tensorsig-0.1.0/src/tensorsig/classical/sparse.py +16 -0
- tensorsig-0.1.0/src/tensorsig/classical/sparse_signature.py +587 -0
- tensorsig-0.1.0/src/tensorsig/classical/windows.py +82 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/__init__.py +45 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_charts/__init__.py +6 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_charts/chronological.py +575 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_charts/colored_descent.py +260 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_charts/pascal.py +180 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_charts/primitive.py +423 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/__init__.py +1 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/_graph_common.py +195 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/adaptive.py +116 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/chordal_nbc.py +110 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/direct_nbc.py +381 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/literal_energy.py +299 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/one_centre_gram.py +264 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/rational_coordinates.py +729 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/star_gram.py +261 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/tree_moments.py +248 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/young_dp.py +286 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_lie_quotients/__init__.py +1 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_lie_quotients/compiler.py +687 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_lie_quotients/enveloping.py +543 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_lie_quotients/rules.py +240 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_lie_quotients/trace_compiler.py +484 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_lie_quotients/upper_set_normal_form.py +439 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_sliding.py +342 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_word_program.py +350 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/active_word_log.py +135 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/codes.py +646 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/coordinates.py +84 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/diagnostics.py +294 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/gap_factorized_volterra.py +455 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/graph_queries.py +243 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/layers.py +419 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/monomials.py +449 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/offset_patterns.py +172 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/pascal_certificate.py +320 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/pascal_rank.py +79 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/plans.py +1292 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/quotient_basis.py +77 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/quotients.py +46 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/rolling_logsignature/__init__.py +297 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/rolling_logsignature/_bch.py +66 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/rolling_logsignature/_compile.py +116 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/rolling_logsignature/_joint.py +420 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/rolling_logsignature/_program.py +159 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/rolling_logsignature/_scan.py +153 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/scaled_monomials.py +316 -0
- tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/windows.py +456 -0
- tensorsig-0.1.0/src/tensorsig/tensor_ops/__init__.py +15 -0
- tensorsig-0.1.0/src/tensorsig/tensor_ops/linear.py +141 -0
- tensorsig-0.1.0/src/tensorsig/tensor_ops/products.py +82 -0
- tensorsig-0.1.0/src/tensorsig/tensor_ops/series.py +458 -0
tensorsig-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Erlend Aune
|
|
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.
|
tensorsig-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tensorsig
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Differentiable tensor signatures, offset-coded signatures, and log-signatures for PyTorch.
|
|
5
|
+
Keywords: deep-learning,pytorch,signatures,time-series
|
|
6
|
+
Author: Erlend Aune
|
|
7
|
+
Author-email: Erlend Aune <erlend.aune@pm.me>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
19
|
+
Requires-Dist: torch>=2.14
|
|
20
|
+
Requires-Python: >=3.14
|
|
21
|
+
Project-URL: Documentation, https://froskekongen.github.io/tensorsig/
|
|
22
|
+
Project-URL: Issues, https://github.com/Froskekongen/tensorsig/issues
|
|
23
|
+
Project-URL: Repository, https://github.com/Froskekongen/tensorsig
|
|
24
|
+
Project-URL: Releases, https://github.com/Froskekongen/tensorsig/releases
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# tensorsig
|
|
28
|
+
|
|
29
|
+
`tensorsig` provides differentiable signature, log-signature, and offset-coded
|
|
30
|
+
signature features for PyTorch.
|
|
31
|
+
|
|
32
|
+
The first release is being prepared. The public API may change between `0.x`
|
|
33
|
+
releases.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
`tensorsig` requires Python 3.14 or newer and PyTorch 2.14 or newer.
|
|
38
|
+
|
|
39
|
+
Until the first PyPI release, install from a checkout of this repository:
|
|
40
|
+
|
|
41
|
+
```console
|
|
42
|
+
python -m pip install .
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
After publication, install with `python -m pip install tensorsig`.
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import torch
|
|
51
|
+
|
|
52
|
+
from tensorsig.classical.signatures import log_signature, signature
|
|
53
|
+
|
|
54
|
+
path = torch.tensor(
|
|
55
|
+
[[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [2.0, 1.0]]],
|
|
56
|
+
dtype=torch.float64,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
first_turn = path[:, :3]
|
|
60
|
+
sig = signature(first_turn, depth=2)
|
|
61
|
+
logsig = log_signature(first_turn, depth=2)
|
|
62
|
+
|
|
63
|
+
assert sig.shape == (1, 6)
|
|
64
|
+
assert logsig.shape == (1, 3)
|
|
65
|
+
torch.testing.assert_close(
|
|
66
|
+
sig,
|
|
67
|
+
torch.tensor([[1.0, 1.0, 0.5, 1.0, 0.0, 0.5]], dtype=path.dtype),
|
|
68
|
+
)
|
|
69
|
+
torch.testing.assert_close(
|
|
70
|
+
logsig,
|
|
71
|
+
torch.tensor([[1.0, 1.0, 0.5]], dtype=path.dtype),
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Start with the practitioner guides to
|
|
76
|
+
[signatures and logsignatures](https://froskekongen.github.io/tensorsig/signatures/).
|
|
77
|
+
The complete callable surface is in the
|
|
78
|
+
[API reference](https://froskekongen.github.io/tensorsig/api/).
|
|
79
|
+
The hosted documentation will become available with the first release. Until
|
|
80
|
+
then, use the local preview described under [Development](#development).
|
|
81
|
+
|
|
82
|
+
## Choosing local features
|
|
83
|
+
|
|
84
|
+
| Need | Start with |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| Classical logsignatures after path lifts | `tensorsig.augmentations` followed by `classical.windows.WindowedLogSignature` in `torch.nn.Sequential` |
|
|
87
|
+
| Signatures or logsignatures retaining local offset information | `offset_coded_signatures.plans.LocalRealization` with a `codes.OffsetCode` |
|
|
88
|
+
| Scalar polynomials in physical increments | `offset_coded_signatures.plans.compile_polynomial` |
|
|
89
|
+
| Apply a compiled local plan to a path | `offset_coded_signatures.layers.LocalFeatures` or `LocalLinear` |
|
|
90
|
+
| Explicit quotient window algorithms on increment streams | `offset_coded_signatures.windows.WindowLogSignature` |
|
|
91
|
+
|
|
92
|
+
The [offset-coded signatures guide](https://froskekongen.github.io/tensorsig/offset_coded_signatures/)
|
|
93
|
+
shows these constructions and their input conventions. Offset-coded windows
|
|
94
|
+
count increments; classical windows count points. Path augmentations are shared
|
|
95
|
+
across both families and run before window extraction.
|
|
96
|
+
|
|
97
|
+
## Citing
|
|
98
|
+
|
|
99
|
+
If you use `tensorsig`, cite the software version or commit you used. Citation
|
|
100
|
+
metadata is provided in
|
|
101
|
+
[`CITATION.cff`](https://github.com/Froskekongen/tensorsig/blob/main/CITATION.cff),
|
|
102
|
+
which GitHub exposes through **Cite this repository** in APA and BibTeX formats.
|
|
103
|
+
|
|
104
|
+
Zenodo DOI links will be added after the first archived release. For
|
|
105
|
+
reproducible work, use that release's version-specific DOI; use the concept
|
|
106
|
+
DOI when referring to `tensorsig` independently of a particular version.
|
|
107
|
+
|
|
108
|
+
## Development
|
|
109
|
+
|
|
110
|
+
The project uses [uv](https://docs.astral.sh/uv/) and keeps its development
|
|
111
|
+
environment in the committed `uv.lock`. Python requirements are defined in
|
|
112
|
+
`pyproject.toml`.
|
|
113
|
+
|
|
114
|
+
```console
|
|
115
|
+
uv sync --locked
|
|
116
|
+
uv run ruff check .
|
|
117
|
+
uv run ruff format --check .
|
|
118
|
+
uv run pytest -q
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Generate and strictly build the complete HTML documentation with:
|
|
122
|
+
|
|
123
|
+
```console
|
|
124
|
+
uv run python scripts/generate_docs.py
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
To build, start a local preview, and open it in your browser:
|
|
128
|
+
|
|
129
|
+
```console
|
|
130
|
+
uv run python scripts/generate_docs.py --serve
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The HTML site is written to `site/`. Stop the preview with Ctrl+C. After editing
|
|
134
|
+
Python guides or docstrings, rerun the script to regenerate the documentation.
|
|
135
|
+
|
|
136
|
+
Validate a prepared release with `scripts/release.sh --check X.Y.Z`, then publish
|
|
137
|
+
it from clean `main` with `scripts/release.sh X.Y.Z`. Keep the package version and
|
|
138
|
+
`CITATION.cff` version aligned, and set `date-released` in `CITATION.cff` to the
|
|
139
|
+
actual release date before running the release checks. GitHub Actions publishes
|
|
140
|
+
to PyPI and deploys the documentation when the GitHub Release is published. The
|
|
141
|
+
PyPI Trusted Publisher and GitHub Pages Actions source must be configured before
|
|
142
|
+
the first release.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
`tensorsig` is released under the
|
|
147
|
+
[MIT License](https://github.com/Froskekongen/tensorsig/blob/main/LICENSE).
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# tensorsig
|
|
2
|
+
|
|
3
|
+
`tensorsig` provides differentiable signature, log-signature, and offset-coded
|
|
4
|
+
signature features for PyTorch.
|
|
5
|
+
|
|
6
|
+
The first release is being prepared. The public API may change between `0.x`
|
|
7
|
+
releases.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
`tensorsig` requires Python 3.14 or newer and PyTorch 2.14 or newer.
|
|
12
|
+
|
|
13
|
+
Until the first PyPI release, install from a checkout of this repository:
|
|
14
|
+
|
|
15
|
+
```console
|
|
16
|
+
python -m pip install .
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
After publication, install with `python -m pip install tensorsig`.
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import torch
|
|
25
|
+
|
|
26
|
+
from tensorsig.classical.signatures import log_signature, signature
|
|
27
|
+
|
|
28
|
+
path = torch.tensor(
|
|
29
|
+
[[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [2.0, 1.0]]],
|
|
30
|
+
dtype=torch.float64,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
first_turn = path[:, :3]
|
|
34
|
+
sig = signature(first_turn, depth=2)
|
|
35
|
+
logsig = log_signature(first_turn, depth=2)
|
|
36
|
+
|
|
37
|
+
assert sig.shape == (1, 6)
|
|
38
|
+
assert logsig.shape == (1, 3)
|
|
39
|
+
torch.testing.assert_close(
|
|
40
|
+
sig,
|
|
41
|
+
torch.tensor([[1.0, 1.0, 0.5, 1.0, 0.0, 0.5]], dtype=path.dtype),
|
|
42
|
+
)
|
|
43
|
+
torch.testing.assert_close(
|
|
44
|
+
logsig,
|
|
45
|
+
torch.tensor([[1.0, 1.0, 0.5]], dtype=path.dtype),
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Start with the practitioner guides to
|
|
50
|
+
[signatures and logsignatures](https://froskekongen.github.io/tensorsig/signatures/).
|
|
51
|
+
The complete callable surface is in the
|
|
52
|
+
[API reference](https://froskekongen.github.io/tensorsig/api/).
|
|
53
|
+
The hosted documentation will become available with the first release. Until
|
|
54
|
+
then, use the local preview described under [Development](#development).
|
|
55
|
+
|
|
56
|
+
## Choosing local features
|
|
57
|
+
|
|
58
|
+
| Need | Start with |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| Classical logsignatures after path lifts | `tensorsig.augmentations` followed by `classical.windows.WindowedLogSignature` in `torch.nn.Sequential` |
|
|
61
|
+
| Signatures or logsignatures retaining local offset information | `offset_coded_signatures.plans.LocalRealization` with a `codes.OffsetCode` |
|
|
62
|
+
| Scalar polynomials in physical increments | `offset_coded_signatures.plans.compile_polynomial` |
|
|
63
|
+
| Apply a compiled local plan to a path | `offset_coded_signatures.layers.LocalFeatures` or `LocalLinear` |
|
|
64
|
+
| Explicit quotient window algorithms on increment streams | `offset_coded_signatures.windows.WindowLogSignature` |
|
|
65
|
+
|
|
66
|
+
The [offset-coded signatures guide](https://froskekongen.github.io/tensorsig/offset_coded_signatures/)
|
|
67
|
+
shows these constructions and their input conventions. Offset-coded windows
|
|
68
|
+
count increments; classical windows count points. Path augmentations are shared
|
|
69
|
+
across both families and run before window extraction.
|
|
70
|
+
|
|
71
|
+
## Citing
|
|
72
|
+
|
|
73
|
+
If you use `tensorsig`, cite the software version or commit you used. Citation
|
|
74
|
+
metadata is provided in
|
|
75
|
+
[`CITATION.cff`](https://github.com/Froskekongen/tensorsig/blob/main/CITATION.cff),
|
|
76
|
+
which GitHub exposes through **Cite this repository** in APA and BibTeX formats.
|
|
77
|
+
|
|
78
|
+
Zenodo DOI links will be added after the first archived release. For
|
|
79
|
+
reproducible work, use that release's version-specific DOI; use the concept
|
|
80
|
+
DOI when referring to `tensorsig` independently of a particular version.
|
|
81
|
+
|
|
82
|
+
## Development
|
|
83
|
+
|
|
84
|
+
The project uses [uv](https://docs.astral.sh/uv/) and keeps its development
|
|
85
|
+
environment in the committed `uv.lock`. Python requirements are defined in
|
|
86
|
+
`pyproject.toml`.
|
|
87
|
+
|
|
88
|
+
```console
|
|
89
|
+
uv sync --locked
|
|
90
|
+
uv run ruff check .
|
|
91
|
+
uv run ruff format --check .
|
|
92
|
+
uv run pytest -q
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Generate and strictly build the complete HTML documentation with:
|
|
96
|
+
|
|
97
|
+
```console
|
|
98
|
+
uv run python scripts/generate_docs.py
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
To build, start a local preview, and open it in your browser:
|
|
102
|
+
|
|
103
|
+
```console
|
|
104
|
+
uv run python scripts/generate_docs.py --serve
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The HTML site is written to `site/`. Stop the preview with Ctrl+C. After editing
|
|
108
|
+
Python guides or docstrings, rerun the script to regenerate the documentation.
|
|
109
|
+
|
|
110
|
+
Validate a prepared release with `scripts/release.sh --check X.Y.Z`, then publish
|
|
111
|
+
it from clean `main` with `scripts/release.sh X.Y.Z`. Keep the package version and
|
|
112
|
+
`CITATION.cff` version aligned, and set `date-released` in `CITATION.cff` to the
|
|
113
|
+
actual release date before running the release checks. GitHub Actions publishes
|
|
114
|
+
to PyPI and deploys the documentation when the GitHub Release is published. The
|
|
115
|
+
PyPI Trusted Publisher and GitHub Pages Actions source must be configured before
|
|
116
|
+
the first release.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
`tensorsig` is released under the
|
|
121
|
+
[MIT License](https://github.com/Froskekongen/tensorsig/blob/main/LICENSE).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tensorsig"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Differentiable tensor signatures, offset-coded signatures, and log-signatures for PyTorch."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
keywords = [
|
|
10
|
+
"deep-learning",
|
|
11
|
+
"pytorch",
|
|
12
|
+
"signatures",
|
|
13
|
+
"time-series",
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Programming Language :: Python :: 3.15",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
24
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
25
|
+
]
|
|
26
|
+
dependencies = ["torch>=2.14"]
|
|
27
|
+
|
|
28
|
+
[[project.authors]]
|
|
29
|
+
name = "Erlend Aune"
|
|
30
|
+
email = "erlend.aune@pm.me"
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Documentation = "https://froskekongen.github.io/tensorsig/"
|
|
34
|
+
Issues = "https://github.com/Froskekongen/tensorsig/issues"
|
|
35
|
+
Repository = "https://github.com/Froskekongen/tensorsig"
|
|
36
|
+
Releases = "https://github.com/Froskekongen/tensorsig/releases"
|
|
37
|
+
|
|
38
|
+
[dependency-groups]
|
|
39
|
+
docs = [
|
|
40
|
+
"griffelib>=2.2.0",
|
|
41
|
+
"jupytext>=1.17.3",
|
|
42
|
+
"mkdocstrings[python]>=0.25.0",
|
|
43
|
+
"zensical>=0.0.57",
|
|
44
|
+
]
|
|
45
|
+
test = [
|
|
46
|
+
{ include-group = "docs" },
|
|
47
|
+
"numpy>=2.3",
|
|
48
|
+
"pytest>=9.0.1",
|
|
49
|
+
"ruff>=0.14.8",
|
|
50
|
+
]
|
|
51
|
+
dev = [
|
|
52
|
+
{ include-group = "test" },
|
|
53
|
+
"esig>=1.0.0",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[build-system]
|
|
57
|
+
requires = ["uv_build>=0.13.6,<0.14.0"]
|
|
58
|
+
build-backend = "uv_build"
|
|
59
|
+
|
|
60
|
+
[tool.uv.build-backend]
|
|
61
|
+
source-exclude = [
|
|
62
|
+
"/src/tensorsig/experimental",
|
|
63
|
+
"*.sqlite",
|
|
64
|
+
"*.sqlite-*",
|
|
65
|
+
".DS_Store",
|
|
66
|
+
"/src/**/*.md",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
select = [
|
|
71
|
+
"E4",
|
|
72
|
+
"E7",
|
|
73
|
+
"E9",
|
|
74
|
+
"F",
|
|
75
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tensorsig"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Differentiable tensor signatures, offset-coded signatures, and log-signatures for PyTorch."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Erlend Aune", email = "erlend.aune@pm.me" },
|
|
11
|
+
]
|
|
12
|
+
keywords = ["deep-learning", "pytorch", "signatures", "time-series"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
19
|
+
"Programming Language :: Python :: 3.14",
|
|
20
|
+
"Programming Language :: Python :: 3.15",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"torch>=2.14",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Documentation = "https://froskekongen.github.io/tensorsig/"
|
|
30
|
+
Issues = "https://github.com/Froskekongen/tensorsig/issues"
|
|
31
|
+
Repository = "https://github.com/Froskekongen/tensorsig"
|
|
32
|
+
Releases = "https://github.com/Froskekongen/tensorsig/releases"
|
|
33
|
+
|
|
34
|
+
[dependency-groups]
|
|
35
|
+
docs = [
|
|
36
|
+
"griffelib>=2.2.0",
|
|
37
|
+
"jupytext>=1.17.3",
|
|
38
|
+
"mkdocstrings[python]>=0.25.0",
|
|
39
|
+
"zensical>=0.0.57",
|
|
40
|
+
]
|
|
41
|
+
test = [
|
|
42
|
+
{ include-group = "docs" },
|
|
43
|
+
"numpy>=2.3",
|
|
44
|
+
"pytest>=9.0.1",
|
|
45
|
+
"ruff>=0.14.8",
|
|
46
|
+
]
|
|
47
|
+
dev = [
|
|
48
|
+
{ include-group = "test" },
|
|
49
|
+
"esig>=1.0.0",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[build-system]
|
|
53
|
+
requires = ["uv_build>=0.13.6,<0.14.0"]
|
|
54
|
+
build-backend = "uv_build"
|
|
55
|
+
|
|
56
|
+
[tool.uv.build-backend]
|
|
57
|
+
source-exclude = [
|
|
58
|
+
"/src/tensorsig/experimental",
|
|
59
|
+
"*.sqlite",
|
|
60
|
+
"*.sqlite-*",
|
|
61
|
+
".DS_Store",
|
|
62
|
+
"/src/**/*.md",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[tool.ruff.lint]
|
|
66
|
+
select = ["E4", "E7", "E9", "F"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Differentiable tensor-signature tools for PyTorch.
|
|
2
|
+
|
|
3
|
+
The public API is organized into domain modules:
|
|
4
|
+
|
|
5
|
+
- ``tensorsig.classical`` for path signatures and log-signatures;
|
|
6
|
+
- ``tensorsig.augmentations`` for composable path lifts;
|
|
7
|
+
- ``tensorsig.algebra`` for Hall and Lyndon basis utilities;
|
|
8
|
+
- ``tensorsig.tensor_ops`` for low-level tensor-algebra operations; and
|
|
9
|
+
- ``tensorsig.offset_coded_signatures`` for local coded features.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from . import classical as classical
|
|
13
|
+
from . import augmentations as augmentations
|
|
14
|
+
from . import algebra as algebra
|
|
15
|
+
from . import tensor_ops as tensor_ops
|
|
16
|
+
from . import offset_coded_signatures as offset_coded_signatures
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"classical",
|
|
20
|
+
"augmentations",
|
|
21
|
+
"algebra",
|
|
22
|
+
"tensor_ops",
|
|
23
|
+
"offset_coded_signatures",
|
|
24
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Free Lie coordinates and Eulerian tensor-word projection.
|
|
2
|
+
|
|
3
|
+
Hall trees and Lyndon words are two coordinate systems for the same truncated
|
|
4
|
+
free Lie algebra. Their modules expose basis construction, dimensions, and
|
|
5
|
+
stable human-readable coordinate labels; numerical log-signature evaluation
|
|
6
|
+
lives in ``tensorsig.classical.signatures``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from . import hall as hall
|
|
10
|
+
from . import lyndon as lyndon
|
|
11
|
+
from . import eulerian as eulerian
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"hall",
|
|
15
|
+
"lyndon",
|
|
16
|
+
"eulerian",
|
|
17
|
+
]
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Simple BCH coefficient generator (tensor-algebra series with exact rationals).
|
|
2
|
+
|
|
3
|
+
This module provides functions to generate Baker-Campbell-Hausdorff coefficients
|
|
4
|
+
using exact rational arithmetic. It computes word -> rational coefficient mappings
|
|
5
|
+
for log(prod_i exp(E_i)), where E_i are the one-letter generators (1..width),
|
|
6
|
+
truncated to a given depth.
|
|
7
|
+
|
|
8
|
+
This is intended for verification/offline coefficient generation (tests, debug),
|
|
9
|
+
not runtime. Arithmetic uses Python's Fractions and concatenation; no external
|
|
10
|
+
CAS is required.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import math
|
|
16
|
+
from fractions import Fraction
|
|
17
|
+
from typing import Dict, Tuple
|
|
18
|
+
|
|
19
|
+
Word = Tuple[int, ...]
|
|
20
|
+
CoeffMap = Dict[Word, Fraction]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _mul_series(a: CoeffMap, b: CoeffMap, depth: int) -> CoeffMap:
|
|
24
|
+
"""Cauchy product of two series, truncating words beyond `depth`."""
|
|
25
|
+
out: CoeffMap = {}
|
|
26
|
+
for w1, c1 in a.items():
|
|
27
|
+
for w2, c2 in b.items():
|
|
28
|
+
w = w1 + w2
|
|
29
|
+
if len(w) > depth:
|
|
30
|
+
continue
|
|
31
|
+
out[w] = out.get(w, Fraction(0)) + c1 * c2
|
|
32
|
+
return out
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _pow_series(a: CoeffMap, n: int, depth: int) -> CoeffMap:
|
|
36
|
+
"""Binary exponentiation of a series (non-negative integer power)."""
|
|
37
|
+
if n < 0:
|
|
38
|
+
raise ValueError("Series exponent must be non-negative.")
|
|
39
|
+
if n == 0:
|
|
40
|
+
return {(): Fraction(1)}
|
|
41
|
+
result: CoeffMap = {(): Fraction(1)}
|
|
42
|
+
base = a
|
|
43
|
+
exp = n
|
|
44
|
+
while exp:
|
|
45
|
+
if exp & 1:
|
|
46
|
+
result = _mul_series(result, base, depth)
|
|
47
|
+
exp //= 2
|
|
48
|
+
if exp:
|
|
49
|
+
base = _mul_series(base, base, depth)
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _exp_single(letter: int, depth: int) -> CoeffMap:
|
|
54
|
+
"""Truncated exp(e_letter) = sum_{k>=0} e_letter^k / k! up to `depth`."""
|
|
55
|
+
out: CoeffMap = {(): Fraction(1)}
|
|
56
|
+
word = (letter,)
|
|
57
|
+
for k in range(1, depth + 1):
|
|
58
|
+
out[word * k] = Fraction(1, math.factorial(k))
|
|
59
|
+
return out
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _log_series(series: CoeffMap, depth: int) -> CoeffMap:
|
|
63
|
+
"""Formal log of a series with constant term 1, truncated to `depth`."""
|
|
64
|
+
if series.get((), None) != 1:
|
|
65
|
+
raise ValueError("log_series expects identity coefficient 1.")
|
|
66
|
+
U = dict(series)
|
|
67
|
+
U.pop((), None)
|
|
68
|
+
result: CoeffMap = {}
|
|
69
|
+
for n in range(1, depth + 1):
|
|
70
|
+
term = _pow_series(U, n, depth)
|
|
71
|
+
sign = -1 if (n % 2 == 0) else 1
|
|
72
|
+
coeff = Fraction(sign, n)
|
|
73
|
+
for w, c in term.items():
|
|
74
|
+
if len(w) == 0:
|
|
75
|
+
continue
|
|
76
|
+
result[w] = result.get(w, Fraction(0)) + coeff * c
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def bch_coeffs(width: int, depth: int) -> CoeffMap:
|
|
81
|
+
"""Coefficients for log(prod_{i=1}^width exp(e_i)) truncated to `depth`.
|
|
82
|
+
|
|
83
|
+
Computes the exact rational coefficients for the Baker-Campbell-Hausdorff
|
|
84
|
+
expansion of the product of exponentials of generators, truncated to the
|
|
85
|
+
specified depth.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
width : int
|
|
90
|
+
Number of generators (path dimension). Must be >= 1.
|
|
91
|
+
depth : int
|
|
92
|
+
Truncation depth. Must be >= 1.
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
CoeffMap
|
|
97
|
+
Dictionary mapping tensor-algebra words (tuples of generator indices)
|
|
98
|
+
to rational coefficients (Fraction objects) in the series expansion.
|
|
99
|
+
Words are tuples of integers from 1 to width, representing the
|
|
100
|
+
generators in order.
|
|
101
|
+
|
|
102
|
+
Examples
|
|
103
|
+
--------
|
|
104
|
+
>>> from tensorsig.algebra.bch_coefficients import bch_coeffs
|
|
105
|
+
>>> from fractions import Fraction
|
|
106
|
+
>>>
|
|
107
|
+
>>> # BCH coefficients for width=2, depth=2
|
|
108
|
+
>>> coeffs = bch_coeffs(2, 2)
|
|
109
|
+
>>> # Check some expected coefficients
|
|
110
|
+
>>> coeffs[(1,)] == Fraction(1, 1)
|
|
111
|
+
True
|
|
112
|
+
>>> coeffs[(2,)] == Fraction(1, 1)
|
|
113
|
+
True
|
|
114
|
+
>>> # The bracket [1, 2] has coefficient 1/2, split across (1, 2) and (2, 1)
|
|
115
|
+
>>> coeffs.get((1, 2), 0) == Fraction(1, 2)
|
|
116
|
+
True
|
|
117
|
+
>>> coeffs.get((2, 1), 0) == Fraction(-1, 2)
|
|
118
|
+
True
|
|
119
|
+
>>> coeffs.get((1, 2), 0) - coeffs.get((2, 1), 0) == Fraction(1, 1)
|
|
120
|
+
True
|
|
121
|
+
>>>
|
|
122
|
+
>>> # BCH coefficients for width=3, depth=2
|
|
123
|
+
>>> coeffs = bch_coeffs(3, 2)
|
|
124
|
+
>>> # Three degree-1 generators
|
|
125
|
+
>>> len([w for w in coeffs.keys() if len(w) == 1])
|
|
126
|
+
3
|
|
127
|
+
"""
|
|
128
|
+
if depth < 1 or width < 1:
|
|
129
|
+
return {}
|
|
130
|
+
# Multiply exp of each generator in order
|
|
131
|
+
prod: CoeffMap = {(): Fraction(1)}
|
|
132
|
+
for letter in range(1, width + 1):
|
|
133
|
+
prod = _mul_series(prod, _exp_single(letter, depth), depth)
|
|
134
|
+
log_series = _log_series(prod, depth)
|
|
135
|
+
return {w: c for w, c in log_series.items() if len(w) <= depth and c != 0}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""First Eulerian idempotent on homogeneous tensor words.
|
|
2
|
+
|
|
3
|
+
The exact permutation coefficients are shared by dense tensor projection and
|
|
4
|
+
quotient enveloping-algebra readouts. Projection preserves tensor dtype,
|
|
5
|
+
device, and autograd; the permutation formula has factorial cost in depth.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from fractions import Fraction
|
|
9
|
+
from functools import lru_cache
|
|
10
|
+
from itertools import permutations
|
|
11
|
+
from math import comb
|
|
12
|
+
|
|
13
|
+
import torch
|
|
14
|
+
from torch import Tensor
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ["eulerian_permutations", "eulerian_project_columns"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@lru_cache(maxsize=None)
|
|
21
|
+
def eulerian_permutations(
|
|
22
|
+
length: int,
|
|
23
|
+
) -> tuple[tuple[tuple[int, ...], Fraction], ...]:
|
|
24
|
+
"""Return permutations and exact first-Eulerian-idempotent coefficients.
|
|
25
|
+
|
|
26
|
+
``length`` is a non-negative integer. Degree zero has an empty schedule,
|
|
27
|
+
since the first Eulerian idempotent annihilates the scalar unit.
|
|
28
|
+
"""
|
|
29
|
+
if not isinstance(length, int) or isinstance(length, bool) or length < 0:
|
|
30
|
+
raise ValueError("length must be a non-negative integer.")
|
|
31
|
+
if length == 0:
|
|
32
|
+
return ()
|
|
33
|
+
schedule = []
|
|
34
|
+
for permutation in permutations(range(length)):
|
|
35
|
+
descents = sum(
|
|
36
|
+
left > right for left, right in zip(permutation, permutation[1:])
|
|
37
|
+
)
|
|
38
|
+
schedule.append(
|
|
39
|
+
(
|
|
40
|
+
permutation,
|
|
41
|
+
Fraction((-1) ** descents, length * comb(length - 1, descents)),
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
return tuple(schedule)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def eulerian_project_columns(matrix: Tensor, width: int, depth: int) -> Tensor:
|
|
48
|
+
"""Apply the first Eulerian idempotent to tensor-word columns.
|
|
49
|
+
|
|
50
|
+
``matrix`` has shape ``(width**depth, columns)`` in lexicographic word
|
|
51
|
+
order. The columns are independent inputs, and the result has the same
|
|
52
|
+
shape. Width and depth must be positive integers.
|
|
53
|
+
"""
|
|
54
|
+
for name, value in (("width", width), ("depth", depth)):
|
|
55
|
+
if not isinstance(value, int) or isinstance(value, bool) or value < 1:
|
|
56
|
+
raise ValueError(f"{name} must be a positive integer.")
|
|
57
|
+
expected_rows = width**depth
|
|
58
|
+
if matrix.ndim != 2 or matrix.shape[0] != expected_rows:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"Expected matrix shape ({expected_rows}, columns), got {tuple(matrix.shape)}."
|
|
61
|
+
)
|
|
62
|
+
if not matrix.dtype.is_floating_point:
|
|
63
|
+
raise ValueError("matrix must have a floating-point dtype.")
|
|
64
|
+
tensor = matrix.reshape(*([width] * depth), matrix.shape[1])
|
|
65
|
+
projected = torch.zeros_like(tensor)
|
|
66
|
+
for permutation, coefficient in eulerian_permutations(depth):
|
|
67
|
+
projected = projected + float(coefficient) * tensor.permute(*permutation, depth)
|
|
68
|
+
return projected.reshape(expected_rows, matrix.shape[1])
|