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.
Files changed (73) hide show
  1. tensorsig-0.1.0/LICENSE +21 -0
  2. tensorsig-0.1.0/PKG-INFO +147 -0
  3. tensorsig-0.1.0/README.md +121 -0
  4. tensorsig-0.1.0/pyproject.toml +75 -0
  5. tensorsig-0.1.0/pyproject.toml.orig +66 -0
  6. tensorsig-0.1.0/src/tensorsig/__init__.py +24 -0
  7. tensorsig-0.1.0/src/tensorsig/algebra/__init__.py +17 -0
  8. tensorsig-0.1.0/src/tensorsig/algebra/bch_coefficients.py +135 -0
  9. tensorsig-0.1.0/src/tensorsig/algebra/eulerian.py +68 -0
  10. tensorsig-0.1.0/src/tensorsig/algebra/hall.py +16 -0
  11. tensorsig-0.1.0/src/tensorsig/algebra/hall_bch.py +386 -0
  12. tensorsig-0.1.0/src/tensorsig/algebra/hall_projection.py +361 -0
  13. tensorsig-0.1.0/src/tensorsig/algebra/lyndon.py +16 -0
  14. tensorsig-0.1.0/src/tensorsig/algebra/lyndon_words.py +114 -0
  15. tensorsig-0.1.0/src/tensorsig/augmentations/__init__.py +260 -0
  16. tensorsig-0.1.0/src/tensorsig/classical/__init__.py +18 -0
  17. tensorsig-0.1.0/src/tensorsig/classical/log_signature.py +527 -0
  18. tensorsig-0.1.0/src/tensorsig/classical/signature.py +430 -0
  19. tensorsig-0.1.0/src/tensorsig/classical/signatures.py +15 -0
  20. tensorsig-0.1.0/src/tensorsig/classical/sparse.py +16 -0
  21. tensorsig-0.1.0/src/tensorsig/classical/sparse_signature.py +587 -0
  22. tensorsig-0.1.0/src/tensorsig/classical/windows.py +82 -0
  23. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/__init__.py +45 -0
  24. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_charts/__init__.py +6 -0
  25. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_charts/chronological.py +575 -0
  26. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_charts/colored_descent.py +260 -0
  27. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_charts/pascal.py +180 -0
  28. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_charts/primitive.py +423 -0
  29. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/__init__.py +1 -0
  30. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/_graph_common.py +195 -0
  31. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/adaptive.py +116 -0
  32. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/chordal_nbc.py +110 -0
  33. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/direct_nbc.py +381 -0
  34. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/literal_energy.py +299 -0
  35. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/one_centre_gram.py +264 -0
  36. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/rational_coordinates.py +729 -0
  37. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/star_gram.py +261 -0
  38. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/tree_moments.py +248 -0
  39. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_graph/young_dp.py +286 -0
  40. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_lie_quotients/__init__.py +1 -0
  41. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_lie_quotients/compiler.py +687 -0
  42. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_lie_quotients/enveloping.py +543 -0
  43. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_lie_quotients/rules.py +240 -0
  44. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_lie_quotients/trace_compiler.py +484 -0
  45. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_lie_quotients/upper_set_normal_form.py +439 -0
  46. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_sliding.py +342 -0
  47. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/_word_program.py +350 -0
  48. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/active_word_log.py +135 -0
  49. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/codes.py +646 -0
  50. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/coordinates.py +84 -0
  51. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/diagnostics.py +294 -0
  52. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/gap_factorized_volterra.py +455 -0
  53. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/graph_queries.py +243 -0
  54. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/layers.py +419 -0
  55. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/monomials.py +449 -0
  56. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/offset_patterns.py +172 -0
  57. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/pascal_certificate.py +320 -0
  58. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/pascal_rank.py +79 -0
  59. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/plans.py +1292 -0
  60. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/quotient_basis.py +77 -0
  61. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/quotients.py +46 -0
  62. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/rolling_logsignature/__init__.py +297 -0
  63. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/rolling_logsignature/_bch.py +66 -0
  64. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/rolling_logsignature/_compile.py +116 -0
  65. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/rolling_logsignature/_joint.py +420 -0
  66. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/rolling_logsignature/_program.py +159 -0
  67. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/rolling_logsignature/_scan.py +153 -0
  68. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/scaled_monomials.py +316 -0
  69. tensorsig-0.1.0/src/tensorsig/offset_coded_signatures/windows.py +456 -0
  70. tensorsig-0.1.0/src/tensorsig/tensor_ops/__init__.py +15 -0
  71. tensorsig-0.1.0/src/tensorsig/tensor_ops/linear.py +141 -0
  72. tensorsig-0.1.0/src/tensorsig/tensor_ops/products.py +82 -0
  73. tensorsig-0.1.0/src/tensorsig/tensor_ops/series.py +458 -0
@@ -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.
@@ -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])