lzsuite 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.
@@ -0,0 +1,11 @@
1
+ .venv*/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .coverage
7
+ htmlcov/
8
+ dist/
9
+ build/
10
+ *.egg-info/
11
+ .DS_Store
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-11-01)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * simplify single-sequence results and enable interactive parallelism
9
+
10
+ ### Features
11
+
12
+ * create initial array analysis and discretization library ([0c1ae2f](https://github.com/El3ssar/lzsuite/commit/0c1ae2f63ab8f201cd529dc24ded8e092b6cf89c))
13
+ * simplify single-sequence results and enable interactive parallelism ([02b189f](https://github.com/El3ssar/lzsuite/commit/02b189f290b25a6c331c5ff11b2b222a383e9c97))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * **api:** expose only user functions and privatize implementation modules ([3baa1fc](https://github.com/El3ssar/lzsuite/commit/3baa1fcf9c48aa9ab637a2de0b8e84dc681d5745))
19
+ * **release:** bootstrap the first release without a synthetic previous version ([89beb7a](https://github.com/El3ssar/lzsuite/commit/89beb7aa8fd6b2ff8730ecdc13ad3952c96ea769))
20
+
21
+ ## Changelog
22
+
23
+ Changes will be recorded here by release-please. Initial local development: 0.1.0.
@@ -0,0 +1,12 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use lzsuite, please cite this software and the underlying lzcomplexity engine as appropriate."
3
+ type: software
4
+ title: "lzsuite: Array-oriented Lempel–Ziv analysis and discretization"
5
+ authors:
6
+ - family-names: "Estévez Moya"
7
+ given-names: "Daniel"
8
+ alias: "El3ssar"
9
+ repository-code: "https://github.com/El3ssar/lzsuite"
10
+ url: "https://github.com/El3ssar/lzsuite"
11
+ license: MIT
12
+ abstract: "A Python wrapper around lzcomplexity for multidimensional arrays, signal discretization, parallel sequence analysis, and information distances."
@@ -0,0 +1,130 @@
1
+ # Contributing to lzsuite
2
+
3
+ ## Set up once
4
+
5
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/), then:
6
+
7
+ ```sh
8
+ git clone https://github.com/El3ssar/lzsuite.git
9
+ cd lzsuite
10
+ uv sync --locked
11
+ uv run pre-commit install --hook-type pre-commit --hook-type commit-msg
12
+ uv run ipython
13
+ ```
14
+
15
+ Or run `make setup`. Everything lives in the repository's `.venv` and uv cache;
16
+ no global Python packages or manual virtual-environment activation are needed.
17
+ The editable install reflects source edits immediately. Restart an interactive
18
+ session after changing already-imported modules.
19
+
20
+ `.python-version` selects Python 3.12 for routine development. `requires-python`
21
+ in `pyproject.toml` sets the supported minimum to 3.10. `uv.lock` records the exact
22
+ development dependency versions across supported interpreters.
23
+
24
+ ## Everyday workflow
25
+
26
+ ```sh
27
+ git switch -c fix/describe-the-change
28
+ # Edit source, tests, documentation, or examples.
29
+ make check
30
+ make build
31
+ git add <changed-files>
32
+ git commit -m "fix: describe the resulting behavior"
33
+ git push -u origin HEAD
34
+ ```
35
+
36
+ The individual checks are:
37
+
38
+ ```sh
39
+ uv run --locked ruff check .
40
+ uv run --locked ruff format --check .
41
+ uv run --locked pytest --cov=lzsuite
42
+ uv build
43
+ uv run --locked twine check dist/*
44
+ ```
45
+
46
+ Use `uv run ruff format .` to format edits, and `uv run ruff check . --fix` for
47
+ automatic lint fixes. Local hooks run lint/format checks and validate commit
48
+ subjects. CI also checks the PR title; use a Conventional Commit title so squash
49
+ merging preserves the release intent.
50
+
51
+ ## Code map
52
+
53
+ ```text
54
+ src/lzsuite/
55
+ __init__.py Public function exports and installed package version
56
+ _analysis.py Complexity, h, factorization, E, full results
57
+ _distance.py All-pairs and corresponding-pair NID
58
+ _discretization.py Built-ins, registry, callable validation
59
+ _arrays.py Axis handling and sequence preparation
60
+ _encoding.py Byte encodings and shared distance codebooks
61
+ _execution.py Inline execution and joblib/loky process workers
62
+ tests/ Backend comparisons, contracts, execution, namespace
63
+ examples/overview.py Commented executable walkthrough
64
+ docs/ API and maintenance guides
65
+ .github/workflows/ CI and release automation
66
+ ```
67
+
68
+ Keep implementation modules private. Add a top-level export only when it is a
69
+ function users should call, and update `tests/test_public_api.py` deliberately.
70
+ Avoid adding helper imports to the public namespace.
71
+
72
+ ## Tests that matter
73
+
74
+ Tests compare against the real installed lzcomplexity engine, including both E
75
+ estimators. Cover scientific encoding choices, arbitrary axes, shape preservation,
76
+ 1D native return types, registry behavior, warning/error cases, and agreement
77
+ between serial and parallel execution.
78
+
79
+ When changing execution, check both the result and the process/thread budget.
80
+ Workers use one Rayon thread; the caller's configuration must remain unchanged.
81
+ When changing a discretizer, include meaningful edge cases such as ties, constant
82
+ signals, and shortened output. Custom methods need no output-length declaration.
83
+
84
+ CI runs Python 3.10–3.14 on Linux and Python 3.12 on macOS and Windows. To test
85
+ another interpreter locally without replacing the default environment:
86
+
87
+ ```sh
88
+ UV_PROJECT_ENVIRONMENT=.venv-py310 uv run --locked --python 3.10 pytest
89
+ UV_PROJECT_ENVIRONMENT=.venv-py314 uv run --locked --python 3.14 pytest
90
+ ```
91
+
92
+ CI also checks an installed wheel and the minimum NumPy/joblib dependencies.
93
+ For interactive smoke testing, `uv run ipython` can call `lzs.h(data)` directly,
94
+ including its default parallel path.
95
+
96
+ ## Dependencies and versioning
97
+
98
+ Runtime requirements live in `pyproject.toml`; reproducible development versions
99
+ live in `uv.lock`. Commit both files when a requirement changes. A lockfile pin
100
+ is not the same as a published package requirement.
101
+
102
+ Use Conventional Commits:
103
+
104
+ ```text
105
+ feat(discretization): add fixed-edge binning
106
+ fix(distance): preserve category meanings across inputs
107
+ perf: reuse calculation work
108
+ refactor: simplify internal array handling
109
+ docs: explain normalization
110
+ feat!: change public result shapes
111
+ ```
112
+
113
+ `feat` proposes a minor bump; `fix` and `perf` propose patch bumps. A `!` or
114
+ `BREAKING CHANGE:` footer declares a breaking change. Under the current default
115
+ policy, breaking changes bump the major version even while the version is 0.x.
116
+ Merging a normal feature PR does not publish a release: release-please prepares
117
+ a separate release PR for review.
118
+
119
+ See [docs/maintenance.md](docs/maintenance.md) for the exact dependency-update
120
+ commands, release procedure, and remaining PyPI/Zenodo setup. Do not hand-edit
121
+ versions in several files: release-please updates `pyproject.toml`, `uv.lock`,
122
+ the release manifest, and the changelog together.
123
+
124
+ ## Reporting problems
125
+
126
+ Open an [issue](https://github.com/El3ssar/lzsuite/issues) with a small input,
127
+ its dtype and shape, the call and parameters, the expected/actual behavior,
128
+ Python/platform information, and the lzsuite/lzcomplexity/NumPy versions. Include
129
+ `n_jobs` when reporting parallel-execution issues. Keep scientific data private
130
+ unless you have permission to share it.
lzsuite-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lzsuite 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.
lzsuite-0.1.0/Makefile ADDED
@@ -0,0 +1,41 @@
1
+ .PHONY: help setup check test lint format build deps-backend deps-update
2
+
3
+ help:
4
+ @echo "setup Install locked dev environment and local hooks"
5
+ @echo "check Run lint, format checks, and tests"
6
+ @echo "test Run tests with coverage"
7
+ @echo "lint Check lint and formatting"
8
+ @echo "format Apply lint fixes and formatting"
9
+ @echo "build Build and validate wheel and source archive"
10
+ @echo "deps-backend Update lzcomplexity within its declared range"
11
+ @echo "deps-update Update all dependency locks within declared ranges"
12
+
13
+ setup:
14
+ uv sync --locked
15
+ uv run --no-sync pre-commit install --hook-type pre-commit --hook-type commit-msg
16
+
17
+ check: lint test
18
+
19
+ lint:
20
+ uv run --locked ruff check .
21
+ uv run --locked ruff format --check .
22
+
23
+ test:
24
+ uv run --locked pytest --cov=lzsuite
25
+
26
+ format:
27
+ uv run --locked ruff check . --fix
28
+ uv run --locked ruff format .
29
+
30
+ build:
31
+ uv build
32
+ uv run --locked twine check dist/*
33
+
34
+ deps-backend:
35
+ uv lock --upgrade-package lzcomplexity
36
+ uv sync --locked
37
+
38
+ # Review the lockfile diff and run make check/build before committing updates.
39
+ deps-update:
40
+ uv lock --upgrade
41
+ uv sync --locked
lzsuite-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,281 @@
1
+ Metadata-Version: 2.5
2
+ Name: lzsuite
3
+ Version: 0.1.0
4
+ Summary: Array-oriented Lempel–Ziv analysis and signal discretization.
5
+ Project-URL: Homepage, https://github.com/El3ssar/lzsuite
6
+ Project-URL: Repository, https://github.com/El3ssar/lzsuite
7
+ Project-URL: Documentation, https://github.com/El3ssar/lzsuite#readme
8
+ Project-URL: Issues, https://github.com/El3ssar/lzsuite/issues
9
+ Project-URL: Changelog, https://github.com/El3ssar/lzsuite/blob/main/CHANGELOG.md
10
+ Author: Daniel Estévez Moya
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: joblib<2,>=1.5
24
+ Requires-Dist: lzcomplexity<4,>=3.0.0
25
+ Requires-Dist: numpy>=1.24
26
+ Description-Content-Type: text/markdown
27
+
28
+ # lzsuite
29
+
30
+ [![CI](https://github.com/El3ssar/lzsuite/actions/workflows/ci.yml/badge.svg)](https://github.com/El3ssar/lzsuite/actions/workflows/ci.yml)
31
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://github.com/El3ssar/lzsuite/blob/main/pyproject.toml)
32
+ [![MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
33
+
34
+ Lempel–Ziv analysis for multidimensional NumPy arrays. Choose the axis containing
35
+ observations, optionally discretize continuous signals, and calculate complexity,
36
+ entropy density, effective measure complexity, or information distances across
37
+ all remaining positions.
38
+
39
+ [lzcomplexity](https://github.com/pleros-ai/lzcomplexity) supplies the numerical
40
+ engine. lzsuite adds array handling, discretization, shared symbol encodings,
41
+ and parallel calculation across sequences.
42
+
43
+ **Early development:** the API can evolve. The repository is available on GitHub;
44
+ PyPI publication and Zenodo registration are planned separately.
45
+
46
+ ## Install and try it
47
+
48
+ From the repository, using [uv](https://docs.astral.sh/uv/):
49
+
50
+ ```sh
51
+ git clone https://github.com/El3ssar/lzsuite.git
52
+ cd lzsuite
53
+ uv sync --locked
54
+ uv run ipython
55
+ ```
56
+
57
+ This creates a local `.venv`, installs lzsuite in editable mode, and installs the
58
+ locked development tools. No activation or global Python packages are needed.
59
+ For a regular environment without uv, use `python -m pip install .` after cloning.
60
+ Python 3.10+ is required; development defaults to Python 3.12.
61
+
62
+ ## Quick start
63
+
64
+ These are separate snippets you can run in the same IPython session.
65
+
66
+ ```python
67
+ import numpy as np
68
+ import lzsuite as lzs
69
+
70
+ rng = np.random.default_rng(42)
71
+ signal = rng.normal(size=1000)
72
+ data = rng.normal(size=(1000, 4, 3)) # observations × channels × trials
73
+ ```
74
+
75
+ One sequence returns an ordinary Python value. No scalar extraction is needed:
76
+
77
+ ```python
78
+ entropy = lzs.h(signal) # float
79
+ count = lzs.complexity(signal) # int
80
+ count, boundaries = lzs.factorization(signal)
81
+ score, increments = lzs.emc(signal) # float and list
82
+ result = lzs.lz76(signal) # dict
83
+ ```
84
+
85
+ Arrays retain the dimensions outside the sequence axis, which defaults to 0:
86
+
87
+ ```python
88
+ entropy = lzs.h(data) # shape (4, 3)
89
+ counts = lzs.complexity(data) # shape (4, 3)
90
+ scores, profiles = lzs.emc(data, max_block_size=8)
91
+ results = lzs.lz76(data, max_block_size=8)
92
+ results[0, 0]["emc"]["value"] # first channel, first trial
93
+ ```
94
+
95
+ Other axes and noncontiguous views work too:
96
+
97
+ ```python
98
+ observations_last = np.moveaxis(data, 0, -1) # shape (4, 3, 1000)
99
+ np.allclose(entropy, lzs.h(observations_last, axis=-1)) # True
100
+ ```
101
+
102
+ A singleton batch dimension is preserved: input `(1000, 1)` returns shape `(1,)`.
103
+ Only a truly 1D input gets the direct Python result. See the full
104
+ [API reference](docs/api.md) for return types.
105
+
106
+ ## Discretization
107
+
108
+ Float arrays use mean binarization by default, independently for each sequence.
109
+ Integers, booleans, and string-array elements are already symbols. An explicitly
110
+ selected discretizer overrides the default for numeric inputs; strings cannot
111
+ be passed to a discretizer.
112
+
113
+ ```python
114
+ lzs.available_discretizers()
115
+ # ('bins', 'mean', 'median', 'permutation', 'quantile')
116
+
117
+ labels = lzs.discretize(signal, method="median") # use a method directly
118
+ entropy = lzs.h(data, discretization="median") # or inside a calculation
119
+ entropy = lzs.h(data, discretization="bins", discretization_kwargs={"n_bins": 4})
120
+ ```
121
+
122
+ | Method | Rule | Parameters |
123
+ |---|---|---|
124
+ | `mean` | Above the sequence mean → 1; equality/below → 0 | — |
125
+ | `median` | Above the sequence median → 1; equality/below → 0 | — |
126
+ | `bins` | Equally wide bins spanning the sequence's range | `n_bins=2` |
127
+ | `quantile` | Bins using empirical quantile edges | `n_bins=2` |
128
+ | `permutation` | Labels for the ordering within delayed windows | `order=3`, `delay=1` |
129
+
130
+ Values on internal bin edges go right. Repeated quantile edges collapse, and
131
+ constant signals yield zero labels. Bin edges are fitted separately per sequence;
132
+ pass a custom method using fixed edges if comparisons require shared thresholds.
133
+ Permutation ties are ordered by position, earlier first. Permutation output has
134
+ `len(signal) - (order - 1) * delay` entries; it is a discretization, not a
135
+ permutation-entropy calculation.
136
+
137
+ ### Your own methods
138
+
139
+ A method receives a numeric 1D array and returns a nonempty 1D array of integer
140
+ labels. Its output length can change. The input is copied before calling it, so
141
+ in-place changes do not modify your original data.
142
+
143
+ ```python
144
+ def threshold(values, *, level=0.0):
145
+ return (values > level).astype(np.uint8)
146
+
147
+
148
+ lzs.h(data, discretization=threshold) # directly supplied callable
149
+ ```
150
+
151
+ Register the same function for reuse by name:
152
+
153
+ ```python
154
+ lzs.register_discretizer("threshold", threshold, replace=True)
155
+ lzs.h(data, discretization="threshold", discretization_kwargs={"level": 0.5})
156
+ ```
157
+
158
+ Decorator registration is also supported:
159
+
160
+ ```python
161
+ @lzs.register_discretizer("every_other", replace=True)
162
+ def every_other(values):
163
+ return (values[::2] > 0).astype(np.uint8)
164
+
165
+
166
+ lzs.h(data, discretization="every_other")
167
+ ```
168
+
169
+ Registration is local to the Python session. Existing names require
170
+ `replace=True` to overwrite. Discretization runs in the calling process, so
171
+ functions defined interactively work with parallel calculations too.
172
+
173
+ ## Information distances
174
+
175
+ One input calculates every pair of sequences:
176
+
177
+ ```python
178
+ matrix = lzs.distance(data) # shape (12, 12)
179
+ ```
180
+
181
+ Rows and columns follow C-order flattening of `(channel, trial)`. Here,
182
+ `(channel, trial)` corresponds to row `channel * 3 + trial`. The matrix is
183
+ symmetric and retains actual self-distances, which may be nonzero.
184
+
185
+ Two inputs compare corresponding sequences:
186
+
187
+ ```python
188
+ other = rng.normal(size=(800, 4, 3))
189
+ paired = lzs.distance(data, other) # shape (4, 3)
190
+ single = lzs.nid(signal, other[:, 0, 0]) # float; nid is an alias for distance
191
+ ```
192
+
193
+ Shapes outside the sequence axis must match exactly; sequence lengths may differ.
194
+ There is no broadcasting. A shared codebook keeps category meanings consistent
195
+ across the inputs. The two-input mode does not compute all cross-pairs.
196
+ A single 1D input to `distance` still gives a `(1, 1)` all-pairs matrix.
197
+
198
+ Both modes accept `axis`, `discretization`, `discretization_kwargs`, and `n_jobs`.
199
+ For float inputs, the default mean threshold is fitted independently to each
200
+ sequence in each input.
201
+
202
+ ## Parallel execution and Rayon
203
+
204
+ **Default: up to four worker processes**, capped by available CPUs and the number
205
+ of calculations. This works in scripts, IPython, and notebooks using joblib's
206
+ loky process runner. Workers are reused across calls to reduce repeated startup.
207
+
208
+ ```python
209
+ lzs.h(data) # up to 4 processes
210
+ lzs.h(data, n_jobs=1) # inline calculation; no worker processes
211
+ lzs.h(data, n_jobs=2) # up to 2 processes
212
+ lzs.distance(data, n_jobs=-1) # all available CPUs, capped by pair count
213
+ ```
214
+
215
+ Each worker uses **one Rayon thread**. Thus four worker processes share the
216
+ calculation workload without each starting a full native thread pool. Worker
217
+ configuration is local to those processes; lzsuite does not change the calling
218
+ process's `RAYON_NUM_THREADS` or initialized Rayon pool. Other common native
219
+ thread pools are also limited to one thread per worker.
220
+
221
+ The inline path (`n_jobs=1`, a single calculation, or one available CPU) preserves
222
+ the calling process's existing native configuration, so lzcomplexity can still
223
+ use Rayon internally there. Discretization, validation, and encoding happen in
224
+ the calling process before native work is distributed.
225
+
226
+ Parallelism can use more memory and is not necessarily faster for small inputs.
227
+ Use `n_jobs=1` when process startup outweighs the calculation. All-pairs matrices
228
+ require quadratic result storage.
229
+
230
+ ## Scientific conventions and validation
231
+
232
+ - The logarithm base defaults to each discretized sequence's distinct-symbol
233
+ count, with a minimum of 2. Use `log_base=2` for explicit bit units. Different
234
+ observed alphabet sizes imply different normalization bases.
235
+ - E defaults to Chao–Shen. `method="shuffle"` selects the alternative estimator
236
+ in `emc` and `lz76`; this option is separate from `discretization`.
237
+ - Numeric symbols are encoded as individual bytes. Each string-array element is
238
+ one category, including multi-character strings. To analyze text characters,
239
+ use `np.array(list("banana"))`.
240
+ - More than 256 distinct symbols emits `RuntimeWarning` and continues with
241
+ category codes reduced modulo 256. Some categories then merge, so results do
242
+ not faithfully describe the original alphabet. Analysis normalization still
243
+ uses the original symbol count; distances check the shared vocabulary.
244
+ - Empty sequences, NaN, infinity, complex values, and object arrays are rejected.
245
+ Empty collections with a nonempty sequence axis return empty arrays.
246
+ - Factor boundaries retain the backend's final-tail convention and can extend
247
+ beyond the sequence end. NID does not force self-distance to zero.
248
+
249
+ Save the discretization settings, sequence length, log base, E method/range,
250
+ package versions, and input data or fingerprint with scientific results.
251
+
252
+ ## Development and maintenance
253
+
254
+ ```sh
255
+ make setup # editable install plus local lint/commit hooks
256
+ make check # lint, formatting, tests
257
+ make build # wheel, source archive, package metadata checks
258
+ ```
259
+
260
+ The commands are also listed individually in [CONTRIBUTING.md](CONTRIBUTING.md).
261
+ The development environment is locked in `uv.lock`; runtime compatibility ranges
262
+ are declared in `pyproject.toml`. The backend dependency is currently
263
+ `lzcomplexity>=3.0.0,<4`, preventing an unreviewed major-version upgrade. See the
264
+ [maintenance and release guide](docs/maintenance.md) for dependency updates,
265
+ versioning, GitHub releases, and the future PyPI/Zenodo steps.
266
+
267
+ Conventional Commits drive release-please version/changelog PRs. Merging a release
268
+ PR creates the `vX.Y.Z` tag and GitHub release. CI checks Python 3.10–3.14 on Linux,
269
+ plus macOS and Windows, and builds package distributions. **There is no PyPI
270
+ upload or Zenodo registration in the current workflows.**
271
+
272
+ The public API contains only callable user functions; implementation modules are
273
+ private. `lzs.__version__` gives the installed version. A complete executable
274
+ walkthrough is in [examples/overview.py](examples/overview.py).
275
+
276
+ ## License and attribution
277
+
278
+ MIT licensed; see [LICENSE](LICENSE). The LZ76 engine and estimator implementation
279
+ are provided by [lzcomplexity](https://github.com/pleros-ai/lzcomplexity), with its
280
+ own authorship and scientific references. Software citation metadata for this
281
+ wrapper is in [CITATION.cff](CITATION.cff).