Metadata-Version: 2.4
Name: quenta
Version: 0.1.0
Summary: Analysis, figures, and reusable utilities for the Towers Task.
Project-URL: Homepage, https://github.com/ThomasMorvan/quenta
Author: Thomas Morvan
License: MIT License
        
        Copyright (c) 2026 Thomas Morvan
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: matplotlib>=3.7
Requires-Dist: numpy>=1.24
Requires-Dist: pandas>=2.0
Requires-Dist: scipy>=1.10
Requires-Dist: tomli>=2.0; python_version < '3.11'
Provides-Extra: dev
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

# quenta
Analysis, figures, and reusable utilities for the towers evidence-accumulation
task.

## Layering

Imports should point **down** this list, never up:

```
notebooks/  # calls + prose, no logic
  plots/  # data -> Figure
  analysis/  # data -> numbers
    io/  # disk <-> data
      settings.py  # machine config, paths
        utils/  # standalone
```

## Install

```bash
git clone <url> && cd quenta
pip install -e ".[dev]"        # or: uv pip install -e ".[dev]"
```

If some other project depends on `quenta` (e.g. the repo for a paper), install
it there pinned to a specific version, never to the latest commit:

```bash
pip install quenta==0.1.0                                              # from PyPI
pip install "quenta @ git+ssh://git@github.com/ThomasMorvan/quenta@v0.1.0"  # from git
```

Pinning matters because anything that produced a figure in a paper must stay
reproducible: a version is a permanent pointer to one commit, so the exact
code can be recovered later even after defaults change.

## Releasing a new version

Three steps, in this order. Do all three, or the version number stops meaning
one specific state of the code.

**1. Bump the version.** Edit `version` in `pyproject.toml`, then commit:

```bash
git commit -am "release v0.1.1"
```

**2. Tag it and push.** This is what a `git+` install resolves:

```bash
git tag v0.1.1
git push && git push --tags
```

**3. Upload to PyPI.** This is what `pip install quenta` resolves:

```bash
rm -rf dist/          # or old builds get uploaded too
python -m build       # writes dist/quenta-0.1.1.tar.gz + .whl
twine upload dist/*
```

Neither a pushed tag nor an uploaded PyPI version can be changed afterwards —
that is the point of them, since other people's installs depend on them
meaning the same thing forever. If a release is wrong, don't fix it in place:
bump to `0.1.2` and release again.

### One-time PyPI setup

Needed once per machine, before step 3 ever works:

```bash
pip install build twine
```

Create an API token at https://pypi.org/manage/account/token/ (scope it to
this project once the first upload exists), and save it in `~/.pypirc`:

```ini
[pypi]
  username = __token__
  password = pypi-AgEIcHlwaS5vcmc...
```

Then `chmod 600 ~/.pypirc`, since that file is a credential.

To rehearse without burning a real version number, upload to
https://test.pypi.org instead: `twine upload -r testpypi dist/*` (separate
account, separate token).

### Updating an install

```bash
git pull                  # this clone; the editable install picks it up
pip install -e ".[dev]"   # only if pyproject.toml dependencies changed
```

In a consumer project, bump the pinned version and reinstall. For a `git+`
pin, add `--force-reinstall`: pip compares version strings, and two different
commits can carry the same one.

## Settings

Data should live **outside** the repo. Paths resolve in this order:

| priority | source | scope |
|---|---|---|
| 1 | an explicit `data_root=` argument | for tests where you need to change root |
| 2 | `settings.local.toml` | this machine, **gitignored** |
| 3 | `settings.toml` | committed defaults |
| 4 | built-in `DEFAULTS` | so an installed package still works |


```bash
cp settings.local.toml.example settings.local.toml
```

Never commit a personal path: a tracked machine-specific file means a merge
conflict every time you change device.

## Run the verification pipeline

```bash
python -m quenta.verify --data-root /path/to/data
# or open notebooks/demo.ipynb
```

Generates a synthetic per-session dataset, makes a fit and summary
at group level, and writes `overview.png/.pdf` to the figures directory.

### Notebook kernel (VS Code)

The system Python has no `pip` and no `ipykernel`, so install:

```bash
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]" jupyter ipykernel
```
(or just run `scripts/setup-venv.sh`, which does the same thing and prints the next step below)

Then in `notebooks/demo.ipynb`: pick kernel (top-right of the
notebook toolbar) --> **Select Another Kernel** --> **Python Environments...** -->
`.venv (Python 3.x)`. If not listed, use **Enter interpreter
path...** and paste the full path to `.venv/bin/python`.

Only need to do that once per clone.

### Commit notebooks with output, without the metadata noise
Inspired by https://gist.github.com/33eyes/431e3d432f73371509d176d0dfb95b6e
Cell outputs (figures, printed numbers) are worth committing as a record of
what the notebook produced. Kernelspec, language_info, and execution counts
are not: they're machine-specific and churn the diff every run, even when
nothing meaningful changed. A git filter strips just that, on the way in.

1. Add the filter to git config by running the following command in bash inside the repo:
```
git config filter.strip-notebook-metadata.clean 'python3 scripts/nb_strip_metadata.py'
```
   (or just run `scripts/setup-git.sh`, which does the same thing)
2. `notebooks/.gitattributes` already routes `*.ipynb` through it:
```
*.ipynb filter=strip-notebook-metadata
```

After that, commit to git as usual. Outputs stay in the committed notebook;
kernelspec/language_info/execution counts don't. Your local file on disk is
untouched either way.

To bypass the filter for one commit (e.g. you want the raw file, metadata and
all), use `git -c filter.strip-notebook-metadata.clean= add <path>` instead of
the usual `git add`.


## Tests
From an activated environment:

```bash
pytest  # everything, quick params, good for a pre-commit hook
pytest --slow  # same tests, full params for statistical confidence
pytest --cov  # which lines the tests actually run; fails under 90%
```

Coverage is opt-in rather than always-on, so a plain `pytest` stays fast. CI
runs it on the `--slow` pass. The floor exists to catch a module that stopped
being tested at all, not to chase 100%.

`if __name__ == "__main__":` blocks are excluded outright (see
`[tool.coverage.report]` in `pyproject.toml`).

From outside environment, prefix with the interpreter: `.venv/bin/python -m pytest`.

Linting is `ruff` (pycodestyle + pyflakes + import order, 79 cols; configured
in `pyproject.toml`), and CI runs it before the tests:

```bash
ruff check .  # report
ruff check . --fix  # fix what's mechanically fixable
```

Tests that pick a value from the `slow` fixture run every time, just with
reduced parameters (e.g. fewer iterations) by default and full ones under
`--slow`, instead of being skipped outright.


## The name
The two behavioral apparatuses are named **Cirith Ungol** and **Orthanc**, for together they make the *Two Towers*. The instrument of reckoning is **Amon Hen**, the Hill of the Eye, from which all things may be seen. And **Quenta** is the word in the High Speech signifying a tale or an account, as in *Quenta Silmarillion*, wherein the lore of many ages was gathered from scattered memories into a single history. Thus is this work likewise named; for out of much that was watched and set down is wrought one account, and out of that account are the figures drawn, and from those figures the tale is sung.
