diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..4fb4d23 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,86 @@ +name: tests + +on: + push: + branches: [main, modernize-package] + pull_request: + workflow_dispatch: + +jobs: + test: + name: py${{ matrix.python-version }} / numpy${{ matrix.numpy }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # pybeh_pd is pure-Python and supports both numpy majors. numpy 1.x has + # no wheels for Python 3.13, so that cell is excluded. + python-version: ["3.11", "3.12", "3.13"] + numpy: ["1", "2"] + exclude: + - python-version: "3.13" + numpy: "1" + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install package + test deps + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[test]" + + - name: Pin numpy major + run: | + if [ "${{ matrix.numpy }}" = "1" ]; then + python -m pip install "numpy<2" + else + python -m pip install "numpy>=2" + fi + + - name: Show resolved versions + run: python -c "import numpy, pandas, scipy; print('numpy', numpy.__version__, '| pandas', pandas.__version__, '| scipy', scipy.__version__)" + + - name: Run test suite + run: pytest -q --cov=pybeh_pd --cov-report=term-missing + + lint: + name: pyright + ruff (pyflakes) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install package + lint tools + run: | + python -m pip install --upgrade pip + python -m pip install -e . pyright ruff + - name: pyright (0 errors required) + run: pyright --project . + - name: ruff pyflakes (F) + run: ruff check --select F src/pybeh_pd + + build: + name: build wheel + sdist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Build distributions + run: | + python -m pip install --upgrade pip build + python -m build + - name: Smoke-install the wheel and import + run: | + python -m pip install dist/*.whl + python -c "import pybeh_pd as pb; print('OK', len([n for n in dir(pb) if not n.startswith('_')]), 'public names')" + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/* diff --git a/.gitignore b/.gitignore index a2c04a9..9019848 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,43 @@ +# Python bytecode __pycache__/ **/__pycache__/ *.pyc + +# Build / packaging artifacts +build/ +dist/ +*.egg-info/ +*.egg +*.so +.eggs/ +wheelhouse/ + +# Test / coverage artifacts +.pytest_cache/ +.coverage +.coverage.* +htmlcov/ +coverage.xml +.cache/ + +# Environments / tooling +.pixi/ +.venv/ +venv/ +.env + +# Editor / OS cruft +.DS_Store +**/.DS_Store +.idea/ +.vscode/ + +# Renovation reports & scratch (uncommitted, human-facing) +RENOVATION_TODO.md +bug_report.md +potential_dead_code.md +documentation_report.md +*_report.md + +# Claude working files (never committed, per user preference) +.claude/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1df36b1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,62 @@ +# Changelog + +All notable changes to `pybeh_pd` are documented here. This project adheres to +[Semantic Versioning](https://semver.org/). + +## 0.1.0 — 2026-06-10 + +First packaged release. The package was modernized from a single-file, +`sys.path.append`-style module into a clean, tested, pip-installable package +**without changing any observable analysis behavior** (a golden-master regression +suite locks the numerical output). + +### Packaging & build +- Converted to a `src/` layout package: `pybeh_pd/{__init__, _core, _pybeh}.py`. + The vendored pybeh subset (formerly `pybeh_copy.py`) is now the internal + `pybeh_pd._pybeh`, imported via a proper intra-package import (no more reliance + on the repo root being on `sys.path`). +- Added a `pyproject.toml` (hatchling backend); installable via + `pip install .` / `pip install -e .` / `pip install git+https://…`. Builds clean + wheel + sdist. +- Single-sourced `__version__` (read by hatchling). +- Added a `pixi.toml` reproducible dev environment (Python 3.11/3.12/3.13) with + `build` / `test` / `test-cov` tasks. +- Moved example notebooks and `LoftusMasson.m` into `examples/` (excluded from the + wheel); removed hardcoded `sys.path.append` lines from the notebooks. +- Added an MIT `LICENSE`. + +### Compatibility +- Supports Python 3.11–3.13 and both numpy 1.x and 2.x (pandas 2.x/3.x). Verified + green on numpy 1.24/1.26/2.4 across pandas 2.3/3.0. + +### Tests & CI +- Added a golden-master **behavior-lock** regression suite plus comprehensive + per-function unit tests (hand-coded values, mathematical properties, edge + cases) covering all public functions. +- Added real-data integration tests on a committed 5-session ltpFR2 sample (no + rhino/cmlreaders needed at test time), reproducing the temporal contiguity + effect and temporal clustering factor. +- Added GitHub Actions: test matrix (Python 3.11–3.13 × numpy 1/2), wheel/sdist + build, and a pyright + ruff lint job. + +### Code quality +- Added pyright type annotations across the package (0 errors / 0 warnings, + basic mode); added `pyrightconfig.json`. +- Removed pyflakes-flagged dead imports and unused locals (behavior-neutral). + +### Known bugs (discovered, NOT fixed in this release) +These were found during modernization and intentionally left unchanged so the +behavior baseline stays clean; they are pinned by tests and will be fixed in a +follow-up against the regression suite: +- **B1** `loftus_masson_unequal_variance_kahana` references an undefined `mat` + (raises on every call). +- **B2** `dist_fact` calls `warnings.warn` without importing `warnings`. +- **B3** `pd_dist_fact_list`'s `if count == np.nan` guard never fires. +- **B4** `pd_min_temp_fact` with the default `max_n_reps=1` raises `IndexError`. +- **B5** the percentile-rank helpers crash on a Python-scalar `actual` under + numpy 2. +- **B6** operator-precedence bug misclassifies NaN-padded recalls as intrusions + in `make_recalls_matrix` / `make_poss_recalls_matrix`. +- **B7** `cousineau` with a single condition raises `ZeroDivisionError`. +- **B8** the documented `groupby('subject').apply(pd_crp, …)` idiom breaks on + pandas 3 (the grouping column is dropped); use an explicit per-subject loop. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..353fac6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Computational Memory Lab, University of Pennsylvania + +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. diff --git a/README.md b/README.md index 5c20a71..b684345 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,103 @@ # pybeh_pd - -A lightweight wrapper around pybeh for easy analysis and plotting of free recall data with pandas and seaborn. -See the attached notebooks for examples of how to use this module and psifr test for comparison to a related package. -It is just a module not yet a package so import by adding the path, i.e. +A lightweight, pure-Python wrapper around [pybeh](https://github.com/pennmem/pybeh) +for analyzing and plotting **free-recall** behavioral data with pandas and seaborn. +It re-expresses pybeh's matrix-based analyses as functions that take **tidy, +long-format pandas DataFrames** of presentation/recall events, and adds +within-subject confidence-interval helpers for plotting. + +A minimal subset of `pybeh` is vendored (`pybeh_pd._pybeh`), so **no separate +`pybeh` install is required**. + +## What it provides + +Given a long-format events DataFrame (one row per presented/recalled item), the +`pd_*` functions compute, per subject: + +- **`pd_crp`** — lag conditional response probability (lag-CRP; the temporal + contiguity effect). +- **`pd_temp_fact`** — temporal clustering factor (Polyn, Norman & Kahana, 2009). +- **`pd_sem_crp`** — semantic CRP, binned by a similarity space. +- **`pd_dist_fact`** — distance/similarity clustering factor. +- **`pd_min_crp` / `pd_min_temp_fact`** — repeated-presentation-aware variants. +- **`pd_sem_crp_list`, `pd_dist_fact_list`, and `*_sub`** — per-list / per-subject + aggregating variants. +- **CI helpers** for within-subject error bars: `cousineau` + (Cousineau–Morey–O'Brien), `loftus_masson_analytic`, and the Loftus–Masson + Kahana ports. + +The underlying matrix builders (`make_recalls_matrix`, `get_all_matrices`, ...) +and pybeh primitives (`crp`, `temp_fact`, ...) are also exposed. + +## Install + +Pure Python — install from source with pip: + +```bash +pip install . # from a clone +pip install git+https://github.com/pennmem/pybeh_pd.git # from GitHub +pip install -e . # editable (for development) ``` -import sys -sys.path.append('~/pybeh_pd') + +Requires Python 3.11–3.13 and numpy / pandas / scipy (installed automatically). + +## Quickstart + +```python +import pandas as pd import pybeh_pd as pb -``` \ No newline at end of file + +# One subject, two 4-item lists. WORD rows are presentations; REC_WORD rows are +# recalls. `itemno` is the item id; recalls reference the presented item numbers. +events = pd.DataFrame({ + "subject": "subj1", "session": 0, + "list": [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], + "type": ["WORD"] * 4 + ["REC_WORD"] * 3 + ["WORD"] * 4 + ["REC_WORD"] * 3, + "itemno": [11, 12, 13, 14, 12, 13, 14, 21, 22, 23, 24, 23, 22, 21], +}) + +# Expects one subject's events; group by subject for many-subject data: +# events.groupby("subject").apply(pb.pd_crp, itemno_column="itemno") +crp = pb.pd_crp(events, lag_num=3) +print(crp[["lag", "prob"]]) +# lag 0 is NaN; forward (+1) and backward (-1) adjacent transitions dominate. + +print("temporal clustering factor:", pb.pd_temp_fact(events)) +``` + +The default column names are `subject` / `session` / `list` (the trial index), +`type` (with `"WORD"` presentations and `"REC_WORD"` recalls), and `itemno`; all +are overridable via keyword arguments (e.g. `itemno_column="item_num"`). + +See [`examples/`](examples/) for notebooks demonstrating FR1, catFR1, repFR1, and +the Loftus–Masson confidence intervals on real datasets. + +## Development + +This repo uses [pixi](https://pixi.sh) for a reproducible dev environment: + +```bash +pixi run build # confirm the package imports +pixi run test # run the test suite +pixi run test-cov # with coverage +``` + +Or with plain pip/pytest: + +```bash +pip install -e ".[test]" +pytest +``` + +The test suite includes a golden-master **behavior-lock** regression suite, +per-function unit tests, and integration tests on a committed 5-session sample of +real ltpFR2 free-recall data (no external data access required). + +## Continuous integration + +GitHub Actions runs the suite across Python 3.11–3.13 and both numpy 1.x and 2.x +(pandas 2.x/3.x), plus a wheel/sdist build and a pyright + ruff lint job. + +## License + +MIT. diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..5dc561d --- /dev/null +++ b/conftest.py @@ -0,0 +1,15 @@ +"""Pytest configuration. + +Puts the ``src/`` layout package and the ``tests/`` helper dir on ``sys.path`` so +``import pybeh_pd`` and ``from factories import ...`` / ``from real_data import +...`` resolve during testing without requiring an install. When the package is +also editable-installed (the pixi envs), the import resolves to the same ``src/`` +tree, so there is no conflict. +""" +import os +import sys + +_ROOT = os.path.dirname(os.path.abspath(__file__)) +for _p in (os.path.join(_ROOT, "src"), os.path.join(_ROOT, "tests")): + if _p not in sys.path: + sys.path.insert(0, _p) diff --git a/FR1_analyses.ipynb b/examples/FR1_analyses.ipynb similarity index 99% rename from FR1_analyses.ipynb rename to examples/FR1_analyses.ipynb index ec33ce4..849e9b7 100644 --- a/FR1_analyses.ipynb +++ b/examples/FR1_analyses.ipynb @@ -13,7 +13,6 @@ "import seaborn as sns\n", "import pandas as pd\n", "import sys\n", - "sys.path.append('/home1/djhalp/pybeh_pd')\n", "import pybeh_pd as pb" ] }, diff --git a/LoftusMasson.m b/examples/LoftusMasson.m similarity index 100% rename from LoftusMasson.m rename to examples/LoftusMasson.m diff --git a/catFR1_analyses.ipynb b/examples/catFR1_analyses.ipynb similarity index 99% rename from catFR1_analyses.ipynb rename to examples/catFR1_analyses.ipynb index e6a6119..187fba5 100644 --- a/catFR1_analyses.ipynb +++ b/examples/catFR1_analyses.ipynb @@ -13,7 +13,6 @@ "import seaborn as sns\n", "import pandas as pd\n", "import sys\n", - "sys.path.append('/home1/djhalp/pybeh_pd')\n", "import pybeh_pd as pb" ] }, diff --git a/loftus_masson_example.ipynb b/examples/loftus_masson_example.ipynb similarity index 99% rename from loftus_masson_example.ipynb rename to examples/loftus_masson_example.ipynb index 6b5c6ca..d6a9cb1 100644 --- a/loftus_masson_example.ipynb +++ b/examples/loftus_masson_example.ipynb @@ -12,7 +12,6 @@ "from scipy import stats\n", "import pandas as pd\n", "import sys\n", - "sys.path.append('/home1/djhalp/pybeh_pd')\n", "import pybeh_pd as pb\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline" diff --git a/psifr_test.ipynb b/examples/psifr_test.ipynb similarity index 100% rename from psifr_test.ipynb rename to examples/psifr_test.ipynb diff --git a/repFR1_analyses.ipynb b/examples/repFR1_analyses.ipynb similarity index 99% rename from repFR1_analyses.ipynb rename to examples/repFR1_analyses.ipynb index af14ff0..b0fdc0f 100644 --- a/repFR1_analyses.ipynb +++ b/examples/repFR1_analyses.ipynb @@ -24,7 +24,6 @@ "import seaborn as sns\n", "import pandas as pd\n", "import sys\n", - "sys.path.append('~/pybeh_pd')\n", "import pybeh_pd as pb\n", "import importlib\n", "importlib.reload(pb)" diff --git a/pixi.lock b/pixi.lock new file mode 100644 index 0000000..e7a2401 --- /dev/null +++ b/pixi.lock @@ -0,0 +1,2927 @@ +version: 7 +platforms: +- name: linux-64 +- name: osx-64 +- name: osx-arm64 +environments: + default: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.14.1-py313h3dea7bd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-8_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-8_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-8_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.2-h0c1763c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.1-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.6-py313hf6604e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-3.0.3-py313hbfd7664_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.16-h6a952e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py313h4b8bb8b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - pypi: . + osx-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/coverage-7.14.1-py313h035b7d0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.3-h25d91c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-8_he492b99_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-8_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.7-h19cb2f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.8.1-hcc62823_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.11.0-8_h859234e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-hf3981d6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.33-openmp_h9e49c7b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.53.2-h8f8c405_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.7-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.6-hcc0dc9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.6-py313hb870fc3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.2-hc881268_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-3.0.3-py313hfd25234_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.13-h3d5d122_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ruff-0.15.16-h1ddadc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py313h9cbb6b6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda + - pypi: . + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.14.1-py313h65a2061_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-8_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-8_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.7-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-8_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.2-h1ae2325_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.7-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.6-py313hce9b930_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-3.0.3-py313h1188861_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.16-h80928e0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py313h52f5312_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - pypi: . + py311: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.14.1-py311h3778330_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-8_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-8_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-8_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.2-h0c1763c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.1-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.6-py311h2e04523_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-3.0.3-py311h8032f78_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.16-h6a952e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py311hbe70eeb_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - pypi: . + osx-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/coverage-7.14.1-py311ha8ae342_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.3-h25d91c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-8_he492b99_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-8_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.7-h19cb2f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.8.1-hcc62823_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.11.0-8_h859234e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.33-openmp_h9e49c7b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.53.2-h8f8c405_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.7-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.6-hcc0dc9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.6-py311h2c4eb96_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.2-hc881268_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-3.0.3-py311h2a74ac8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.15-ha9537fe_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ruff-0.15.16-h1ddadc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py311h556693a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda + - pypi: . + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.14.1-py311hc290fe0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-8_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-8_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.7-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-8_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.2-h1ae2325_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.7-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.6-py311hbd1492f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-3.0.3-py311h8948835_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.15-h8561d8f_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.16-h80928e0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py311h9a58382_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - pypi: . + py312: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.14.1-py312h8a5da7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-8_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-8_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-8_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.2-h0c1763c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.1-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.6-py312h33ff503_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-3.0.3-py312h8ecdadd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.13-hd63d673_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.16-h6a952e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py312h54fa4ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - pypi: . + osx-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/coverage-7.14.1-py312heb39f77_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.3-h25d91c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-8_he492b99_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-8_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.7-h19cb2f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.8.1-hcc62823_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.11.0-8_h859234e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.33-openmp_h9e49c7b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.53.2-h8f8c405_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.7-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.6-hcc0dc9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.6-py312h746d82c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.2-hc881268_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-3.0.3-py312h8e27051_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.13-ha9537fe_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ruff-0.15.16-h1ddadc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py312h6309490_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda + - pypi: . + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.14.1-py312h04c11ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-8_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-8_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.7-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-8_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.2-h1ae2325_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.7-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.6-py312ha003a3f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-3.0.3-py312h6510ced_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.13-h8561d8f_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.16-h80928e0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py312h4519d97_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - pypi: . + py313: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.14.1-py313h3dea7bd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-8_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-8_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-8_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.2-h0c1763c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.1-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.6-py313hf6604e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-3.0.3-py313hbfd7664_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.16-h6a952e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py313h4b8bb8b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - pypi: . + osx-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/coverage-7.14.1-py313h035b7d0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.3-h25d91c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-8_he492b99_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-8_h9b27e0a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.7-h19cb2f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.8.1-hcc62823_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.11.0-8_h859234e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-hf3981d6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.33-openmp_h9e49c7b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.53.2-h8f8c405_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.7-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.6-hcc0dc9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.6-py313hb870fc3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.2-hc881268_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-3.0.3-py313hfd25234_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.13-h3d5d122_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ruff-0.15.16-h1ddadc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py313h9cbb6b6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda + - pypi: . + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.14.1-py313h65a2061_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-8_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-8_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.7-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-8_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.2-h1ae2325_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.7-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.6-py313hce9b930_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-3.0.3-py313h1188861_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.16-h80928e0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py313h52f5312_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - pypi: . +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + build_number: 20 + sha256: 1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9 + md5: a9f577daf3de00bca7c3c76c0ecbd1de + depends: + - __glibc >=2.17,<3.0.a0 + - libgomp >=7.5.0 + constrains: + - openmp_impl <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 28948 + timestamp: 1770939786096 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 + md5: d2ffd7602c02f2b316fd921d39876885 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 260182 + timestamp: 1771350215188 +- conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.14.1-py311h3778330_0.conda + sha256: ebefe7c8a41d14e4a50c5b862cf0226b3ac745495415bb6fb0db364b945cfe3a + md5: f875c239f662e1b31fbf32282f1da087 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - tomli + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/coverage?source=hash-mapping + size: 399156 + timestamp: 1779838054673 +- conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.14.1-py312h8a5da7c_0.conda + sha256: 80b990c6870c721bcde5e14e71d3560bac3dad93b54d027f723dca2bb7ccda03 + md5: 6668e2af2de730400bdce9cf2ea132f9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - tomli + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/coverage?source=hash-mapping + size: 389696 + timestamp: 1779838017522 +- conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.14.1-py313h3dea7bd_0.conda + sha256: b4ff99ffe4e60119c2f99fa29234b4267f7e0f43dbf5396dad0f8adaf95284e2 + md5: 86bbb569988f077e5cb30acac5799599 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - tomli + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/coverage?source=hash-mapping + size: 396489 + timestamp: 1779837909103 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c + md5: 18335a698559cdbcd86150a48bf54ba6 + depends: + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-64 2.45.1 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 728002 + timestamp: 1774197446916 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-8_h4a7cf45_openblas.conda + build_number: 8 + sha256: b2da6bfd72a1c9cb143ccf64bf5b28790cb4eb58bd1cb978f6537b2322f7d48b + md5: 00fc660ab1b2f5ca07e92b4900d10c79 + depends: + - libopenblas >=0.3.33,<0.3.34.0a0 + - libopenblas >=0.3.33,<1.0a0 + constrains: + - blas 2.308 openblas + - mkl <2027 + - libcblas 3.11.0 8*_openblas + - liblapack 3.11.0 8*_openblas + - liblapacke 3.11.0 8*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18804 + timestamp: 1779859100675 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-8_h0358290_openblas.conda + build_number: 8 + sha256: 1a2bc77bb26520255904a3d9b1f40e6bf0bf9d8d3405c7709dd162282820915a + md5: 33a413f1095f8325e5c30fde3b0d2445 + depends: + - libblas 3.11.0 8_h4a7cf45_openblas + constrains: + - blas 2.308 openblas + - liblapacke 3.11.0 8*_openblas + - liblapack 3.11.0 8*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18778 + timestamp: 1779859107964 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_0.conda + sha256: 363018b25fdb5534c79783d912bd4b685a3547f4fc5996357ad548899b0ee8e7 + md5: 93764a5ca80616e9c10106cdaec92f74 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - expat 2.8.1.* + license: MIT + license_family: MIT + purls: [] + size: 77294 + timestamp: 1779278686680 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 + md5: a360c33a5abe61c07959e449fa1453eb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 58592 + timestamp: 1769456073053 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda + sha256: 8e0a3b5e41272e5678499b5dfc4cddb673f9e935de01eb0767ce857001229f46 + md5: 57736f29cc2b0ec0b6c2952d3f101b6a + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgcc-ng ==15.2.0=*_19 + - libgomp 15.2.0 he0feb66_19 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 1041084 + timestamp: 1778269013026 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + sha256: 9dcf54adfaa5e861123c2da4f2f0451a685464ea7e5a41ad91cf67b31d658d98 + md5: 331ee9b72b9dff570d56b1302c5ab37d + depends: + - libgcc 15.2.0 he0feb66_19 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 27694 + timestamp: 1778269016987 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda + sha256: 561a42758ef25b9ce308c4e2cf56daee4f06138385a17e29a492cd928e00be6f + md5: 42bf7eca1a951735fa06c0e3c0d5c8e6 + depends: + - libgfortran5 15.2.0 h68bc16d_19 + constrains: + - libgfortran-ng ==15.2.0=*_19 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 27655 + timestamp: 1778269042954 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + sha256: 057978bb69fea29ed715a9b98adf71015c31baecc4aeb2bfc20d4fd5d83579d4 + md5: 85072b0ad177c966294f129b7c04a2d5 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15.2.0 + constrains: + - libgfortran 15.2.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 2483673 + timestamp: 1778269025089 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + sha256: 5abe4ab9d93f6c9757d654f1969ae2267d4505315c1f2f8fe705fd60af084f1b + md5: faac990cb7aedc7f3a2224f2c9b0c26c + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 603817 + timestamp: 1778268942614 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-8_h47877c9_openblas.conda + build_number: 8 + sha256: 168e327d737059553e15cc6ec36d76b9bbb3931c2a7721555fd68b4c9348b247 + md5: 809be8ba8712c77bc7d44c2d99390dc4 + depends: + - libblas 3.11.0 8_h4a7cf45_openblas + constrains: + - blas 2.308 openblas + - libcblas 3.11.0 8*_openblas + - liblapacke 3.11.0 8*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18790 + timestamp: 1779859115086 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + sha256: ec30e52a3c1bf7d0425380a189d209a52baa03f22fb66dd3eb587acaa765bd6d + md5: b88d90cad08e6bc8ad540cb310a761fb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + size: 113478 + timestamp: 1775825492909 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843 + md5: 2c21e66f50753a083cbe6b80f38268fa + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 92400 + timestamp: 1769482286018 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 + md5: d864d34357c3b65a4b731f78c0801dc4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-only + license_family: GPL + purls: [] + size: 33731 + timestamp: 1750274110928 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda + sha256: 3d9aa85648e5e18a6d66db98b8c4317cc426721ad7a220aa86330d1ccedc8903 + md5: 2d3278b721e40468295ca755c3b84070 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libgfortran + - libgfortran5 >=14.3.0 + constrains: + - openblas >=0.3.33,<0.3.34.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 5931919 + timestamp: 1776993658641 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.2-h0c1763c_0.conda + sha256: 1ab603b6ec93933e76027e1f23b21b22b858ba1b56f1e1695ef6fe5e80cb7358 + md5: 062b0ac602fb0adf250e3dfa86f221c4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + size: 957849 + timestamp: 1780574429573 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda + sha256: dff1058c76ec6b8759e41cefa2508162d00e4a5e6721aa68ec3fd10094e702dc + md5: 5794b3bdc38177caf969dabd3af08549 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 15.2.0 he0feb66_19 + constrains: + - libstdcxx-ng ==15.2.0=*_19 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 5852044 + timestamp: 1778269036376 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.1-h5347b49_0.conda + sha256: 3f0edf1280e2f6684a986f821eaa3e123d2694a00b31b96ca0d4a4c12c129231 + md5: 7d0a66598195ef00b6efc55aefc7453b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 40163 + timestamp: 1779118517630 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c + md5: 5aa797f8787fe7a17d1b0821485b5adc + depends: + - libgcc-ng >=12 + license: LGPL-2.1-or-later + purls: [] + size: 100393 + timestamp: 1702724383534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 + md5: d87ff7921124eccd67248aa483c23fec + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 63629 + timestamp: 1774072609062 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + sha256: fc89f74bbe362fb29fa3c037697a89bec140b346a2469a90f7936d1d7ea4d8a3 + md5: fc21868a1a5aacc937e7a18747acb8a5 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: X11 AND BSD-3-Clause + purls: [] + size: 918956 + timestamp: 1777422145199 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.6-py311h2e04523_0.conda + sha256: 8e8fb64c1a51282e8940d57d116aec54a4d66da59594973ae9c0b35d419b9a81 + md5: 5d4e35d7097b88c8b1455ef9f6ddf511 + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libblas >=3.9.0,<4.0a0 + - python_abi 3.11.* *_cp311 + - liblapack >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=compressed-mapping + size: 9389525 + timestamp: 1779169198155 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.6-py312h33ff503_0.conda + sha256: dfcbeadb3e7ad0da7a55a0525884ca34c19584154e13cc4159396b305d1bd445 + md5: 6e31d55ee1110fda83b4f4045f4d73ff + depends: + - python + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - liblapack >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + - python_abi 3.12.* *_cp312 + - libcblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=compressed-mapping + size: 8759520 + timestamp: 1779169200325 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.6-py313hf6604e3_0.conda + sha256: 3740c9bc562db9c6f252f8697c5c7948bb48784346856f6d6308aba72ea4f00b + md5: a5fdb80595ec7912e6b1634b2abd4b50 + depends: + - python + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libcblas >=3.9.0,<4.0a0 + - python_abi 3.13.* *_cp313 + - liblapack >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=compressed-mapping + size: 8864096 + timestamp: 1779169199037 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + sha256: c0ef482280e38c71a08ad6d71448194b719630345b0c9c60744a2010e8a8e0cb + md5: da1b85b6a87e141f5140bb9924cecab0 + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3167099 + timestamp: 1775587756857 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-3.0.3-py311h8032f78_0.conda + sha256: a1d380a93246b95051210a7523717f22cd5a714994990092e312bd61a688b15c + md5: b97631feb50f20710c402cf71e173f4b + depends: + - python + - numpy >=1.26.0 + - python-dateutil >=2.8.2 + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - numpy >=1.23,<3 + - python_abi 3.11.* *_cp311 + constrains: + - adbc-driver-postgresql >=1.2.0 + - adbc-driver-sqlite >=1.2.0 + - beautifulsoup4 >=4.12.3 + - blosc >=1.21.3 + - bottleneck >=1.4.2 + - fastparquet >=2024.11.0 + - fsspec >=2024.10.0 + - gcsfs >=2024.10.0 + - html5lib >=1.1 + - hypothesis >=6.116.0 + - jinja2 >=3.1.5 + - lxml >=5.3.0 + - matplotlib >=3.9.3 + - numba >=0.60.0 + - numexpr >=2.10.2 + - odfpy >=1.4.1 + - openpyxl >=3.1.5 + - psycopg2 >=2.9.10 + - pyarrow >=13.0.0 + - pyiceberg >=0.8.1 + - pymysql >=1.1.1 + - pyqt5 >=5.15.9 + - pyreadstat >=1.2.8 + - pytables >=3.10.1 + - pytest >=8.3.4 + - pytest-xdist >=3.6.1 + - python-calamine >=0.3.0 + - pytz >=2024.2 + - pyxlsb >=1.0.10 + - qtpy >=2.4.2 + - scipy >=1.14.1 + - s3fs >=2024.10.0 + - sqlalchemy >=2.0.36 + - tabulate >=0.9.0 + - xarray >=2024.10.0 + - xlrd >=2.0.1 + - xlsxwriter >=3.2.0 + - zstandard >=0.23.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pandas?source=compressed-mapping + size: 15174736 + timestamp: 1778602614189 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-3.0.3-py312h8ecdadd_0.conda + sha256: 009408dcfdc789b8a1748d6a63fd2134ea2edc8474231ea7beba0ac3ad772a37 + md5: 15c437bfa4cbddd379b95357c9aa4150 + depends: + - python + - numpy >=1.26.0 + - python-dateutil >=2.8.2 + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.12.* *_cp312 + - numpy >=1.23,<3 + constrains: + - adbc-driver-postgresql >=1.2.0 + - adbc-driver-sqlite >=1.2.0 + - beautifulsoup4 >=4.12.3 + - blosc >=1.21.3 + - bottleneck >=1.4.2 + - fastparquet >=2024.11.0 + - fsspec >=2024.10.0 + - gcsfs >=2024.10.0 + - html5lib >=1.1 + - hypothesis >=6.116.0 + - jinja2 >=3.1.5 + - lxml >=5.3.0 + - matplotlib >=3.9.3 + - numba >=0.60.0 + - numexpr >=2.10.2 + - odfpy >=1.4.1 + - openpyxl >=3.1.5 + - psycopg2 >=2.9.10 + - pyarrow >=13.0.0 + - pyiceberg >=0.8.1 + - pymysql >=1.1.1 + - pyqt5 >=5.15.9 + - pyreadstat >=1.2.8 + - pytables >=3.10.1 + - pytest >=8.3.4 + - pytest-xdist >=3.6.1 + - python-calamine >=0.3.0 + - pytz >=2024.2 + - pyxlsb >=1.0.10 + - qtpy >=2.4.2 + - scipy >=1.14.1 + - s3fs >=2024.10.0 + - sqlalchemy >=2.0.36 + - tabulate >=0.9.0 + - xarray >=2024.10.0 + - xlrd >=2.0.1 + - xlsxwriter >=3.2.0 + - zstandard >=0.23.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pandas?source=compressed-mapping + size: 14872605 + timestamp: 1778602625175 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-3.0.3-py313hbfd7664_0.conda + sha256: a02d58327a57eb2f149c040b31c758fc6acea7c6aa5cb09b40b146eb6ed637d9 + md5: 70d4dd67877354f6912af31177cb1117 + depends: + - python + - numpy >=1.26.0 + - python-dateutil >=2.8.2 + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + - numpy >=1.23,<3 + constrains: + - adbc-driver-postgresql >=1.2.0 + - adbc-driver-sqlite >=1.2.0 + - beautifulsoup4 >=4.12.3 + - blosc >=1.21.3 + - bottleneck >=1.4.2 + - fastparquet >=2024.11.0 + - fsspec >=2024.10.0 + - gcsfs >=2024.10.0 + - html5lib >=1.1 + - hypothesis >=6.116.0 + - jinja2 >=3.1.5 + - lxml >=5.3.0 + - matplotlib >=3.9.3 + - numba >=0.60.0 + - numexpr >=2.10.2 + - odfpy >=1.4.1 + - openpyxl >=3.1.5 + - psycopg2 >=2.9.10 + - pyarrow >=13.0.0 + - pyiceberg >=0.8.1 + - pymysql >=1.1.1 + - pyqt5 >=5.15.9 + - pyreadstat >=1.2.8 + - pytables >=3.10.1 + - pytest >=8.3.4 + - pytest-xdist >=3.6.1 + - python-calamine >=0.3.0 + - pytz >=2024.2 + - pyxlsb >=1.0.10 + - qtpy >=2.4.2 + - scipy >=1.14.1 + - s3fs >=2024.10.0 + - sqlalchemy >=2.0.36 + - tabulate >=0.9.0 + - xarray >=2024.10.0 + - xlrd >=2.0.1 + - xlsxwriter >=3.2.0 + - zstandard >=0.23.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pandas?source=compressed-mapping + size: 15001668 + timestamp: 1778602610159 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda + sha256: bf6a32c69889d38482436a786bea32276756cedf0e9805cc856ffd088e8d00f0 + md5: a5ebcefec0c12a333bcd6d7bf3bddc1f + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.2,<6.0a0 + - libnsl >=2.0.1,<2.1.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libuuid >=2.41.3,<3.0a0 + - libxcrypt >=4.4.36 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 30949404 + timestamp: 1772730362552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.13-hd63d673_0_cpython.conda + sha256: a44655c1c3e1d43ed8704890a91e12afd68130414ea2c0872e154e5633a13d7e + md5: 7eccb41177e15cc672e1babe9056018e + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.2,<6.0a0 + - libnsl >=2.0.1,<2.1.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libuuid >=2.41.3,<3.0a0 + - libxcrypt >=4.4.36 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + purls: [] + size: 31608571 + timestamp: 1772730708989 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda + build_number: 100 + sha256: 7f77eb57648f545c1f58e10035d0d9d66b0a0efb7c4b58d3ed89ec7269afdde1 + md5: 05051be49267378d2fcd12931e319ac3 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.5,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libuuid >=2.42,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.6,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + size: 37358322 + timestamp: 1775614712638 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 + md5: d7d95fc8287ea7bf33e0e7116d2b95ec + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 345073 + timestamp: 1765813471974 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.16-h6a952e8_0.conda + noarch: python + sha256: 8b0f50a439826eedfcd2741985aa55d8af7d281a4cebde7a8c2ceda6bbeb1bc4 + md5: 8d5840b229d9e957ac2af3c3b4e0eadc + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruff?source=compressed-mapping + size: 9192459 + timestamp: 1780611849620 +- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py311hbe70eeb_1.conda + sha256: 3ae2ff1d1cc5930de2ca6ac03216118bdf13b2af6098e28e827f1ba25bfcbc4e + md5: 089de2ee37e4e19885c985a4fe4aaf14 + depends: + - __glibc >=2.17,<3.0.a0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libgcc >=14 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - libstdcxx >=14 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=1.25.2 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scipy?source=compressed-mapping + size: 17303931 + timestamp: 1779874783665 +- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py312h54fa4ab_1.conda + sha256: d5ac05ad45c0d48731eb189c2cbb2bb99f0e3cb7e1acaad373cb2f1f2597fc75 + md5: 15995ecb2ef890778ba9a3750190f09d + depends: + - __glibc >=2.17,<3.0.a0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libgcc >=14 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - libstdcxx >=14 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=1.25.2 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scipy?source=compressed-mapping + size: 16828243 + timestamp: 1779874781187 +- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py313h4b8bb8b_1.conda + sha256: 2ecb1a3d6aacd20e279a72196954bc8de2e83302823f9dd9f8b9b3310d1bf515 + md5: 4b098461b0b5edff1a9359c25e675cfd + depends: + - __glibc >=2.17,<3.0.a0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libgcc >=14 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - libstdcxx >=14 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=1.25.2 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scipy?source=compressed-mapping + size: 17184702 + timestamp: 1779874789436 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac + md5: cffd3bdd58090148f4cfcd831f4b26ab + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + constrains: + - xorg-libx11 >=1.8.12,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3301196 + timestamp: 1769460227866 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 + md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 + depends: + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 601375 + timestamp: 1764777111296 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda + sha256: 9812a303a1395e1dafbd92e5bc8a1ff6013bcbba0a09c7f03a8d23e43560aa9b + md5: 489b8e97e666c93f68fdb35c3c9b957f + depends: + - __unix + license: ISC + purls: [] + size: 129868 + timestamp: 1779289852439 +- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 + md5: 962b9857ee8e7018c22f2776ffa0b2d7 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/colorama?source=hash-mapping + size: 27011 + timestamp: 1733218222191 +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 + md5: 8e662bd460bda79b1ea39194e3c4c9ab + depends: + - python >=3.10 + - typing_extensions >=4.6.0 + license: MIT and PSF-2.0 + purls: + - pkg:pypi/exceptiongroup?source=hash-mapping + size: 21333 + timestamp: 1763918099466 +- conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + sha256: e1a9e3b1c8fe62dc3932a616c284b5d8cbe3124bbfbedcf4ce5c828cb166ee19 + md5: 9614359868482abba1bd15ce465e3c42 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/iniconfig?source=hash-mapping + size: 13387 + timestamp: 1760831448842 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + sha256: 3906abfb6511a3bb309e39b9b1b7bc38f50a723971de2395489fd1f379255890 + md5: 4c06a92e74452cfa53623a81592e8934 + depends: + - python >=3.8 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/packaging?source=hash-mapping + size: 91574 + timestamp: 1777103621679 +- conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + sha256: e14aafa63efa0528ca99ba568eaf506eb55a0371d12e6250aaaa61718d2eb62e + md5: d7585b6550ad04c8c5e21097ada2888e + depends: + - python >=3.9 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/pluggy?source=hash-mapping + size: 25877 + timestamp: 1764896838868 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + sha256: cf70b2f5ad9ae472b71235e5c8a736c9316df3705746de419b59d442e8348e86 + md5: 16c18772b340887160c79a6acc022db0 + depends: + - python >=3.10 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/pygments?source=hash-mapping + size: 893031 + timestamp: 1774796815820 +- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.3-pyhc364b38_1.conda + sha256: 960f59442173eee0731906a9077bd5ccf60f4b4226f05a22d1728ab9a21a879c + md5: 6a991452eadf2771952f39d43615bb3e + depends: + - colorama >=0.4 + - pygments >=2.7.2 + - python >=3.10 + - iniconfig >=1.0.1 + - packaging >=22 + - pluggy >=1.5,<2 + - tomli >=1 + - exceptiongroup >=1 + - python + constrains: + - pytest-faulthandler >=2 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pytest?source=hash-mapping + size: 299984 + timestamp: 1775644472530 +- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda + sha256: 44e42919397bd00bfaa47358a6ca93d4c21493a8c18600176212ec21a8d25ca5 + md5: 67d1790eefa81ed305b89d8e314c7923 + depends: + - coverage >=7.10.6 + - pluggy >=1.2 + - pytest >=7 + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/pytest-cov?source=hash-mapping + size: 29559 + timestamp: 1774139250481 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + sha256: d6a17ece93bbd5139e02d2bd7dbfa80bee1a4261dced63f65f679121686bf664 + md5: 5b8d21249ff20967101ffa321cab24e8 + depends: + - python >=3.9 + - six >=1.5 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/python-dateutil?source=hash-mapping + size: 233310 + timestamp: 1751104122689 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + build_number: 8 + sha256: fddf123692aa4b1fc48f0471e346400d9852d96eeed77dbfdd746fa50a8ff894 + md5: 8fcb6b0e2161850556231336dae58358 + constrains: + - python 3.11.* *_cpython + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 7003 + timestamp: 1752805919375 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + build_number: 8 + sha256: 80677180dd3c22deb7426ca89d6203f1c7f1f256f2d5a94dc210f6e758229809 + md5: c3efd25ac4d74b1584d2f7a57195ddf1 + constrains: + - python 3.12.* *_cpython + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6958 + timestamp: 1752805918820 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + build_number: 8 + sha256: 210bffe7b121e651419cb196a2a63687b087497595c9be9d20ebe97dd06060a7 + md5: 94305520c52a4aa3f6c2b1ff6008d9f8 + constrains: + - python 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 7002 + timestamp: 1752805902938 +- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d + md5: 3339e3b65d58accf4ca4fb8748ab16b3 + depends: + - python >=3.9 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/six?source=hash-mapping + size: 18455 + timestamp: 1753199211006 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + sha256: 91cafdb64268e43e0e10d30bd1bef5af392e69f00edd34dfaf909f69ab2da6bd + md5: b5325cf06a000c5b14970462ff5e4d58 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/tomli?source=hash-mapping + size: 21561 + timestamp: 1774492402955 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 + md5: 0caa1af407ecff61170c9437a808404d + depends: + - python >=3.10 + - python + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/typing-extensions?source=hash-mapping + size: 51692 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c + md5: ad659d0a2b3e47e38d829aa8cad2d610 + license: LicenseRef-Public-Domain + purls: [] + size: 119135 + timestamp: 1767016325805 +- conda: https://conda.anaconda.org/conda-forge/osx-64/_openmp_mutex-4.5-7_kmp_llvm.conda + build_number: 7 + sha256: 30006902a9274de8abdad5a9f02ef7c8bb3d69a503486af0c1faee30b023e5b7 + md5: eaac87c21aff3ed21ad9656697bb8326 + depends: + - llvm-openmp >=9.0.1 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 8328 + timestamp: 1764092562779 +- conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_9.conda + sha256: 9f242f13537ef1ce195f93f0cc162965d6cc79da578568d6d8e50f70dd025c42 + md5: 4173ac3b19ec0a4f400b4f782910368b + depends: + - __osx >=10.13 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 133427 + timestamp: 1771350680709 +- conda: https://conda.anaconda.org/conda-forge/osx-64/coverage-7.14.1-py311ha8ae342_0.conda + sha256: 286d4d6bf016f56d71f58c2dd495410213fb5e4053a0545d48638bfd346683bc + md5: 145a45fb204a76aac34696a6fac42ece + depends: + - __osx >=11.0 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - tomli + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/coverage?source=hash-mapping + size: 396933 + timestamp: 1779838320779 +- conda: https://conda.anaconda.org/conda-forge/osx-64/coverage-7.14.1-py312heb39f77_0.conda + sha256: 5fa71aeb0be43ccf8f5b5ac4102efd23d22e44d4e713e802251d5456a90bb62c + md5: 0b1ec37d2e54ba95731d07e273228789 + depends: + - __osx >=11.0 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - tomli + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/coverage?source=hash-mapping + size: 388114 + timestamp: 1779838145107 +- conda: https://conda.anaconda.org/conda-forge/osx-64/coverage-7.14.1-py313h035b7d0_0.conda + sha256: 24bdd78378c4b2ed32e701254c7bc0dcab74d5b366bafb6d42ba2ffd017549d4 + md5: 6f795259f9dcc6de273f1c6f626f2234 + depends: + - __osx >=11.0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - tomli + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/coverage?source=hash-mapping + size: 395003 + timestamp: 1779838290292 +- conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.3-h25d91c4_0.conda + sha256: 1294117122d55246bb83ad5b589e2a031aacdf2d0b1f99fd338aa4394f881735 + md5: 627eca44e62e2b665eeec57a984a7f00 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 12273764 + timestamp: 1773822733780 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libblas-3.11.0-8_he492b99_openblas.conda + build_number: 8 + sha256: 55cf9f92a2d07c33f8a32c44ff1528ea48fd69677cc003a4532d09b71cb8a316 + md5: 7da1e8ab7c4498db9457c191d82930a3 + depends: + - libopenblas >=0.3.33,<0.3.34.0a0 + - libopenblas >=0.3.33,<1.0a0 + constrains: + - mkl <2027 + - blas 2.308 openblas + - liblapacke 3.11.0 8*_openblas + - libcblas 3.11.0 8*_openblas + - liblapack 3.11.0 8*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 19048 + timestamp: 1779860008916 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.11.0-8_h9b27e0a_openblas.conda + build_number: 8 + sha256: 50eb650a17a34ea45fe2b31e60a98632d1f8c203308014dcef93043d54612482 + md5: 4f116127b172bbba835c1e0491efd86f + depends: + - libblas 3.11.0 8_he492b99_openblas + constrains: + - liblapacke 3.11.0 8*_openblas + - blas 2.308 openblas + - liblapack 3.11.0 8*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 19049 + timestamp: 1779860025163 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.7-h19cb2f5_0.conda + sha256: c03c298355dea54b729ed6c5f1e6dbd0e2426906039eba8aa2ba1254d005b7d8 + md5: 423373b842c3861da6cfa8c8915798ce + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + size: 564939 + timestamp: 1780442565078 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.8.1-hcc62823_0.conda + sha256: 460afe7ba0882e6d2fcc0ad1568dce27025110ec09c2b9ce9e3b49d61e52ce6b + md5: f95dc08366f2a452005062b5bcceac51 + depends: + - __osx >=11.0 + constrains: + - expat 2.8.1.* + license: MIT + license_family: MIT + purls: [] + size: 75654 + timestamp: 1779279058576 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + sha256: 951958d1792238006fdc6fce7f71f1b559534743b26cc1333497d46e5903a2d6 + md5: 66a0dc7464927d0853b590b6f53ba3ea + depends: + - __osx >=10.13 + license: MIT + license_family: MIT + purls: [] + size: 53583 + timestamp: 1769456300951 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libgcc-15.2.0-h08519bb_19.conda + sha256: 17a5dcd818f89173db51d7d1acd77615cb77db7b4c2b5f571d4dafe559430ab5 + md5: 4bf33d5ca73f4b89d3495285a42414a4 + depends: + - _openmp_mutex + constrains: + - libgomp 15.2.0 19 + - libgcc-ng ==15.2.0=*_19 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 424164 + timestamp: 1778271183296 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran-15.2.0-h7e5c614_19.conda + sha256: 519045363b87b870be779d38f0bfd325d4b787acdaa0a2136a92c1081eff5112 + md5: d362f41203d0a1d2d4940446f95374c9 + depends: + - libgfortran5 15.2.0 hd16e46c_19 + constrains: + - libgfortran-ng ==15.2.0=*_19 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 139925 + timestamp: 1778271458366 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-15.2.0-hd16e46c_19.conda + sha256: c7f5f6e80357d6d5bc69588c16144205b0c79cf32cd090ccb5afef9d557632af + md5: 1cddb3f7e54f5871297afc0fafa61c2c + depends: + - libgcc >=15.2.0 + constrains: + - libgfortran 15.2.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 1063687 + timestamp: 1778271196574 +- conda: https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.11.0-8_h859234e_openblas.conda + build_number: 8 + sha256: 56a68fce5a63d4583a42c212324d62ac292376b8bf05986a551bd640e7fa137d + md5: e11ee849bd2a573a0f6e53b1b67ebf37 + depends: + - libblas 3.11.0 8_he492b99_openblas + constrains: + - liblapacke 3.11.0 8*_openblas + - libcblas 3.11.0 8*_openblas + - blas 2.308 openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 19030 + timestamp: 1779860046842 +- conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_0.conda + sha256: d9e2006051529aec5578c6efeb13bb6a7200a014b2d5a77a579e83a8049d5f3c + md5: becdfbfe7049fa248e52aa37a9df09e2 + depends: + - __osx >=11.0 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + size: 105724 + timestamp: 1775826029494 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-hf3981d6_1.conda + sha256: 1096c740109386607938ab9f09a7e9bca06d86770a284777586d6c378b8fb3fd + md5: ec88ba8a245855935b871a7324373105 + depends: + - __osx >=10.13 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 79899 + timestamp: 1769482558610 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.33-openmp_h9e49c7b_0.conda + sha256: 2c2ffe7c3ab7becd47ad308946873d2bdc219625af32a53d10efbaa54b595d31 + md5: 30666a6f0afe1471e999eca7ae5c8179 + depends: + - __osx >=11.0 + - libgfortran + - libgfortran5 >=14.3.0 + - llvm-openmp >=19.1.7 + constrains: + - openblas >=0.3.33,<0.3.34.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 6287889 + timestamp: 1776996499823 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.53.2-h8f8c405_0.conda + sha256: 4d4f3135d390d192ab9cdf3711d87e3be6bb7f3959c52a96e2f333b30960d6fb + md5: 4c019bd25570899d0f9755de01b89021 + depends: + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + size: 1010419 + timestamp: 1780575011758 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_2.conda + sha256: 4c6da089952b2d70150c74234679d6f7ac04f4a98f9432dec724968f912691e7 + md5: 30439ff30578e504ee5e0b390afc8c65 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 59000 + timestamp: 1774073052242 +- conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.7-h0d3cbff_0.conda + sha256: c8eeb6bca45680db8974b78e0524b2ab3c285a9916a0b3356329d1f949b1311b + md5: 301c1db2d75ac8a91f46d21652e08dd6 + depends: + - __osx >=11.0 + constrains: + - openmp 22.1.7|22.1.7.* + - intel-openmp <0.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + size: 310879 + timestamp: 1780456054580 +- conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.6-hcc0dc9a_0.conda + sha256: f5f7e006ff4271305ab4cc08eedd855c67a571793c3d18aff73f645f088a8cae + md5: 31b8740cf1b2588d4e61c81191004061 + depends: + - __osx >=11.0 + license: X11 AND BSD-3-Clause + purls: [] + size: 831711 + timestamp: 1777423052277 +- conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.6-py311h2c4eb96_0.conda + sha256: 47fa3ad9a49348efb5662d7850a433607ee4fabf259709731437a969c3006fa9 + md5: 0cb49ff5e81a76c101f1a561cf1f2a76 + depends: + - python + - __osx >=11.0 + - libcxx >=19 + - liblapack >=3.9.0,<4.0a0 + - python_abi 3.11.* *_cp311 + - libcblas >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=hash-mapping + size: 8593034 + timestamp: 1779169256521 +- conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.6-py312h746d82c_0.conda + sha256: e7837f62b874c987c1bd2eda335ae9b977caf61a5227c23e4e8cceef88bb21b6 + md5: 86c91d10224283ed367225057a09e4a3 + depends: + - python + - libcxx >=19 + - __osx >=11.0 + - libcblas >=3.9.0,<4.0a0 + - python_abi 3.12.* *_cp312 + - libblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=compressed-mapping + size: 7997002 + timestamp: 1779782916096 +- conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.4.6-py313hb870fc3_0.conda + sha256: 5ac50239781b7cd7581126e626f0dc13944de3fefd950b874700047d7e0aa53f + md5: 6b8ec37e54d1ef1a635038a9d6c4a672 + depends: + - python + - __osx >=11.0 + - libcxx >=19 + - liblapack >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=hash-mapping + size: 8068573 + timestamp: 1779169285266 +- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.2-hc881268_0.conda + sha256: 334fd49ea31b99114f5afb1ec44555dc8c90640648302a4f8f838ee345d1ec50 + md5: 5cf0ece4375c73d7a5765e83565a69c7 + depends: + - __osx >=11.0 + - ca-certificates + license: Apache-2.0 + license_family: Apache + purls: [] + size: 2776564 + timestamp: 1775589970694 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-3.0.3-py311h2a74ac8_0.conda + sha256: 63ec8115217bdef61a20856cfaabaf814882a9ea9c8085141e20e3ff0debc23f + md5: e31c570c36ac80b7d8db81b1771622bd + depends: + - python + - numpy >=1.26.0 + - python-dateutil >=2.8.2 + - __osx >=11.0 + - libcxx >=19 + - python_abi 3.11.* *_cp311 + - numpy >=1.23,<3 + constrains: + - adbc-driver-postgresql >=1.2.0 + - adbc-driver-sqlite >=1.2.0 + - beautifulsoup4 >=4.12.3 + - blosc >=1.21.3 + - bottleneck >=1.4.2 + - fastparquet >=2024.11.0 + - fsspec >=2024.10.0 + - gcsfs >=2024.10.0 + - html5lib >=1.1 + - hypothesis >=6.116.0 + - jinja2 >=3.1.5 + - lxml >=5.3.0 + - matplotlib >=3.9.3 + - numba >=0.60.0 + - numexpr >=2.10.2 + - odfpy >=1.4.1 + - openpyxl >=3.1.5 + - psycopg2 >=2.9.10 + - pyarrow >=13.0.0 + - pyiceberg >=0.8.1 + - pymysql >=1.1.1 + - pyqt5 >=5.15.9 + - pyreadstat >=1.2.8 + - pytables >=3.10.1 + - pytest >=8.3.4 + - pytest-xdist >=3.6.1 + - python-calamine >=0.3.0 + - pytz >=2024.2 + - pyxlsb >=1.0.10 + - qtpy >=2.4.2 + - scipy >=1.14.1 + - s3fs >=2024.10.0 + - sqlalchemy >=2.0.36 + - tabulate >=0.9.0 + - xarray >=2024.10.0 + - xlrd >=2.0.1 + - xlsxwriter >=3.2.0 + - zstandard >=0.23.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pandas?source=hash-mapping + size: 14558446 + timestamp: 1778602873120 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-3.0.3-py312h8e27051_0.conda + sha256: d72b541b510e3a1db86db3ce8d4c30bddc945c3c89eb2c9d16fde0cc9f82e497 + md5: 8cfffbf760a7d7abc16c79141ead177a + depends: + - python + - numpy >=1.26.0 + - python-dateutil >=2.8.2 + - libcxx >=19 + - __osx >=11.0 + - numpy >=1.23,<3 + - python_abi 3.12.* *_cp312 + constrains: + - adbc-driver-postgresql >=1.2.0 + - adbc-driver-sqlite >=1.2.0 + - beautifulsoup4 >=4.12.3 + - blosc >=1.21.3 + - bottleneck >=1.4.2 + - fastparquet >=2024.11.0 + - fsspec >=2024.10.0 + - gcsfs >=2024.10.0 + - html5lib >=1.1 + - hypothesis >=6.116.0 + - jinja2 >=3.1.5 + - lxml >=5.3.0 + - matplotlib >=3.9.3 + - numba >=0.60.0 + - numexpr >=2.10.2 + - odfpy >=1.4.1 + - openpyxl >=3.1.5 + - psycopg2 >=2.9.10 + - pyarrow >=13.0.0 + - pyiceberg >=0.8.1 + - pymysql >=1.1.1 + - pyqt5 >=5.15.9 + - pyreadstat >=1.2.8 + - pytables >=3.10.1 + - pytest >=8.3.4 + - pytest-xdist >=3.6.1 + - python-calamine >=0.3.0 + - pytz >=2024.2 + - pyxlsb >=1.0.10 + - qtpy >=2.4.2 + - scipy >=1.14.1 + - s3fs >=2024.10.0 + - sqlalchemy >=2.0.36 + - tabulate >=0.9.0 + - xarray >=2024.10.0 + - xlrd >=2.0.1 + - xlsxwriter >=3.2.0 + - zstandard >=0.23.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pandas?source=hash-mapping + size: 14170082 + timestamp: 1778602746933 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-3.0.3-py313hfd25234_0.conda + sha256: e544e54be83b7be300813a3503ca915c8a7c6e82f550e616326732a9d9d09014 + md5: 4942165df70ab41f6487ceaa0ff6bb67 + depends: + - python + - numpy >=1.26.0 + - python-dateutil >=2.8.2 + - libcxx >=19 + - __osx >=11.0 + - numpy >=1.23,<3 + - python_abi 3.13.* *_cp313 + constrains: + - adbc-driver-postgresql >=1.2.0 + - adbc-driver-sqlite >=1.2.0 + - beautifulsoup4 >=4.12.3 + - blosc >=1.21.3 + - bottleneck >=1.4.2 + - fastparquet >=2024.11.0 + - fsspec >=2024.10.0 + - gcsfs >=2024.10.0 + - html5lib >=1.1 + - hypothesis >=6.116.0 + - jinja2 >=3.1.5 + - lxml >=5.3.0 + - matplotlib >=3.9.3 + - numba >=0.60.0 + - numexpr >=2.10.2 + - odfpy >=1.4.1 + - openpyxl >=3.1.5 + - psycopg2 >=2.9.10 + - pyarrow >=13.0.0 + - pyiceberg >=0.8.1 + - pymysql >=1.1.1 + - pyqt5 >=5.15.9 + - pyreadstat >=1.2.8 + - pytables >=3.10.1 + - pytest >=8.3.4 + - pytest-xdist >=3.6.1 + - python-calamine >=0.3.0 + - pytz >=2024.2 + - pyxlsb >=1.0.10 + - qtpy >=2.4.2 + - scipy >=1.14.1 + - s3fs >=2024.10.0 + - sqlalchemy >=2.0.36 + - tabulate >=0.9.0 + - xarray >=2024.10.0 + - xlrd >=2.0.1 + - xlsxwriter >=3.2.0 + - zstandard >=0.23.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pandas?source=hash-mapping + size: 14290954 + timestamp: 1778602759825 +- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.15-ha9537fe_0_cpython.conda + sha256: e02e12cd8d391f18bb3bf91d07e16b993592ec0d76ee37cf639390b766e0e687 + md5: 93b802a91de90b2c17b808608726bf45 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 15664115 + timestamp: 1772730794934 +- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.12.13-ha9537fe_0_cpython.conda + sha256: fb592ceb1bc247d19247d5535083da4a79721553e29e1290f5d81c07d4f086b5 + md5: ec05996c0d914a4e98ee3c7d789083f8 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + purls: [] + size: 13672169 + timestamp: 1772730464626 +- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.13-h3d5d122_100_cp313.conda + build_number: 100 + sha256: 6f71b48fe93ebc0dd42c80358b75020f6ad12ed4772fb3555da36000139c0dc7 + md5: 8948c8c7c653ad668d55bbbd6836178b + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.5,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.6,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + size: 17650454 + timestamp: 1775616128232 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda + sha256: 4614af680aa0920e82b953fece85a03007e0719c3399f13d7de64176874b80d5 + md5: eefd65452dfe7cce476a519bece46704 + depends: + - __osx >=10.13 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 317819 + timestamp: 1765813692798 +- conda: https://conda.anaconda.org/conda-forge/osx-64/ruff-0.15.16-h1ddadc8_0.conda + noarch: python + sha256: 694b662dd988bda4168b54fe7313b9cc0378215d796e38d7f3e3b22a211e2e27 + md5: da82dbe7191b3de371645a43579bd427 + depends: + - python + - __osx >=11.0 + constrains: + - __osx >=10.13 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruff?source=compressed-mapping + size: 9247784 + timestamp: 1780612065116 +- conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py311h556693a_1.conda + sha256: 4e68affca9e1d14cdb1fe6910c459ec4bd01b1217a867f7cfbf40830951f80aa + md5: 972007d34efaf5755603391a91e7d50f + depends: + - __osx >=11.0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libcxx >=19 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=1.25.2 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scipy?source=hash-mapping + size: 15513987 + timestamp: 1779875850168 +- conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py312h6309490_1.conda + sha256: 4b3663a4f1a92c881ba0fc4d317eee04831adc44400d85bac5b27c503445f6ad + md5: 284f71322e48e8ae8ce23d48356df042 + depends: + - __osx >=11.0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libcxx >=19 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=1.25.2 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scipy?source=hash-mapping + size: 15137152 + timestamp: 1779876260804 +- conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.17.1-py313h9cbb6b6_1.conda + sha256: d19a981b1cf0dc11b91a049caa3c983dba49161175c21c8f28c1c9114799c9e0 + md5: 8543e2546bb797f336d4469a41c16f18 + depends: + - __osx >=11.0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libcxx >=19 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=1.25.2 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scipy?source=compressed-mapping + size: 15365931 + timestamp: 1779875663184 +- conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda + sha256: 7f0d9c320288532873e2d8486c331ec6d87919c9028208d3f6ac91dc8f99a67b + md5: 6e6efb7463f8cef69dbcb4c2205bf60e + depends: + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3282953 + timestamp: 1769460532442 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + build_number: 7 + sha256: 7acaa2e0782cad032bdaf756b536874346ac1375745fb250e9bdd6a48a7ab3cd + md5: a44032f282e7d2acdeb1c240308052dd + depends: + - llvm-openmp >=9.0.1 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 8325 + timestamp: 1764092507920 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + sha256: 540fe54be35fac0c17feefbdc3e29725cce05d7367ffedfaaa1bdda234b019df + md5: 620b85a3f45526a8bc4d23fd78fc22f0 + depends: + - __osx >=11.0 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 124834 + timestamp: 1771350416561 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.14.1-py311hc290fe0_0.conda + sha256: d9475f473084602003da38e373604b48b674b5fbd5939eb6f26b757cbda89f28 + md5: 2e3107762a2b8bb31093fe14bab1fe17 + depends: + - __osx >=11.0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + - tomli + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/coverage?source=hash-mapping + size: 397978 + timestamp: 1779838426505 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.14.1-py312h04c11ed_0.conda + sha256: 62f7590a0e6456ff8f534c3f6213e4ec50443d3409fa3babfa30a38822a2b0fa + md5: 86b295185747ca5b09e95d7d33280382 + depends: + - __osx >=11.0 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + - tomli + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/coverage?source=hash-mapping + size: 388500 + timestamp: 1779838256904 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.14.1-py313h65a2061_0.conda + sha256: 46d98e0d517ecf6bff6160b2200a27f88da681786d4eb223cd5949d73a0b7610 + md5: e3f15d7b559de10dd9f60bd345efcdaa + depends: + - __osx >=11.0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + - tomli + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/coverage?source=hash-mapping + size: 396380 + timestamp: 1779838267496 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + sha256: 3a7907a17e9937d3a46dfd41cffaf815abad59a569440d1e25177c15fd0684e5 + md5: f1182c91c0de31a7abd40cedf6a5ebef + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 12361647 + timestamp: 1773822915649 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-8_h51639a9_openblas.conda + build_number: 8 + sha256: 8f5ec18ead0619a9cf0f38b49796c22f6fc0f44850c0df2baea0f5277db16e75 + md5: dbfe729181a32741ae63ecb41eefbac6 + depends: + - libopenblas >=0.3.33,<0.3.34.0a0 + - libopenblas >=0.3.33,<1.0a0 + constrains: + - blas 2.308 openblas + - liblapack 3.11.0 8*_openblas + - liblapacke 3.11.0 8*_openblas + - libcblas 3.11.0 8*_openblas + - mkl <2027 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18949 + timestamp: 1779859141315 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-8_hb0561ab_openblas.conda + build_number: 8 + sha256: f93efcd44bc24f97c2478c7474d3baa6801a057974f330e1d06bedc33e4c778f + md5: 03a2ef3491da9e5b4d18c03e9f4b3109 + depends: + - libblas 3.11.0 8_h51639a9_openblas + constrains: + - blas 2.308 openblas + - liblapack 3.11.0 8*_openblas + - liblapacke 3.11.0 8*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18911 + timestamp: 1779859147634 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.7-h55c6f16_0.conda + sha256: cceb668dc1b71f054b1036dd83eca2e02c0c3a4b2ba3ad28c74a982d819597a3 + md5: 0325fbe13eb6dd39234eb305ac1b3cb8 + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + size: 568252 + timestamp: 1780441702930 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_0.conda + sha256: 3133fb6bfa871288b92c8b8752696686a841bf4ffe035aa3038033c9e15b738e + md5: ef22e9ab1dc7c2f334252f565f90b3b8 + depends: + - __osx >=11.0 + constrains: + - expat 2.8.1.* + license: MIT + license_family: MIT + purls: [] + size: 69110 + timestamp: 1779278728511 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + sha256: 6686a26466a527585e6a75cc2a242bf4a3d97d6d6c86424a441677917f28bec7 + md5: 43c04d9cb46ef176bb2a4c77e324d599 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 40979 + timestamp: 1769456747661 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + sha256: 06644fa4d34d57c9e48f4d84b1256f9e5f654fdb37f43acc8a58a396952d42b7 + md5: 644058123986582db33aebd4ae2ca184 + depends: + - _openmp_mutex + constrains: + - libgcc-ng ==15.2.0=*_19 + - libgomp 15.2.0 19 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 404080 + timestamp: 1778273064154 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda + sha256: d4837b3b9b30af3132d260225e91ab9dde83be04c59513f500cc81050fb37486 + md5: 1ea03f87cdb1078fbc0e2b2deb63752c + depends: + - libgfortran5 15.2.0 hdae7583_19 + constrains: + - libgfortran-ng ==15.2.0=*_19 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 139675 + timestamp: 1778273280875 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda + sha256: d0a68b7a121d115b80c169e24d1265dcc25a3fe58d107df1bbc430797e226d88 + md5: ba36d8c606a6a53fe0b8c12d47267b3d + depends: + - libgcc >=15.2.0 + constrains: + - libgfortran 15.2.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 599691 + timestamp: 1778273075448 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-8_hd9741b5_openblas.conda + build_number: 8 + sha256: 8a076fe82142a00fe85f5a5a5351e286e8064f0100fe13608d19182cd0018c25 + md5: 85adeb3d469d082dbd9c8c39e36dec57 + depends: + - libblas 3.11.0 8_h51639a9_openblas + constrains: + - libcblas 3.11.0 8*_openblas + - blas 2.308 openblas + - liblapacke 3.11.0 8*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18925 + timestamp: 1779859153970 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda + sha256: 34878d87275c298f1a732c6806349125cebbf340d24c6c23727268184bba051e + md5: b1fd823b5ae54fbec272cea0811bd8a9 + depends: + - __osx >=11.0 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + size: 92472 + timestamp: 1775825802659 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda + sha256: 1089c7f15d5b62c622625ec6700732ece83be8b705da8c6607f4dabb0c4bd6d2 + md5: 57c4be259f5e0b99a5983799a228ae55 + depends: + - __osx >=11.0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 73690 + timestamp: 1769482560514 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda + sha256: 9dd455b2d172aeedfa2058d324b5b5822b0bc1b7c1f32cd183d7078540d2f6eb + md5: 909e41855c29f0d52ae630198cd57135 + depends: + - __osx >=11.0 + - libgfortran + - libgfortran5 >=14.3.0 + - llvm-openmp >=19.1.7 + constrains: + - openblas >=0.3.33,<0.3.34.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 4304965 + timestamp: 1776995497368 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.2-h1ae2325_0.conda + sha256: 862463917e8ef5ac3ebdaf8f19914634b457609cc27ba678b7197124cefeb1f7 + md5: 1ebde5c677f00765233a17e278571177 + depends: + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + size: 927724 + timestamp: 1780575223548 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + sha256: 361415a698514b19a852f5d1123c5da746d4642139904156ddfca7c922d23a05 + md5: bc5a5721b6439f2f62a84f2548136082 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 47759 + timestamp: 1774072956767 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.7-hc7d1edf_0.conda + sha256: 6bf27376f11198c01a88a1c8234470f45bce0aa7502b7e7988ef03ef5db3a890 + md5: 7c6a5897a8bc5b6d509a4ee9dec7fcc8 + depends: + - __osx >=11.0 + constrains: + - openmp 22.1.7|22.1.7.* + - intel-openmp <0.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + size: 285162 + timestamp: 1780455637760 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + sha256: 4ea6c620b87bd1d42bb2ccc2c87cd2483fa2d7f9e905b14c223f11ff3f4c455d + md5: 343d10ed5b44030a2f67193905aea159 + depends: + - __osx >=11.0 + license: X11 AND BSD-3-Clause + purls: [] + size: 805509 + timestamp: 1777423252320 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.6-py311hbd1492f_0.conda + sha256: 08e5062ab9bce23adef1c62282a99d035780e43eb8a843b0f11d8a1e967fe123 + md5: 7738446d4be7ac8b56e6d6e3bdb7e52b + depends: + - python + - libcxx >=19 + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=hash-mapping + size: 7456206 + timestamp: 1779169211856 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.6-py312ha003a3f_0.conda + sha256: b09e03dace335a6f303352fc4e167243e04d7026d55008546fa643d224fb0bad + md5: 9f554fdfa902971390975b489e678c03 + depends: + - python + - __osx >=11.0 + - libcxx >=19 + - python_abi 3.12.* *_cp312 + - liblapack >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=compressed-mapping + size: 6843172 + timestamp: 1779169213435 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.6-py313hce9b930_0.conda + sha256: 3f79e4755d6feafe2d9ce9e42cf28a2054ce404c5b9a89fde16eb48fd25e89c5 + md5: 13243cfdfeece38ffd42780e315129cf + depends: + - python + - __osx >=11.0 + - libcxx >=19 + - liblapack >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + - python_abi 3.13.* *_cp313 + - libcblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=hash-mapping + size: 6928597 + timestamp: 1779169217159 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + sha256: c91bf510c130a1ea1b6ff023e28bac0ccaef869446acd805e2016f69ebdc49ea + md5: 25dcccd4f80f1638428613e0d7c9b4e1 + depends: + - __osx >=11.0 + - ca-certificates + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3106008 + timestamp: 1775587972483 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-3.0.3-py311h8948835_0.conda + sha256: a220a05380062dce89512f60a85aaf754beeea7774e66c57116e3d7323738391 + md5: b3ff79b6b7aca8a977cc29f2962c2f47 + depends: + - python + - numpy >=1.26.0 + - python-dateutil >=2.8.2 + - python 3.11.* *_cpython + - libcxx >=19 + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + - numpy >=1.23,<3 + constrains: + - adbc-driver-postgresql >=1.2.0 + - adbc-driver-sqlite >=1.2.0 + - beautifulsoup4 >=4.12.3 + - blosc >=1.21.3 + - bottleneck >=1.4.2 + - fastparquet >=2024.11.0 + - fsspec >=2024.10.0 + - gcsfs >=2024.10.0 + - html5lib >=1.1 + - hypothesis >=6.116.0 + - jinja2 >=3.1.5 + - lxml >=5.3.0 + - matplotlib >=3.9.3 + - numba >=0.60.0 + - numexpr >=2.10.2 + - odfpy >=1.4.1 + - openpyxl >=3.1.5 + - psycopg2 >=2.9.10 + - pyarrow >=13.0.0 + - pyiceberg >=0.8.1 + - pymysql >=1.1.1 + - pyqt5 >=5.15.9 + - pyreadstat >=1.2.8 + - pytables >=3.10.1 + - pytest >=8.3.4 + - pytest-xdist >=3.6.1 + - python-calamine >=0.3.0 + - pytz >=2024.2 + - pyxlsb >=1.0.10 + - qtpy >=2.4.2 + - scipy >=1.14.1 + - s3fs >=2024.10.0 + - sqlalchemy >=2.0.36 + - tabulate >=0.9.0 + - xarray >=2024.10.0 + - xlrd >=2.0.1 + - xlsxwriter >=3.2.0 + - zstandard >=0.23.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pandas?source=hash-mapping + size: 14329411 + timestamp: 1778602822615 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-3.0.3-py312h6510ced_0.conda + sha256: 7202013525593f57a452dac7e5fee9f26478822be3ba5c893643517b8627406d + md5: 4581a32b837950217327fcab93214313 + depends: + - python + - numpy >=1.26.0 + - python-dateutil >=2.8.2 + - __osx >=11.0 + - python 3.12.* *_cpython + - libcxx >=19 + - python_abi 3.12.* *_cp312 + - numpy >=1.23,<3 + constrains: + - adbc-driver-postgresql >=1.2.0 + - adbc-driver-sqlite >=1.2.0 + - beautifulsoup4 >=4.12.3 + - blosc >=1.21.3 + - bottleneck >=1.4.2 + - fastparquet >=2024.11.0 + - fsspec >=2024.10.0 + - gcsfs >=2024.10.0 + - html5lib >=1.1 + - hypothesis >=6.116.0 + - jinja2 >=3.1.5 + - lxml >=5.3.0 + - matplotlib >=3.9.3 + - numba >=0.60.0 + - numexpr >=2.10.2 + - odfpy >=1.4.1 + - openpyxl >=3.1.5 + - psycopg2 >=2.9.10 + - pyarrow >=13.0.0 + - pyiceberg >=0.8.1 + - pymysql >=1.1.1 + - pyqt5 >=5.15.9 + - pyreadstat >=1.2.8 + - pytables >=3.10.1 + - pytest >=8.3.4 + - pytest-xdist >=3.6.1 + - python-calamine >=0.3.0 + - pytz >=2024.2 + - pyxlsb >=1.0.10 + - qtpy >=2.4.2 + - scipy >=1.14.1 + - s3fs >=2024.10.0 + - sqlalchemy >=2.0.36 + - tabulate >=0.9.0 + - xarray >=2024.10.0 + - xlrd >=2.0.1 + - xlsxwriter >=3.2.0 + - zstandard >=0.23.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pandas?source=hash-mapping + size: 13926263 + timestamp: 1778602825408 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-3.0.3-py313h1188861_0.conda + sha256: 5fd41083894c2b7b9ba3f02a0d4ddbab17c6c1f645fdc1f3f1325522eb2a1a28 + md5: 12dd2c60321105aa1f869373ae27de42 + depends: + - python + - numpy >=1.26.0 + - python-dateutil >=2.8.2 + - libcxx >=19 + - __osx >=11.0 + - python 3.13.* *_cp313 + - numpy >=1.23,<3 + - python_abi 3.13.* *_cp313 + constrains: + - adbc-driver-postgresql >=1.2.0 + - adbc-driver-sqlite >=1.2.0 + - beautifulsoup4 >=4.12.3 + - blosc >=1.21.3 + - bottleneck >=1.4.2 + - fastparquet >=2024.11.0 + - fsspec >=2024.10.0 + - gcsfs >=2024.10.0 + - html5lib >=1.1 + - hypothesis >=6.116.0 + - jinja2 >=3.1.5 + - lxml >=5.3.0 + - matplotlib >=3.9.3 + - numba >=0.60.0 + - numexpr >=2.10.2 + - odfpy >=1.4.1 + - openpyxl >=3.1.5 + - psycopg2 >=2.9.10 + - pyarrow >=13.0.0 + - pyiceberg >=0.8.1 + - pymysql >=1.1.1 + - pyqt5 >=5.15.9 + - pyreadstat >=1.2.8 + - pytables >=3.10.1 + - pytest >=8.3.4 + - pytest-xdist >=3.6.1 + - python-calamine >=0.3.0 + - pytz >=2024.2 + - pyxlsb >=1.0.10 + - qtpy >=2.4.2 + - scipy >=1.14.1 + - s3fs >=2024.10.0 + - sqlalchemy >=2.0.36 + - tabulate >=0.9.0 + - xarray >=2024.10.0 + - xlrd >=2.0.1 + - xlsxwriter >=3.2.0 + - zstandard >=0.23.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pandas?source=hash-mapping + size: 14056402 + timestamp: 1778602842319 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.15-h8561d8f_0_cpython.conda + sha256: 9a846065863925b2562126a5c6fecd7a972e84aaa4de9e686ad3715ca506acfa + md5: 49c7d96c58b969585cf09fb01d74e08e + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 14753109 + timestamp: 1772730203101 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.13-h8561d8f_0_cpython.conda + sha256: e658e647a4a15981573d6018928dec2c448b10c77c557c29872043ff23c0eb6a + md5: 8e7608172fa4d1b90de9a745c2fd2b81 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + purls: [] + size: 12127424 + timestamp: 1772730755512 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda + build_number: 100 + sha256: d0fffc5fde21d1ae350da545dfb9e115a8c53bed8a9c5761f9efd4a5581853c1 + md5: 9991a930e81d3873eba7a299ba783ec4 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.5,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.6,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + size: 12966447 + timestamp: 1775615694085 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + sha256: a77010528efb4b548ac2a4484eaf7e1c3907f2aec86123ed9c5212ae44502477 + md5: f8381319127120ce51e081dce4865cf4 + depends: + - __osx >=11.0 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 313930 + timestamp: 1765813902568 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.16-h80928e0_0.conda + noarch: python + sha256: 6ce34bec1817caf6aa22c7ce28c4aeadd5fe02ea00d74425c498f4ec35276a1c + md5: 316cae3a5f921c633f8a6e1eb48604d8 + depends: + - python + - __osx >=11.0 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruff?source=compressed-mapping + size: 8426033 + timestamp: 1780612122132 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py311h9a58382_1.conda + sha256: b45f87414da242a9e40eb934e89513a856e6236d681611c2c9a21d074b03ef5a + md5: 15f96f91b13cbefddbf998368d06adef + depends: + - __osx >=11.0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libcxx >=19 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=1.25.2 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scipy?source=compressed-mapping + size: 13954661 + timestamp: 1779874558902 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py312h4519d97_1.conda + sha256: c0ed2cbfa3485bac8570e52b577475778c603ee92d078a8b164d1ddec992e577 + md5: 173d5eeba324363d9171946e86a81687 + depends: + - __osx >=11.0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libcxx >=19 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=1.25.2 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scipy?source=compressed-mapping + size: 13936510 + timestamp: 1779874824714 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.17.1-py313h52f5312_1.conda + sha256: b828f5d0f77e890bc5ec8b2a391bf27c01d468a8b83667bf7786e9a6a1ff12e8 + md5: f441d9cefca60be8589c309e3af2e6d8 + depends: + - __osx >=11.0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libcxx >=19 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=1.25.2 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scipy?source=compressed-mapping + size: 14049103 + timestamp: 1779874780525 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + sha256: 799cab4b6cde62f91f750149995d149bc9db525ec12595e8a1d91b9317f038b3 + md5: a9d86bc62f39b94c4661716624eb21b0 + depends: + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3127137 + timestamp: 1769460817696 +- pypi: . + name: pybeh-pd + requires_dist: + - numpy>=1.24 + - pandas>=2.0 + - scipy>=1.10 + - pytest>=7 ; extra == 'test' + - pytest-cov ; extra == 'test' + requires_python: '>=3.11,<3.14' diff --git a/pixi.toml b/pixi.toml new file mode 100644 index 0000000..db0ed57 --- /dev/null +++ b/pixi.toml @@ -0,0 +1,41 @@ +[workspace] +name = "pybeh_pd" +description = "Pandas-friendly wrappers around pybeh for free-recall behavioral analysis" +channels = ["conda-forge"] +platforms = ["linux-64", "osx-64", "osx-arm64"] + +[dependencies] +python = ">=3.11,<3.14" +numpy = ">=1.24" +pandas = ">=2.0" +scipy = ">=1.10" + +# Editable install of this package (src/ layout) so `import pybeh_pd` resolves the +# real install path in every environment, not just via sys.path. +[pypi-dependencies] +pybeh_pd = { path = ".", editable = true } + +[feature.test.dependencies] +pytest = ">=7" +pytest-cov = "*" +ruff = "*" + +[feature.py311.dependencies] +python = "3.11.*" +[feature.py312.dependencies] +python = "3.12.*" +[feature.py313.dependencies] +python = "3.13.*" + +[environments] +default = ["test"] +py311 = ["py311", "test"] +py312 = ["py312", "test"] +py313 = ["py313", "test"] + +[tasks] +# Pure-Python package: no compiled extensions to build. This confirms the +# editable install resolves and the package imports. +build = "python -c \"import pybeh_pd; print('pybeh_pd OK at', pybeh_pd.__file__)\"" +test = "pytest -q" +test-cov = "pytest -q --cov=pybeh_pd --cov=pybeh_copy --cov-report=term-missing" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8986840 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,61 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "pybeh_pd" +dynamic = ["version"] +description = "Pandas-friendly wrappers around pybeh for free-recall behavioral analysis and plotting" +readme = "README.md" +requires-python = ">=3.11,<3.14" +license = { file = "LICENSE" } +authors = [{ name = "Computational Memory Lab (pennmem)" }] +keywords = ["free recall", "memory", "psychology", "pybeh", "lag-CRP", "pandas"] +classifiers = [ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering", + "Operating System :: OS Independent", +] +dependencies = [ + "numpy>=1.24", + "pandas>=2.0", + "scipy>=1.10", +] + +[project.optional-dependencies] +test = ["pytest>=7", "pytest-cov"] + +[project.urls] +Homepage = "https://github.com/pennmem/pybeh_pd" +Repository = "https://github.com/pennmem/pybeh_pd" + +[tool.hatch.version] +path = "src/pybeh_pd/__init__.py" + +[tool.hatch.build.targets.wheel] +packages = ["src/pybeh_pd"] + +[tool.ruff] +src = ["src"] + +[tool.ruff.lint] +# Scope to pyflakes (F) for the renovation: dead imports + unused locals, without +# imposing broader style churn on the legacy source. +select = ["F"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +markers = [ + "hand_coded: concrete input -> hand-verified expected output", + "property: mathematical property (bounds, symmetry, identity, consistency)", + "edge_case: inputs that could silently invalidate results", + "consistency: cross-function / large-sample agreement", + "regression: golden-master behavior lock", + "integration: end-to-end pipeline on synthetic or real data", + "pins_bug: pins CURRENT (known-buggy) behavior; see bug_report.md", +] diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000..90be875 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,7 @@ +{ + "include": ["src/pybeh_pd"], + "pythonVersion": "3.11", + "typeCheckingMode": "basic", + "reportMissingImports": true, + "reportMissingModuleSource": false +} diff --git a/src/pybeh_pd/__init__.py b/src/pybeh_pd/__init__.py new file mode 100644 index 0000000..183f967 --- /dev/null +++ b/src/pybeh_pd/__init__.py @@ -0,0 +1,17 @@ +"""pybeh_pd — pandas-friendly wrappers around pybeh for free-recall analysis. + +The public API is unchanged from the original single-file module: ``import +pybeh_pd as pb`` then call ``pb.pd_crp`` / ``pb.pd_temp_fact`` / ``pb.cousineau`` +etc. The implementation now lives in two internal modules: + +- ``_core`` — the analysis functions and pandas wrappers (formerly pybeh_pd.py). +- ``_pybeh`` — a vendored subset of pybeh (formerly pybeh_copy.py) so no external + pybeh install is required. + +Everything public from ``_core`` (which itself re-exports the vendored ``crp``, +``temp_fact``, ``make_clean_recalls_mask2d``, ``dist_percentile_rank``, +``temp_percentile_rank``) is re-exported here, preserving the original surface. +""" +__version__ = "0.1.0" + +from ._core import * # noqa: F401,F403 diff --git a/pybeh_pd.py b/src/pybeh_pd/_core.py similarity index 91% rename from pybeh_pd.py rename to src/pybeh_pd/_core.py index 28428aa..fa4905c 100644 --- a/pybeh_pd.py +++ b/src/pybeh_pd/_core.py @@ -1,9 +1,11 @@ +from __future__ import annotations +from typing import Optional, Any import pandas as pd import numpy as np import scipy as sp -from scipy import stats -from numpy import matlib -from scipy.spatial import distance, distance_matrix +from scipy import stats # noqa: F401 # loads scipy.stats submodule (used as sp.stats) + re-exported +from numpy import matlib # noqa: F401 # loads numpy.matlib submodule (used as np.matlib) + re-exported +from scipy.spatial import distance_matrix # from pybeh.make_recalls_matrix import make_recalls_matrix # from pybeh.crp import crp # from pybeh.spc import spc @@ -11,7 +13,7 @@ # from pybeh.temp_fact import temp_fact # from pybeh.dist_fact import dist_fact, dist_percentile_rank # from pybeh.mask_maker import make_clean_recalls_mask2d -from pybeh_copy import crp, temp_fact, make_clean_recalls_mask2d, dist_percentile_rank, temp_percentile_rank +from ._pybeh import crp, temp_fact, make_clean_recalls_mask2d, dist_percentile_rank, temp_percentile_rank def get_itemno_matrices(evs, itemno_column='itemno', list_index=['subject', 'session', 'list']): """Expects as input a dataframe (df) for one subject""" @@ -36,7 +38,7 @@ def get_itemno_matrices(evs, itemno_column='itemno', list_index=['subject', 'ses # d = {'prob': prob, 'serialpos': sps} # return pd.DataFrame(d, index=sps) -def make_recalls_matrix(pres_itemnos=None, rec_itemnos=None, max_n_reps=1): +def make_recalls_matrix(pres_itemnos: Optional[np.ndarray] = None, rec_itemnos: Optional[np.ndarray] = None, max_n_reps: int = 1) -> np.ndarray: ''' MAKE_RECALLS_MATRIX Make a standard recalls matrix. Given presented and recalled item numbers, finds the position of @@ -63,20 +65,19 @@ def make_recalls_matrix(pres_itemnos=None, rec_itemnos=None, max_n_reps=1): :param rec_itemnos: :return: ''' - n_trials = np.shape(pres_itemnos)[0] - n_items = np.shape(pres_itemnos)[1] - n_recalls = np.shape(rec_itemnos)[1] - + n_trials = np.shape(pres_itemnos)[0] # type: ignore[reportArgumentType] # None default is a required-arg sentinel + n_recalls = np.shape(rec_itemnos)[1] # type: ignore[reportArgumentType] # None default is a required-arg sentinel + recalls = np.zeros([n_trials, n_recalls, max_n_reps], dtype=int) for trial in np.arange(n_trials): for recall in np.arange(n_recalls): - if (rec_itemnos[trial, recall]) == 0 | (np.isnan(rec_itemnos[trial, recall])): + if (rec_itemnos[trial, recall]) == 0 | (np.isnan(rec_itemnos[trial, recall])): # type: ignore[reportOptionalSubscript] # None default is a required-arg sentinel continue - elif rec_itemnos[trial, recall] > 0: + elif rec_itemnos[trial, recall] > 0: # type: ignore[reportOptionalSubscript] # None default is a required-arg sentinel - serialpos = np.where(rec_itemnos[trial, recall] == pres_itemnos[trial,:])[0]+1 + serialpos = np.where(rec_itemnos[trial, recall] == pres_itemnos[trial,:])[0]+1 # type: ignore[reportOptionalSubscript] # None default is a required-arg sentinel if len(serialpos) > max_n_reps: raise Exception('An item was presented more than max_n_reps.') @@ -90,20 +91,20 @@ def make_recalls_matrix(pres_itemnos=None, rec_itemnos=None, max_n_reps=1): recalls = np.squeeze(recalls, axis=2) return recalls -def make_poss_recalls_matrix(pres_itemnos=None, max_n_reps=1): - n_trials = np.shape(pres_itemnos)[0] - n_items = np.shape(pres_itemnos)[1] - +def make_poss_recalls_matrix(pres_itemnos: Optional[np.ndarray] = None, max_n_reps: int = 1) -> np.ndarray: + n_trials = np.shape(pres_itemnos)[0] # type: ignore[reportArgumentType] # None default is a required-arg sentinel + n_items = np.shape(pres_itemnos)[1] # type: ignore[reportArgumentType] # None default is a required-arg sentinel + recalls = np.zeros([n_trials, n_items, max_n_reps], dtype=int) for trial in np.arange(n_trials): for item in np.arange(n_items): - if (pres_itemnos[trial, item]) == 0 | (np.isnan(pres_itemnos[trial, item])): + if (pres_itemnos[trial, item]) == 0 | (np.isnan(pres_itemnos[trial, item])): # type: ignore[reportOptionalSubscript] # None default is a required-arg sentinel continue - elif pres_itemnos[trial, item] > 0: + elif pres_itemnos[trial, item] > 0: # type: ignore[reportOptionalSubscript] # None default is a required-arg sentinel - serialpos = np.where(pres_itemnos[trial,item] == pres_itemnos[trial,:])[0]+1 + serialpos = np.where(pres_itemnos[trial,item] == pres_itemnos[trial,:])[0]+1 # type: ignore[reportOptionalSubscript] # None default is a required-arg sentinel if len(serialpos) > max_n_reps: raise Exception('An item was presented more than max_n_reps.') @@ -125,7 +126,7 @@ def get_min_trans(serialpos, rec): return pt[np.argmin(np.abs(pt))] def get_all_matrices(df, itemno_column='itemno', list_index=['subject', 'session', 'list'], pres_type="WORD", rec_type="REC_WORD", type_column='type', max_n_reps=1): - types = [pres_type, rec_type] + types = [pres_type, rec_type] # noqa: F841 # referenced in df.query via @types below #only include lists if both presentations and recalls are present (i.e. ntypes == 2) df = df.query(type_column + ' in @types') ntypes_df = df[list_index + [type_column]].groupby(list_index).agg({type_column: 'nunique'}).reset_index().rename(columns={type_column: 'ntypes'}) @@ -178,7 +179,7 @@ def pd_min_crp(df, lag_num=5, itemno_column='itemno', list_index=['subject', 'se 'lag': np.arange(-lag_num, (lag_num+1))} return pd.DataFrame(crp_dict, index=np.arange(-lag_num, (lag_num+1))) -def min_crp(recalls=None, poss_recalls=None, subjects=None, listLength=None, lag_num=None, skip_first_n=0): +def min_crp(recalls: Optional[np.ndarray] = None, poss_recalls: Optional[np.ndarray] = None, subjects: Any = None, listLength: Optional[int] = None, lag_num: Optional[int] = None, skip_first_n: int = 0) -> np.ndarray: ''' CRP Conditional response probability as a function of lag (lag-CRP). @@ -287,9 +288,9 @@ def min_crp(recalls=None, poss_recalls=None, subjects=None, listLength=None, lag next_rec = trial_recs[k + 1] next_rec = next_rec[next_rec != 0] if with_repeats: - pt = np.unique(np.array([get_min_trans(serialpos[serialpos != 0], rec) for serialpos in poss_recalls[j] if serialpos[0] not in seen], dtype=int))#don't increment more than once + pt = np.unique(np.array([get_min_trans(serialpos[serialpos != 0], rec) for serialpos in poss_recalls[j] if serialpos[0] not in seen], dtype=int))#don't increment more than once # type: ignore[reportOptionalSubscript] # None default is a required-arg sentinel else: - pt = np.unique(np.array([get_min_trans(serialpos[serialpos != 0], rec) for serialpos in poss_recalls[j] if serialpos not in seen], dtype=int))#don't increment more than once + pt = np.unique(np.array([get_min_trans(serialpos[serialpos != 0], rec) for serialpos in poss_recalls[j] if serialpos not in seen], dtype=int))#don't increment more than once # type: ignore[reportOptionalSubscript] # None default is a required-arg sentinel # for min lag-crp, we get the minimum possible distances poss[pt + listLength - 1] += 1 trans = get_min_trans(next_rec, rec) @@ -353,7 +354,7 @@ def pd_sem_crp(df, itemno_column='itemno', return pd.DataFrame(crp_dict).query('prob == prob') #remove bins with no data -def min_temp_fact(recalls=None, poss_recalls=None, subjects=None, listLength=None, skip_first_n=0): +def min_temp_fact(recalls: Optional[np.ndarray] = None, poss_recalls: Optional[np.ndarray] = None, subjects: Any = None, listLength: Optional[int] = None, skip_first_n: int = 0) -> np.ndarray: """ Returns the lag-based temporal clustering factor for each subject (Polyn, Norman, & Kahana, 2009). :param recalls: A trials x recalls matrix containing the serial positions (between 1 and listLength) of words @@ -385,20 +386,14 @@ def min_temp_fact(recalls=None, poss_recalls=None, subjects=None, listLength=Non recalls = np.array(recalls) subjects = np.array(subjects) - # Initialize range for possible next recalls, based on list length - possibles_range = range(1, listLength + 1) - # Initialize arrays to store each participant's results usub = np.unique(subjects) total = np.zeros_like(usub, dtype=float) count = np.zeros_like(usub, dtype=float) - + mask_recalls = recalls if mask_recalls.ndim == 3: - with_repeats = True mask_recalls = mask_recalls[:, :, 0] - else: - with_repeats = False # Identify locations of all correct recalls (not PLI, ELI, or repetition) clean_recalls_mask = np.array(make_clean_recalls_mask2d(mask_recalls)) @@ -417,7 +412,7 @@ def min_temp_fact(recalls=None, poss_recalls=None, subjects=None, listLength=Non # Identify possible transitions # possibles = np.array([abs(item - serialpos) for item in possibles_range if item not in seen]) possibles = abs(np.unique(np.array( - [get_min_trans(serialpos[serialpos != 0], rec) for serialpos in poss_recalls[i] if serialpos[0] not in seen], + [get_min_trans(serialpos[serialpos != 0], rec) for serialpos in poss_recalls[i] if serialpos[0] not in seen], # type: ignore[reportOptionalSubscript] # None default is a required-arg sentinel dtype=int)))#don't increment more than once # Identify actual transition # next_serialpos = trial_data[j + 1] @@ -501,7 +496,7 @@ def pd_dist_fact(df, rec_itemnos=None, itemno_column='itemno', skip_first_n=skip_first_n) return dist_fact_arr[0] -def sem_crp(recalls=None, recalls_itemnos=None, pres_itemnos=None, subjects=None, sem_sims=None, n_bins=10, bins=None, listLength=None, ret_counts=False): +def sem_crp(recalls: Optional[np.ndarray] = None, recalls_itemnos: Optional[np.ndarray] = None, pres_itemnos: Optional[np.ndarray] = None, subjects: Any = None, sem_sims: Any = None, n_bins: int = 10, bins: Any = None, listLength: Optional[int] = None, ret_counts: bool = False) -> tuple[Any, ...]: """bins should not include an upper bin""" if recalls_itemnos is None: raise Exception('You must pass a recalls-by-item-numbers matrix.') @@ -590,7 +585,7 @@ def sem_crp(recalls=None, recalls_itemnos=None, pres_itemnos=None, subjects=None else: return bin_means, crp -def dist_fact(rec_itemnos=None, pres_itemnos=None, subjects=None, dist_mat=None, is_similarity=False, skip_first_n=0, ret_counts=False): +def dist_fact(rec_itemnos: Optional[np.ndarray] = None, pres_itemnos: Optional[np.ndarray] = None, subjects: Any = None, dist_mat: Any = None, is_similarity: bool = False, skip_first_n: int = 0, ret_counts: bool = False): """ Returns a clustering factor score for each subject, based on the provided distance metric (Polyn, Norman, & Kahana, 2009). Can also be used with a similarity matrix (e.g. LSA, word2vec) if is_similarity is set to True. @@ -637,7 +632,7 @@ def dist_fact(rec_itemnos=None, pres_itemnos=None, subjects=None, dist_mat=None, # Provide a warning if the user inputs a dist_mat that looks like a similarity matrix (scores on diagonal are # large), but has left is_similarity as False if (not is_similarity) and np.nanmean(np.diagonal(dist_mat)) > np.nanmean(dist_mat): - warnings.warn('It looks like you might be using a similarity matrix (e.g. LSA, word2vec) instead of a distance' + warnings.warn('It looks like you might be using a similarity matrix (e.g. LSA, word2vec) instead of a distance' # type: ignore[reportUndefinedVariable] # noqa: F821 # bug B2 (missing import), see bug_report.md ' matrix, but you currently have is_similarity set to False. If you are using a similarity' ' matrix, make sure to set is_similarity to True when running dist_fact().') @@ -678,7 +673,7 @@ def dist_fact(rec_itemnos=None, pres_itemnos=None, subjects=None, dist_mat=None, return final_data, total, count return final_data -def pd_sem_crp_list(df, sim_columns=None, bins=None, pres_type="WORD", +def pd_sem_crp_list(df, sim_columns=None, bins: Any = None, pres_type="WORD", rec_type="REC_WORD", type_column='type', serialpos_col='serialpos', ret_counts=False, p=2): """Expects as input a dataframe (df) for one list. Doesn't require separate word_vals, expects them to be next to item. @@ -782,7 +777,6 @@ def loftus_masson_analytic(df_long, sub_col, cond_col, value_col): df_a['M'] = df_a[value_col].mean() df_a['M_S'] = df_a.groupby([sub_col])[value_col].transform('mean') df_a['M_C'] = df_a.groupby([cond_col])[value_col].transform('mean') - M_C = df_a.groupby([cond_col]).agg({value_col: 'mean'})[value_col].values #appendix A df_a['S_W'] = ((df_a[value_col] + df_a['M'] - df_a['M_S'] - df_a['M_C']) ** 2) @@ -790,13 +784,13 @@ def loftus_masson_analytic(df_long, sub_col, cond_col, value_col): df = (n_subs - 1) * (n_conds - 1) MS_SxC = SS_W / df SE = np.sqrt(MS_SxC / n_subs) - CI_equal = SE * sp.stats.t.ppf(0.975, df) # Eq. 2, pg. 482 + CI_equal = SE * sp.stats.t.ppf(0.975, df) # Eq. 2, pg. 482 # type: ignore[reportAttributeAccessIssue] # sp.stats imported via `from scipy import stats` #Unequal variances df_MS = df_a.groupby([cond_col, 'M_C'], as_index=False).agg({'S_W': 'sum'}) df_MS['MS_W'] = df_MS['S_W'] / (n_subs - 1) df_MS['estimator'] = (n_conds / (n_conds - 1)) * (df_MS['MS_W'] - (MS_SxC / (n_conds))) - df_MS['CI_unequal'] = np.sqrt(df_MS['estimator'] / n_subs) * sp.stats.t.ppf(0.975, (n_subs-1)) + df_MS['CI_unequal'] = np.sqrt(df_MS['estimator'] / n_subs) * sp.stats.t.ppf(0.975, (n_subs-1)) # type: ignore[reportAttributeAccessIssue] # sp.stats imported via `from scipy import stats` df_MS['CI_equal'] = CI_equal return df_MS[[cond_col, 'M_C', 'CI_unequal', 'CI_equal']] @@ -842,18 +836,18 @@ def loftus_masson_equal_variance_kahana(dat): SSint = SS_T - SSrow - SScol df_int = (numRows * numCols - 1) - (numRows - 1) - (numCols - 1) MSint = SSint / df_int - criterion = sp.stats.t.ppf(0.975, df_int) + criterion = sp.stats.t.ppf(0.975, df_int) # type: ignore[reportAttributeAccessIssue] # sp.stats imported via `from scipy import stats` # implementation of Loftus-Masson (1994), equation (2) CI = np.sqrt(MSint / numRows) * criterion * np.ones(numCols) return CI def loftus_masson_unequal_variance_kahana(dat): - dat = mat + dat = mat # type: ignore[reportUndefinedVariable] # noqa: F821 # bug B1 (undefined `mat`), see bug_report.md # normalize the data grandMean = np.nanmean(dat) subjMean = np.nanmean(dat, axis=1) - subjMean = np.matlib.repmat(subjMean, dat.shape[1], 1).T + subjMean = np.matlib.repmat(subjMean, dat.shape[1], 1).T # type: ignore[reportAttributeAccessIssue] # np.matlib imported via `from numpy import matlib` dat = dat - (subjMean - grandMean) # compute sums @@ -882,5 +876,5 @@ def loftus_masson_unequal_variance_kahana(dat): MS_w = (np.nansum(dat ** 2, axis=0) - ((Tc ** 2) / Nsubj)) / (Nsubj-1) # p.484 estimator = (NcondValid / (NcondValid - 1)) * (MS_w - (MS_SxC / NcondValid)) - CI = np.sqrt(estimator / Nsubj) * sp.stats.t.ppf(0.975, dat.shape[0] - 1) + CI = np.sqrt(estimator / Nsubj) * sp.stats.t.ppf(0.975, dat.shape[0] - 1) # type: ignore[reportAttributeAccessIssue] # sp.stats imported via `from scipy import stats` return Tc, CI \ No newline at end of file diff --git a/pybeh_copy.py b/src/pybeh_pd/_pybeh.py similarity index 94% rename from pybeh_copy.py rename to src/pybeh_pd/_pybeh.py index eb00e51..3a5c27e 100644 --- a/pybeh_copy.py +++ b/src/pybeh_pd/_pybeh.py @@ -1,7 +1,12 @@ +from __future__ import annotations + +from typing import Any, Optional, Union + import numpy as np import copy -def make_clean_recalls_mask2d(data): + +def make_clean_recalls_mask2d(data: Any) -> Any: """makes a clean mask without repetition and intrusion""" result = copy.deepcopy(data) for num, item in enumerate(data): @@ -15,7 +20,13 @@ def make_clean_recalls_mask2d(data): result[num][index] = 0 return result -def crp(recalls=None, subjects=None, listLength=None, lag_num=None, skip_first_n=0): +def crp( + recalls: Optional[Any] = None, + subjects: Optional[Any] = None, + listLength: Optional[int] = None, + lag_num: Optional[int] = None, + skip_first_n: int = 0, +) -> np.ndarray: ''' CRP Conditional response probability as a function of lag (lag-CRP). @@ -120,7 +131,12 @@ def crp(recalls=None, subjects=None, listLength=None, lag_num=None, skip_first_n return result[:, listLength - lag_num - 1:listLength + lag_num] -def temp_fact(recalls=None, subjects=None, listLength=None, skip_first_n=0): +def temp_fact( + recalls: Optional[Any] = None, + subjects: Optional[Any] = None, + listLength: Optional[int] = None, + skip_first_n: int = 0, +) -> np.ndarray: """ Returns the lag-based temporal clustering factor for each subject (Polyn, Norman, & Kahana, 2009). :param recalls: A trials x recalls matrix containing the serial positions (between 1 and listLength) of words @@ -192,7 +208,10 @@ def temp_fact(recalls=None, subjects=None, listLength=None, skip_first_n=0): return final_data -def temp_percentile_rank(actual, possible): +def temp_percentile_rank( + actual: Union[int, float, np.number, Any], + possible: Any, +) -> Optional[Union[float, np.floating]]: """ Helper function to return the percentile rank of the actual transition within the list of possible transitions. :param actual: The distance of the actual transition that was made. @@ -220,7 +239,11 @@ def temp_percentile_rank(actual, possible): return ptile_rank -def dist_percentile_rank(actual, possible, is_similarity=False): +def dist_percentile_rank( + actual: Union[int, float, np.number, Any], + possible: Any, + is_similarity: bool = False, +) -> Optional[Union[float, np.floating]]: """ Helper function to return the percentile rank of the actual transition within the list of possible transitions. :param actual: The distance of the actual transition that was made. diff --git a/tests/data/_regenerate_ltpfr2.py b/tests/data/_regenerate_ltpfr2.py new file mode 100644 index 0000000..c144254 --- /dev/null +++ b/tests/data/_regenerate_ltpfr2.py @@ -0,0 +1,61 @@ +"""Regenerate the committed ltpFR2 real-data fixture + comprehensive golden. + +One-time/maintenance script. Requires cmlreaders + the rhino /protocols/ltp mount. +Writes: + tests/data/ltpfr2_5sessions.csv - 5 sessions of LTP093 (WORD + REC_WORD events) + tests/data/ltpfr2_golden.npz - frozen pybeh_pd outputs on that data + +The golden freezes every analysis that applies to plain free-recall events (no +external semantic space): the matrix builders, the lag-CRP family (incl. the +repeated-presentation variants), and the temporal clustering factor, both +pooled-over-sessions and per-session. Semantic CRP / distance-factor are NOT +included here because ltpFR2 events carry no semantic feature columns; those are +locked on the synthetic suite (tests/regression) where features exist. + +The integration test reads these committed files and needs neither cmlreaders nor +rhino. Re-run only to refresh the fixture (an intentional, reviewed change). +""" +import os +import sys + +import numpy as np +import pandas as pd +import cmlreaders as cml + +HERE = os.path.dirname(__file__) +sys.path.insert(0, os.path.join(HERE, "..", "..", "src")) # src/ (pybeh_pd pkg) +sys.path.insert(0, os.path.join(HERE, "..")) # tests/ (real_data) +from real_data import capture_outputs # noqa: E402 + +SUBJECT, N_SESSIONS, ITEMNO = "LTP093", 5, "item_num" + + +def load(): + idx = cml.get_data_index("ltp", rootdir="/").query("experiment == 'ltpFR2'") + rows = (idx.query("subject == @SUBJECT and session != 23") + .sort_values("session").head(N_SESSIONS)) + keep = ["subject", "session", "list", "type", "item_num", "serialpos"] + frames = [] + for r in rows.itertuples(): + ev = cml.CMLReader(subject=r.subject, session=int(r.session), + experiment="ltpFR2", rootdir="/").load("events") + if "list" in ev.columns: + ev = ev.drop(columns="list") + ev = ev.rename(columns={"trial": "list"}).query("type in ['WORD','REC_WORD']") + frames.append(ev[keep]) + return pd.concat(frames, ignore_index=True) + + +def main(): + df = load() + df.to_csv(os.path.join(HERE, "ltpfr2_5sessions.csv"), index=False) + golden = capture_outputs(df) + np.savez(os.path.join(HERE, "ltpfr2_golden.npz"), **golden) + print(f"wrote ltpfr2_5sessions.csv rows={len(df)}") + print(f"golden: {len(golden)} arrays") + for k in sorted(golden): + print(f" {k:28s} shape={golden[k].shape}") + + +if __name__ == "__main__": + main() diff --git a/tests/data/ltpfr2_5sessions.csv b/tests/data/ltpfr2_5sessions.csv new file mode 100644 index 0000000..dca953a --- /dev/null +++ b/tests/data/ltpfr2_5sessions.csv @@ -0,0 +1,4250 @@ +subject,session,list,type,item_num,serialpos +LTP093,0,1,WORD,75,1 +LTP093,0,1,WORD,857,2 +LTP093,0,1,WORD,584,3 +LTP093,0,1,WORD,442,4 +LTP093,0,1,WORD,142,5 +LTP093,0,1,WORD,1493,6 +LTP093,0,1,WORD,1243,7 +LTP093,0,1,WORD,1635,8 +LTP093,0,1,WORD,1006,9 +LTP093,0,1,WORD,782,10 +LTP093,0,1,WORD,1219,11 +LTP093,0,1,WORD,499,12 +LTP093,0,1,WORD,935,13 +LTP093,0,1,WORD,1229,14 +LTP093,0,1,WORD,1047,15 +LTP093,0,1,WORD,696,16 +LTP093,0,1,WORD,658,17 +LTP093,0,1,WORD,267,18 +LTP093,0,1,WORD,1295,19 +LTP093,0,1,WORD,226,20 +LTP093,0,1,WORD,1461,21 +LTP093,0,1,WORD,94,22 +LTP093,0,1,WORD,734,23 +LTP093,0,1,WORD,876,24 +LTP093,0,1,REC_WORD,75,1 +LTP093,0,1,REC_WORD,857,2 +LTP093,0,1,REC_WORD,584,3 +LTP093,0,1,REC_WORD,442,4 +LTP093,0,1,REC_WORD,142,5 +LTP093,0,1,REC_WORD,1493,6 +LTP093,0,1,REC_WORD,1243,7 +LTP093,0,1,REC_WORD,734,23 +LTP093,0,1,REC_WORD,226,20 +LTP093,0,1,REC_WORD,935,13 +LTP093,0,1,REC_WORD,1229,14 +LTP093,0,1,REC_WORD,1047,15 +LTP093,0,1,REC_WORD,696,16 +LTP093,0,1,REC_WORD,1295,19 +LTP093,0,1,REC_WORD,1006,9 +LTP093,0,1,REC_WORD,782,10 +LTP093,0,1,REC_WORD,94,22 +LTP093,0,1,REC_WORD,734,23 +LTP093,0,2,WORD,494,1 +LTP093,0,2,WORD,1374,2 +LTP093,0,2,WORD,169,3 +LTP093,0,2,WORD,1032,4 +LTP093,0,2,WORD,997,5 +LTP093,0,2,WORD,1246,6 +LTP093,0,2,WORD,592,7 +LTP093,0,2,WORD,1606,8 +LTP093,0,2,WORD,1609,9 +LTP093,0,2,WORD,1358,10 +LTP093,0,2,WORD,1113,11 +LTP093,0,2,WORD,1545,12 +LTP093,0,2,WORD,749,13 +LTP093,0,2,WORD,1119,14 +LTP093,0,2,WORD,1057,15 +LTP093,0,2,WORD,697,16 +LTP093,0,2,WORD,573,17 +LTP093,0,2,WORD,1158,18 +LTP093,0,2,WORD,1521,19 +LTP093,0,2,WORD,1526,20 +LTP093,0,2,WORD,388,21 +LTP093,0,2,WORD,599,22 +LTP093,0,2,WORD,134,23 +LTP093,0,2,WORD,177,24 +LTP093,0,2,REC_WORD,388,21 +LTP093,0,2,REC_WORD,599,22 +LTP093,0,2,REC_WORD,134,23 +LTP093,0,2,REC_WORD,177,24 +LTP093,0,2,REC_WORD,494,1 +LTP093,0,2,REC_WORD,1374,2 +LTP093,0,2,REC_WORD,169,3 +LTP093,0,2,REC_WORD,1032,4 +LTP093,0,2,REC_WORD,1609,9 +LTP093,0,2,REC_WORD,1358,10 +LTP093,0,2,REC_WORD,997,5 +LTP093,0,2,REC_WORD,1246,6 +LTP093,0,2,REC_WORD,749,13 +LTP093,0,2,REC_WORD,1119,14 +LTP093,0,2,REC_WORD,573,17 +LTP093,0,2,REC_WORD,1158,18 +LTP093,0,2,REC_WORD,1521,19 +LTP093,0,3,WORD,988,1 +LTP093,0,3,WORD,1611,2 +LTP093,0,3,WORD,989,3 +LTP093,0,3,WORD,635,4 +LTP093,0,3,WORD,159,5 +LTP093,0,3,WORD,437,6 +LTP093,0,3,WORD,1580,7 +LTP093,0,3,WORD,488,8 +LTP093,0,3,WORD,38,9 +LTP093,0,3,WORD,872,10 +LTP093,0,3,WORD,108,11 +LTP093,0,3,WORD,895,12 +LTP093,0,3,WORD,682,13 +LTP093,0,3,WORD,827,14 +LTP093,0,3,WORD,839,15 +LTP093,0,3,WORD,390,16 +LTP093,0,3,WORD,953,17 +LTP093,0,3,WORD,879,18 +LTP093,0,3,WORD,618,19 +LTP093,0,3,WORD,994,20 +LTP093,0,3,WORD,1242,21 +LTP093,0,3,WORD,1349,22 +LTP093,0,3,WORD,683,23 +LTP093,0,3,WORD,852,24 +LTP093,0,3,REC_WORD,994,20 +LTP093,0,3,REC_WORD,1349,22 +LTP093,0,3,REC_WORD,618,19 +LTP093,0,3,REC_WORD,879,18 +LTP093,0,3,REC_WORD,953,17 +LTP093,0,3,REC_WORD,635,4 +LTP093,0,3,REC_WORD,159,5 +LTP093,0,3,REC_WORD,988,1 +LTP093,0,3,REC_WORD,1611,2 +LTP093,0,3,REC_WORD,989,3 +LTP093,0,3,REC_WORD,437,6 +LTP093,0,3,REC_WORD,1580,7 +LTP093,0,3,REC_WORD,390,16 +LTP093,0,4,WORD,530,1 +LTP093,0,4,WORD,793,2 +LTP093,0,4,WORD,1411,3 +LTP093,0,4,WORD,431,4 +LTP093,0,4,WORD,1405,5 +LTP093,0,4,WORD,800,6 +LTP093,0,4,WORD,1636,7 +LTP093,0,4,WORD,1287,8 +LTP093,0,4,WORD,1199,9 +LTP093,0,4,WORD,1221,10 +LTP093,0,4,WORD,1066,11 +LTP093,0,4,WORD,1332,12 +LTP093,0,4,WORD,1021,13 +LTP093,0,4,WORD,848,14 +LTP093,0,4,WORD,719,15 +LTP093,0,4,WORD,316,16 +LTP093,0,4,WORD,1527,17 +LTP093,0,4,WORD,778,18 +LTP093,0,4,WORD,1108,19 +LTP093,0,4,WORD,833,20 +LTP093,0,4,WORD,386,21 +LTP093,0,4,WORD,446,22 +LTP093,0,4,WORD,763,23 +LTP093,0,4,WORD,1155,24 +LTP093,0,4,REC_WORD,763,23 +LTP093,0,5,WORD,707,1 +LTP093,0,5,WORD,1194,2 +LTP093,0,5,WORD,172,3 +LTP093,0,5,WORD,339,4 +LTP093,0,5,WORD,29,5 +LTP093,0,5,WORD,1070,6 +LTP093,0,5,WORD,13,7 +LTP093,0,5,WORD,338,8 +LTP093,0,5,WORD,439,9 +LTP093,0,5,WORD,1011,10 +LTP093,0,5,WORD,888,11 +LTP093,0,5,WORD,1414,12 +LTP093,0,5,WORD,248,13 +LTP093,0,5,WORD,1326,14 +LTP093,0,5,WORD,866,15 +LTP093,0,5,WORD,689,16 +LTP093,0,5,WORD,1395,17 +LTP093,0,5,WORD,959,18 +LTP093,0,5,WORD,1256,19 +LTP093,0,5,WORD,211,20 +LTP093,0,5,WORD,1587,21 +LTP093,0,5,WORD,1124,22 +LTP093,0,5,WORD,1282,23 +LTP093,0,5,WORD,295,24 +LTP093,0,5,REC_WORD,1124,22 +LTP093,0,5,REC_WORD,707,1 +LTP093,0,5,REC_WORD,1194,2 +LTP093,0,5,REC_WORD,172,3 +LTP093,0,5,REC_WORD,339,4 +LTP093,0,5,REC_WORD,1395,17 +LTP093,0,5,REC_WORD,959,18 +LTP093,0,5,REC_WORD,1256,19 +LTP093,0,6,WORD,348,1 +LTP093,0,6,WORD,1544,2 +LTP093,0,6,WORD,136,3 +LTP093,0,6,WORD,327,4 +LTP093,0,6,WORD,798,5 +LTP093,0,6,WORD,575,6 +LTP093,0,6,WORD,1388,7 +LTP093,0,6,WORD,812,8 +LTP093,0,6,WORD,12,9 +LTP093,0,6,WORD,1472,10 +LTP093,0,6,WORD,495,11 +LTP093,0,6,WORD,755,12 +LTP093,0,6,WORD,1087,13 +LTP093,0,6,WORD,341,14 +LTP093,0,6,WORD,1183,15 +LTP093,0,6,WORD,383,16 +LTP093,0,6,WORD,1633,17 +LTP093,0,6,WORD,996,18 +LTP093,0,6,WORD,340,19 +LTP093,0,6,WORD,1392,20 +LTP093,0,6,WORD,667,21 +LTP093,0,6,WORD,88,22 +LTP093,0,6,WORD,1245,23 +LTP093,0,6,WORD,1329,24 +LTP093,0,6,REC_WORD,755,12 +LTP093,0,6,REC_WORD,495,11 +LTP093,0,6,REC_WORD,1183,15 +LTP093,0,6,REC_WORD,383,16 +LTP093,0,6,REC_WORD,1633,17 +LTP093,0,6,REC_WORD,340,19 +LTP093,0,6,REC_WORD,12,9 +LTP093,0,6,REC_WORD,1472,10 +LTP093,0,6,REC_WORD,327,4 +LTP093,0,6,REC_WORD,136,3 +LTP093,0,6,REC_WORD,996,18 +LTP093,0,7,WORD,1562,1 +LTP093,0,7,WORD,184,2 +LTP093,0,7,WORD,492,3 +LTP093,0,7,WORD,466,4 +LTP093,0,7,WORD,1080,5 +LTP093,0,7,WORD,1292,6 +LTP093,0,7,WORD,1505,7 +LTP093,0,7,WORD,1486,8 +LTP093,0,7,WORD,1625,9 +LTP093,0,7,WORD,1089,10 +LTP093,0,7,WORD,1456,11 +LTP093,0,7,WORD,732,12 +LTP093,0,7,WORD,901,13 +LTP093,0,7,WORD,861,14 +LTP093,0,7,WORD,1622,15 +LTP093,0,7,WORD,796,16 +LTP093,0,7,WORD,372,17 +LTP093,0,7,WORD,661,18 +LTP093,0,7,WORD,746,19 +LTP093,0,7,WORD,1496,20 +LTP093,0,7,WORD,733,21 +LTP093,0,7,WORD,1002,22 +LTP093,0,7,WORD,358,23 +LTP093,0,7,WORD,627,24 +LTP093,0,7,REC_WORD,1562,1 +LTP093,0,7,REC_WORD,184,2 +LTP093,0,7,REC_WORD,1002,22 +LTP093,0,7,REC_WORD,492,3 +LTP093,0,7,REC_WORD,466,4 +LTP093,0,7,REC_WORD,1486,8 +LTP093,0,7,REC_WORD,1625,9 +LTP093,0,7,REC_WORD,1089,10 +LTP093,0,7,REC_WORD,1456,11 +LTP093,0,7,REC_WORD,1080,5 +LTP093,0,7,REC_WORD,1292,6 +LTP093,0,7,REC_WORD,1544,2 +LTP093,0,8,WORD,131,1 +LTP093,0,8,WORD,484,2 +LTP093,0,8,WORD,429,3 +LTP093,0,8,WORD,61,4 +LTP093,0,8,WORD,1515,5 +LTP093,0,8,WORD,847,6 +LTP093,0,8,WORD,598,7 +LTP093,0,8,WORD,1385,8 +LTP093,0,8,WORD,415,9 +LTP093,0,8,WORD,1125,10 +LTP093,0,8,WORD,561,11 +LTP093,0,8,WORD,645,12 +LTP093,0,8,WORD,278,13 +LTP093,0,8,WORD,1403,14 +LTP093,0,8,WORD,1382,15 +LTP093,0,8,WORD,578,16 +LTP093,0,8,WORD,821,17 +LTP093,0,8,WORD,435,18 +LTP093,0,8,WORD,692,19 +LTP093,0,8,WORD,1536,20 +LTP093,0,8,WORD,395,21 +LTP093,0,8,WORD,714,22 +LTP093,0,8,WORD,1448,23 +LTP093,0,8,WORD,654,24 +LTP093,0,8,REC_WORD,692,19 +LTP093,0,8,REC_WORD,1536,20 +LTP093,0,8,REC_WORD,415,9 +LTP093,0,8,REC_WORD,1125,10 +LTP093,0,8,REC_WORD,561,11 +LTP093,0,8,REC_WORD,645,12 +LTP093,0,8,REC_WORD,429,3 +LTP093,0,9,WORD,179,1 +LTP093,0,9,WORD,999,2 +LTP093,0,9,WORD,417,3 +LTP093,0,9,WORD,809,4 +LTP093,0,9,WORD,579,5 +LTP093,0,9,WORD,541,6 +LTP093,0,9,WORD,1488,7 +LTP093,0,9,WORD,397,8 +LTP093,0,9,WORD,604,9 +LTP093,0,9,WORD,724,10 +LTP093,0,9,WORD,593,11 +LTP093,0,9,WORD,1280,12 +LTP093,0,9,WORD,343,13 +LTP093,0,9,WORD,737,14 +LTP093,0,9,WORD,299,15 +LTP093,0,9,WORD,1631,16 +LTP093,0,9,WORD,1294,17 +LTP093,0,9,WORD,829,18 +LTP093,0,9,WORD,258,19 +LTP093,0,9,WORD,1238,20 +LTP093,0,9,WORD,559,21 +LTP093,0,9,WORD,1533,22 +LTP093,0,9,WORD,1017,23 +LTP093,0,9,WORD,168,24 +LTP093,0,9,REC_WORD,179,1 +LTP093,0,9,REC_WORD,999,2 +LTP093,0,9,REC_WORD,417,3 +LTP093,0,9,REC_WORD,397,8 +LTP093,0,9,REC_WORD,604,9 +LTP093,0,9,REC_WORD,724,10 +LTP093,0,9,REC_WORD,593,11 +LTP093,0,9,REC_WORD,1488,7 +LTP093,0,9,REC_WORD,541,6 +LTP093,0,9,REC_WORD,1294,17 +LTP093,0,9,REC_WORD,829,18 +LTP093,0,9,REC_WORD,343,13 +LTP093,0,9,REC_WORD,1280,12 +LTP093,0,9,REC_WORD,1017,23 +LTP093,0,9,REC_WORD,559,21 +LTP093,0,10,WORD,908,1 +LTP093,0,10,WORD,183,2 +LTP093,0,10,WORD,546,3 +LTP093,0,10,WORD,1557,4 +LTP093,0,10,WORD,600,5 +LTP093,0,10,WORD,1581,6 +LTP093,0,10,WORD,602,7 +LTP093,0,10,WORD,520,8 +LTP093,0,10,WORD,24,9 +LTP093,0,10,WORD,1628,10 +LTP093,0,10,WORD,1451,11 +LTP093,0,10,WORD,328,12 +LTP093,0,10,WORD,1530,13 +LTP093,0,10,WORD,1200,14 +LTP093,0,10,WORD,922,15 +LTP093,0,10,WORD,1546,16 +LTP093,0,10,WORD,591,17 +LTP093,0,10,WORD,576,18 +LTP093,0,10,WORD,1227,19 +LTP093,0,10,WORD,1071,20 +LTP093,0,10,WORD,834,21 +LTP093,0,10,WORD,1351,22 +LTP093,0,10,WORD,496,23 +LTP093,0,10,WORD,401,24 +LTP093,0,10,REC_WORD,401,24 +LTP093,0,10,REC_WORD,1530,13 +LTP093,0,10,REC_WORD,1200,14 +LTP093,0,10,REC_WORD,908,1 +LTP093,0,10,REC_WORD,183,2 +LTP093,0,10,REC_WORD,546,3 +LTP093,0,10,REC_WORD,520,8 +LTP093,0,10,REC_WORD,602,7 +LTP093,0,10,REC_WORD,24,9 +LTP093,0,11,WORD,1331,1 +LTP093,0,11,WORD,1618,2 +LTP093,0,11,WORD,1022,3 +LTP093,0,11,WORD,1592,4 +LTP093,0,11,WORD,1605,5 +LTP093,0,11,WORD,1512,6 +LTP093,0,11,WORD,1415,7 +LTP093,0,11,WORD,1250,8 +LTP093,0,11,WORD,39,9 +LTP093,0,11,WORD,1535,10 +LTP093,0,11,WORD,1343,11 +LTP093,0,11,WORD,315,12 +LTP093,0,11,WORD,1519,13 +LTP093,0,11,WORD,1284,14 +LTP093,0,11,WORD,1440,15 +LTP093,0,11,WORD,932,16 +LTP093,0,11,WORD,164,17 +LTP093,0,11,WORD,373,18 +LTP093,0,11,WORD,100,19 +LTP093,0,11,WORD,255,20 +LTP093,0,11,WORD,1608,21 +LTP093,0,11,WORD,1149,22 +LTP093,0,11,WORD,162,23 +LTP093,0,11,WORD,543,24 +LTP093,0,11,REC_WORD,162,23 +LTP093,0,11,REC_WORD,1415,7 +LTP093,0,11,REC_WORD,1618,2 +LTP093,0,11,REC_WORD,1149,22 +LTP093,0,11,REC_WORD,1608,21 +LTP093,0,11,REC_WORD,255,20 +LTP093,0,11,REC_WORD,1519,13 +LTP093,0,11,REC_WORD,1022,3 +LTP093,0,11,REC_WORD,1592,4 +LTP093,0,12,WORD,1508,1 +LTP093,0,12,WORD,790,2 +LTP093,0,12,WORD,410,3 +LTP093,0,12,WORD,1104,4 +LTP093,0,12,WORD,360,5 +LTP093,0,12,WORD,235,6 +LTP093,0,12,WORD,726,7 +LTP093,0,12,WORD,325,8 +LTP093,0,12,WORD,723,9 +LTP093,0,12,WORD,871,10 +LTP093,0,12,WORD,307,11 +LTP093,0,12,WORD,1016,12 +LTP093,0,12,WORD,1123,13 +LTP093,0,12,WORD,1060,14 +LTP093,0,12,WORD,1400,15 +LTP093,0,12,WORD,5,16 +LTP093,0,12,WORD,413,17 +LTP093,0,12,WORD,1111,18 +LTP093,0,12,WORD,1315,19 +LTP093,0,12,WORD,1159,20 +LTP093,0,12,WORD,145,21 +LTP093,0,12,WORD,86,22 +LTP093,0,12,WORD,760,23 +LTP093,0,12,WORD,565,24 +LTP093,0,12,REC_WORD,86,22 +LTP093,0,12,REC_WORD,145,21 +LTP093,0,12,REC_WORD,726,7 +LTP093,0,12,REC_WORD,1123,13 +LTP093,0,12,REC_WORD,360,5 +LTP093,0,12,REC_WORD,1104,4 +LTP093,0,12,REC_WORD,235,6 +LTP093,0,12,REC_WORD,790,2 +LTP093,0,12,REC_WORD,410,3 +LTP093,0,12,REC_WORD,1508,1 +LTP093,0,13,WORD,670,1 +LTP093,0,13,WORD,1176,2 +LTP093,0,13,WORD,198,3 +LTP093,0,13,WORD,66,4 +LTP093,0,13,WORD,1468,5 +LTP093,0,13,WORD,508,6 +LTP093,0,13,WORD,292,7 +LTP093,0,13,WORD,82,8 +LTP093,0,13,WORD,980,9 +LTP093,0,13,WORD,874,10 +LTP093,0,13,WORD,133,11 +LTP093,0,13,WORD,1076,12 +LTP093,0,13,WORD,441,13 +LTP093,0,13,WORD,1422,14 +LTP093,0,13,WORD,200,15 +LTP093,0,13,WORD,974,16 +LTP093,0,13,WORD,1408,17 +LTP093,0,13,WORD,666,18 +LTP093,0,13,WORD,1426,19 +LTP093,0,13,WORD,143,20 +LTP093,0,13,WORD,754,21 +LTP093,0,13,WORD,84,22 +LTP093,0,13,WORD,10,23 +LTP093,0,13,WORD,1031,24 +LTP093,0,13,REC_WORD,198,3 +LTP093,0,13,REC_WORD,66,4 +LTP093,0,13,REC_WORD,754,21 +LTP093,0,13,REC_WORD,670,1 +LTP093,0,13,REC_WORD,1176,2 +LTP093,0,13,REC_WORD,1031,24 +LTP093,0,14,WORD,1301,1 +LTP093,0,14,WORD,368,2 +LTP093,0,14,WORD,1171,3 +LTP093,0,14,WORD,119,4 +LTP093,0,14,WORD,1384,5 +LTP093,0,14,WORD,483,6 +LTP093,0,14,WORD,1478,7 +LTP093,0,14,WORD,408,8 +LTP093,0,14,WORD,940,9 +LTP093,0,14,WORD,1147,10 +LTP093,0,14,WORD,280,11 +LTP093,0,14,WORD,1224,12 +LTP093,0,14,WORD,870,13 +LTP093,0,14,WORD,919,14 +LTP093,0,14,WORD,580,15 +LTP093,0,14,WORD,1209,16 +LTP093,0,14,WORD,216,17 +LTP093,0,14,WORD,1341,18 +LTP093,0,14,WORD,858,19 +LTP093,0,14,WORD,1262,20 +LTP093,0,14,WORD,1140,21 +LTP093,0,14,WORD,728,22 +LTP093,0,14,WORD,1420,23 +LTP093,0,14,WORD,1327,24 +LTP093,0,14,REC_WORD,1420,23 +LTP093,0,14,REC_WORD,1327,24 +LTP093,0,14,REC_WORD,870,13 +LTP093,0,14,REC_WORD,919,14 +LTP093,0,14,REC_WORD,1147,10 +LTP093,0,14,REC_WORD,940,9 +LTP093,0,14,REC_WORD,1171,3 +LTP093,0,14,REC_WORD,119,4 +LTP093,0,14,REC_WORD,1384,5 +LTP093,0,14,REC_WORD,483,6 +LTP093,0,15,WORD,1345,1 +LTP093,0,15,WORD,641,2 +LTP093,0,15,WORD,1620,3 +LTP093,0,15,WORD,512,4 +LTP093,0,15,WORD,1036,5 +LTP093,0,15,WORD,1333,6 +LTP093,0,15,WORD,1092,7 +LTP093,0,15,WORD,553,8 +LTP093,0,15,WORD,254,9 +LTP093,0,15,WORD,353,10 +LTP093,0,15,WORD,1319,11 +LTP093,0,15,WORD,792,12 +LTP093,0,15,WORD,304,13 +LTP093,0,15,WORD,1075,14 +LTP093,0,15,WORD,715,15 +LTP093,0,15,WORD,642,16 +LTP093,0,15,WORD,567,17 +LTP093,0,15,WORD,570,18 +LTP093,0,15,WORD,794,19 +LTP093,0,15,WORD,987,20 +LTP093,0,15,WORD,447,21 +LTP093,0,15,WORD,497,22 +LTP093,0,15,WORD,1532,23 +LTP093,0,15,WORD,1192,24 +LTP093,0,15,REC_WORD,497,22 +LTP093,0,15,REC_WORD,1192,24 +LTP093,0,15,REC_WORD,1532,23 +LTP093,0,15,REC_WORD,641,2 +LTP093,0,15,REC_WORD,1345,1 +LTP093,0,15,REC_WORD,1620,3 +LTP093,0,15,REC_WORD,512,4 +LTP093,0,16,WORD,203,1 +LTP093,0,16,WORD,389,2 +LTP093,0,16,WORD,1299,3 +LTP093,0,16,WORD,731,4 +LTP093,0,16,WORD,972,5 +LTP093,0,16,WORD,1328,6 +LTP093,0,16,WORD,498,7 +LTP093,0,16,WORD,1251,8 +LTP093,0,16,WORD,785,9 +LTP093,0,16,WORD,660,10 +LTP093,0,16,WORD,801,11 +LTP093,0,16,WORD,1260,12 +LTP093,0,16,WORD,995,13 +LTP093,0,16,WORD,1272,14 +LTP093,0,16,WORD,35,15 +LTP093,0,16,WORD,166,16 +LTP093,0,16,WORD,485,17 +LTP093,0,16,WORD,1522,18 +LTP093,0,16,WORD,1330,19 +LTP093,0,16,WORD,776,20 +LTP093,0,16,WORD,771,21 +LTP093,0,16,WORD,708,22 +LTP093,0,16,WORD,1086,23 +LTP093,0,16,WORD,977,24 +LTP093,0,16,REC_WORD,203,1 +LTP093,0,16,REC_WORD,389,2 +LTP093,0,16,REC_WORD,731,4 +LTP093,0,16,REC_WORD,1299,3 +LTP093,0,16,REC_WORD,776,20 +LTP093,0,16,REC_WORD,977,24 +LTP093,0,16,REC_WORD,485,17 +LTP093,0,16,REC_WORD,995,13 +LTP093,0,16,REC_WORD,1272,14 +LTP093,0,16,REC_WORD,35,15 +LTP093,0,17,WORD,764,1 +LTP093,0,17,WORD,1144,2 +LTP093,0,17,WORD,1000,3 +LTP093,0,17,WORD,1520,4 +LTP093,0,17,WORD,104,5 +LTP093,0,17,WORD,1621,6 +LTP093,0,17,WORD,1275,7 +LTP093,0,17,WORD,675,8 +LTP093,0,17,WORD,1035,9 +LTP093,0,17,WORD,405,10 +LTP093,0,17,WORD,68,11 +LTP093,0,17,WORD,1065,12 +LTP093,0,17,WORD,283,13 +LTP093,0,17,WORD,1565,14 +LTP093,0,17,WORD,912,15 +LTP093,0,17,WORD,261,16 +LTP093,0,17,WORD,55,17 +LTP093,0,17,WORD,1132,18 +LTP093,0,17,WORD,188,19 +LTP093,0,17,WORD,633,20 +LTP093,0,17,WORD,425,21 +LTP093,0,17,WORD,937,22 +LTP093,0,17,WORD,590,23 +LTP093,0,17,WORD,885,24 +LTP093,0,17,REC_WORD,188,19 +LTP093,0,17,REC_WORD,633,20 +LTP093,0,17,REC_WORD,55,17 +LTP093,0,17,REC_WORD,1132,18 +LTP093,0,17,REC_WORD,590,23 +LTP093,0,17,REC_WORD,885,24 +LTP093,0,17,REC_WORD,283,13 +LTP093,0,17,REC_WORD,1565,14 +LTP093,0,17,REC_WORD,1065,12 +LTP093,0,17,REC_WORD,68,11 +LTP093,0,18,WORD,1303,1 +LTP093,0,18,WORD,699,2 +LTP093,0,18,WORD,1355,3 +LTP093,0,18,WORD,91,4 +LTP093,0,18,WORD,139,5 +LTP093,0,18,WORD,1061,6 +LTP093,0,18,WORD,1093,7 +LTP093,0,18,WORD,1088,8 +LTP093,0,18,WORD,840,9 +LTP093,0,18,WORD,960,10 +LTP093,0,18,WORD,659,11 +LTP093,0,18,WORD,1015,12 +LTP093,0,18,WORD,1261,13 +LTP093,0,18,WORD,240,14 +LTP093,0,18,WORD,1095,15 +LTP093,0,18,WORD,622,16 +LTP093,0,18,WORD,1370,17 +LTP093,0,18,WORD,738,18 +LTP093,0,18,WORD,1172,19 +LTP093,0,18,WORD,392,20 +LTP093,0,18,WORD,519,21 +LTP093,0,18,WORD,1369,22 +LTP093,0,18,WORD,1578,23 +LTP093,0,18,WORD,509,24 +LTP093,0,18,REC_WORD,1369,22 +LTP093,0,18,REC_WORD,1578,23 +LTP093,0,18,REC_WORD,519,21 +LTP093,0,18,REC_WORD,392,20 +LTP093,0,18,REC_WORD,1303,1 +LTP093,0,18,REC_WORD,699,2 +LTP093,0,19,WORD,378,1 +LTP093,0,19,WORD,624,2 +LTP093,0,19,WORD,199,3 +LTP093,0,19,WORD,187,4 +LTP093,0,19,WORD,601,5 +LTP093,0,19,WORD,843,6 +LTP093,0,19,WORD,1096,7 +LTP093,0,19,WORD,1438,8 +LTP093,0,19,WORD,1593,9 +LTP093,0,19,WORD,336,10 +LTP093,0,19,WORD,109,11 +LTP093,0,19,WORD,761,12 +LTP093,0,19,WORD,1503,13 +LTP093,0,19,WORD,127,14 +LTP093,0,19,WORD,916,15 +LTP093,0,19,WORD,585,16 +LTP093,0,19,WORD,893,17 +LTP093,0,19,WORD,210,18 +LTP093,0,19,WORD,175,19 +LTP093,0,19,WORD,929,20 +LTP093,0,19,WORD,230,21 +LTP093,0,19,WORD,1511,22 +LTP093,0,19,WORD,869,23 +LTP093,0,19,WORD,490,24 +LTP093,0,19,REC_WORD,929,20 +LTP093,0,19,REC_WORD,230,21 +LTP093,0,19,REC_WORD,1511,22 +LTP093,0,19,REC_WORD,869,23 +LTP093,0,19,REC_WORD,175,19 +LTP093,0,19,REC_WORD,210,18 +LTP093,0,19,REC_WORD,490,24 +LTP093,0,19,REC_WORD,109,11 +LTP093,0,19,REC_WORD,761,12 +LTP093,0,19,REC_WORD,1096,7 +LTP093,0,19,REC_WORD,1438,8 +LTP093,0,19,REC_WORD,843,6 +LTP093,0,19,REC_WORD,601,5 +LTP093,0,19,REC_WORD,336,10 +LTP093,0,19,REC_WORD,1593,9 +LTP093,0,20,WORD,1215,1 +LTP093,0,20,WORD,610,2 +LTP093,0,20,WORD,900,3 +LTP093,0,20,WORD,1143,4 +LTP093,0,20,WORD,1357,5 +LTP093,0,20,WORD,346,6 +LTP093,0,20,WORD,1203,7 +LTP093,0,20,WORD,873,8 +LTP093,0,20,WORD,1373,9 +LTP093,0,20,WORD,46,10 +LTP093,0,20,WORD,1427,11 +LTP093,0,20,WORD,1139,12 +LTP093,0,20,WORD,783,13 +LTP093,0,20,WORD,684,14 +LTP093,0,20,WORD,467,15 +LTP093,0,20,WORD,313,16 +LTP093,0,20,WORD,1307,17 +LTP093,0,20,WORD,1555,18 +LTP093,0,20,WORD,807,19 +LTP093,0,20,WORD,1423,20 +LTP093,0,20,WORD,691,21 +LTP093,0,20,WORD,1202,22 +LTP093,0,20,WORD,1098,23 +LTP093,0,20,WORD,976,24 +LTP093,0,20,REC_WORD,1215,1 +LTP093,0,20,REC_WORD,610,2 +LTP093,0,20,REC_WORD,900,3 +LTP093,0,20,REC_WORD,1202,22 +LTP093,0,20,REC_WORD,346,6 +LTP093,0,20,REC_WORD,313,16 +LTP093,0,20,REC_WORD,1307,17 +LTP093,0,20,REC_WORD,1555,18 +LTP093,0,20,REC_WORD,467,15 +LTP093,0,21,WORD,1317,1 +LTP093,0,21,WORD,67,2 +LTP093,0,21,WORD,662,3 +LTP093,0,21,WORD,501,4 +LTP093,0,21,WORD,1019,5 +LTP093,0,21,WORD,1117,6 +LTP093,0,21,WORD,83,7 +LTP093,0,21,WORD,605,8 +LTP093,0,21,WORD,883,9 +LTP093,0,21,WORD,125,10 +LTP093,0,21,WORD,702,11 +LTP093,0,21,WORD,614,12 +LTP093,0,21,WORD,1184,13 +LTP093,0,21,WORD,752,14 +LTP093,0,21,WORD,766,15 +LTP093,0,21,WORD,6,16 +LTP093,0,21,WORD,1293,17 +LTP093,0,21,WORD,63,18 +LTP093,0,21,WORD,769,19 +LTP093,0,21,WORD,1356,20 +LTP093,0,21,WORD,804,21 +LTP093,0,21,WORD,400,22 +LTP093,0,21,WORD,1148,23 +LTP093,0,21,WORD,1572,24 +LTP093,0,21,REC_WORD,614,12 +LTP093,0,21,REC_WORD,1356,20 +LTP093,0,21,REC_WORD,804,21 +LTP093,0,21,REC_WORD,400,22 +LTP093,0,21,REC_WORD,125,10 +LTP093,0,21,REC_WORD,702,11 +LTP093,0,21,REC_WORD,662,3 +LTP093,0,21,REC_WORD,501,4 +LTP093,0,22,WORD,137,1 +LTP093,0,22,WORD,620,2 +LTP093,0,22,WORD,194,3 +LTP093,0,22,WORD,1085,4 +LTP093,0,22,WORD,795,5 +LTP093,0,22,WORD,171,6 +LTP093,0,22,WORD,1112,7 +LTP093,0,22,WORD,623,8 +LTP093,0,22,WORD,157,9 +LTP093,0,22,WORD,1051,10 +LTP093,0,22,WORD,1079,11 +LTP093,0,22,WORD,1232,12 +LTP093,0,22,WORD,569,13 +LTP093,0,22,WORD,878,14 +LTP093,0,22,WORD,1460,15 +LTP093,0,22,WORD,33,16 +LTP093,0,22,WORD,1049,17 +LTP093,0,22,WORD,265,18 +LTP093,0,22,WORD,947,19 +LTP093,0,22,WORD,1407,20 +LTP093,0,22,WORD,249,21 +LTP093,0,22,WORD,403,22 +LTP093,0,22,WORD,1037,23 +LTP093,0,22,WORD,814,24 +LTP093,0,22,REC_WORD,403,22 +LTP093,0,22,REC_WORD,1037,23 +LTP093,0,22,REC_WORD,171,6 +LTP093,0,22,REC_WORD,814,24 +LTP093,0,22,REC_WORD,947,19 +LTP093,0,22,REC_WORD,1079,11 +LTP093,0,22,REC_WORD,1232,12 +LTP093,0,23,WORD,1524,1 +LTP093,0,23,WORD,384,2 +LTP093,0,23,WORD,1347,3 +LTP093,0,23,WORD,1013,4 +LTP093,0,23,WORD,1009,5 +LTP093,0,23,WORD,849,6 +LTP093,0,23,WORD,407,7 +LTP093,0,23,WORD,476,8 +LTP093,0,23,WORD,223,9 +LTP093,0,23,WORD,1312,10 +LTP093,0,23,WORD,1626,11 +LTP093,0,23,WORD,64,12 +LTP093,0,23,WORD,1170,13 +LTP093,0,23,WORD,1048,14 +LTP093,0,23,WORD,1368,15 +LTP093,0,23,WORD,628,16 +LTP093,0,23,WORD,1416,17 +LTP093,0,23,WORD,1197,18 +LTP093,0,23,WORD,371,19 +LTP093,0,23,WORD,637,20 +LTP093,0,23,WORD,718,21 +LTP093,0,23,WORD,713,22 +LTP093,0,23,WORD,1258,23 +LTP093,0,23,WORD,308,24 +LTP093,0,23,REC_WORD,628,16 +LTP093,0,23,REC_WORD,407,7 +LTP093,0,23,REC_WORD,476,8 +LTP093,0,23,REC_WORD,64,12 +LTP093,0,24,WORD,253,1 +LTP093,0,24,WORD,955,2 +LTP093,0,24,WORD,745,3 +LTP093,0,24,WORD,427,4 +LTP093,0,24,WORD,1152,5 +LTP093,0,24,WORD,820,6 +LTP093,0,24,WORD,606,7 +LTP093,0,24,WORD,846,8 +LTP093,0,24,WORD,53,9 +LTP093,0,24,WORD,323,10 +LTP093,0,24,WORD,1173,11 +LTP093,0,24,WORD,1629,12 +LTP093,0,24,WORD,706,13 +LTP093,0,24,WORD,1024,14 +LTP093,0,24,WORD,1371,15 +LTP093,0,24,WORD,113,16 +LTP093,0,24,WORD,963,17 +LTP093,0,24,WORD,1027,18 +LTP093,0,24,WORD,1271,19 +LTP093,0,24,WORD,1501,20 +LTP093,0,24,WORD,1436,21 +LTP093,0,24,WORD,982,22 +LTP093,0,24,WORD,1044,23 +LTP093,0,24,WORD,639,24 +LTP093,0,24,REC_WORD,639,24 +LTP093,0,24,REC_WORD,982,22 +LTP093,0,24,REC_WORD,1044,23 +LTP093,0,24,REC_WORD,113,16 +LTP093,0,24,REC_WORD,963,17 +LTP093,0,24,REC_WORD,1024,14 +LTP093,0,24,REC_WORD,1371,15 +LTP093,0,24,REC_WORD,1027,18 +LTP093,0,24,REC_WORD,1271,19 +LTP093,0,24,REC_WORD,253,1 +LTP093,1,1,WORD,1355,1 +LTP093,1,1,WORD,1123,2 +LTP093,1,1,WORD,974,3 +LTP093,1,1,WORD,1356,4 +LTP093,1,1,WORD,731,5 +LTP093,1,1,WORD,1260,6 +LTP093,1,1,WORD,660,7 +LTP093,1,1,WORD,183,8 +LTP093,1,1,WORD,1148,9 +LTP093,1,1,WORD,1158,10 +LTP093,1,1,WORD,157,11 +LTP093,1,1,WORD,1098,12 +LTP093,1,1,WORD,989,13 +LTP093,1,1,WORD,304,14 +LTP093,1,1,WORD,1017,15 +LTP093,1,1,WORD,162,16 +LTP093,1,1,WORD,661,17 +LTP093,1,1,WORD,125,18 +LTP093,1,1,WORD,519,19 +LTP093,1,1,WORD,1395,20 +LTP093,1,1,WORD,782,21 +LTP093,1,1,WORD,485,22 +LTP093,1,1,WORD,1496,23 +LTP093,1,1,WORD,715,24 +LTP093,1,1,REC_WORD,1355,1 +LTP093,1,1,REC_WORD,1123,2 +LTP093,1,1,REC_WORD,1148,9 +LTP093,1,1,REC_WORD,1158,10 +LTP093,1,1,REC_WORD,1395,20 +LTP093,1,1,REC_WORD,519,19 +LTP093,1,1,REC_WORD,660,7 +LTP093,1,1,REC_WORD,183,8 +LTP093,1,1,REC_WORD,782,21 +LTP093,1,1,REC_WORD,162,16 +LTP093,1,1,REC_WORD,974,3 +LTP093,1,2,WORD,1170,1 +LTP093,1,2,WORD,390,2 +LTP093,1,2,WORD,1229,3 +LTP093,1,2,WORD,439,4 +LTP093,1,2,WORD,295,5 +LTP093,1,2,WORD,1544,6 +LTP093,1,2,WORD,1065,7 +LTP093,1,2,WORD,963,8 +LTP093,1,2,WORD,682,9 +LTP093,1,2,WORD,1307,10 +LTP093,1,2,WORD,1261,11 +LTP093,1,2,WORD,1341,12 +LTP093,1,2,WORD,5,13 +LTP093,1,2,WORD,737,14 +LTP093,1,2,WORD,177,15 +LTP093,1,2,WORD,1238,16 +LTP093,1,2,WORD,203,17 +LTP093,1,2,WORD,1224,18 +LTP093,1,2,WORD,814,19 +LTP093,1,2,WORD,292,20 +LTP093,1,2,WORD,488,21 +LTP093,1,2,WORD,1426,22 +LTP093,1,2,WORD,83,23 +LTP093,1,2,WORD,579,24 +LTP093,1,2,REC_WORD,203,17 +LTP093,1,2,REC_WORD,1238,16 +LTP093,1,2,REC_WORD,1224,18 +LTP093,1,2,REC_WORD,814,19 +LTP093,1,2,REC_WORD,963,8 +LTP093,1,2,REC_WORD,1065,7 +LTP093,1,2,REC_WORD,292,20 +LTP093,1,2,REC_WORD,488,21 +LTP093,1,2,REC_WORD,1229,3 +LTP093,1,2,REC_WORD,439,4 +LTP093,1,2,REC_WORD,295,5 +LTP093,1,2,REC_WORD,1544,6 +LTP093,1,3,WORD,1605,1 +LTP093,1,3,WORD,1620,2 +LTP093,1,3,WORD,373,3 +LTP093,1,3,WORD,827,4 +LTP093,1,3,WORD,1282,5 +LTP093,1,3,WORD,386,6 +LTP093,1,3,WORD,999,7 +LTP093,1,3,WORD,343,8 +LTP093,1,3,WORD,134,9 +LTP093,1,3,WORD,1275,10 +LTP093,1,3,WORD,602,11 +LTP093,1,3,WORD,1565,12 +LTP093,1,3,WORD,929,13 +LTP093,1,3,WORD,849,14 +LTP093,1,3,WORD,1411,15 +LTP093,1,3,WORD,585,16 +LTP093,1,3,WORD,1036,17 +LTP093,1,3,WORD,1315,18 +LTP093,1,3,WORD,1176,19 +LTP093,1,3,WORD,1629,20 +LTP093,1,3,WORD,1070,21 +LTP093,1,3,WORD,1545,22 +LTP093,1,3,WORD,1328,23 +LTP093,1,3,WORD,447,24 +LTP093,1,3,REC_WORD,134,9 +LTP093,1,3,REC_WORD,1275,10 +LTP093,1,4,WORD,988,1 +LTP093,1,4,WORD,1124,2 +LTP093,1,4,WORD,1111,3 +LTP093,1,4,WORD,1155,4 +LTP093,1,4,WORD,783,5 +LTP093,1,4,WORD,1060,6 +LTP093,1,4,WORD,1002,7 +LTP093,1,4,WORD,1271,8 +LTP093,1,4,WORD,143,9 +LTP093,1,4,WORD,1349,10 +LTP093,1,4,WORD,137,11 +LTP093,1,4,WORD,10,12 +LTP093,1,4,WORD,1420,13 +LTP093,1,4,WORD,1370,14 +LTP093,1,4,WORD,997,15 +LTP093,1,4,WORD,670,16 +LTP093,1,4,WORD,360,17 +LTP093,1,4,WORD,119,18 +LTP093,1,4,WORD,1451,19 +LTP093,1,4,WORD,267,20 +LTP093,1,4,WORD,980,21 +LTP093,1,4,WORD,216,22 +LTP093,1,4,WORD,618,23 +LTP093,1,4,WORD,746,24 +LTP093,1,4,REC_WORD,1271,8 +LTP093,1,4,REC_WORD,1002,7 +LTP093,1,4,REC_WORD,1124,2 +LTP093,1,4,REC_WORD,988,1 +LTP093,1,4,REC_WORD,1111,3 +LTP093,1,4,REC_WORD,1060,6 +LTP093,1,4,REC_WORD,1155,4 +LTP093,1,4,REC_WORD,783,5 +LTP093,1,5,WORD,496,1 +LTP093,1,5,WORD,397,2 +LTP093,1,5,WORD,738,3 +LTP093,1,5,WORD,766,4 +LTP093,1,5,WORD,623,5 +LTP093,1,5,WORD,1280,6 +LTP093,1,5,WORD,1331,7 +LTP093,1,5,WORD,429,8 +LTP093,1,5,WORD,1295,9 +LTP093,1,5,WORD,1415,10 +LTP093,1,5,WORD,1592,11 +LTP093,1,5,WORD,353,12 +LTP093,1,5,WORD,707,13 +LTP093,1,5,WORD,1303,14 +LTP093,1,5,WORD,793,15 +LTP093,1,5,WORD,1203,16 +LTP093,1,5,WORD,348,17 +LTP093,1,5,WORD,166,18 +LTP093,1,5,WORD,976,19 +LTP093,1,5,WORD,1511,20 +LTP093,1,5,WORD,446,21 +LTP093,1,5,WORD,495,22 +LTP093,1,5,WORD,199,23 +LTP093,1,5,WORD,888,24 +LTP093,1,5,REC_WORD,397,2 +LTP093,1,5,REC_WORD,496,1 +LTP093,1,5,REC_WORD,199,23 +LTP093,1,5,REC_WORD,1280,6 +LTP093,1,5,REC_WORD,429,8 +LTP093,1,5,REC_WORD,1295,9 +LTP093,1,5,REC_WORD,738,3 +LTP093,1,5,REC_WORD,1415,10 +LTP093,1,5,REC_WORD,766,4 +LTP093,1,5,REC_WORD,707,13 +LTP093,1,5,REC_WORD,1303,14 +LTP093,1,5,REC_WORD,353,12 +LTP093,1,5,REC_WORD,348,17 +LTP093,1,5,REC_WORD,1203,16 +LTP093,1,5,REC_WORD,166,18 +LTP093,1,5,REC_WORD,446,21 +LTP093,1,5,REC_WORD,495,22 +LTP093,1,6,WORD,1438,1 +LTP093,1,6,WORD,708,2 +LTP093,1,6,WORD,1505,3 +LTP093,1,6,WORD,937,4 +LTP093,1,6,WORD,1089,5 +LTP093,1,6,WORD,620,6 +LTP093,1,6,WORD,796,7 +LTP093,1,6,WORD,94,8 +LTP093,1,6,WORD,901,9 +LTP093,1,6,WORD,955,10 +LTP093,1,6,WORD,1371,11 +LTP093,1,6,WORD,600,12 +LTP093,1,6,WORD,1611,13 +LTP093,1,6,WORD,499,14 +LTP093,1,6,WORD,254,15 +LTP093,1,6,WORD,908,16 +LTP093,1,6,WORD,437,17 +LTP093,1,6,WORD,1173,18 +LTP093,1,6,WORD,84,19 +LTP093,1,6,WORD,622,20 +LTP093,1,6,WORD,1092,21 +LTP093,1,6,WORD,400,22 +LTP093,1,6,WORD,1088,23 +LTP093,1,6,WORD,1557,24 +LTP093,1,6,REC_WORD,1092,21 +LTP093,1,6,REC_WORD,400,22 +LTP093,1,6,REC_WORD,1557,24 +LTP093,1,6,REC_WORD,84,19 +LTP093,1,6,REC_WORD,908,16 +LTP093,1,6,REC_WORD,437,17 +LTP093,1,6,REC_WORD,1173,18 +LTP093,1,6,REC_WORD,254,15 +LTP093,1,6,REC_WORD,499,14 +LTP093,1,6,REC_WORD,622,20 +LTP093,1,6,REC_WORD,1089,5 +LTP093,1,6,REC_WORD,620,6 +LTP093,1,6,REC_WORD,1371,11 +LTP093,1,6,REC_WORD,1611,13 +LTP093,1,6,REC_WORD,600,12 +LTP093,1,7,WORD,637,1 +LTP093,1,7,WORD,280,2 +LTP093,1,7,WORD,1448,3 +LTP093,1,7,WORD,1071,4 +LTP093,1,7,WORD,164,5 +LTP093,1,7,WORD,508,6 +LTP093,1,7,WORD,1416,7 +LTP093,1,7,WORD,63,8 +LTP093,1,7,WORD,24,9 +LTP093,1,7,WORD,308,10 +LTP093,1,7,WORD,75,11 +LTP093,1,7,WORD,1427,12 +LTP093,1,7,WORD,1209,13 +LTP093,1,7,WORD,1382,14 +LTP093,1,7,WORD,1440,15 +LTP093,1,7,WORD,1140,16 +LTP093,1,7,WORD,798,17 +LTP093,1,7,WORD,223,18 +LTP093,1,7,WORD,410,19 +LTP093,1,7,WORD,1530,20 +LTP093,1,7,WORD,1159,21 +LTP093,1,7,WORD,1125,22 +LTP093,1,7,WORD,543,23 +LTP093,1,7,WORD,1172,24 +LTP093,1,7,REC_WORD,637,1 +LTP093,1,7,REC_WORD,280,2 +LTP093,1,7,REC_WORD,1448,3 +LTP093,1,7,REC_WORD,1071,4 +LTP093,1,7,REC_WORD,164,5 +LTP093,1,7,REC_WORD,508,6 +LTP093,1,7,REC_WORD,308,10 +LTP093,1,7,REC_WORD,1416,7 +LTP093,1,7,REC_WORD,63,8 +LTP093,1,7,REC_WORD,75,11 +LTP093,1,7,REC_WORD,1427,12 +LTP093,1,7,REC_WORD,1440,15 +LTP093,1,7,REC_WORD,1140,16 +LTP093,1,7,REC_WORD,1530,20 +LTP093,1,7,REC_WORD,1159,21 +LTP093,1,8,WORD,1035,1 +LTP093,1,8,WORD,1113,2 +LTP093,1,8,WORD,1032,3 +LTP093,1,8,WORD,1358,4 +LTP093,1,8,WORD,553,5 +LTP093,1,8,WORD,763,6 +LTP093,1,8,WORD,675,7 +LTP093,1,8,WORD,1184,8 +LTP093,1,8,WORD,466,9 +LTP093,1,8,WORD,389,10 +LTP093,1,8,WORD,1330,11 +LTP093,1,8,WORD,131,12 +LTP093,1,8,WORD,1385,13 +LTP093,1,8,WORD,760,14 +LTP093,1,8,WORD,1521,15 +LTP093,1,8,WORD,1143,16 +LTP093,1,8,WORD,761,17 +LTP093,1,8,WORD,919,18 +LTP093,1,8,WORD,184,19 +LTP093,1,8,WORD,745,20 +LTP093,1,8,WORD,401,21 +LTP093,1,8,WORD,598,22 +LTP093,1,8,WORD,776,23 +LTP093,1,8,WORD,1533,24 +LTP093,1,8,REC_WORD,1533,24 +LTP093,1,8,REC_WORD,760,14 +LTP093,1,8,REC_WORD,1385,13 +LTP093,1,8,REC_WORD,-1,-999 +LTP093,1,9,WORD,723,1 +LTP093,1,9,WORD,1581,2 +LTP093,1,9,WORD,689,3 +LTP093,1,9,WORD,1117,4 +LTP093,1,9,WORD,299,5 +LTP093,1,9,WORD,1027,6 +LTP093,1,9,WORD,1079,7 +LTP093,1,9,WORD,1501,8 +LTP093,1,9,WORD,972,9 +LTP093,1,9,WORD,198,10 +LTP093,1,9,WORD,1044,11 +LTP093,1,9,WORD,843,12 +LTP093,1,9,WORD,829,13 +LTP093,1,9,WORD,769,14 +LTP093,1,9,WORD,840,15 +LTP093,1,9,WORD,346,16 +LTP093,1,9,WORD,1532,17 +LTP093,1,9,WORD,441,18 +LTP093,1,9,WORD,1345,19 +LTP093,1,9,WORD,1373,20 +LTP093,1,9,WORD,726,21 +LTP093,1,9,WORD,476,22 +LTP093,1,9,WORD,1312,23 +LTP093,1,9,WORD,696,24 +LTP093,1,9,REC_WORD,723,1 +LTP093,1,9,REC_WORD,1581,2 +LTP093,1,9,REC_WORD,198,10 +LTP093,1,9,REC_WORD,1044,11 +LTP093,1,9,REC_WORD,1501,8 +LTP093,1,9,REC_WORD,696,24 +LTP093,1,9,REC_WORD,726,21 +LTP093,1,9,REC_WORD,476,22 +LTP093,1,9,REC_WORD,1312,23 +LTP093,1,10,WORD,1562,1 +LTP093,1,10,WORD,755,2 +LTP093,1,10,WORD,1333,3 +LTP093,1,10,WORD,235,4 +LTP093,1,10,WORD,1057,5 +LTP093,1,10,WORD,1515,6 +LTP093,1,10,WORD,395,7 +LTP093,1,10,WORD,425,8 +LTP093,1,10,WORD,1152,9 +LTP093,1,10,WORD,1093,10 +LTP093,1,10,WORD,384,11 +LTP093,1,10,WORD,230,12 +LTP093,1,10,WORD,1519,13 +LTP093,1,10,WORD,846,14 +LTP093,1,10,WORD,1048,15 +LTP093,1,10,WORD,1221,16 +LTP093,1,10,WORD,1329,17 +LTP093,1,10,WORD,800,18 +LTP093,1,10,WORD,1520,19 +LTP093,1,10,WORD,1478,20 +LTP093,1,10,WORD,1608,21 +LTP093,1,10,WORD,210,22 +LTP093,1,10,WORD,1578,23 +LTP093,1,10,WORD,249,24 +LTP093,1,10,REC_WORD,210,22 +LTP093,1,10,REC_WORD,1520,19 +LTP093,1,10,REC_WORD,1478,20 +LTP093,1,10,REC_WORD,1608,21 +LTP093,1,10,REC_WORD,1333,3 +LTP093,1,10,REC_WORD,755,2 +LTP093,1,10,REC_WORD,1562,1 +LTP093,1,10,REC_WORD,1329,17 +LTP093,1,10,REC_WORD,800,18 +LTP093,1,10,REC_WORD,1152,9 +LTP093,1,11,WORD,569,1 +LTP093,1,11,WORD,1392,2 +LTP093,1,11,WORD,100,3 +LTP093,1,11,WORD,658,4 +LTP093,1,11,WORD,1232,5 +LTP093,1,11,WORD,1095,6 +LTP093,1,11,WORD,498,7 +LTP093,1,11,WORD,1006,8 +LTP093,1,11,WORD,1461,9 +LTP093,1,11,WORD,1096,10 +LTP093,1,11,WORD,403,11 +LTP093,1,11,WORD,718,12 +LTP093,1,11,WORD,139,13 +LTP093,1,11,WORD,1171,14 +LTP093,1,11,WORD,278,15 +LTP093,1,11,WORD,1460,16 +LTP093,1,11,WORD,1022,17 +LTP093,1,11,WORD,1242,18 +LTP093,1,11,WORD,67,19 +LTP093,1,11,WORD,935,20 +LTP093,1,11,WORD,1422,21 +LTP093,1,11,WORD,431,22 +LTP093,1,11,WORD,861,23 +LTP093,1,11,WORD,1251,24 +LTP093,1,11,REC_WORD,1242,18 +LTP093,1,11,REC_WORD,935,20 +LTP093,1,11,REC_WORD,67,19 +LTP093,1,11,REC_WORD,100,3 +LTP093,1,11,REC_WORD,658,4 +LTP093,1,11,REC_WORD,861,23 +LTP093,1,12,WORD,142,1 +LTP093,1,12,WORD,211,2 +LTP093,1,12,WORD,240,3 +LTP093,1,12,WORD,490,4 +LTP093,1,12,WORD,1403,5 +LTP093,1,12,WORD,839,6 +LTP093,1,12,WORD,442,7 +LTP093,1,12,WORD,494,8 +LTP093,1,12,WORD,226,9 +LTP093,1,12,WORD,719,10 +LTP093,1,12,WORD,492,11 +LTP093,1,12,WORD,1536,12 +LTP093,1,12,WORD,895,13 +LTP093,1,12,WORD,792,14 +LTP093,1,12,WORD,1287,15 +LTP093,1,12,WORD,484,16 +LTP093,1,12,WORD,1085,17 +LTP093,1,12,WORD,866,18 +LTP093,1,12,WORD,520,19 +LTP093,1,12,WORD,175,20 +LTP093,1,12,WORD,307,21 +LTP093,1,12,WORD,605,22 +LTP093,1,12,WORD,809,23 +LTP093,1,12,WORD,316,24 +LTP093,1,12,REC_WORD,809,23 +LTP093,1,12,REC_WORD,316,24 +LTP093,1,12,REC_WORD,307,21 +LTP093,1,12,REC_WORD,605,22 +LTP093,1,12,REC_WORD,1536,12 +LTP093,1,12,REC_WORD,492,11 +LTP093,1,12,REC_WORD,719,10 +LTP093,1,12,REC_WORD,226,9 +LTP093,1,12,REC_WORD,442,7 +LTP093,1,12,REC_WORD,494,8 +LTP093,1,12,REC_WORD,142,1 +LTP093,1,12,REC_WORD,211,2 +LTP093,1,12,REC_WORD,839,6 +LTP093,1,12,REC_WORD,240,3 +LTP093,1,12,REC_WORD,490,4 +LTP093,1,12,REC_WORD,175,20 +LTP093,1,13,WORD,372,1 +LTP093,1,13,WORD,820,2 +LTP093,1,13,WORD,1343,3 +LTP093,1,13,WORD,1246,4 +LTP093,1,13,WORD,873,5 +LTP093,1,13,WORD,654,6 +LTP093,1,13,WORD,1256,7 +LTP093,1,13,WORD,82,8 +LTP093,1,13,WORD,388,9 +LTP093,1,13,WORD,559,10 +LTP093,1,13,WORD,592,11 +LTP093,1,13,WORD,702,12 +LTP093,1,13,WORD,1626,13 +LTP093,1,13,WORD,871,14 +LTP093,1,13,WORD,1388,15 +LTP093,1,13,WORD,1535,16 +LTP093,1,13,WORD,691,17 +LTP093,1,13,WORD,168,18 +LTP093,1,13,WORD,982,19 +LTP093,1,13,WORD,1488,20 +LTP093,1,13,WORD,847,21 +LTP093,1,13,WORD,771,22 +LTP093,1,13,WORD,1369,23 +LTP093,1,13,WORD,248,24 +LTP093,1,13,REC_WORD,1388,15 +LTP093,1,13,REC_WORD,1535,16 +LTP093,1,13,REC_WORD,873,5 +LTP093,1,13,REC_WORD,654,6 +LTP093,1,13,REC_WORD,691,17 +LTP093,1,13,REC_WORD,168,18 +LTP093,1,14,WORD,576,1 +LTP093,1,14,WORD,922,2 +LTP093,1,14,WORD,408,3 +LTP093,1,14,WORD,1119,4 +LTP093,1,14,WORD,546,5 +LTP093,1,14,WORD,258,6 +LTP093,1,14,WORD,778,7 +LTP093,1,14,WORD,1087,8 +LTP093,1,14,WORD,109,9 +LTP093,1,14,WORD,108,10 +LTP093,1,14,WORD,415,11 +LTP093,1,14,WORD,1049,12 +LTP093,1,14,WORD,1326,13 +LTP093,1,14,WORD,795,14 +LTP093,1,14,WORD,313,15 +LTP093,1,14,WORD,340,16 +LTP093,1,14,WORD,1299,17 +LTP093,1,14,WORD,764,18 +LTP093,1,14,WORD,338,19 +LTP093,1,14,WORD,684,20 +LTP093,1,14,WORD,1199,21 +LTP093,1,14,WORD,590,22 +LTP093,1,14,WORD,1194,23 +LTP093,1,14,WORD,512,24 +LTP093,1,14,REC_WORD,1199,21 +LTP093,1,14,REC_WORD,590,22 +LTP093,1,14,REC_WORD,778,7 +LTP093,1,14,REC_WORD,1087,8 +LTP093,1,15,WORD,1009,1 +LTP093,1,15,WORD,878,2 +LTP093,1,15,WORD,666,3 +LTP093,1,15,WORD,1524,4 +LTP093,1,15,WORD,1197,5 +LTP093,1,15,WORD,858,6 +LTP093,1,15,WORD,194,7 +LTP093,1,15,WORD,1508,8 +LTP093,1,15,WORD,561,9 +LTP093,1,15,WORD,35,10 +LTP093,1,15,WORD,1609,11 +LTP093,1,15,WORD,1019,12 +LTP093,1,15,WORD,68,13 +LTP093,1,15,WORD,812,14 +LTP093,1,15,WORD,1527,15 +LTP093,1,15,WORD,61,16 +LTP093,1,15,WORD,659,17 +LTP093,1,15,WORD,662,18 +LTP093,1,15,WORD,64,19 +LTP093,1,15,WORD,885,20 +LTP093,1,15,WORD,940,21 +LTP093,1,15,WORD,1011,22 +LTP093,1,15,WORD,790,23 +LTP093,1,15,WORD,1319,24 +LTP093,1,15,REC_WORD,1197,5 +LTP093,1,15,REC_WORD,194,7 +LTP093,1,15,REC_WORD,1508,8 +LTP093,1,15,REC_WORD,1609,11 +LTP093,1,15,REC_WORD,35,10 +LTP093,1,15,REC_WORD,1009,1 +LTP093,1,15,REC_WORD,812,14 +LTP093,1,15,REC_WORD,1527,15 +LTP093,1,15,REC_WORD,878,2 +LTP093,1,16,WORD,405,1 +LTP093,1,16,WORD,497,2 +LTP093,1,16,WORD,1132,3 +LTP093,1,16,WORD,1622,4 +LTP093,1,16,WORD,916,5 +LTP093,1,16,WORD,91,6 +LTP093,1,16,WORD,127,7 +LTP093,1,16,WORD,1258,8 +LTP093,1,16,WORD,724,9 +LTP093,1,16,WORD,1144,10 +LTP093,1,16,WORD,593,11 +LTP093,1,16,WORD,584,12 +LTP093,1,16,WORD,1080,13 +LTP093,1,16,WORD,1636,14 +LTP093,1,16,WORD,977,15 +LTP093,1,16,WORD,1200,16 +LTP093,1,16,WORD,1374,17 +LTP093,1,16,WORD,1021,18 +LTP093,1,16,WORD,575,19 +LTP093,1,16,WORD,1468,20 +LTP093,1,16,WORD,699,21 +LTP093,1,16,WORD,1546,22 +LTP093,1,16,WORD,1037,23 +LTP093,1,16,WORD,255,24 +LTP093,1,16,REC_WORD,699,21 +LTP093,1,16,REC_WORD,1546,22 +LTP093,1,16,REC_WORD,1037,23 +LTP093,1,16,REC_WORD,255,24 +LTP093,1,16,REC_WORD,724,9 +LTP093,1,16,REC_WORD,1144,10 +LTP093,1,16,REC_WORD,1132,3 +LTP093,1,16,REC_WORD,1622,4 +LTP093,1,16,REC_WORD,584,12 +LTP093,1,16,REC_WORD,977,15 +LTP093,1,16,REC_WORD,1374,17 +LTP093,1,16,REC_WORD,1258,8 +LTP093,1,16,REC_WORD,1021,18 +LTP093,1,16,REC_WORD,1200,16 +LTP093,1,17,WORD,1243,1 +LTP093,1,17,WORD,1635,2 +LTP093,1,17,WORD,435,3 +LTP093,1,17,WORD,601,4 +LTP093,1,17,WORD,642,5 +LTP093,1,17,WORD,947,6 +LTP093,1,17,WORD,1024,7 +LTP093,1,17,WORD,1347,8 +LTP093,1,17,WORD,728,9 +LTP093,1,17,WORD,392,10 +LTP093,1,17,WORD,1301,11 +LTP093,1,17,WORD,1112,12 +LTP093,1,17,WORD,994,13 +LTP093,1,17,WORD,960,14 +LTP093,1,17,WORD,641,15 +LTP093,1,17,WORD,6,16 +LTP093,1,17,WORD,169,17 +LTP093,1,17,WORD,483,18 +LTP093,1,17,WORD,580,19 +LTP093,1,17,WORD,1400,20 +LTP093,1,17,WORD,55,21 +LTP093,1,17,WORD,987,22 +LTP093,1,17,WORD,706,23 +LTP093,1,17,WORD,1245,24 +LTP093,1,17,REC_WORD,1245,24 +LTP093,1,17,REC_WORD,706,23 +LTP093,1,17,REC_WORD,1243,1 +LTP093,1,17,REC_WORD,1635,2 +LTP093,1,17,REC_WORD,435,3 +LTP093,1,17,REC_WORD,601,4 +LTP093,1,17,REC_WORD,947,6 +LTP093,1,17,REC_WORD,642,5 +LTP093,1,17,REC_WORD,55,21 +LTP093,1,17,REC_WORD,483,18 +LTP093,1,17,REC_WORD,580,19 +LTP093,1,17,REC_WORD,1347,8 +LTP093,1,17,REC_WORD,728,9 +LTP093,1,17,REC_WORD,1301,11 +LTP093,1,18,WORD,88,1 +LTP093,1,18,WORD,624,2 +LTP093,1,18,WORD,1472,3 +LTP093,1,18,WORD,1076,4 +LTP093,1,18,WORD,1219,5 +LTP093,1,18,WORD,628,6 +LTP093,1,18,WORD,1147,7 +LTP093,1,18,WORD,1015,8 +LTP093,1,18,WORD,413,9 +LTP093,1,18,WORD,1384,10 +LTP093,1,18,WORD,567,11 +LTP093,1,18,WORD,752,12 +LTP093,1,18,WORD,804,13 +LTP093,1,18,WORD,368,14 +LTP093,1,18,WORD,1183,15 +LTP093,1,18,WORD,785,16 +LTP093,1,18,WORD,1061,17 +LTP093,1,18,WORD,509,18 +LTP093,1,18,WORD,1572,19 +LTP093,1,18,WORD,614,20 +LTP093,1,18,WORD,1456,21 +LTP093,1,18,WORD,683,22 +LTP093,1,18,WORD,159,23 +LTP093,1,18,WORD,1047,24 +LTP093,1,18,REC_WORD,1047,24 +LTP093,1,18,REC_WORD,159,23 +LTP093,1,18,REC_WORD,1183,15 +LTP093,1,18,REC_WORD,683,22 +LTP093,1,18,REC_WORD,1219,5 +LTP093,1,18,REC_WORD,628,6 +LTP093,1,18,REC_WORD,1384,10 +LTP093,1,18,REC_WORD,567,11 +LTP093,1,18,REC_WORD,624,2 +LTP093,1,18,REC_WORD,88,1 +LTP093,1,19,WORD,900,1 +LTP093,1,19,WORD,39,2 +LTP093,1,19,WORD,1408,3 +LTP093,1,19,WORD,633,4 +LTP093,1,19,WORD,1621,5 +LTP093,1,19,WORD,315,6 +LTP093,1,19,WORD,1512,7 +LTP093,1,19,WORD,1555,8 +LTP093,1,19,WORD,358,9 +LTP093,1,19,WORD,1250,10 +LTP093,1,19,WORD,1407,11 +LTP093,1,19,WORD,749,12 +LTP093,1,19,WORD,541,13 +LTP093,1,19,WORD,29,14 +LTP093,1,19,WORD,639,15 +LTP093,1,19,WORD,714,16 +LTP093,1,19,WORD,893,17 +LTP093,1,19,WORD,1503,18 +LTP093,1,19,WORD,53,19 +LTP093,1,19,WORD,12,20 +LTP093,1,19,WORD,1633,21 +LTP093,1,19,WORD,1317,22 +LTP093,1,19,WORD,1293,23 +LTP093,1,19,WORD,1522,24 +LTP093,1,19,REC_WORD,1317,22 +LTP093,1,19,REC_WORD,749,12 +LTP093,1,19,REC_WORD,541,13 +LTP093,1,19,REC_WORD,1503,18 +LTP093,1,19,REC_WORD,893,17 +LTP093,1,19,REC_WORD,1407,11 +LTP093,1,19,REC_WORD,1408,3 +LTP093,1,19,REC_WORD,633,4 +LTP093,1,19,REC_WORD,714,16 +LTP093,1,20,WORD,1357,1 +LTP093,1,20,WORD,834,2 +LTP093,1,20,WORD,1351,3 +LTP093,1,20,WORD,171,4 +LTP093,1,20,WORD,253,5 +LTP093,1,20,WORD,200,6 +LTP093,1,20,WORD,801,7 +LTP093,1,20,WORD,645,8 +LTP093,1,20,WORD,86,9 +LTP093,1,20,WORD,1436,10 +LTP093,1,20,WORD,378,11 +LTP093,1,20,WORD,635,12 +LTP093,1,20,WORD,857,13 +LTP093,1,20,WORD,1486,14 +LTP093,1,20,WORD,1332,15 +LTP093,1,20,WORD,912,16 +LTP093,1,20,WORD,1631,17 +LTP093,1,20,WORD,407,18 +LTP093,1,20,WORD,336,19 +LTP093,1,20,WORD,38,20 +LTP093,1,20,WORD,417,21 +LTP093,1,20,WORD,874,22 +LTP093,1,20,WORD,187,23 +LTP093,1,20,WORD,732,24 +LTP093,1,20,REC_WORD,1357,1 +LTP093,1,20,REC_WORD,834,2 +LTP093,1,20,REC_WORD,1351,3 +LTP093,1,20,REC_WORD,171,4 +LTP093,1,20,REC_WORD,801,7 +LTP093,1,20,REC_WORD,645,8 +LTP093,1,21,WORD,1262,1 +LTP093,1,21,WORD,833,2 +LTP093,1,21,WORD,932,3 +LTP093,1,21,WORD,754,4 +LTP093,1,21,WORD,570,5 +LTP093,1,21,WORD,172,6 +LTP093,1,21,WORD,1227,7 +LTP093,1,21,WORD,1013,8 +LTP093,1,21,WORD,995,9 +LTP093,1,21,WORD,852,10 +LTP093,1,21,WORD,667,11 +LTP093,1,21,WORD,959,12 +LTP093,1,21,WORD,1139,13 +LTP093,1,21,WORD,113,14 +LTP093,1,21,WORD,136,15 +LTP093,1,21,WORD,573,16 +LTP093,1,21,WORD,1192,17 +LTP093,1,21,WORD,1051,18 +LTP093,1,21,WORD,66,19 +LTP093,1,21,WORD,872,20 +LTP093,1,21,WORD,327,21 +LTP093,1,21,WORD,1587,22 +LTP093,1,21,WORD,578,23 +LTP093,1,21,WORD,697,24 +LTP093,1,21,REC_WORD,578,23 +LTP093,1,21,REC_WORD,697,24 +LTP093,1,21,REC_WORD,959,12 +LTP093,1,21,REC_WORD,667,11 +LTP093,1,21,REC_WORD,1139,13 +LTP093,1,21,REC_WORD,113,14 +LTP093,1,21,REC_WORD,754,4 +LTP093,1,21,REC_WORD,932,3 +LTP093,1,21,REC_WORD,995,9 +LTP093,1,21,REC_WORD,852,10 +LTP093,1,22,WORD,1618,1 +LTP093,1,22,WORD,1606,2 +LTP093,1,22,WORD,341,3 +LTP093,1,22,WORD,13,4 +LTP093,1,22,WORD,734,5 +LTP093,1,22,WORD,604,6 +LTP093,1,22,WORD,807,7 +LTP093,1,22,WORD,133,8 +LTP093,1,22,WORD,599,9 +LTP093,1,22,WORD,530,10 +LTP093,1,22,WORD,869,11 +LTP093,1,22,WORD,1000,12 +LTP093,1,22,WORD,323,13 +LTP093,1,22,WORD,1086,14 +LTP093,1,22,WORD,627,15 +LTP093,1,22,WORD,794,16 +LTP093,1,22,WORD,1405,17 +LTP093,1,22,WORD,427,18 +LTP093,1,22,WORD,1493,19 +LTP093,1,22,WORD,188,20 +LTP093,1,22,WORD,33,21 +LTP093,1,22,WORD,1628,22 +LTP093,1,22,WORD,1149,23 +LTP093,1,22,WORD,1016,24 +LTP093,1,22,REC_WORD,1618,1 +LTP093,1,22,REC_WORD,1606,2 +LTP093,1,22,REC_WORD,341,3 +LTP093,1,22,REC_WORD,807,7 +LTP093,1,22,REC_WORD,1405,17 +LTP093,1,22,REC_WORD,133,8 +LTP093,1,22,REC_WORD,599,9 +LTP093,1,22,REC_WORD,869,11 +LTP093,1,23,WORD,996,1 +LTP093,1,23,WORD,565,2 +LTP093,1,23,WORD,848,3 +LTP093,1,23,WORD,1368,4 +LTP093,1,23,WORD,1414,5 +LTP093,1,23,WORD,179,6 +LTP093,1,23,WORD,1294,7 +LTP093,1,23,WORD,46,8 +LTP093,1,23,WORD,606,9 +LTP093,1,23,WORD,104,10 +LTP093,1,23,WORD,145,11 +LTP093,1,23,WORD,501,12 +LTP093,1,23,WORD,733,13 +LTP093,1,23,WORD,1580,14 +LTP093,1,23,WORD,1423,15 +LTP093,1,23,WORD,1202,16 +LTP093,1,23,WORD,713,17 +LTP093,1,23,WORD,1593,18 +LTP093,1,23,WORD,692,19 +LTP093,1,23,WORD,339,20 +LTP093,1,23,WORD,1292,21 +LTP093,1,23,WORD,328,22 +LTP093,1,23,WORD,1108,23 +LTP093,1,23,WORD,610,24 +LTP093,1,23,REC_WORD,692,19 +LTP093,1,23,REC_WORD,339,20 +LTP093,1,23,REC_WORD,1292,21 +LTP093,1,23,REC_WORD,713,17 +LTP093,1,23,REC_WORD,1593,18 +LTP093,1,23,REC_WORD,1294,7 +LTP093,1,23,REC_WORD,46,8 +LTP093,1,23,REC_WORD,606,9 +LTP093,1,23,REC_WORD,1423,15 +LTP093,1,23,REC_WORD,1202,16 +LTP093,1,23,REC_WORD,328,22 +LTP093,1,23,REC_WORD,996,1 +LTP093,1,23,REC_WORD,565,2 +LTP093,1,23,REC_WORD,848,3 +LTP093,1,23,REC_WORD,1368,4 +LTP093,1,24,WORD,821,1 +LTP093,1,24,WORD,953,2 +LTP093,1,24,WORD,1327,3 +LTP093,1,24,WORD,467,4 +LTP093,1,24,WORD,1625,5 +LTP093,1,24,WORD,883,6 +LTP093,1,24,WORD,879,7 +LTP093,1,24,WORD,1104,8 +LTP093,1,24,WORD,265,9 +LTP093,1,24,WORD,1215,10 +LTP093,1,24,WORD,1075,11 +LTP093,1,24,WORD,870,12 +LTP093,1,24,WORD,1066,13 +LTP093,1,24,WORD,591,14 +LTP093,1,24,WORD,283,15 +LTP093,1,24,WORD,383,16 +LTP093,1,24,WORD,1272,17 +LTP093,1,24,WORD,261,18 +LTP093,1,24,WORD,325,19 +LTP093,1,24,WORD,371,20 +LTP093,1,24,WORD,1031,21 +LTP093,1,24,WORD,876,22 +LTP093,1,24,WORD,1526,23 +LTP093,1,24,WORD,1284,24 +LTP093,1,24,REC_WORD,821,1 +LTP093,1,24,REC_WORD,953,2 +LTP093,1,24,REC_WORD,1284,24 +LTP093,1,24,REC_WORD,1526,23 +LTP093,1,24,REC_WORD,325,19 +LTP093,1,24,REC_WORD,1215,10 +LTP093,1,24,REC_WORD,1066,13 +LTP093,1,24,REC_WORD,591,14 +LTP093,1,24,REC_WORD,383,16 +LTP093,1,24,REC_WORD,1272,17 +LTP093,1,24,REC_WORD,261,18 +LTP093,1,24,REC_WORD,1327,3 +LTP093,1,24,REC_WORD,467,4 +LTP093,2,1,WORD,585,1 +LTP093,2,1,WORD,509,2 +LTP093,2,1,WORD,666,3 +LTP093,2,1,WORD,1355,4 +LTP093,2,1,WORD,706,5 +LTP093,2,1,WORD,599,6 +LTP093,2,1,WORD,746,7 +LTP093,2,1,WORD,1327,8 +LTP093,2,1,WORD,827,9 +LTP093,2,1,WORD,336,10 +LTP093,2,1,WORD,1333,11 +LTP093,2,1,WORD,576,12 +LTP093,2,1,WORD,610,13 +LTP093,2,1,WORD,230,14 +LTP093,2,1,WORD,1349,15 +LTP093,2,1,WORD,249,16 +LTP093,2,1,WORD,1408,17 +LTP093,2,1,WORD,226,18 +LTP093,2,1,WORD,999,19 +LTP093,2,1,WORD,283,20 +LTP093,2,1,WORD,893,21 +LTP093,2,1,WORD,360,22 +LTP093,2,1,WORD,408,23 +LTP093,2,1,WORD,339,24 +LTP093,2,1,REC_WORD,585,1 +LTP093,2,1,REC_WORD,509,2 +LTP093,2,1,REC_WORD,666,3 +LTP093,2,1,REC_WORD,1327,8 +LTP093,2,1,REC_WORD,706,5 +LTP093,2,1,REC_WORD,599,6 +LTP093,2,1,REC_WORD,827,9 +LTP093,2,1,REC_WORD,1333,11 +LTP093,2,1,REC_WORD,336,10 +LTP093,2,1,REC_WORD,1349,15 +LTP093,2,1,REC_WORD,746,7 +LTP093,2,1,REC_WORD,408,23 +LTP093,2,1,REC_WORD,339,24 +LTP093,2,1,REC_WORD,999,19 +LTP093,2,1,REC_WORD,283,20 +LTP093,2,2,WORD,1199,1 +LTP093,2,2,WORD,427,2 +LTP093,2,2,WORD,624,3 +LTP093,2,2,WORD,200,4 +LTP093,2,2,WORD,371,5 +LTP093,2,2,WORD,578,6 +LTP093,2,2,WORD,10,7 +LTP093,2,2,WORD,1307,8 +LTP093,2,2,WORD,261,9 +LTP093,2,2,WORD,131,10 +LTP093,2,2,WORD,425,11 +LTP093,2,2,WORD,1555,12 +LTP093,2,2,WORD,641,13 +LTP093,2,2,WORD,53,14 +LTP093,2,2,WORD,1016,15 +LTP093,2,2,WORD,429,16 +LTP093,2,2,WORD,1608,17 +LTP093,2,2,WORD,1172,18 +LTP093,2,2,WORD,839,19 +LTP093,2,2,WORD,771,20 +LTP093,2,2,WORD,308,21 +LTP093,2,2,WORD,1098,22 +LTP093,2,2,WORD,1303,23 +LTP093,2,2,WORD,1232,24 +LTP093,2,2,REC_WORD,429,16 +LTP093,2,2,REC_WORD,1608,17 +LTP093,2,2,REC_WORD,1098,22 +LTP093,2,2,REC_WORD,1303,23 +LTP093,2,2,REC_WORD,131,10 +LTP093,2,2,REC_WORD,425,11 +LTP093,2,2,REC_WORD,1555,12 +LTP093,2,2,REC_WORD,261,9 +LTP093,2,2,REC_WORD,1307,8 +LTP093,2,2,REC_WORD,641,13 +LTP093,2,3,WORD,348,1 +LTP093,2,3,WORD,113,2 +LTP093,2,3,WORD,1002,3 +LTP093,2,3,WORD,849,4 +LTP093,2,3,WORD,490,5 +LTP093,2,3,WORD,846,6 +LTP093,2,3,WORD,499,7 +LTP093,2,3,WORD,602,8 +LTP093,2,3,WORD,1117,9 +LTP093,2,3,WORD,622,10 +LTP093,2,3,WORD,1044,11 +LTP093,2,3,WORD,292,12 +LTP093,2,3,WORD,104,13 +LTP093,2,3,WORD,39,14 +LTP093,2,3,WORD,67,15 +LTP093,2,3,WORD,763,16 +LTP093,2,3,WORD,1451,17 +LTP093,2,3,WORD,833,18 +LTP093,2,3,WORD,1512,19 +LTP093,2,3,WORD,807,20 +LTP093,2,3,WORD,1108,21 +LTP093,2,3,WORD,61,22 +LTP093,2,3,WORD,876,23 +LTP093,2,3,WORD,785,24 +LTP093,2,3,REC_WORD,348,1 +LTP093,2,3,REC_WORD,113,2 +LTP093,2,3,REC_WORD,1002,3 +LTP093,2,3,REC_WORD,849,4 +LTP093,2,3,REC_WORD,490,5 +LTP093,2,3,REC_WORD,846,6 +LTP093,2,3,REC_WORD,602,8 +LTP093,2,3,REC_WORD,1117,9 +LTP093,2,3,REC_WORD,622,10 +LTP093,2,3,REC_WORD,1044,11 +LTP093,2,3,REC_WORD,1108,21 +LTP093,2,3,REC_WORD,39,14 +LTP093,2,3,REC_WORD,833,18 +LTP093,2,4,WORD,1572,1 +LTP093,2,4,WORD,692,2 +LTP093,2,4,WORD,1245,3 +LTP093,2,4,WORD,38,4 +LTP093,2,4,WORD,386,5 +LTP093,2,4,WORD,820,6 +LTP093,2,4,WORD,313,7 +LTP093,2,4,WORD,1123,8 +LTP093,2,4,WORD,766,9 +LTP093,2,4,WORD,299,10 +LTP093,2,4,WORD,1243,11 +LTP093,2,4,WORD,691,12 +LTP093,2,4,WORD,134,13 +LTP093,2,4,WORD,579,14 +LTP093,2,4,WORD,1438,15 +LTP093,2,4,WORD,861,16 +LTP093,2,4,WORD,1629,17 +LTP093,2,4,WORD,1370,18 +LTP093,2,4,WORD,136,19 +LTP093,2,4,WORD,1171,20 +LTP093,2,4,WORD,405,21 +LTP093,2,4,WORD,84,22 +LTP093,2,4,WORD,1148,23 +LTP093,2,4,WORD,417,24 +LTP093,2,4,REC_WORD,405,21 +LTP093,2,4,REC_WORD,84,22 +LTP093,2,4,REC_WORD,1171,20 +LTP093,2,4,REC_WORD,136,19 +LTP093,2,4,REC_WORD,691,12 +LTP093,2,4,REC_WORD,134,13 +LTP093,2,4,REC_WORD,579,14 +LTP093,2,4,REC_WORD,1629,17 +LTP093,2,4,REC_WORD,1370,18 +LTP093,2,4,REC_WORD,1438,15 +LTP093,2,4,REC_WORD,861,16 +LTP093,2,4,REC_WORD,386,5 +LTP093,2,4,REC_WORD,820,6 +LTP093,2,4,REC_WORD,766,9 +LTP093,2,5,WORD,639,1 +LTP093,2,5,WORD,24,2 +LTP093,2,5,WORD,1611,3 +LTP093,2,5,WORD,1532,4 +LTP093,2,5,WORD,972,5 +LTP093,2,5,WORD,633,6 +LTP093,2,5,WORD,248,7 +LTP093,2,5,WORD,790,8 +LTP093,2,5,WORD,699,9 +LTP093,2,5,WORD,501,10 +LTP093,2,5,WORD,929,11 +LTP093,2,5,WORD,1331,12 +LTP093,2,5,WORD,530,13 +LTP093,2,5,WORD,258,14 +LTP093,2,5,WORD,1139,15 +LTP093,2,5,WORD,1626,16 +LTP093,2,5,WORD,982,17 +LTP093,2,5,WORD,570,18 +LTP093,2,5,WORD,497,19 +LTP093,2,5,WORD,754,20 +LTP093,2,5,WORD,1503,21 +LTP093,2,5,WORD,340,22 +LTP093,2,5,WORD,341,23 +LTP093,2,5,WORD,442,24 +LTP093,2,5,REC_WORD,442,24 +LTP093,2,5,REC_WORD,754,20 +LTP093,2,5,REC_WORD,929,11 +LTP093,2,5,REC_WORD,1331,12 +LTP093,2,5,REC_WORD,1139,15 +LTP093,2,5,REC_WORD,1626,16 +LTP093,2,5,REC_WORD,639,1 +LTP093,2,5,REC_WORD,24,2 +LTP093,2,5,REC_WORD,972,5 +LTP093,2,5,REC_WORD,633,6 +LTP093,2,5,REC_WORD,1611,3 +LTP093,2,6,WORD,778,1 +LTP093,2,6,WORD,1176,2 +LTP093,2,6,WORD,670,3 +LTP093,2,6,WORD,1369,4 +LTP093,2,6,WORD,976,5 +LTP093,2,6,WORD,1158,6 +LTP093,2,6,WORD,508,7 +LTP093,2,6,WORD,728,8 +LTP093,2,6,WORD,1143,9 +LTP093,2,6,WORD,1405,10 +LTP093,2,6,WORD,496,11 +LTP093,2,6,WORD,731,12 +LTP093,2,6,WORD,642,13 +LTP093,2,6,WORD,1299,14 +LTP093,2,6,WORD,593,15 +LTP093,2,6,WORD,447,16 +LTP093,2,6,WORD,1414,17 +LTP093,2,6,WORD,682,18 +LTP093,2,6,WORD,372,19 +LTP093,2,6,WORD,1508,20 +LTP093,2,6,WORD,1024,21 +LTP093,2,6,WORD,235,22 +LTP093,2,6,WORD,467,23 +LTP093,2,6,WORD,955,24 +LTP093,2,6,REC_WORD,778,1 +LTP093,2,6,REC_WORD,1176,2 +LTP093,2,6,REC_WORD,670,3 +LTP093,2,6,REC_WORD,1369,4 +LTP093,2,6,REC_WORD,372,19 +LTP093,2,6,REC_WORD,1508,20 +LTP093,2,6,REC_WORD,728,8 +LTP093,2,6,REC_WORD,1299,14 +LTP093,2,6,REC_WORD,593,15 +LTP093,2,6,REC_WORD,976,5 +LTP093,2,6,REC_WORD,1158,6 +LTP093,2,6,REC_WORD,467,23 +LTP093,2,7,WORD,1373,1 +LTP093,2,7,WORD,1022,2 +LTP093,2,7,WORD,708,3 +LTP093,2,7,WORD,1251,4 +LTP093,2,7,WORD,1242,5 +LTP093,2,7,WORD,980,6 +LTP093,2,7,WORD,1132,7 +LTP093,2,7,WORD,1111,8 +LTP093,2,7,WORD,600,9 +LTP093,2,7,WORD,519,10 +LTP093,2,7,WORD,977,11 +LTP093,2,7,WORD,658,12 +LTP093,2,7,WORD,1079,13 +LTP093,2,7,WORD,1238,14 +LTP093,2,7,WORD,908,15 +LTP093,2,7,WORD,1488,16 +LTP093,2,7,WORD,745,17 +LTP093,2,7,WORD,1203,18 +LTP093,2,7,WORD,1544,19 +LTP093,2,7,WORD,1357,20 +LTP093,2,7,WORD,1524,21 +LTP093,2,7,WORD,169,22 +LTP093,2,7,WORD,1423,23 +LTP093,2,7,WORD,1580,24 +LTP093,2,7,REC_WORD,708,3 +LTP093,2,7,REC_WORD,1251,4 +LTP093,2,7,REC_WORD,169,22 +LTP093,2,7,REC_WORD,1423,23 +LTP093,2,7,REC_WORD,1524,21 +LTP093,2,7,REC_WORD,1357,20 +LTP093,2,7,REC_WORD,600,9 +LTP093,2,7,REC_WORD,519,10 +LTP093,2,7,REC_WORD,908,15 +LTP093,2,7,REC_WORD,1488,16 +LTP093,2,7,REC_WORD,745,17 +LTP093,2,7,REC_WORD,1079,13 +LTP093,2,7,REC_WORD,1238,14 +LTP093,2,7,REC_WORD,977,11 +LTP093,2,7,REC_WORD,658,12 +LTP093,2,7,REC_WORD,1373,1 +LTP093,2,8,WORD,794,1 +LTP093,2,8,WORD,63,2 +LTP093,2,8,WORD,1194,3 +LTP093,2,8,WORD,707,4 +LTP093,2,8,WORD,253,5 +LTP093,2,8,WORD,1036,6 +LTP093,2,8,WORD,1341,7 +LTP093,2,8,WORD,885,8 +LTP093,2,8,WORD,1535,9 +LTP093,2,8,WORD,1159,10 +LTP093,2,8,WORD,343,11 +LTP093,2,8,WORD,565,12 +LTP093,2,8,WORD,1011,13 +LTP093,2,8,WORD,724,14 +LTP093,2,8,WORD,323,15 +LTP093,2,8,WORD,325,16 +LTP093,2,8,WORD,1545,17 +LTP093,2,8,WORD,1486,18 +LTP093,2,8,WORD,601,19 +LTP093,2,8,WORD,1256,20 +LTP093,2,8,WORD,760,21 +LTP093,2,8,WORD,1426,22 +LTP093,2,8,WORD,199,23 +LTP093,2,8,WORD,591,24 +LTP093,2,8,REC_WORD,591,24 +LTP093,2,8,REC_WORD,199,23 +LTP093,2,8,REC_WORD,1194,3 +LTP093,2,8,REC_WORD,707,4 +LTP093,2,8,REC_WORD,601,19 +LTP093,2,8,REC_WORD,760,21 +LTP093,2,8,REC_WORD,1426,22 +LTP093,2,8,REC_WORD,1256,20 +LTP093,2,9,WORD,989,1 +LTP093,2,9,WORD,660,2 +LTP093,2,9,WORD,922,3 +LTP093,2,9,WORD,697,4 +LTP093,2,9,WORD,383,5 +LTP093,2,9,WORD,870,6 +LTP093,2,9,WORD,733,7 +LTP093,2,9,WORD,732,8 +LTP093,2,9,WORD,278,9 +LTP093,2,9,WORD,947,10 +LTP093,2,9,WORD,316,11 +LTP093,2,9,WORD,1460,12 +LTP093,2,9,WORD,1049,13 +LTP093,2,9,WORD,937,14 +LTP093,2,9,WORD,592,15 +LTP093,2,9,WORD,776,16 +LTP093,2,9,WORD,485,17 +LTP093,2,9,WORD,1221,18 +LTP093,2,9,WORD,240,19 +LTP093,2,9,WORD,100,20 +LTP093,2,9,WORD,1021,21 +LTP093,2,9,WORD,1280,22 +LTP093,2,9,WORD,960,23 +LTP093,2,9,WORD,1330,24 +LTP093,2,9,REC_WORD,960,23 +LTP093,2,9,REC_WORD,1330,24 +LTP093,2,9,REC_WORD,592,15 +LTP093,2,9,REC_WORD,776,16 +LTP093,2,9,REC_WORD,485,17 +LTP093,2,9,REC_WORD,383,5 +LTP093,2,9,REC_WORD,660,2 +LTP093,2,9,REC_WORD,278,9 +LTP093,2,9,REC_WORD,947,10 +LTP093,2,9,REC_WORD,316,11 +LTP093,2,9,REC_WORD,1460,12 +LTP093,2,10,WORD,866,1 +LTP093,2,10,WORD,795,2 +LTP093,2,10,WORD,1075,3 +LTP093,2,10,WORD,801,4 +LTP093,2,10,WORD,1592,5 +LTP093,2,10,WORD,1505,6 +LTP093,2,10,WORD,987,7 +LTP093,2,10,WORD,1326,8 +LTP093,2,10,WORD,83,9 +LTP093,2,10,WORD,254,10 +LTP093,2,10,WORD,812,11 +LTP093,2,10,WORD,1368,12 +LTP093,2,10,WORD,1071,13 +LTP093,2,10,WORD,718,14 +LTP093,2,10,WORD,1416,15 +LTP093,2,10,WORD,1149,16 +LTP093,2,10,WORD,492,17 +LTP093,2,10,WORD,384,18 +LTP093,2,10,WORD,211,19 +LTP093,2,10,WORD,1197,20 +LTP093,2,10,WORD,1275,21 +LTP093,2,10,WORD,1461,22 +LTP093,2,10,WORD,667,23 +LTP093,2,10,WORD,94,24 +LTP093,2,10,REC_WORD,1416,15 +LTP093,2,10,REC_WORD,1149,16 +LTP093,2,10,REC_WORD,492,17 +LTP093,2,10,REC_WORD,384,18 +LTP093,2,10,REC_WORD,866,1 +LTP093,2,10,REC_WORD,801,4 +LTP093,2,10,REC_WORD,1075,3 +LTP093,2,10,REC_WORD,1592,5 +LTP093,2,10,REC_WORD,1505,6 +LTP093,2,10,REC_WORD,987,7 +LTP093,2,10,REC_WORD,83,9 +LTP093,2,10,REC_WORD,254,10 +LTP093,2,10,REC_WORD,796,-999 +LTP093,2,11,WORD,1581,1 +LTP093,2,11,WORD,994,2 +LTP093,2,11,WORD,66,3 +LTP093,2,11,WORD,1000,4 +LTP093,2,11,WORD,1565,5 +LTP093,2,11,WORD,476,6 +LTP093,2,11,WORD,874,7 +LTP093,2,11,WORD,614,8 +LTP093,2,11,WORD,869,9 +LTP093,2,11,WORD,91,10 +LTP093,2,11,WORD,1385,11 +LTP093,2,11,WORD,1403,12 +LTP093,2,11,WORD,82,13 +LTP093,2,11,WORD,1209,14 +LTP093,2,11,WORD,378,15 +LTP093,2,11,WORD,397,16 +LTP093,2,11,WORD,737,17 +LTP093,2,11,WORD,1113,18 +LTP093,2,11,WORD,400,19 +LTP093,2,11,WORD,1609,20 +LTP093,2,11,WORD,171,21 +LTP093,2,11,WORD,872,22 +LTP093,2,11,WORD,858,23 +LTP093,2,11,WORD,395,24 +LTP093,2,11,REC_WORD,400,19 +LTP093,2,11,REC_WORD,1609,20 +LTP093,2,11,REC_WORD,737,17 +LTP093,2,11,REC_WORD,1113,18 +LTP093,2,11,REC_WORD,171,21 +LTP093,2,11,REC_WORD,872,22 +LTP093,2,11,REC_WORD,858,23 +LTP093,2,11,REC_WORD,395,24 +LTP093,2,11,REC_WORD,1581,1 +LTP093,2,11,REC_WORD,994,2 +LTP093,2,11,REC_WORD,66,3 +LTP093,2,11,REC_WORD,1000,4 +LTP093,2,11,REC_WORD,869,9 +LTP093,2,11,REC_WORD,91,10 +LTP093,2,11,REC_WORD,874,7 +LTP093,2,11,REC_WORD,614,8 +LTP093,2,12,WORD,187,1 +LTP093,2,12,WORD,620,2 +LTP093,2,12,WORD,307,3 +LTP093,2,12,WORD,177,4 +LTP093,2,12,WORD,1587,5 +LTP093,2,12,WORD,33,6 +LTP093,2,12,WORD,1192,7 +LTP093,2,12,WORD,1527,8 +LTP093,2,12,WORD,439,9 +LTP093,2,12,WORD,295,10 +LTP093,2,12,WORD,1329,11 +LTP093,2,12,WORD,1522,12 +LTP093,2,12,WORD,1147,13 +LTP093,2,12,WORD,543,14 +LTP093,2,12,WORD,847,15 +LTP093,2,12,WORD,1301,16 +LTP093,2,12,WORD,437,17 +LTP093,2,12,WORD,689,18 +LTP093,2,12,WORD,541,19 +LTP093,2,12,WORD,1382,20 +LTP093,2,12,WORD,1472,21 +LTP093,2,12,WORD,1261,22 +LTP093,2,12,WORD,1358,23 +LTP093,2,12,WORD,304,24 +LTP093,2,12,REC_WORD,1472,21 +LTP093,2,12,REC_WORD,1261,22 +LTP093,2,12,REC_WORD,1358,23 +LTP093,2,12,REC_WORD,304,24 +LTP093,2,12,REC_WORD,1301,16 +LTP093,2,12,REC_WORD,847,15 +LTP093,2,12,REC_WORD,1192,7 +LTP093,2,12,REC_WORD,1527,8 +LTP093,2,12,REC_WORD,307,3 +LTP093,2,12,REC_WORD,177,4 +LTP093,2,12,REC_WORD,1329,11 +LTP093,2,12,REC_WORD,1522,12 +LTP093,2,12,REC_WORD,1147,13 +LTP093,2,12,REC_WORD,543,14 +LTP093,2,12,REC_WORD,187,1 +LTP093,2,12,REC_WORD,620,2 +LTP093,2,13,WORD,338,1 +LTP093,2,13,WORD,714,2 +LTP093,2,13,WORD,498,3 +LTP093,2,13,WORD,1085,4 +LTP093,2,13,WORD,315,5 +LTP093,2,13,WORD,1347,6 +LTP093,2,13,WORD,916,7 +LTP093,2,13,WORD,1295,8 +LTP093,2,13,WORD,659,9 +LTP093,2,13,WORD,623,10 +LTP093,2,13,WORD,1420,11 +LTP093,2,13,WORD,1456,12 +LTP093,2,13,WORD,198,13 +LTP093,2,13,WORD,210,14 +LTP093,2,13,WORD,804,15 +LTP093,2,13,WORD,142,16 +LTP093,2,13,WORD,1144,17 +LTP093,2,13,WORD,1536,18 +LTP093,2,13,WORD,1048,19 +LTP093,2,13,WORD,255,20 +LTP093,2,13,WORD,1415,21 +LTP093,2,13,WORD,1593,22 +LTP093,2,13,WORD,713,23 +LTP093,2,13,WORD,1215,24 +LTP093,2,13,REC_WORD,1415,21 +LTP093,2,13,REC_WORD,255,20 +LTP093,2,13,REC_WORD,1048,19 +LTP093,2,13,REC_WORD,315,5 +LTP093,2,13,REC_WORD,1347,6 +LTP093,2,13,REC_WORD,1593,22 +LTP093,2,13,REC_WORD,713,23 +LTP093,2,13,REC_WORD,1215,24 +LTP093,2,13,REC_WORD,338,1 +LTP093,2,13,REC_WORD,714,2 +LTP093,2,13,REC_WORD,498,3 +LTP093,2,13,REC_WORD,1085,4 +LTP093,2,14,WORD,1070,1 +LTP093,2,14,WORD,1292,2 +LTP093,2,14,WORD,1384,3 +LTP093,2,14,WORD,1051,4 +LTP093,2,14,WORD,6,5 +LTP093,2,14,WORD,410,6 +LTP093,2,14,WORD,1061,7 +LTP093,2,14,WORD,1224,8 +LTP093,2,14,WORD,157,9 +LTP093,2,14,WORD,166,10 +LTP093,2,14,WORD,13,11 +LTP093,2,14,WORD,1112,12 +LTP093,2,14,WORD,1086,13 +LTP093,2,14,WORD,216,14 +LTP093,2,14,WORD,919,15 +LTP093,2,14,WORD,1351,16 +LTP093,2,14,WORD,184,17 +LTP093,2,14,WORD,963,18 +LTP093,2,14,WORD,1533,19 +LTP093,2,14,WORD,483,20 +LTP093,2,14,WORD,1227,21 +LTP093,2,14,WORD,645,22 +LTP093,2,14,WORD,1411,23 +LTP093,2,14,WORD,407,24 +LTP093,2,14,REC_WORD,1533,19 +LTP093,2,14,REC_WORD,483,20 +LTP093,2,14,REC_WORD,1227,21 +LTP093,2,14,REC_WORD,645,22 +LTP093,2,14,REC_WORD,1292,2 +LTP093,2,14,REC_WORD,6,5 +LTP093,2,14,REC_WORD,1224,8 +LTP093,2,14,REC_WORD,1061,7 +LTP093,2,15,WORD,466,1 +LTP093,2,15,WORD,188,2 +LTP093,2,15,WORD,1633,3 +LTP093,2,15,WORD,1317,4 +LTP093,2,15,WORD,75,5 +LTP093,2,15,WORD,996,6 +LTP093,2,15,WORD,183,7 +LTP093,2,15,WORD,749,8 +LTP093,2,15,WORD,1557,9 +LTP093,2,15,WORD,1202,10 +LTP093,2,15,WORD,265,11 +LTP093,2,15,WORD,1448,12 +LTP093,2,15,WORD,873,13 +LTP093,2,15,WORD,12,14 +LTP093,2,15,WORD,755,15 +LTP093,2,15,WORD,559,16 +LTP093,2,15,WORD,1436,17 +LTP093,2,15,WORD,1047,18 +LTP093,2,15,WORD,719,19 +LTP093,2,15,WORD,675,20 +LTP093,2,15,WORD,1515,21 +LTP093,2,15,WORD,661,22 +LTP093,2,15,WORD,413,23 +LTP093,2,15,WORD,637,24 +LTP093,2,15,REC_WORD,413,23 +LTP093,2,15,REC_WORD,637,24 +LTP093,2,15,REC_WORD,1515,21 +LTP093,2,15,REC_WORD,661,22 +LTP093,2,15,REC_WORD,559,16 +LTP093,2,15,REC_WORD,755,15 +LTP093,2,15,REC_WORD,1436,17 +LTP093,2,15,REC_WORD,1047,18 +LTP093,2,15,REC_WORD,1557,9 +LTP093,2,15,REC_WORD,1202,10 +LTP093,2,15,REC_WORD,873,13 +LTP093,2,15,REC_WORD,12,14 +LTP093,2,15,REC_WORD,183,7 +LTP093,2,15,REC_WORD,749,8 +LTP093,2,15,REC_WORD,996,6 +LTP093,2,15,REC_WORD,75,5 +LTP093,2,16,WORD,829,1 +LTP093,2,16,WORD,1096,2 +LTP093,2,16,WORD,683,3 +LTP093,2,16,WORD,1496,4 +LTP093,2,16,WORD,723,5 +LTP093,2,16,WORD,796,6 +LTP093,2,16,WORD,857,7 +LTP093,2,16,WORD,1140,8 +LTP093,2,16,WORD,995,9 +LTP093,2,16,WORD,159,10 +LTP093,2,16,WORD,1262,11 +LTP093,2,16,WORD,912,12 +LTP093,2,16,WORD,997,13 +LTP093,2,16,WORD,940,14 +LTP093,2,16,WORD,512,15 +LTP093,2,16,WORD,798,16 +LTP093,2,16,WORD,575,17 +LTP093,2,16,WORD,590,18 +LTP093,2,16,WORD,783,19 +LTP093,2,16,WORD,175,20 +LTP093,2,16,WORD,567,21 +LTP093,2,16,WORD,1520,22 +LTP093,2,16,WORD,1092,23 +LTP093,2,16,WORD,598,24 +LTP093,2,16,REC_WORD,1520,22 +LTP093,2,16,REC_WORD,1092,23 +LTP093,2,16,REC_WORD,175,20 +LTP093,2,16,REC_WORD,567,21 +LTP093,2,16,REC_WORD,798,16 +LTP093,2,16,REC_WORD,575,17 +LTP093,2,16,REC_WORD,590,18 +LTP093,2,16,REC_WORD,783,19 +LTP093,2,16,REC_WORD,1096,2 +LTP093,2,16,REC_WORD,829,1 +LTP093,2,16,REC_WORD,912,12 +LTP093,2,16,REC_WORD,997,13 +LTP093,2,16,REC_WORD,683,3 +LTP093,2,16,REC_WORD,1496,4 +LTP093,2,16,REC_WORD,796,6 +LTP093,2,16,REC_WORD,857,7 +LTP093,2,16,REC_WORD,1140,8 +LTP093,2,16,REC_WORD,995,9 +LTP093,2,16,REC_WORD,159,10 +LTP093,2,17,WORD,635,1 +LTP093,2,17,WORD,446,2 +LTP093,2,17,WORD,605,3 +LTP093,2,17,WORD,900,4 +LTP093,2,17,WORD,573,5 +LTP093,2,17,WORD,734,6 +LTP093,2,17,WORD,1511,7 +LTP093,2,17,WORD,139,8 +LTP093,2,17,WORD,1287,9 +LTP093,2,17,WORD,1501,10 +LTP093,2,17,WORD,1271,11 +LTP093,2,17,WORD,388,12 +LTP093,2,17,WORD,782,13 +LTP093,2,17,WORD,752,14 +LTP093,2,17,WORD,441,15 +LTP093,2,17,WORD,328,16 +LTP093,2,17,WORD,1293,17 +LTP093,2,17,WORD,800,18 +LTP093,2,17,WORD,1605,19 +LTP093,2,17,WORD,1272,20 +LTP093,2,17,WORD,64,21 +LTP093,2,17,WORD,1345,22 +LTP093,2,17,WORD,959,23 +LTP093,2,17,WORD,1027,24 +LTP093,2,17,REC_WORD,64,21 +LTP093,2,17,REC_WORD,1345,22 +LTP093,2,17,REC_WORD,959,23 +LTP093,2,17,REC_WORD,-1,-999 +LTP093,2,17,REC_WORD,1027,24 +LTP093,2,17,REC_WORD,388,12 +LTP093,2,17,REC_WORD,1293,17 +LTP093,2,17,REC_WORD,752,14 +LTP093,2,17,REC_WORD,635,1 +LTP093,2,17,REC_WORD,446,2 +LTP093,2,17,REC_WORD,605,3 +LTP093,2,17,REC_WORD,900,4 +LTP093,2,17,REC_WORD,734,6 +LTP093,2,17,REC_WORD,573,5 +LTP093,2,17,REC_WORD,1511,7 +LTP093,2,17,REC_WORD,139,8 +LTP093,2,18,WORD,1200,1 +LTP093,2,18,WORD,852,2 +LTP093,2,18,WORD,1173,3 +LTP093,2,18,WORD,68,4 +LTP093,2,18,WORD,1124,5 +LTP093,2,18,WORD,1356,6 +LTP093,2,18,WORD,1521,7 +LTP093,2,18,WORD,194,8 +LTP093,2,18,WORD,901,9 +LTP093,2,18,WORD,618,10 +LTP093,2,18,WORD,55,11 +LTP093,2,18,WORD,1493,12 +LTP093,2,18,WORD,553,13 +LTP093,2,18,WORD,164,14 +LTP093,2,18,WORD,1095,15 +LTP093,2,18,WORD,1076,16 +LTP093,2,18,WORD,1229,17 +LTP093,2,18,WORD,145,18 +LTP093,2,18,WORD,1006,19 +LTP093,2,18,WORD,1519,20 +LTP093,2,18,WORD,726,21 +LTP093,2,18,WORD,203,22 +LTP093,2,18,WORD,1060,23 +LTP093,2,18,WORD,584,24 +LTP093,2,18,REC_WORD,726,21 +LTP093,2,18,REC_WORD,203,22 +LTP093,2,18,REC_WORD,584,24 +LTP093,2,18,REC_WORD,1060,23 +LTP093,2,18,REC_WORD,553,13 +LTP093,2,18,REC_WORD,1493,12 +LTP093,2,18,REC_WORD,1076,16 +LTP093,2,18,REC_WORD,1521,7 +LTP093,2,18,REC_WORD,194,8 +LTP093,2,18,REC_WORD,901,9 +LTP093,2,18,REC_WORD,618,10 +LTP093,2,18,REC_WORD,1124,5 +LTP093,2,18,REC_WORD,1356,6 +LTP093,2,18,REC_WORD,1200,1 +LTP093,2,18,REC_WORD,852,2 +LTP093,2,18,REC_WORD,68,4 +LTP093,2,18,REC_WORD,1173,3 +LTP093,2,19,WORD,1057,1 +LTP093,2,19,WORD,1407,2 +LTP093,2,19,WORD,1031,3 +LTP093,2,19,WORD,654,4 +LTP093,2,19,WORD,488,5 +LTP093,2,19,WORD,1312,6 +LTP093,2,19,WORD,1374,7 +LTP093,2,19,WORD,1219,8 +LTP093,2,19,WORD,1440,9 +LTP093,2,19,WORD,133,10 +LTP093,2,19,WORD,137,11 +LTP093,2,19,WORD,1017,12 +LTP093,2,19,WORD,162,13 +LTP093,2,19,WORD,1284,14 +LTP093,2,19,WORD,606,15 +LTP093,2,19,WORD,179,16 +LTP093,2,19,WORD,1400,17 +LTP093,2,19,WORD,389,18 +LTP093,2,19,WORD,764,19 +LTP093,2,19,WORD,1319,20 +LTP093,2,19,WORD,1088,21 +LTP093,2,19,WORD,871,22 +LTP093,2,19,WORD,792,23 +LTP093,2,19,WORD,1427,24 +LTP093,2,19,REC_WORD,871,22 +LTP093,2,19,REC_WORD,792,23 +LTP093,2,19,REC_WORD,1319,20 +LTP093,2,19,REC_WORD,488,5 +LTP093,2,19,REC_WORD,1312,6 +LTP093,2,19,REC_WORD,1374,7 +LTP093,2,19,REC_WORD,1219,8 +LTP093,2,19,REC_WORD,1440,9 +LTP093,2,19,REC_WORD,133,10 +LTP093,2,19,REC_WORD,179,16 +LTP093,2,19,REC_WORD,1284,14 +LTP093,2,19,REC_WORD,162,13 +LTP093,2,20,WORD,1422,1 +LTP093,2,20,WORD,1258,2 +LTP093,2,20,WORD,494,3 +LTP093,2,20,WORD,888,4 +LTP093,2,20,WORD,1392,5 +LTP093,2,20,WORD,1065,6 +LTP093,2,20,WORD,1015,7 +LTP093,2,20,WORD,1037,8 +LTP093,2,20,WORD,1013,9 +LTP093,2,20,WORD,1093,10 +LTP093,2,20,WORD,1636,11 +LTP093,2,20,WORD,883,12 +LTP093,2,20,WORD,29,13 +LTP093,2,20,WORD,738,14 +LTP093,2,20,WORD,1119,15 +LTP093,2,20,WORD,109,16 +LTP093,2,20,WORD,1562,17 +LTP093,2,20,WORD,1478,18 +LTP093,2,20,WORD,125,19 +LTP093,2,20,WORD,628,20 +LTP093,2,20,WORD,702,21 +LTP093,2,20,WORD,1087,22 +LTP093,2,20,WORD,809,23 +LTP093,2,20,WORD,769,24 +LTP093,2,20,REC_WORD,1015,7 +LTP093,2,20,REC_WORD,1037,8 +LTP093,2,20,REC_WORD,1013,9 +LTP093,2,20,REC_WORD,1093,10 +LTP093,2,20,REC_WORD,1119,15 +LTP093,2,20,REC_WORD,1562,17 +LTP093,2,20,REC_WORD,1478,18 +LTP093,2,20,REC_WORD,109,16 +LTP093,2,20,REC_WORD,125,19 +LTP093,2,20,REC_WORD,702,21 +LTP093,2,20,REC_WORD,1087,22 +LTP093,2,20,REC_WORD,809,23 +LTP093,2,21,WORD,520,1 +LTP093,2,21,WORD,932,2 +LTP093,2,21,WORD,1089,3 +LTP093,2,21,WORD,1526,4 +LTP093,2,21,WORD,1315,5 +LTP093,2,21,WORD,1371,6 +LTP093,2,21,WORD,604,7 +LTP093,2,21,WORD,627,8 +LTP093,2,21,WORD,358,9 +LTP093,2,21,WORD,435,10 +LTP093,2,21,WORD,431,11 +LTP093,2,21,WORD,1530,12 +LTP093,2,21,WORD,1628,13 +LTP093,2,21,WORD,696,14 +LTP093,2,21,WORD,793,15 +LTP093,2,21,WORD,267,16 +LTP093,2,21,WORD,119,17 +LTP093,2,21,WORD,546,18 +LTP093,2,21,WORD,1019,19 +LTP093,2,21,WORD,840,20 +LTP093,2,21,WORD,821,21 +LTP093,2,21,WORD,415,22 +LTP093,2,21,WORD,1125,23 +LTP093,2,21,WORD,392,24 +LTP093,2,21,REC_WORD,821,21 +LTP093,2,21,REC_WORD,415,22 +LTP093,2,21,REC_WORD,1125,23 +LTP093,2,21,REC_WORD,392,24 +LTP093,2,21,REC_WORD,604,7 +LTP093,2,21,REC_WORD,627,8 +LTP093,2,21,REC_WORD,1530,12 +LTP093,2,21,REC_WORD,431,11 +LTP093,2,21,REC_WORD,932,2 +LTP093,2,21,REC_WORD,520,1 +LTP093,2,21,REC_WORD,1089,3 +LTP093,2,21,REC_WORD,1526,4 +LTP093,2,22,WORD,1621,1 +LTP093,2,22,WORD,953,2 +LTP093,2,22,WORD,879,3 +LTP093,2,22,WORD,1395,4 +LTP093,2,22,WORD,1620,5 +LTP093,2,22,WORD,1282,6 +LTP093,2,22,WORD,1622,7 +LTP093,2,22,WORD,715,8 +LTP093,2,22,WORD,327,9 +LTP093,2,22,WORD,1246,10 +LTP093,2,22,WORD,1184,11 +LTP093,2,22,WORD,168,12 +LTP093,2,22,WORD,848,13 +LTP093,2,22,WORD,1388,14 +LTP093,2,22,WORD,895,15 +LTP093,2,22,WORD,1250,16 +LTP093,2,22,WORD,1009,17 +LTP093,2,22,WORD,1618,18 +LTP093,2,22,WORD,1332,19 +LTP093,2,22,WORD,1546,20 +LTP093,2,22,WORD,35,21 +LTP093,2,22,WORD,988,22 +LTP093,2,22,WORD,368,23 +LTP093,2,22,WORD,108,24 +LTP093,2,22,REC_WORD,1250,16 +LTP093,2,22,REC_WORD,1009,17 +LTP093,2,22,REC_WORD,1621,1 +LTP093,2,22,REC_WORD,953,2 +LTP093,2,22,REC_WORD,1395,4 +LTP093,2,22,REC_WORD,879,3 +LTP093,2,22,REC_WORD,1620,5 +LTP093,2,22,REC_WORD,1282,6 +LTP093,2,22,REC_WORD,1282,6 +LTP093,2,22,REC_WORD,715,8 +LTP093,2,22,REC_WORD,108,24 +LTP093,2,22,REC_WORD,848,13 +LTP093,2,22,REC_WORD,168,12 +LTP093,2,23,WORD,127,1 +LTP093,2,23,WORD,1170,2 +LTP093,2,23,WORD,1635,3 +LTP093,2,23,WORD,403,4 +LTP093,2,23,WORD,580,5 +LTP093,2,23,WORD,1155,6 +LTP093,2,23,WORD,974,7 +LTP093,2,23,WORD,1468,8 +LTP093,2,23,WORD,1606,9 +LTP093,2,23,WORD,484,10 +LTP093,2,23,WORD,834,11 +LTP093,2,23,WORD,662,12 +LTP093,2,23,WORD,1152,13 +LTP093,2,23,WORD,1343,14 +LTP093,2,23,WORD,1183,15 +LTP093,2,23,WORD,1625,16 +LTP093,2,23,WORD,88,17 +LTP093,2,23,WORD,684,18 +LTP093,2,23,WORD,373,19 +LTP093,2,23,WORD,86,20 +LTP093,2,23,WORD,1032,21 +LTP093,2,23,WORD,1104,22 +LTP093,2,23,WORD,143,23 +LTP093,2,23,WORD,1578,24 +LTP093,2,23,REC_WORD,834,11 +LTP093,2,23,REC_WORD,86,20 +LTP093,2,23,REC_WORD,1032,21 +LTP093,2,23,REC_WORD,662,12 +LTP093,2,23,REC_WORD,1152,13 +LTP093,2,23,REC_WORD,1104,22 +LTP093,2,23,REC_WORD,143,23 +LTP093,2,23,REC_WORD,1578,24 +LTP093,2,24,WORD,172,1 +LTP093,2,24,WORD,280,2 +LTP093,2,24,WORD,353,3 +LTP093,2,24,WORD,1631,4 +LTP093,2,24,WORD,495,5 +LTP093,2,24,WORD,1260,6 +LTP093,2,24,WORD,814,7 +LTP093,2,24,WORD,1066,8 +LTP093,2,24,WORD,843,9 +LTP093,2,24,WORD,390,10 +LTP093,2,24,WORD,346,11 +LTP093,2,24,WORD,935,12 +LTP093,2,24,WORD,401,13 +LTP093,2,24,WORD,1080,14 +LTP093,2,24,WORD,761,15 +LTP093,2,24,WORD,223,16 +LTP093,2,24,WORD,561,17 +LTP093,2,24,WORD,46,18 +LTP093,2,24,WORD,1035,19 +LTP093,2,24,WORD,878,20 +LTP093,2,24,WORD,569,21 +LTP093,2,24,WORD,1294,22 +LTP093,2,24,WORD,5,23 +LTP093,2,24,WORD,1328,24 +LTP093,2,24,REC_WORD,561,17 +LTP093,2,24,REC_WORD,46,18 +LTP093,2,24,REC_WORD,569,21 +LTP093,2,24,REC_WORD,1294,22 +LTP093,2,24,REC_WORD,1328,24 +LTP093,2,24,REC_WORD,5,23 +LTP093,2,24,REC_WORD,172,1 +LTP093,2,24,REC_WORD,280,2 +LTP093,2,24,REC_WORD,1631,4 +LTP093,3,1,WORD,764,1 +LTP093,3,1,WORD,1293,2 +LTP093,3,1,WORD,1408,3 +LTP093,3,1,WORD,203,4 +LTP093,3,1,WORD,1092,5 +LTP093,3,1,WORD,569,6 +LTP093,3,1,WORD,1426,7 +LTP093,3,1,WORD,136,8 +LTP093,3,1,WORD,1400,9 +LTP093,3,1,WORD,1140,10 +LTP093,3,1,WORD,660,11 +LTP093,3,1,WORD,389,12 +LTP093,3,1,WORD,441,13 +LTP093,3,1,WORD,1557,14 +LTP093,3,1,WORD,446,15 +LTP093,3,1,WORD,1621,16 +LTP093,3,1,WORD,304,17 +LTP093,3,1,WORD,800,18 +LTP093,3,1,WORD,323,19 +LTP093,3,1,WORD,1405,20 +LTP093,3,1,WORD,553,21 +LTP093,3,1,WORD,1319,22 +LTP093,3,1,WORD,1080,23 +LTP093,3,1,WORD,707,24 +LTP093,3,1,REC_WORD,764,1 +LTP093,3,1,REC_WORD,1293,2 +LTP093,3,1,REC_WORD,1092,5 +LTP093,3,1,REC_WORD,569,6 +LTP093,3,1,REC_WORD,304,17 +LTP093,3,1,REC_WORD,800,18 +LTP093,3,1,REC_WORD,323,19 +LTP093,3,1,REC_WORD,1405,20 +LTP093,3,1,REC_WORD,553,21 +LTP093,3,1,REC_WORD,1319,22 +LTP093,3,1,REC_WORD,1080,23 +LTP093,3,1,REC_WORD,707,24 +LTP093,3,1,REC_WORD,1557,14 +LTP093,3,2,WORD,1605,1 +LTP093,3,2,WORD,1358,2 +LTP093,3,2,WORD,1371,3 +LTP093,3,2,WORD,223,4 +LTP093,3,2,WORD,1024,5 +LTP093,3,2,WORD,895,6 +LTP093,3,2,WORD,1493,7 +LTP093,3,2,WORD,974,8 +LTP093,3,2,WORD,501,9 +LTP093,3,2,WORD,610,10 +LTP093,3,2,WORD,697,11 +LTP093,3,2,WORD,1036,12 +LTP093,3,2,WORD,125,13 +LTP093,3,2,WORD,198,14 +LTP093,3,2,WORD,280,15 +LTP093,3,2,WORD,1544,16 +LTP093,3,2,WORD,812,17 +LTP093,3,2,WORD,995,18 +LTP093,3,2,WORD,1242,19 +LTP093,3,2,WORD,336,20 +LTP093,3,2,WORD,691,21 +LTP093,3,2,WORD,1486,22 +LTP093,3,2,WORD,308,23 +LTP093,3,2,WORD,732,24 +LTP093,3,2,REC_WORD,280,15 +LTP093,3,2,REC_WORD,1544,16 +LTP093,3,2,REC_WORD,995,18 +LTP093,3,2,REC_WORD,812,17 +LTP093,3,2,REC_WORD,1605,1 +LTP093,3,2,REC_WORD,1358,2 +LTP093,3,3,WORD,1505,1 +LTP093,3,3,WORD,988,2 +LTP093,3,3,WORD,590,3 +LTP093,3,3,WORD,628,4 +LTP093,3,3,WORD,1262,5 +LTP093,3,3,WORD,1260,6 +LTP093,3,3,WORD,1000,7 +LTP093,3,3,WORD,1159,8 +LTP093,3,3,WORD,814,9 +LTP093,3,3,WORD,874,10 +LTP093,3,3,WORD,1436,11 +LTP093,3,3,WORD,403,12 +LTP093,3,3,WORD,726,13 +LTP093,3,3,WORD,1022,14 +LTP093,3,3,WORD,371,15 +LTP093,3,3,WORD,989,16 +LTP093,3,3,WORD,1587,17 +LTP093,3,3,WORD,737,18 +LTP093,3,3,WORD,959,19 +LTP093,3,3,WORD,833,20 +LTP093,3,3,WORD,1315,21 +LTP093,3,3,WORD,683,22 +LTP093,3,3,WORD,1581,23 +LTP093,3,3,WORD,1224,24 +LTP093,3,3,REC_WORD,1315,21 +LTP093,3,3,REC_WORD,683,22 +LTP093,3,3,REC_WORD,1581,23 +LTP093,3,3,REC_WORD,1224,24 +LTP093,3,3,REC_WORD,814,9 +LTP093,3,3,REC_WORD,628,4 +LTP093,3,3,REC_WORD,590,3 +LTP093,3,3,REC_WORD,1505,1 +LTP093,3,3,REC_WORD,988,2 +LTP093,3,4,WORD,1203,1 +LTP093,3,4,WORD,1144,2 +LTP093,3,4,WORD,1215,3 +LTP093,3,4,WORD,1172,4 +LTP093,3,4,WORD,1015,5 +LTP093,3,4,WORD,83,6 +LTP093,3,4,WORD,109,7 +LTP093,3,4,WORD,1200,8 +LTP093,3,4,WORD,637,9 +LTP093,3,4,WORD,1440,10 +LTP093,3,4,WORD,879,11 +LTP093,3,4,WORD,776,12 +LTP093,3,4,WORD,1496,13 +LTP093,3,4,WORD,1139,14 +LTP093,3,4,WORD,1572,15 +LTP093,3,4,WORD,1355,16 +LTP093,3,4,WORD,199,17 +LTP093,3,4,WORD,1618,18 +LTP093,3,4,WORD,210,19 +LTP093,3,4,WORD,714,20 +LTP093,3,4,WORD,1526,21 +LTP093,3,4,WORD,1625,22 +LTP093,3,4,WORD,761,23 +LTP093,3,4,WORD,682,24 +LTP093,3,4,REC_WORD,1526,21 +LTP093,3,4,REC_WORD,1625,22 +LTP093,3,4,REC_WORD,761,23 +LTP093,3,4,REC_WORD,682,24 +LTP093,3,4,REC_WORD,210,19 +LTP093,3,4,REC_WORD,714,20 +LTP093,3,4,REC_WORD,1203,1 +LTP093,3,4,REC_WORD,199,17 +LTP093,3,4,REC_WORD,1618,18 +LTP093,3,4,REC_WORD,637,9 +LTP093,3,4,REC_WORD,1440,10 +LTP093,3,5,WORD,1124,1 +LTP093,3,5,WORD,706,2 +LTP093,3,5,WORD,1031,3 +LTP093,3,5,WORD,888,4 +LTP093,3,5,WORD,675,5 +LTP093,3,5,WORD,492,6 +LTP093,3,5,WORD,584,7 +LTP093,3,5,WORD,341,8 +LTP093,3,5,WORD,847,9 +LTP093,3,5,WORD,1009,10 +LTP093,3,5,WORD,1147,11 +LTP093,3,5,WORD,1631,12 +LTP093,3,5,WORD,1411,13 +LTP093,3,5,WORD,1070,14 +LTP093,3,5,WORD,240,15 +LTP093,3,5,WORD,749,16 +LTP093,3,5,WORD,388,17 +LTP093,3,5,WORD,13,18 +LTP093,3,5,WORD,661,19 +LTP093,3,5,WORD,88,20 +LTP093,3,5,WORD,1368,21 +LTP093,3,5,WORD,883,22 +LTP093,3,5,WORD,599,23 +LTP093,3,5,WORD,977,24 +LTP093,3,5,REC_WORD,661,19 +LTP093,3,5,REC_WORD,88,20 +LTP093,3,5,REC_WORD,1368,21 +LTP093,3,5,REC_WORD,883,22 +LTP093,3,5,REC_WORD,977,24 +LTP093,3,5,REC_WORD,599,23 +LTP093,3,5,REC_WORD,1124,1 +LTP093,3,5,REC_WORD,706,2 +LTP093,3,5,REC_WORD,1031,3 +LTP093,3,5,REC_WORD,888,4 +LTP093,3,5,REC_WORD,675,5 +LTP093,3,5,REC_WORD,492,6 +LTP093,3,5,REC_WORD,584,7 +LTP093,3,5,REC_WORD,341,8 +LTP093,3,5,REC_WORD,1147,11 +LTP093,3,6,WORD,235,1 +LTP093,3,6,WORD,1016,2 +LTP093,3,6,WORD,1292,3 +LTP093,3,6,WORD,33,4 +LTP093,3,6,WORD,1176,5 +LTP093,3,6,WORD,1117,6 +LTP093,3,6,WORD,64,7 +LTP093,3,6,WORD,1261,8 +LTP093,3,6,WORD,752,9 +LTP093,3,6,WORD,1197,10 +LTP093,3,6,WORD,1415,11 +LTP093,3,6,WORD,1282,12 +LTP093,3,6,WORD,585,13 +LTP093,3,6,WORD,783,14 +LTP093,3,6,WORD,937,15 +LTP093,3,6,WORD,1524,16 +LTP093,3,6,WORD,829,17 +LTP093,3,6,WORD,1044,18 +LTP093,3,6,WORD,316,19 +LTP093,3,6,WORD,1407,20 +LTP093,3,6,WORD,1369,21 +LTP093,3,6,WORD,620,22 +LTP093,3,6,WORD,495,23 +LTP093,3,6,WORD,1250,24 +LTP093,3,6,REC_WORD,1415,11 +LTP093,3,6,REC_WORD,1415,11 +LTP093,3,6,REC_WORD,752,9 +LTP093,3,6,REC_WORD,1197,10 +LTP093,3,6,REC_WORD,235,1 +LTP093,3,6,REC_WORD,1016,2 +LTP093,3,6,REC_WORD,33,4 +LTP093,3,6,REC_WORD,1292,3 +LTP093,3,6,REC_WORD,1176,5 +LTP093,3,6,REC_WORD,1117,6 +LTP093,3,6,REC_WORD,64,7 +LTP093,3,6,REC_WORD,1261,8 +LTP093,3,7,WORD,343,1 +LTP093,3,7,WORD,932,2 +LTP093,3,7,WORD,1593,3 +LTP093,3,7,WORD,633,4 +LTP093,3,7,WORD,1104,5 +LTP093,3,7,WORD,1520,6 +LTP093,3,7,WORD,1194,7 +LTP093,3,7,WORD,796,8 +LTP093,3,7,WORD,498,9 +LTP093,3,7,WORD,267,10 +LTP093,3,7,WORD,410,11 +LTP093,3,7,WORD,1343,12 +LTP093,3,7,WORD,1155,13 +LTP093,3,7,WORD,666,14 +LTP093,3,7,WORD,1345,15 +LTP093,3,7,WORD,104,16 +LTP093,3,7,WORD,1307,17 +LTP093,3,7,WORD,667,18 +LTP093,3,7,WORD,1521,19 +LTP093,3,7,WORD,1546,20 +LTP093,3,7,WORD,415,21 +LTP093,3,7,WORD,417,22 +LTP093,3,7,WORD,849,23 +LTP093,3,7,WORD,1519,24 +LTP093,3,7,REC_WORD,932,2 +LTP093,3,7,REC_WORD,343,1 +LTP093,3,7,REC_WORD,666,14 +LTP093,3,7,REC_WORD,1345,15 +LTP093,3,7,REC_WORD,1104,5 +LTP093,3,7,REC_WORD,1520,6 +LTP093,3,7,REC_WORD,104,16 +LTP093,3,7,REC_WORD,1307,17 +LTP093,3,7,REC_WORD,633,4 +LTP093,3,8,WORD,184,1 +LTP093,3,8,WORD,1095,2 +LTP093,3,8,WORD,1287,3 +LTP093,3,8,WORD,1532,4 +LTP093,3,8,WORD,68,5 +LTP093,3,8,WORD,1609,6 +LTP093,3,8,WORD,1633,7 +LTP093,3,8,WORD,1209,8 +LTP093,3,8,WORD,795,9 +LTP093,3,8,WORD,876,10 +LTP093,3,8,WORD,1098,11 +LTP093,3,8,WORD,1312,12 +LTP093,3,8,WORD,1232,13 +LTP093,3,8,WORD,1326,14 +LTP093,3,8,WORD,1112,15 +LTP093,3,8,WORD,601,16 +LTP093,3,8,WORD,1170,17 +LTP093,3,8,WORD,1183,18 +LTP093,3,8,WORD,771,19 +LTP093,3,8,WORD,885,20 +LTP093,3,8,WORD,1333,21 +LTP093,3,8,WORD,1301,22 +LTP093,3,8,WORD,580,23 +LTP093,3,8,WORD,476,24 +LTP093,3,8,REC_WORD,580,23 +LTP093,3,8,REC_WORD,476,24 +LTP093,3,8,REC_WORD,1170,17 +LTP093,3,8,REC_WORD,1183,18 +LTP093,3,8,REC_WORD,771,19 +LTP093,3,8,REC_WORD,885,20 +LTP093,3,8,REC_WORD,1609,6 +LTP093,3,8,REC_WORD,68,5 +LTP093,3,8,REC_WORD,1098,11 +LTP093,3,8,REC_WORD,1312,12 +LTP093,3,8,REC_WORD,1301,22 +LTP093,3,8,REC_WORD,1232,13 +LTP093,3,8,REC_WORD,1326,14 +LTP093,3,8,REC_WORD,601,16 +LTP093,3,8,REC_WORD,1112,15 +LTP093,3,9,WORD,953,1 +LTP093,3,9,WORD,183,2 +LTP093,3,9,WORD,346,3 +LTP093,3,9,WORD,827,4 +LTP093,3,9,WORD,383,5 +LTP093,3,9,WORD,866,6 +LTP093,3,9,WORD,137,7 +LTP093,3,9,WORD,166,8 +LTP093,3,9,WORD,226,9 +LTP093,3,9,WORD,901,10 +LTP093,3,9,WORD,1530,11 +LTP093,3,9,WORD,1451,12 +LTP093,3,9,WORD,327,13 +LTP093,3,9,WORD,598,14 +LTP093,3,9,WORD,338,15 +LTP093,3,9,WORD,38,16 +LTP093,3,9,WORD,996,17 +LTP093,3,9,WORD,1622,18 +LTP093,3,9,WORD,719,19 +LTP093,3,9,WORD,1158,20 +LTP093,3,9,WORD,1327,21 +LTP093,3,9,WORD,1280,22 +LTP093,3,9,WORD,840,23 +LTP093,3,9,WORD,893,24 +LTP093,3,9,REC_WORD,1622,18 +LTP093,3,9,REC_WORD,719,19 +LTP093,3,9,REC_WORD,1158,20 +LTP093,3,9,REC_WORD,1327,21 +LTP093,3,9,REC_WORD,996,17 +LTP093,3,9,REC_WORD,38,16 +LTP093,3,9,REC_WORD,953,1 +LTP093,3,9,REC_WORD,183,2 +LTP093,3,9,REC_WORD,346,3 +LTP093,3,9,REC_WORD,827,4 +LTP093,3,9,REC_WORD,383,5 +LTP093,3,9,REC_WORD,901,10 +LTP093,3,9,REC_WORD,1530,11 +LTP093,3,9,REC_WORD,1451,12 +LTP093,3,9,REC_WORD,226,9 +LTP093,3,9,REC_WORD,166,8 +LTP093,3,10,WORD,1275,1 +LTP093,3,10,WORD,715,2 +LTP093,3,10,WORD,10,3 +LTP093,3,10,WORD,1027,4 +LTP093,3,10,WORD,1328,5 +LTP093,3,10,WORD,1478,6 +LTP093,3,10,WORD,108,7 +LTP093,3,10,WORD,1202,8 +LTP093,3,10,WORD,1011,9 +LTP093,3,10,WORD,723,10 +LTP093,3,10,WORD,1294,11 +LTP093,3,10,WORD,670,12 +LTP093,3,10,WORD,900,13 +LTP093,3,10,WORD,622,14 +LTP093,3,10,WORD,1356,15 +LTP093,3,10,WORD,86,16 +LTP093,3,10,WORD,935,17 +LTP093,3,10,WORD,131,18 +LTP093,3,10,WORD,955,19 +LTP093,3,10,WORD,1427,20 +LTP093,3,10,WORD,794,21 +LTP093,3,10,WORD,922,22 +LTP093,3,10,WORD,1057,23 +LTP093,3,10,WORD,623,24 +LTP093,3,10,REC_WORD,1356,15 +LTP093,3,10,REC_WORD,86,16 +LTP093,3,10,REC_WORD,935,17 +LTP093,3,10,REC_WORD,131,18 +LTP093,3,10,REC_WORD,794,21 +LTP093,3,10,REC_WORD,922,22 +LTP093,3,10,REC_WORD,623,24 +LTP093,3,11,WORD,766,1 +LTP093,3,11,WORD,798,2 +LTP093,3,11,WORD,604,3 +LTP093,3,11,WORD,258,4 +LTP093,3,11,WORD,1284,5 +LTP093,3,11,WORD,171,6 +LTP093,3,11,WORD,567,7 +LTP093,3,11,WORD,1382,8 +LTP093,3,11,WORD,358,9 +LTP093,3,11,WORD,328,10 +LTP093,3,11,WORD,731,11 +LTP093,3,11,WORD,1221,12 +LTP093,3,11,WORD,200,13 +LTP093,3,11,WORD,1456,14 +LTP093,3,11,WORD,1219,15 +LTP093,3,11,WORD,1123,16 +LTP093,3,11,WORD,724,17 +LTP093,3,11,WORD,642,18 +LTP093,3,11,WORD,618,19 +LTP093,3,11,WORD,1420,20 +LTP093,3,11,WORD,175,21 +LTP093,3,11,WORD,940,22 +LTP093,3,11,WORD,591,23 +LTP093,3,11,WORD,1535,24 +LTP093,3,11,REC_WORD,642,18 +LTP093,3,11,REC_WORD,618,19 +LTP093,3,11,REC_WORD,1420,20 +LTP093,3,11,REC_WORD,175,21 +LTP093,3,11,REC_WORD,171,6 +LTP093,3,11,REC_WORD,604,3 +LTP093,3,11,REC_WORD,328,10 +LTP093,3,12,WORD,561,1 +LTP093,3,12,WORD,689,2 +LTP093,3,12,WORD,497,3 +LTP093,3,12,WORD,520,4 +LTP093,3,12,WORD,1545,5 +LTP093,3,12,WORD,29,6 +LTP093,3,12,WORD,769,7 +LTP093,3,12,WORD,1096,8 +LTP093,3,12,WORD,1199,9 +LTP093,3,12,WORD,1245,10 +LTP093,3,12,WORD,172,11 +LTP093,3,12,WORD,912,12 +LTP093,3,12,WORD,746,13 +LTP093,3,12,WORD,278,14 +LTP093,3,12,WORD,1611,15 +LTP093,3,12,WORD,407,16 +LTP093,3,12,WORD,1251,17 +LTP093,3,12,WORD,858,18 +LTP093,3,12,WORD,821,19 +LTP093,3,12,WORD,1395,20 +LTP093,3,12,WORD,1051,21 +LTP093,3,12,WORD,696,22 +LTP093,3,12,WORD,592,23 +LTP093,3,12,WORD,801,24 +LTP093,3,12,REC_WORD,696,22 +LTP093,3,12,REC_WORD,592,23 +LTP093,3,12,REC_WORD,520,4 +LTP093,3,12,REC_WORD,497,3 +LTP093,3,12,REC_WORD,1096,8 +LTP093,3,12,REC_WORD,1199,9 +LTP093,3,12,REC_WORD,407,16 +LTP093,3,12,REC_WORD,1251,17 +LTP093,3,12,REC_WORD,858,18 +LTP093,3,12,REC_WORD,821,19 +LTP093,3,12,REC_WORD,912,12 +LTP093,3,12,REC_WORD,746,13 +LTP093,3,12,REC_WORD,278,14 +LTP093,3,12,REC_WORD,1611,15 +LTP093,3,13,WORD,1562,1 +LTP093,3,13,WORD,1347,2 +LTP093,3,13,WORD,754,3 +LTP093,3,13,WORD,429,4 +LTP093,3,13,WORD,67,5 +LTP093,3,13,WORD,1580,6 +LTP093,3,13,WORD,871,7 +LTP093,3,13,WORD,755,8 +LTP093,3,13,WORD,1460,9 +LTP093,3,13,WORD,1626,10 +LTP093,3,13,WORD,24,11 +LTP093,3,13,WORD,1085,12 +LTP093,3,13,WORD,1527,13 +LTP093,3,13,WORD,187,14 +LTP093,3,13,WORD,976,15 +LTP093,3,13,WORD,606,16 +LTP093,3,13,WORD,1303,17 +LTP093,3,13,WORD,573,18 +LTP093,3,13,WORD,1048,19 +LTP093,3,13,WORD,254,20 +LTP093,3,13,WORD,339,21 +LTP093,3,13,WORD,565,22 +LTP093,3,13,WORD,1021,23 +LTP093,3,13,WORD,627,24 +LTP093,3,13,REC_WORD,1021,23 +LTP093,3,13,REC_WORD,627,24 +LTP093,3,13,REC_WORD,1562,1 +LTP093,3,13,REC_WORD,1347,2 +LTP093,3,13,REC_WORD,1048,19 +LTP093,3,13,REC_WORD,254,20 +LTP093,3,13,REC_WORD,1580,6 +LTP093,3,14,WORD,1171,1 +LTP093,3,14,WORD,134,2 +LTP093,3,14,WORD,162,3 +LTP093,3,14,WORD,484,4 +LTP093,3,14,WORD,519,5 +LTP093,3,14,WORD,408,6 +LTP093,3,14,WORD,100,7 +LTP093,3,14,WORD,873,8 +LTP093,3,14,WORD,684,9 +LTP093,3,14,WORD,413,10 +LTP093,3,14,WORD,496,11 +LTP093,3,14,WORD,980,12 +LTP093,3,14,WORD,1351,13 +LTP093,3,14,WORD,292,14 +LTP093,3,14,WORD,869,15 +LTP093,3,14,WORD,1468,16 +LTP093,3,14,WORD,1086,17 +LTP093,3,14,WORD,1508,18 +LTP093,3,14,WORD,1512,19 +LTP093,3,14,WORD,1578,20 +LTP093,3,14,WORD,353,21 +LTP093,3,14,WORD,1119,22 +LTP093,3,14,WORD,1061,23 +LTP093,3,14,WORD,654,24 +LTP093,3,14,REC_WORD,1171,1 +LTP093,3,14,REC_WORD,134,2 +LTP093,3,14,REC_WORD,1512,19 +LTP093,3,14,REC_WORD,1578,20 +LTP093,3,14,REC_WORD,684,9 +LTP093,3,14,REC_WORD,413,10 +LTP093,3,14,REC_WORD,496,11 +LTP093,3,14,REC_WORD,980,12 +LTP093,3,14,REC_WORD,1351,13 +LTP093,3,14,REC_WORD,292,14 +LTP093,3,14,REC_WORD,869,15 +LTP093,3,14,REC_WORD,1086,17 +LTP093,3,14,REC_WORD,1468,16 +LTP093,3,14,REC_WORD,1508,18 +LTP093,3,14,REC_WORD,162,3 +LTP093,3,14,REC_WORD,484,4 +LTP093,3,14,REC_WORD,519,5 +LTP093,3,14,REC_WORD,408,6 +LTP093,3,14,REC_WORD,100,7 +LTP093,3,14,REC_WORD,873,8 +LTP093,3,14,REC_WORD,353,21 +LTP093,3,14,REC_WORD,1119,22 +LTP093,3,15,WORD,295,1 +LTP093,3,15,WORD,315,2 +LTP093,3,15,WORD,1392,3 +LTP093,3,15,WORD,1002,4 +LTP093,3,15,WORD,442,5 +LTP093,3,15,WORD,437,6 +LTP093,3,15,WORD,614,7 +LTP093,3,15,WORD,1438,8 +LTP093,3,15,WORD,1522,9 +LTP093,3,15,WORD,63,10 +LTP093,3,15,WORD,467,11 +LTP093,3,15,WORD,397,12 +LTP093,3,15,WORD,35,13 +LTP093,3,15,WORD,908,14 +LTP093,3,15,WORD,1148,15 +LTP093,3,15,WORD,283,16 +LTP093,3,15,WORD,792,17 +LTP093,3,15,WORD,1246,18 +LTP093,3,15,WORD,1184,19 +LTP093,3,15,WORD,1037,20 +LTP093,3,15,WORD,692,21 +LTP093,3,15,WORD,431,22 +LTP093,3,15,WORD,499,23 +LTP093,3,15,WORD,782,24 +LTP093,3,15,REC_WORD,782,24 +LTP093,3,15,REC_WORD,1184,19 +LTP093,3,15,REC_WORD,692,21 +LTP093,3,16,WORD,760,1 +LTP093,3,16,WORD,61,2 +LTP093,3,16,WORD,75,3 +LTP093,3,16,WORD,127,4 +LTP093,3,16,WORD,708,5 +LTP093,3,16,WORD,972,6 +LTP093,3,16,WORD,253,7 +LTP093,3,16,WORD,386,8 +LTP093,3,16,WORD,999,9 +LTP093,3,16,WORD,1472,10 +LTP093,3,16,WORD,846,11 +LTP093,3,16,WORD,512,12 +LTP093,3,16,WORD,1349,13 +LTP093,3,16,WORD,947,14 +LTP093,3,16,WORD,872,15 +LTP093,3,16,WORD,1272,16 +LTP093,3,16,WORD,1511,17 +LTP093,3,16,WORD,1047,18 +LTP093,3,16,WORD,143,19 +LTP093,3,16,WORD,1555,20 +LTP093,3,16,WORD,1271,21 +LTP093,3,16,WORD,1017,22 +LTP093,3,16,WORD,870,23 +LTP093,3,16,WORD,494,24 +LTP093,3,16,REC_WORD,1511,17 +LTP093,3,16,REC_WORD,1047,18 +LTP093,3,16,REC_WORD,872,15 +LTP093,3,16,REC_WORD,1272,16 +LTP093,3,16,REC_WORD,253,7 +LTP093,3,16,REC_WORD,760,1 +LTP093,3,16,REC_WORD,61,2 +LTP093,3,16,REC_WORD,870,23 +LTP093,3,16,REC_WORD,494,24 +LTP093,3,17,WORD,91,1 +LTP093,3,17,WORD,159,2 +LTP093,3,17,WORD,997,3 +LTP093,3,17,WORD,447,4 +LTP093,3,17,WORD,848,5 +LTP093,3,17,WORD,348,6 +LTP093,3,17,WORD,728,7 +LTP093,3,17,WORD,1620,8 +LTP093,3,17,WORD,390,9 +LTP093,3,17,WORD,194,10 +LTP093,3,17,WORD,593,11 +LTP093,3,17,WORD,659,12 +LTP093,3,17,WORD,987,13 +LTP093,3,17,WORD,790,14 +LTP093,3,17,WORD,578,15 +LTP093,3,17,WORD,1087,16 +LTP093,3,17,WORD,216,17 +LTP093,3,17,WORD,1341,18 +LTP093,3,17,WORD,1533,19 +LTP093,3,17,WORD,168,20 +LTP093,3,17,WORD,211,21 +LTP093,3,17,WORD,1295,22 +LTP093,3,17,WORD,490,23 +LTP093,3,17,WORD,1071,24 +LTP093,3,17,REC_WORD,1533,19 +LTP093,3,17,REC_WORD,168,20 +LTP093,3,17,REC_WORD,1071,24 +LTP093,3,17,REC_WORD,211,21 +LTP093,3,17,REC_WORD,447,4 +LTP093,3,17,REC_WORD,848,5 +LTP093,3,17,REC_WORD,593,11 +LTP093,3,17,REC_WORD,987,13 +LTP093,3,17,REC_WORD,194,10 +LTP093,3,17,REC_WORD,390,9 +LTP093,3,17,REC_WORD,659,12 +LTP093,3,17,REC_WORD,194,10 +LTP093,3,17,REC_WORD,159,2 +LTP093,3,17,REC_WORD,997,3 +LTP093,3,17,REC_WORD,848,5 +LTP093,3,17,REC_WORD,216,17 +LTP093,3,17,REC_WORD,1341,18 +LTP093,3,17,REC_WORD,1087,16 +LTP093,3,18,WORD,733,1 +LTP093,3,18,WORD,635,2 +LTP093,3,18,WORD,230,3 +LTP093,3,18,WORD,1501,4 +LTP093,3,18,WORD,82,5 +LTP093,3,18,WORD,1606,6 +LTP093,3,18,WORD,1448,7 +LTP093,3,18,WORD,1330,8 +LTP093,3,18,WORD,839,9 +LTP093,3,18,WORD,1374,10 +LTP093,3,18,WORD,360,11 +LTP093,3,18,WORD,179,12 +LTP093,3,18,WORD,1536,13 +LTP093,3,18,WORD,1088,14 +LTP093,3,18,WORD,738,15 +LTP093,3,18,WORD,139,16 +LTP093,3,18,WORD,307,17 +LTP093,3,18,WORD,425,18 +LTP093,3,18,WORD,427,19 +LTP093,3,18,WORD,919,20 +LTP093,3,18,WORD,575,21 +LTP093,3,18,WORD,1422,22 +LTP093,3,18,WORD,1173,23 +LTP093,3,18,WORD,745,24 +LTP093,3,18,REC_WORD,1422,22 +LTP093,3,18,REC_WORD,1173,23 +LTP093,3,18,REC_WORD,745,24 +LTP093,3,18,REC_WORD,230,3 +LTP093,3,18,REC_WORD,1501,4 +LTP093,3,18,REC_WORD,733,1 +LTP093,3,18,REC_WORD,1448,7 +LTP093,3,18,REC_WORD,1330,8 +LTP093,3,18,REC_WORD,839,9 +LTP093,3,18,REC_WORD,82,5 +LTP093,3,18,REC_WORD,-1,-999 +LTP093,3,19,WORD,662,1 +LTP093,3,19,WORD,624,2 +LTP093,3,19,WORD,1152,3 +LTP093,3,19,WORD,384,4 +LTP093,3,19,WORD,1414,5 +LTP093,3,19,WORD,1243,6 +LTP093,3,19,WORD,119,7 +LTP093,3,19,WORD,1384,8 +LTP093,3,19,WORD,645,9 +LTP093,3,19,WORD,39,10 +LTP093,3,19,WORD,1093,11 +LTP093,3,19,WORD,1076,12 +LTP093,3,19,WORD,188,13 +LTP093,3,19,WORD,1006,14 +LTP093,3,19,WORD,843,15 +LTP093,3,19,WORD,299,16 +LTP093,3,19,WORD,1060,17 +LTP093,3,19,WORD,1385,18 +LTP093,3,19,WORD,12,19 +LTP093,3,19,WORD,1635,20 +LTP093,3,19,WORD,1065,21 +LTP093,3,19,WORD,963,22 +LTP093,3,19,WORD,1049,23 +LTP093,3,19,WORD,1258,24 +LTP093,3,19,REC_WORD,963,22 +LTP093,3,19,REC_WORD,1049,23 +LTP093,3,19,REC_WORD,1258,24 +LTP093,3,19,REC_WORD,1384,8 +LTP093,3,19,REC_WORD,645,9 +LTP093,3,19,REC_WORD,1243,6 +LTP093,3,19,REC_WORD,384,4 +LTP093,3,19,REC_WORD,39,10 +LTP093,3,19,REC_WORD,1093,11 +LTP093,3,20,WORD,960,1 +LTP093,3,20,WORD,53,2 +LTP093,3,20,WORD,1416,3 +LTP093,3,20,WORD,325,4 +LTP093,3,20,WORD,66,5 +LTP093,3,20,WORD,372,6 +LTP093,3,20,WORD,5,7 +LTP093,3,20,WORD,878,8 +LTP093,3,20,WORD,1035,9 +LTP093,3,20,WORD,378,10 +LTP093,3,20,WORD,1149,11 +LTP093,3,20,WORD,1461,12 +LTP093,3,20,WORD,340,13 +LTP093,3,20,WORD,1373,14 +LTP093,3,20,WORD,713,15 +LTP093,3,20,WORD,1636,16 +LTP093,3,20,WORD,570,17 +LTP093,3,20,WORD,265,18 +LTP093,3,20,WORD,261,19 +LTP093,3,20,WORD,1317,20 +LTP093,3,20,WORD,807,21 +LTP093,3,20,WORD,1111,22 +LTP093,3,20,WORD,1229,23 +LTP093,3,20,WORD,994,24 +LTP093,3,20,REC_WORD,1229,23 +LTP093,3,20,REC_WORD,994,24 +LTP093,3,20,REC_WORD,261,19 +LTP093,3,20,REC_WORD,1317,20 +LTP093,3,20,REC_WORD,570,17 +LTP093,3,20,REC_WORD,265,18 +LTP093,3,20,REC_WORD,960,1 +LTP093,3,20,REC_WORD,1416,3 +LTP093,3,20,REC_WORD,53,2 +LTP093,3,20,REC_WORD,325,4 +LTP093,3,20,REC_WORD,-1,-999 +LTP093,3,20,REC_WORD,149,-999 +LTP093,3,20,REC_WORD,5,7 +LTP093,3,21,WORD,142,1 +LTP093,3,21,WORD,1238,2 +LTP093,3,21,WORD,508,3 +LTP093,3,21,WORD,488,4 +LTP093,3,21,WORD,466,5 +LTP093,3,21,WORD,401,6 +LTP093,3,21,WORD,1608,7 +LTP093,3,21,WORD,1019,8 +LTP093,3,21,WORD,600,9 +LTP093,3,21,WORD,559,10 +LTP093,3,21,WORD,1013,11 +LTP093,3,21,WORD,734,12 +LTP093,3,21,WORD,133,13 +LTP093,3,21,WORD,84,14 +LTP093,3,21,WORD,368,15 +LTP093,3,21,WORD,809,16 +LTP093,3,21,WORD,169,17 +LTP093,3,21,WORD,778,18 +LTP093,3,21,WORD,1388,19 +LTP093,3,21,WORD,1066,20 +LTP093,3,21,WORD,1331,21 +LTP093,3,21,WORD,400,22 +LTP093,3,21,WORD,405,23 +LTP093,3,21,WORD,702,24 +LTP093,3,21,REC_WORD,1388,19 +LTP093,3,21,REC_WORD,1066,20 +LTP093,3,21,REC_WORD,778,18 +LTP093,3,21,REC_WORD,1331,21 +LTP093,3,21,REC_WORD,400,22 +LTP093,3,22,WORD,982,1 +LTP093,3,22,WORD,699,2 +LTP093,3,22,WORD,804,3 +LTP093,3,22,WORD,639,4 +LTP093,3,22,WORD,145,5 +LTP093,3,22,WORD,439,6 +LTP093,3,22,WORD,1565,7 +LTP093,3,22,WORD,861,8 +LTP093,3,22,WORD,483,9 +LTP093,3,22,WORD,1125,10 +LTP093,3,22,WORD,1227,11 +LTP093,3,22,WORD,255,12 +LTP093,3,22,WORD,820,13 +LTP093,3,22,WORD,1299,14 +LTP093,3,22,WORD,485,15 +LTP093,3,22,WORD,395,16 +LTP093,3,22,WORD,248,17 +LTP093,3,22,WORD,94,18 +LTP093,3,22,WORD,530,19 +LTP093,3,22,WORD,785,20 +LTP093,3,22,WORD,1503,21 +LTP093,3,22,WORD,658,22 +LTP093,3,22,WORD,579,23 +LTP093,3,22,WORD,435,24 +LTP093,3,22,REC_WORD,1503,21 +LTP093,3,22,REC_WORD,658,22 +LTP093,3,22,REC_WORD,248,17 +LTP093,3,22,REC_WORD,94,18 +LTP093,3,22,REC_WORD,485,15 +LTP093,3,22,REC_WORD,395,16 +LTP093,3,22,REC_WORD,982,1 +LTP093,3,22,REC_WORD,699,2 +LTP093,3,22,REC_WORD,804,3 +LTP093,3,22,REC_WORD,639,4 +LTP093,3,23,WORD,1423,1 +LTP093,3,23,WORD,1143,2 +LTP093,3,23,WORD,763,3 +LTP093,3,23,WORD,6,4 +LTP093,3,23,WORD,1075,5 +LTP093,3,23,WORD,1192,6 +LTP093,3,23,WORD,373,7 +LTP093,3,23,WORD,1113,8 +LTP093,3,23,WORD,1132,9 +LTP093,3,23,WORD,793,10 +LTP093,3,23,WORD,1628,11 +LTP093,3,23,WORD,509,12 +LTP093,3,23,WORD,1032,13 +LTP093,3,23,WORD,929,14 +LTP093,3,23,WORD,164,15 +LTP093,3,23,WORD,177,16 +LTP093,3,23,WORD,1079,17 +LTP093,3,23,WORD,1515,18 +LTP093,3,23,WORD,1357,19 +LTP093,3,23,WORD,1108,20 +LTP093,3,23,WORD,718,21 +LTP093,3,23,WORD,1629,22 +LTP093,3,23,WORD,1089,23 +LTP093,3,23,WORD,916,24 +LTP093,3,23,REC_WORD,1629,22 +LTP093,3,23,REC_WORD,1089,23 +LTP093,3,23,REC_WORD,916,24 +LTP093,3,23,REC_WORD,1515,18 +LTP093,3,23,REC_WORD,509,12 +LTP093,3,23,REC_WORD,1628,11 +LTP093,3,24,WORD,543,1 +LTP093,3,24,WORD,541,2 +LTP093,3,24,WORD,46,3 +LTP093,3,24,WORD,1488,4 +LTP093,3,24,WORD,857,5 +LTP093,3,24,WORD,576,6 +LTP093,3,24,WORD,1329,7 +LTP093,3,24,WORD,1403,8 +LTP093,3,24,WORD,55,9 +LTP093,3,24,WORD,313,10 +LTP093,3,24,WORD,641,11 +LTP093,3,24,WORD,392,12 +LTP093,3,24,WORD,1256,13 +LTP093,3,24,WORD,852,14 +LTP093,3,24,WORD,157,15 +LTP093,3,24,WORD,1332,16 +LTP093,3,24,WORD,834,17 +LTP093,3,24,WORD,249,18 +LTP093,3,24,WORD,113,19 +LTP093,3,24,WORD,1370,20 +LTP093,3,24,WORD,1592,21 +LTP093,3,24,WORD,546,22 +LTP093,3,24,WORD,602,23 +LTP093,3,24,WORD,605,24 +LTP093,3,24,REC_WORD,602,23 +LTP093,3,24,REC_WORD,605,24 +LTP093,3,24,REC_WORD,834,17 +LTP093,3,24,REC_WORD,1329,7 +LTP093,3,24,REC_WORD,641,11 +LTP093,3,24,REC_WORD,1332,16 +LTP093,3,24,REC_WORD,157,15 +LTP093,3,24,REC_WORD,541,2 +LTP093,3,24,REC_WORD,543,1 +LTP093,3,24,REC_WORD,46,3 +LTP093,3,24,REC_WORD,1488,4 +LTP093,3,24,REC_WORD,852,14 +LTP093,3,24,REC_WORD,55,9 +LTP093,3,24,REC_WORD,313,10 +LTP093,3,24,REC_WORD,-1,-999 +LTP093,3,24,REC_WORD,1256,13 +LTP093,4,1,WORD,1070,1 +LTP093,4,1,WORD,113,2 +LTP093,4,1,WORD,1351,3 +LTP093,4,1,WORD,1636,4 +LTP093,4,1,WORD,446,5 +LTP093,4,1,WORD,662,6 +LTP093,4,1,WORD,1593,7 +LTP093,4,1,WORD,187,8 +LTP093,4,1,WORD,1631,9 +LTP093,4,1,WORD,800,10 +LTP093,4,1,WORD,919,11 +LTP093,4,1,WORD,972,12 +LTP093,4,1,WORD,833,13 +LTP093,4,1,WORD,852,14 +LTP093,4,1,WORD,1368,15 +LTP093,4,1,WORD,1009,16 +LTP093,4,1,WORD,1085,17 +LTP093,4,1,WORD,718,18 +LTP093,4,1,WORD,820,19 +LTP093,4,1,WORD,732,20 +LTP093,4,1,WORD,1292,21 +LTP093,4,1,WORD,108,22 +LTP093,4,1,WORD,35,23 +LTP093,4,1,WORD,12,24 +LTP093,4,1,REC_WORD,820,19 +LTP093,4,1,REC_WORD,732,20 +LTP093,4,1,REC_WORD,35,23 +LTP093,4,1,REC_WORD,108,22 +LTP093,4,1,REC_WORD,1070,1 +LTP093,4,1,REC_WORD,113,2 +LTP093,4,1,REC_WORD,1593,7 +LTP093,4,1,REC_WORD,187,8 +LTP093,4,1,REC_WORD,1009,16 +LTP093,4,1,REC_WORD,718,18 +LTP093,4,1,REC_WORD,662,6 +LTP093,4,1,REC_WORD,446,5 +LTP093,4,2,WORD,386,1 +LTP093,4,2,WORD,1104,2 +LTP093,4,2,WORD,633,3 +LTP093,4,2,WORD,697,4 +LTP093,4,2,WORD,776,5 +LTP093,4,2,WORD,1544,6 +LTP093,4,2,WORD,1357,7 +LTP093,4,2,WORD,1036,8 +LTP093,4,2,WORD,1071,9 +LTP093,4,2,WORD,415,10 +LTP093,4,2,WORD,620,11 +LTP093,4,2,WORD,1301,12 +LTP093,4,2,WORD,6,13 +LTP093,4,2,WORD,590,14 +LTP093,4,2,WORD,139,15 +LTP093,4,2,WORD,1468,16 +LTP093,4,2,WORD,1037,17 +LTP093,4,2,WORD,125,18 +LTP093,4,2,WORD,764,19 +LTP093,4,2,WORD,866,20 +LTP093,4,2,WORD,240,21 +LTP093,4,2,WORD,883,22 +LTP093,4,2,WORD,179,23 +LTP093,4,2,WORD,1125,24 +LTP093,4,2,REC_WORD,179,23 +LTP093,4,2,REC_WORD,1125,24 +LTP093,4,2,REC_WORD,1301,12 +LTP093,4,2,REC_WORD,883,22 +LTP093,4,2,REC_WORD,240,21 +LTP093,4,2,REC_WORD,764,19 +LTP093,4,2,REC_WORD,866,20 +LTP093,4,2,REC_WORD,620,11 +LTP093,4,2,REC_WORD,633,3 +LTP093,4,2,REC_WORD,697,4 +LTP093,4,2,REC_WORD,776,5 +LTP093,4,2,REC_WORD,1544,6 +LTP093,4,2,REC_WORD,386,1 +LTP093,4,2,REC_WORD,1104,2 +LTP093,4,2,REC_WORD,1071,9 +LTP093,4,3,WORD,599,1 +LTP093,4,3,WORD,1087,2 +LTP093,4,3,WORD,1095,3 +LTP093,4,3,WORD,977,4 +LTP093,4,3,WORD,1159,5 +LTP093,4,3,WORD,1243,6 +LTP093,4,3,WORD,901,7 +LTP093,4,3,WORD,955,8 +LTP093,4,3,WORD,576,9 +LTP093,4,3,WORD,280,10 +LTP093,4,3,WORD,1608,11 +LTP093,4,3,WORD,1562,12 +LTP093,4,3,WORD,1451,13 +LTP093,4,3,WORD,1414,14 +LTP093,4,3,WORD,199,15 +LTP093,4,3,WORD,1519,16 +LTP093,4,3,WORD,308,17 +LTP093,4,3,WORD,216,18 +LTP093,4,3,WORD,1086,19 +LTP093,4,3,WORD,484,20 +LTP093,4,3,WORD,1000,21 +LTP093,4,3,WORD,1251,22 +LTP093,4,3,WORD,136,23 +LTP093,4,3,WORD,1333,24 +LTP093,4,3,REC_WORD,599,1 +LTP093,4,3,REC_WORD,1087,2 +LTP093,4,3,REC_WORD,484,20 +LTP093,4,3,REC_WORD,1333,24 +LTP093,4,3,REC_WORD,136,23 +LTP093,4,3,REC_WORD,1251,22 +LTP093,4,3,REC_WORD,1608,11 +LTP093,4,3,REC_WORD,1562,12 +LTP093,4,3,REC_WORD,1451,13 +LTP093,4,3,REC_WORD,199,15 +LTP093,4,3,REC_WORD,1414,14 +LTP093,4,3,REC_WORD,1519,16 +LTP093,4,4,WORD,323,1 +LTP093,4,4,WORD,708,2 +LTP093,4,4,WORD,86,3 +LTP093,4,4,WORD,771,4 +LTP093,4,4,WORD,1511,5 +LTP093,4,4,WORD,408,6 +LTP093,4,4,WORD,371,7 +LTP093,4,4,WORD,384,8 +LTP093,4,4,WORD,501,9 +LTP093,4,4,WORD,1427,10 +LTP093,4,4,WORD,999,11 +LTP093,4,4,WORD,622,12 +LTP093,4,4,WORD,13,13 +LTP093,4,4,WORD,1232,14 +LTP093,4,4,WORD,1260,15 +LTP093,4,4,WORD,373,16 +LTP093,4,4,WORD,10,17 +LTP093,4,4,WORD,137,18 +LTP093,4,4,WORD,336,19 +LTP093,4,4,WORD,1194,20 +LTP093,4,4,WORD,1426,21 +LTP093,4,4,WORD,1096,22 +LTP093,4,4,WORD,1015,23 +LTP093,4,4,WORD,210,24 +LTP093,4,4,REC_WORD,1426,21 +LTP093,4,4,REC_WORD,1096,22 +LTP093,4,4,REC_WORD,336,19 +LTP093,4,4,REC_WORD,210,24 +LTP093,4,4,REC_WORD,10,17 +LTP093,4,4,REC_WORD,137,18 +LTP093,4,4,REC_WORD,999,11 +LTP093,4,4,REC_WORD,622,12 +LTP093,4,5,WORD,171,1 +LTP093,4,5,WORD,1520,2 +LTP093,4,5,WORD,1388,3 +LTP093,4,5,WORD,976,4 +LTP093,4,5,WORD,368,5 +LTP093,4,5,WORD,1486,6 +LTP093,4,5,WORD,559,7 +LTP093,4,5,WORD,338,8 +LTP093,4,5,WORD,1044,9 +LTP093,4,5,WORD,395,10 +LTP093,4,5,WORD,200,11 +LTP093,4,5,WORD,546,12 +LTP093,4,5,WORD,1395,13 +LTP093,4,5,WORD,143,14 +LTP093,4,5,WORD,1152,15 +LTP093,4,5,WORD,1027,16 +LTP093,4,5,WORD,1347,17 +LTP093,4,5,WORD,1006,18 +LTP093,4,5,WORD,168,19 +LTP093,4,5,WORD,593,20 +LTP093,4,5,WORD,405,21 +LTP093,4,5,WORD,530,22 +LTP093,4,5,WORD,508,23 +LTP093,4,5,WORD,645,24 +LTP093,4,5,REC_WORD,1006,18 +LTP093,4,5,REC_WORD,593,20 +LTP093,4,5,REC_WORD,168,19 +LTP093,4,5,REC_WORD,405,21 +LTP093,4,5,REC_WORD,530,22 +LTP093,4,5,REC_WORD,508,23 +LTP093,4,5,REC_WORD,645,24 +LTP093,4,5,REC_WORD,1152,15 +LTP093,4,5,REC_WORD,1027,16 +LTP093,4,5,REC_WORD,1347,17 +LTP093,4,5,REC_WORD,1520,2 +LTP093,4,5,REC_WORD,171,1 +LTP093,4,5,REC_WORD,1388,3 +LTP093,4,5,REC_WORD,976,4 +LTP093,4,5,REC_WORD,1044,9 +LTP093,4,5,REC_WORD,395,10 +LTP093,4,5,REC_WORD,200,11 +LTP093,4,5,REC_WORD,1486,6 +LTP093,4,6,WORD,858,1 +LTP093,4,6,WORD,223,2 +LTP093,4,6,WORD,358,3 +LTP093,4,6,WORD,1416,4 +LTP093,4,6,WORD,702,5 +LTP093,4,6,WORD,29,6 +LTP093,4,6,WORD,172,7 +LTP093,4,6,WORD,874,8 +LTP093,4,6,WORD,932,9 +LTP093,4,6,WORD,519,10 +LTP093,4,6,WORD,278,11 +LTP093,4,6,WORD,104,12 +LTP093,4,6,WORD,798,13 +LTP093,4,6,WORD,417,14 +LTP093,4,6,WORD,639,15 +LTP093,4,6,WORD,1611,16 +LTP093,4,6,WORD,796,17 +LTP093,4,6,WORD,861,18 +LTP093,4,6,WORD,575,19 +LTP093,4,6,WORD,1140,20 +LTP093,4,6,WORD,1526,21 +LTP093,4,6,WORD,520,22 +LTP093,4,6,WORD,340,23 +LTP093,4,6,WORD,253,24 +LTP093,4,6,REC_WORD,1526,21 +LTP093,4,6,REC_WORD,520,22 +LTP093,4,6,REC_WORD,340,23 +LTP093,4,6,REC_WORD,861,18 +LTP093,4,6,REC_WORD,575,19 +LTP093,4,6,REC_WORD,1140,20 +LTP093,4,6,REC_WORD,1416,4 +LTP093,4,6,REC_WORD,29,6 +LTP093,4,6,REC_WORD,874,8 +LTP093,4,6,REC_WORD,519,10 +LTP093,4,6,REC_WORD,932,9 +LTP093,4,6,REC_WORD,278,11 +LTP093,4,6,REC_WORD,639,15 +LTP093,4,6,REC_WORD,1611,16 +LTP093,4,6,REC_WORD,796,17 +LTP093,4,7,WORD,1293,1 +LTP093,4,7,WORD,203,2 +LTP093,4,7,WORD,755,3 +LTP093,4,7,WORD,996,4 +LTP093,4,7,WORD,1202,5 +LTP093,4,7,WORD,388,6 +LTP093,4,7,WORD,1035,7 +LTP093,4,7,WORD,485,8 +LTP093,4,7,WORD,579,9 +LTP093,4,7,WORD,46,10 +LTP093,4,7,WORD,995,11 +LTP093,4,7,WORD,315,12 +LTP093,4,7,WORD,1501,13 +LTP093,4,7,WORD,1108,14 +LTP093,4,7,WORD,1358,15 +LTP093,4,7,WORD,389,16 +LTP093,4,7,WORD,211,17 +LTP093,4,7,WORD,1197,18 +LTP093,4,7,WORD,659,19 +LTP093,4,7,WORD,1287,20 +LTP093,4,7,WORD,553,21 +LTP093,4,7,WORD,441,22 +LTP093,4,7,WORD,1408,23 +LTP093,4,7,WORD,974,24 +LTP093,4,7,REC_WORD,659,19 +LTP093,4,7,REC_WORD,1287,20 +LTP093,4,7,REC_WORD,553,21 +LTP093,4,7,REC_WORD,389,16 +LTP093,4,7,REC_WORD,211,17 +LTP093,4,7,REC_WORD,1197,18 +LTP093,4,7,REC_WORD,996,4 +LTP093,4,7,REC_WORD,1202,5 +LTP093,4,7,REC_WORD,388,6 +LTP093,4,7,REC_WORD,1035,7 +LTP093,4,7,REC_WORD,485,8 +LTP093,4,7,REC_WORD,579,9 +LTP093,4,7,REC_WORD,46,10 +LTP093,4,7,REC_WORD,995,11 +LTP093,4,7,REC_WORD,315,12 +LTP093,4,7,REC_WORD,1293,1 +LTP093,4,7,REC_WORD,203,2 +LTP093,4,7,REC_WORD,755,3 +LTP093,4,8,WORD,348,1 +LTP093,4,8,WORD,872,2 +LTP093,4,8,WORD,1319,3 +LTP093,4,8,WORD,792,4 +LTP093,4,8,WORD,1016,5 +LTP093,4,8,WORD,839,6 +LTP093,4,8,WORD,1080,7 +LTP093,4,8,WORD,383,8 +LTP093,4,8,WORD,1505,9 +LTP093,4,8,WORD,134,10 +LTP093,4,8,WORD,1423,11 +LTP093,4,8,WORD,738,12 +LTP093,4,8,WORD,1572,13 +LTP093,4,8,WORD,1147,14 +LTP093,4,8,WORD,580,15 +LTP093,4,8,WORD,407,16 +LTP093,4,8,WORD,908,17 +LTP093,4,8,WORD,1048,18 +LTP093,4,8,WORD,869,19 +LTP093,4,8,WORD,829,20 +LTP093,4,8,WORD,912,21 +LTP093,4,8,WORD,1621,22 +LTP093,4,8,WORD,1158,23 +LTP093,4,8,WORD,483,24 +LTP093,4,8,REC_WORD,1621,22 +LTP093,4,8,REC_WORD,1158,23 +LTP093,4,8,REC_WORD,483,24 +LTP093,4,8,REC_WORD,869,19 +LTP093,4,8,REC_WORD,829,20 +LTP093,4,8,REC_WORD,912,21 +LTP093,4,8,REC_WORD,1080,7 +LTP093,4,8,REC_WORD,383,8 +LTP093,4,8,REC_WORD,1505,9 +LTP093,4,9,WORD,1076,1 +LTP093,4,9,WORD,627,2 +LTP093,4,9,WORD,1089,3 +LTP093,4,9,WORD,33,4 +LTP093,4,9,WORD,1411,5 +LTP093,4,9,WORD,637,6 +LTP093,4,9,WORD,265,7 +LTP093,4,9,WORD,1124,8 +LTP093,4,9,WORD,5,9 +LTP093,4,9,WORD,916,10 +LTP093,4,9,WORD,641,11 +LTP093,4,9,WORD,719,12 +LTP093,4,9,WORD,1369,13 +LTP093,4,9,WORD,1345,14 +LTP093,4,9,WORD,878,15 +LTP093,4,9,WORD,893,16 +LTP093,4,9,WORD,327,17 +LTP093,4,9,WORD,254,18 +LTP093,4,9,WORD,390,19 +LTP093,4,9,WORD,1011,20 +LTP093,4,9,WORD,175,21 +LTP093,4,9,WORD,1057,22 +LTP093,4,9,WORD,980,23 +LTP093,4,9,WORD,1580,24 +LTP093,4,9,REC_WORD,1057,22 +LTP093,4,9,REC_WORD,980,23 +LTP093,4,9,REC_WORD,1580,24 +LTP093,4,9,REC_WORD,1369,13 +LTP093,4,9,REC_WORD,1345,14 +LTP093,4,9,REC_WORD,878,15 +LTP093,4,9,REC_WORD,916,10 +LTP093,4,9,REC_WORD,641,11 +LTP093,4,9,REC_WORD,719,12 +LTP093,4,9,REC_WORD,265,7 +LTP093,4,9,REC_WORD,1124,8 +LTP093,4,9,REC_WORD,5,9 +LTP093,4,9,REC_WORD,33,4 +LTP093,4,9,REC_WORD,1411,5 +LTP093,4,9,REC_WORD,637,6 +LTP093,4,9,REC_WORD,327,17 +LTP093,4,9,REC_WORD,893,16 +LTP093,4,9,REC_WORD,254,18 +LTP093,4,9,REC_WORD,1076,1 +LTP093,4,9,REC_WORD,627,2 +LTP093,4,9,REC_WORD,1089,3 +LTP093,4,10,WORD,997,1 +LTP093,4,10,WORD,658,2 +LTP093,4,10,WORD,1628,3 +LTP093,4,10,WORD,642,4 +LTP093,4,10,WORD,635,5 +LTP093,4,10,WORD,1075,6 +LTP093,4,10,WORD,1371,7 +LTP093,4,10,WORD,1113,8 +LTP093,4,10,WORD,963,9 +LTP093,4,10,WORD,1192,10 +LTP093,4,10,WORD,1238,11 +LTP093,4,10,WORD,888,12 +LTP093,4,10,WORD,68,13 +LTP093,4,10,WORD,400,14 +LTP093,4,10,WORD,1246,15 +LTP093,4,10,WORD,734,16 +LTP093,4,10,WORD,585,17 +LTP093,4,10,WORD,804,18 +LTP093,4,10,WORD,1478,19 +LTP093,4,10,WORD,360,20 +LTP093,4,10,WORD,1508,21 +LTP093,4,10,WORD,184,22 +LTP093,4,10,WORD,1271,23 +LTP093,4,10,WORD,188,24 +LTP093,4,10,REC_WORD,184,22 +LTP093,4,10,REC_WORD,1271,23 +LTP093,4,10,REC_WORD,188,24 +LTP093,4,10,REC_WORD,1478,19 +LTP093,4,10,REC_WORD,360,20 +LTP093,4,10,REC_WORD,1508,21 +LTP093,4,10,REC_WORD,658,2 +LTP093,4,10,REC_WORD,635,5 +LTP093,4,10,REC_WORD,642,4 +LTP093,4,10,REC_WORD,1075,6 +LTP093,4,10,REC_WORD,1192,10 +LTP093,4,10,REC_WORD,734,16 +LTP093,4,10,REC_WORD,585,17 +LTP093,4,10,REC_WORD,804,18 +LTP093,4,11,WORD,304,1 +LTP093,4,11,WORD,1284,2 +LTP093,4,11,WORD,785,3 +LTP093,4,11,WORD,959,4 +LTP093,4,11,WORD,127,5 +LTP093,4,11,WORD,1456,6 +LTP093,4,11,WORD,684,7 +LTP093,4,11,WORD,1545,8 +LTP093,4,11,WORD,235,9 +LTP093,4,11,WORD,109,10 +LTP093,4,11,WORD,1587,11 +LTP093,4,11,WORD,1307,12 +LTP093,4,11,WORD,605,13 +LTP093,4,11,WORD,1149,14 +LTP093,4,11,WORD,1031,15 +LTP093,4,11,WORD,847,16 +LTP093,4,11,WORD,1415,17 +LTP093,4,11,WORD,543,18 +LTP093,4,11,WORD,783,19 +LTP093,4,11,WORD,1013,20 +LTP093,4,11,WORD,1061,21 +LTP093,4,11,WORD,1176,22 +LTP093,4,11,WORD,447,23 +LTP093,4,11,WORD,1098,24 +LTP093,4,11,REC_WORD,1176,22 +LTP093,4,11,REC_WORD,447,23 +LTP093,4,11,REC_WORD,1098,24 +LTP093,4,11,REC_WORD,783,19 +LTP093,4,11,REC_WORD,1013,20 +LTP093,4,11,REC_WORD,1061,21 +LTP093,4,11,REC_WORD,304,1 +LTP093,4,11,REC_WORD,1284,2 +LTP093,4,11,REC_WORD,785,3 +LTP093,4,11,REC_WORD,959,4 +LTP093,4,11,REC_WORD,127,5 +LTP093,4,11,REC_WORD,1456,6 +LTP093,4,11,REC_WORD,847,16 +LTP093,4,11,REC_WORD,1415,17 +LTP093,4,11,REC_WORD,543,18 +LTP093,4,11,REC_WORD,109,10 +LTP093,4,11,REC_WORD,1587,11 +LTP093,4,11,REC_WORD,1307,12 +LTP093,4,12,WORD,592,1 +LTP093,4,12,WORD,1629,2 +LTP093,4,12,WORD,353,3 +LTP093,4,12,WORD,752,4 +LTP093,4,12,WORD,1578,5 +LTP093,4,12,WORD,876,6 +LTP093,4,12,WORD,1117,7 +LTP093,4,12,WORD,1535,8 +LTP093,4,12,WORD,782,9 +LTP093,4,12,WORD,1515,10 +LTP093,4,12,WORD,565,11 +LTP093,4,12,WORD,1382,12 +LTP093,4,12,WORD,1224,13 +LTP093,4,12,WORD,814,14 +LTP093,4,12,WORD,591,15 +LTP093,4,12,WORD,1503,16 +LTP093,4,12,WORD,248,17 +LTP093,4,12,WORD,94,18 +LTP093,4,12,WORD,267,19 +LTP093,4,12,WORD,316,20 +LTP093,4,12,WORD,410,21 +LTP093,4,12,WORD,339,22 +LTP093,4,12,WORD,602,23 +LTP093,4,12,WORD,307,24 +LTP093,4,12,REC_WORD,339,22 +LTP093,4,12,REC_WORD,316,20 +LTP093,4,12,REC_WORD,307,24 +LTP093,4,12,REC_WORD,1224,13 +LTP093,4,12,REC_WORD,814,14 +LTP093,4,12,REC_WORD,267,19 +LTP093,4,12,REC_WORD,94,18 +LTP093,4,12,REC_WORD,1117,7 +LTP093,4,13,WORD,1521,1 +LTP093,4,13,WORD,1317,2 +LTP093,4,13,WORD,1633,3 +LTP093,4,13,WORD,793,4 +LTP093,4,13,WORD,706,5 +LTP093,4,13,WORD,1533,6 +LTP093,4,13,WORD,295,7 +LTP093,4,13,WORD,1327,8 +LTP093,4,13,WORD,987,9 +LTP093,4,13,WORD,325,10 +LTP093,4,13,WORD,760,11 +LTP093,4,13,WORD,490,12 +LTP093,4,13,WORD,1262,13 +LTP093,4,13,WORD,821,14 +LTP093,4,13,WORD,1635,15 +LTP093,4,13,WORD,61,16 +LTP093,4,13,WORD,1047,17 +LTP093,4,13,WORD,88,18 +LTP093,4,13,WORD,499,19 +LTP093,4,13,WORD,1312,20 +LTP093,4,13,WORD,801,21 +LTP093,4,13,WORD,1522,22 +LTP093,4,13,WORD,1171,23 +LTP093,4,13,WORD,1422,24 +LTP093,4,13,REC_WORD,1522,22 +LTP093,4,13,REC_WORD,1171,23 +LTP093,4,13,REC_WORD,1422,24 +LTP093,4,13,REC_WORD,499,19 +LTP093,4,13,REC_WORD,1312,20 +LTP093,4,13,REC_WORD,801,21 +LTP093,4,13,REC_WORD,61,16 +LTP093,4,13,REC_WORD,88,18 +LTP093,4,13,REC_WORD,1521,1 +LTP093,4,13,REC_WORD,1317,2 +LTP093,4,13,REC_WORD,1633,3 +LTP093,4,13,REC_WORD,295,7 +LTP093,4,13,REC_WORD,1327,8 +LTP093,4,13,REC_WORD,987,9 +LTP093,4,14,WORD,1605,1 +LTP093,4,14,WORD,435,2 +LTP093,4,14,WORD,1112,3 +LTP093,4,14,WORD,183,4 +LTP093,4,14,WORD,922,5 +LTP093,4,14,WORD,1438,6 +LTP093,4,14,WORD,670,7 +LTP093,4,14,WORD,1299,8 +LTP093,4,14,WORD,848,9 +LTP093,4,14,WORD,733,10 +LTP093,4,14,WORD,929,11 +LTP093,4,14,WORD,728,12 +LTP093,4,14,WORD,1119,13 +LTP093,4,14,WORD,1374,14 +LTP093,4,14,WORD,258,15 +LTP093,4,14,WORD,169,16 +LTP093,4,14,WORD,1403,17 +LTP093,4,14,WORD,496,18 +LTP093,4,14,WORD,1343,19 +LTP093,4,14,WORD,346,20 +LTP093,4,14,WORD,425,21 +LTP093,4,14,WORD,1294,22 +LTP093,4,14,WORD,623,23 +LTP093,4,14,WORD,255,24 +LTP093,4,14,REC_WORD,1343,19 +LTP093,4,14,REC_WORD,346,20 +LTP093,4,14,REC_WORD,425,21 +LTP093,4,14,REC_WORD,1294,22 +LTP093,4,14,REC_WORD,1119,13 +LTP093,4,14,REC_WORD,1374,14 +LTP093,4,14,REC_WORD,258,15 +LTP093,4,14,REC_WORD,733,10 +LTP093,4,14,REC_WORD,728,12 +LTP093,4,15,WORD,1370,1 +LTP093,4,15,WORD,569,2 +LTP093,4,15,WORD,1184,3 +LTP093,4,15,WORD,1405,4 +LTP093,4,15,WORD,598,5 +LTP093,4,15,WORD,1144,6 +LTP093,4,15,WORD,1173,7 +LTP093,4,15,WORD,466,8 +LTP093,4,15,WORD,857,9 +LTP093,4,15,WORD,1407,10 +LTP093,4,15,WORD,715,11 +LTP093,4,15,WORD,754,12 +LTP093,4,15,WORD,1331,13 +LTP093,4,15,WORD,1088,14 +LTP093,4,15,WORD,133,15 +LTP093,4,15,WORD,994,16 +LTP093,4,15,WORD,177,17 +LTP093,4,15,WORD,226,18 +LTP093,4,15,WORD,1242,19 +LTP093,4,15,WORD,157,20 +LTP093,4,15,WORD,498,21 +LTP093,4,15,WORD,1472,22 +LTP093,4,15,WORD,397,23 +LTP093,4,15,WORD,427,24 +LTP093,4,15,REC_WORD,1472,22 +LTP093,4,15,REC_WORD,397,23 +LTP093,4,15,REC_WORD,427,24 +LTP093,4,15,REC_WORD,1242,19 +LTP093,4,15,REC_WORD,157,20 +LTP093,4,15,REC_WORD,498,21 +LTP093,4,15,REC_WORD,1331,13 +LTP093,4,15,REC_WORD,1088,14 +LTP093,4,15,REC_WORD,133,15 +LTP093,4,15,REC_WORD,569,2 +LTP093,4,15,REC_WORD,1370,1 +LTP093,4,15,REC_WORD,1184,3 +LTP093,4,15,REC_WORD,466,8 +LTP093,4,15,REC_WORD,1173,7 +LTP093,4,15,REC_WORD,857,9 +LTP093,4,16,WORD,790,1 +LTP093,4,16,WORD,1592,2 +LTP093,4,16,WORD,849,3 +LTP093,4,16,WORD,100,4 +LTP093,4,16,WORD,795,5 +LTP093,4,16,WORD,1618,6 +LTP093,4,16,WORD,766,7 +LTP093,4,16,WORD,512,8 +LTP093,4,16,WORD,1392,9 +LTP093,4,16,WORD,1256,10 +LTP093,4,16,WORD,55,11 +LTP093,4,16,WORD,723,12 +LTP093,4,16,WORD,749,13 +LTP093,4,16,WORD,1565,14 +LTP093,4,16,WORD,1420,15 +LTP093,4,16,WORD,1315,16 +LTP093,4,16,WORD,261,17 +LTP093,4,16,WORD,1209,18 +LTP093,4,16,WORD,1295,19 +LTP093,4,16,WORD,1060,20 +LTP093,4,16,WORD,683,21 +LTP093,4,16,WORD,667,22 +LTP093,4,16,WORD,66,23 +LTP093,4,16,WORD,873,24 +LTP093,4,16,REC_WORD,667,22 +LTP093,4,16,REC_WORD,66,23 +LTP093,4,16,REC_WORD,873,24 +LTP093,4,16,REC_WORD,1295,19 +LTP093,4,16,REC_WORD,1060,20 +LTP093,4,16,REC_WORD,683,21 +LTP093,4,16,REC_WORD,1315,16 +LTP093,4,16,REC_WORD,1209,18 +LTP093,4,16,REC_WORD,723,12 +LTP093,4,16,REC_WORD,1256,10 +LTP093,4,16,REC_WORD,55,11 +LTP093,4,16,REC_WORD,749,13 +LTP093,4,16,REC_WORD,1565,14 +LTP093,4,16,REC_WORD,1420,15 +LTP093,4,16,REC_WORD,766,7 +LTP093,4,16,REC_WORD,512,8 +LTP093,4,16,REC_WORD,1392,9 +LTP093,4,17,WORD,194,1 +LTP093,4,17,WORD,429,2 +LTP093,4,17,WORD,1493,3 +LTP093,4,17,WORD,1199,4 +LTP093,4,17,WORD,38,5 +LTP093,4,17,WORD,230,6 +LTP093,4,17,WORD,1356,7 +LTP093,4,17,WORD,731,8 +LTP093,4,17,WORD,604,9 +LTP093,4,17,WORD,84,10 +LTP093,4,17,WORD,895,11 +LTP093,4,17,WORD,1022,12 +LTP093,4,17,WORD,1139,13 +LTP093,4,17,WORD,1527,14 +LTP093,4,17,WORD,1332,15 +LTP093,4,17,WORD,960,16 +LTP093,4,17,WORD,1019,17 +LTP093,4,17,WORD,1155,18 +LTP093,4,17,WORD,1021,19 +LTP093,4,17,WORD,900,20 +LTP093,4,17,WORD,661,21 +LTP093,4,17,WORD,871,22 +LTP093,4,17,WORD,1329,23 +LTP093,4,17,WORD,1625,24 +LTP093,4,17,REC_WORD,1329,23 +LTP093,4,17,REC_WORD,1625,24 +LTP093,4,17,REC_WORD,900,20 +LTP093,4,17,REC_WORD,1021,19 +LTP093,4,17,REC_WORD,1155,18 +LTP093,4,17,REC_WORD,194,1 +LTP093,4,17,REC_WORD,429,2 +LTP093,4,17,REC_WORD,1493,3 +LTP093,4,17,REC_WORD,731,8 +LTP093,4,17,REC_WORD,604,9 +LTP093,4,17,REC_WORD,1199,4 +LTP093,4,18,WORD,24,1 +LTP093,4,18,WORD,1261,2 +LTP093,4,18,WORD,1051,3 +LTP093,4,18,WORD,1066,4 +LTP093,4,18,WORD,492,5 +LTP093,4,18,WORD,692,6 +LTP093,4,18,WORD,1448,7 +LTP093,4,18,WORD,1170,8 +LTP093,4,18,WORD,713,9 +LTP093,4,18,WORD,1203,10 +LTP093,4,18,WORD,1123,11 +LTP093,4,18,WORD,313,12 +LTP093,4,18,WORD,606,13 +LTP093,4,18,WORD,378,14 +LTP093,4,18,WORD,476,15 +LTP093,4,18,WORD,675,16 +LTP093,4,18,WORD,763,17 +LTP093,4,18,WORD,1488,18 +LTP093,4,18,WORD,570,19 +LTP093,4,18,WORD,249,20 +LTP093,4,18,WORD,1132,21 +LTP093,4,18,WORD,292,22 +LTP093,4,18,WORD,618,23 +LTP093,4,18,WORD,746,24 +LTP093,4,18,REC_WORD,292,22 +LTP093,4,18,REC_WORD,618,23 +LTP093,4,18,REC_WORD,746,24 +LTP093,4,18,REC_WORD,1066,4 +LTP093,4,18,REC_WORD,492,5 +LTP093,4,18,REC_WORD,692,6 +LTP093,4,18,REC_WORD,606,13 +LTP093,4,18,REC_WORD,378,14 +LTP093,4,18,REC_WORD,476,15 +LTP093,4,18,REC_WORD,1203,10 +LTP093,4,18,REC_WORD,1123,11 +LTP093,4,18,REC_WORD,313,12 +LTP093,4,19,WORD,988,1 +LTP093,4,19,WORD,561,2 +LTP093,4,19,WORD,610,3 +LTP093,4,19,WORD,1032,4 +LTP093,4,19,WORD,982,5 +LTP093,4,19,WORD,1557,6 +LTP093,4,19,WORD,1024,7 +LTP093,4,19,WORD,1496,8 +LTP093,4,19,WORD,401,9 +LTP093,4,19,WORD,870,10 +LTP093,4,19,WORD,1330,11 +LTP093,4,19,WORD,1065,12 +LTP093,4,19,WORD,341,13 +LTP093,4,19,WORD,1215,14 +LTP093,4,19,WORD,1092,15 +LTP093,4,19,WORD,1245,16 +LTP093,4,19,WORD,654,17 +LTP093,4,19,WORD,67,18 +LTP093,4,19,WORD,1079,19 +LTP093,4,19,WORD,1581,20 +LTP093,4,19,WORD,689,21 +LTP093,4,19,WORD,628,22 +LTP093,4,19,WORD,809,23 +LTP093,4,19,WORD,1626,24 +LTP093,4,19,REC_WORD,628,22 +LTP093,4,19,REC_WORD,809,23 +LTP093,4,19,REC_WORD,1626,24 +LTP093,4,19,REC_WORD,1079,19 +LTP093,4,19,REC_WORD,1581,20 +LTP093,4,19,REC_WORD,689,21 +LTP093,4,19,REC_WORD,654,17 +LTP093,4,19,REC_WORD,67,18 +LTP093,4,20,WORD,198,1 +LTP093,4,20,WORD,1524,2 +LTP093,4,20,WORD,495,3 +LTP093,4,20,WORD,691,4 +LTP093,4,20,WORD,1250,5 +LTP093,4,20,WORD,299,6 +LTP093,4,20,WORD,843,7 +LTP093,4,20,WORD,467,8 +LTP093,4,20,WORD,1183,9 +LTP093,4,20,WORD,1461,10 +LTP093,4,20,WORD,431,11 +LTP093,4,20,WORD,601,12 +LTP093,4,20,WORD,1530,13 +LTP093,4,20,WORD,940,14 +LTP093,4,20,WORD,64,15 +LTP093,4,20,WORD,737,16 +LTP093,4,20,WORD,1272,17 +LTP093,4,20,WORD,584,18 +LTP093,4,20,WORD,761,19 +LTP093,4,20,WORD,1219,20 +LTP093,4,20,WORD,328,21 +LTP093,4,20,WORD,1620,22 +LTP093,4,20,WORD,1093,23 +LTP093,4,20,WORD,1049,24 +LTP093,4,20,REC_WORD,1219,20 +LTP093,4,20,REC_WORD,328,21 +LTP093,4,20,REC_WORD,1620,22 +LTP093,4,20,REC_WORD,940,14 +LTP093,4,20,REC_WORD,64,15 +LTP093,4,20,REC_WORD,737,16 +LTP093,4,20,REC_WORD,299,6 +LTP093,4,20,REC_WORD,691,4 +LTP093,4,20,REC_WORD,1250,5 +LTP093,4,21,WORD,745,1 +LTP093,4,21,WORD,1440,2 +LTP093,4,21,WORD,1143,3 +LTP093,4,21,WORD,372,4 +LTP093,4,21,WORD,1200,5 +LTP093,4,21,WORD,1227,6 +LTP093,4,21,WORD,1555,7 +LTP093,4,21,WORD,1111,8 +LTP093,4,21,WORD,1373,9 +LTP093,4,21,WORD,1017,10 +LTP093,4,21,WORD,162,11 +LTP093,4,21,WORD,600,12 +LTP093,4,21,WORD,392,13 +LTP093,4,21,WORD,807,14 +LTP093,4,21,WORD,83,15 +LTP093,4,21,WORD,682,16 +LTP093,4,21,WORD,1221,17 +LTP093,4,21,WORD,769,18 +LTP093,4,21,WORD,91,19 +LTP093,4,21,WORD,1532,20 +LTP093,4,21,WORD,846,21 +LTP093,4,21,WORD,1609,22 +LTP093,4,21,WORD,439,23 +LTP093,4,21,WORD,1355,24 +LTP093,4,21,REC_WORD,1609,22 +LTP093,4,21,REC_WORD,439,23 +LTP093,4,21,REC_WORD,1355,24 +LTP093,4,21,REC_WORD,91,19 +LTP093,4,21,REC_WORD,1532,20 +LTP093,4,21,REC_WORD,846,21 +LTP093,4,21,REC_WORD,1017,10 +LTP093,4,21,REC_WORD,162,11 +LTP093,4,21,REC_WORD,600,12 +LTP093,4,21,REC_WORD,392,13 +LTP093,4,21,REC_WORD,807,14 +LTP093,4,21,REC_WORD,83,15 +LTP093,4,21,REC_WORD,1555,7 +LTP093,4,21,REC_WORD,1111,8 +LTP093,4,21,REC_WORD,1373,9 +LTP093,4,21,REC_WORD,372,4 +LTP093,4,21,REC_WORD,1200,5 +LTP093,4,21,REC_WORD,1227,6 +LTP093,4,21,REC_WORD,745,1 +LTP093,4,21,REC_WORD,1440,2 +LTP093,4,21,REC_WORD,1143,3 +LTP093,4,22,WORD,614,1 +LTP093,4,22,WORD,497,2 +LTP093,4,22,WORD,437,3 +LTP093,4,22,WORD,1606,4 +LTP093,4,22,WORD,834,5 +LTP093,4,22,WORD,989,6 +LTP093,4,22,WORD,794,7 +LTP093,4,22,WORD,567,8 +LTP093,4,22,WORD,660,9 +LTP093,4,22,WORD,666,10 +LTP093,4,22,WORD,1400,11 +LTP093,4,22,WORD,494,12 +LTP093,4,22,WORD,1546,13 +LTP093,4,22,WORD,1512,14 +LTP093,4,22,WORD,696,15 +LTP093,4,22,WORD,714,16 +LTP093,4,22,WORD,840,17 +LTP093,4,22,WORD,488,18 +LTP093,4,22,WORD,283,19 +LTP093,4,22,WORD,39,20 +LTP093,4,22,WORD,1172,21 +LTP093,4,22,WORD,573,22 +LTP093,4,22,WORD,578,23 +LTP093,4,22,WORD,1282,24 +LTP093,4,22,REC_WORD,573,22 +LTP093,4,22,REC_WORD,578,23 +LTP093,4,22,REC_WORD,1282,24 +LTP093,4,22,REC_WORD,283,19 +LTP093,4,22,REC_WORD,39,20 +LTP093,4,22,REC_WORD,1172,21 +LTP093,4,23,WORD,166,1 +LTP093,4,23,WORD,159,2 +LTP093,4,23,WORD,812,3 +LTP093,4,23,WORD,827,4 +LTP093,4,23,WORD,726,5 +LTP093,4,23,WORD,145,6 +LTP093,4,23,WORD,1002,7 +LTP093,4,23,WORD,724,8 +LTP093,4,23,WORD,1280,9 +LTP093,4,23,WORD,1326,10 +LTP093,4,23,WORD,1303,11 +LTP093,4,23,WORD,1536,12 +LTP093,4,23,WORD,131,13 +LTP093,4,23,WORD,509,14 +LTP093,4,23,WORD,1328,15 +LTP093,4,23,WORD,879,16 +LTP093,4,23,WORD,707,17 +LTP093,4,23,WORD,413,18 +LTP093,4,23,WORD,953,19 +LTP093,4,23,WORD,699,20 +LTP093,4,23,WORD,53,21 +LTP093,4,23,WORD,164,22 +LTP093,4,23,WORD,1275,23 +LTP093,4,23,WORD,142,24 +LTP093,4,23,REC_WORD,164,22 +LTP093,4,23,REC_WORD,1275,23 +LTP093,4,23,REC_WORD,142,24 +LTP093,4,23,REC_WORD,953,19 +LTP093,4,23,REC_WORD,699,20 +LTP093,4,23,REC_WORD,53,21 +LTP093,4,23,REC_WORD,879,16 +LTP093,4,23,REC_WORD,413,18 +LTP093,4,23,REC_WORD,707,17 +LTP093,4,23,REC_WORD,827,4 +LTP093,4,23,REC_WORD,726,5 +LTP093,4,23,REC_WORD,145,6 +LTP093,4,23,REC_WORD,1002,7 +LTP093,4,23,REC_WORD,724,8 +LTP093,4,23,REC_WORD,1280,9 +LTP093,4,23,REC_WORD,131,13 +LTP093,4,23,REC_WORD,509,14 +LTP093,4,23,REC_WORD,1328,15 +LTP093,4,24,WORD,935,1 +LTP093,4,24,WORD,541,2 +LTP093,4,24,WORD,1436,3 +LTP093,4,24,WORD,442,4 +LTP093,4,24,WORD,1622,5 +LTP093,4,24,WORD,947,6 +LTP093,4,24,WORD,1258,7 +LTP093,4,24,WORD,1385,8 +LTP093,4,24,WORD,1229,9 +LTP093,4,24,WORD,1349,10 +LTP093,4,24,WORD,885,11 +LTP093,4,24,WORD,63,12 +LTP093,4,24,WORD,75,13 +LTP093,4,24,WORD,1341,14 +LTP093,4,24,WORD,937,15 +LTP093,4,24,WORD,403,16 +LTP093,4,24,WORD,624,17 +LTP093,4,24,WORD,82,18 +LTP093,4,24,WORD,119,19 +LTP093,4,24,WORD,778,20 +LTP093,4,24,WORD,1460,21 +LTP093,4,24,WORD,343,22 +LTP093,4,24,WORD,1148,23 +LTP093,4,24,WORD,1384,24 +LTP093,4,24,REC_WORD,343,22 +LTP093,4,24,REC_WORD,1148,23 +LTP093,4,24,REC_WORD,1384,24 +LTP093,4,24,REC_WORD,1349,10 +LTP093,4,24,REC_WORD,885,11 +LTP093,4,24,REC_WORD,63,12 +LTP093,4,24,REC_WORD,1341,14 +LTP093,4,24,REC_WORD,75,13 +LTP093,4,24,REC_WORD,442,4 +LTP093,4,24,REC_WORD,1622,5 +LTP093,4,24,REC_WORD,947,6 +LTP093,4,24,REC_WORD,541,2 +LTP093,4,24,REC_WORD,935,1 +LTP093,4,24,REC_WORD,1436,3 diff --git a/tests/data/ltpfr2_golden.npz b/tests/data/ltpfr2_golden.npz new file mode 100644 index 0000000..0f0f09d Binary files /dev/null and b/tests/data/ltpfr2_golden.npz differ diff --git a/tests/factories.py b/tests/factories.py new file mode 100644 index 0000000..935fcdb --- /dev/null +++ b/tests/factories.py @@ -0,0 +1,110 @@ +"""Fixture factories for pybeh_pd regression/behavior tests. + +These synthesize the two data structures the package operates on: + +1. **pybeh matrices** — ``pres_itemnos`` / ``rec_itemnos`` (and the derived + recalls matrix), as consumed by the matrix-level functions (``crp``, + ``temp_fact``, ``sem_crp``, ``dist_fact``, ...). +2. **long-format event DataFrames** — one row per presentation / recall / + word-value event, as consumed by the ``pd_*`` wrappers. + +Everything is seeded so captures are reproducible. A simulated free-recall +"experiment" presents items 1..list_length on each list and recalls a random +ordering of a subset, optionally inserting extra-list intrusions (-1) and +repeats, mirroring the matrix conventions the code documents. +""" +from __future__ import annotations + +import numpy as np +import pandas as pd + + +def make_experiment( + n_lists=6, + list_length=8, + n_features=5, + p_recall=0.7, + p_intrusion=0.1, + seed=0, +): + """Simulate one subject's free-recall experiment. + + Returns a dict with the pybeh-style matrices and a long-format events + DataFrame (columns: subject, session, list, type, itemno + feature columns + ``f0..f{n_features-1}`` on the WORD_VALS rows). + """ + rng = np.random.default_rng(seed) + pool_size = n_lists * list_length + + # Stable semantic features per pool item (itemnos are 1-based). + features = rng.normal(size=(pool_size, n_features)) + + pres_rows = [] + rec_rows = [] + pres_itemnos = [] + rec_itemnos = [] + max_recs = 0 + + for li in range(n_lists): + itemnos = np.arange(li * list_length, (li + 1) * list_length) + 1 + pres_itemnos.append(itemnos.tolist()) + for pos, ino in enumerate(itemnos): + # Presentation rows also carry the item's semantic features, so the + # per-list `pd_*_list` API (which reads features off the WORD rows, + # not separate WORD_VALS rows) can be exercised. + prow: dict[str, object] = dict( + subject="s0", session=0, list=li, type="WORD", + itemno=int(ino), serialpos=pos + 1) + for j in range(n_features): + prow[f"f{j}"] = float(features[ino - 1, j]) + pres_rows.append(prow) + + # Recall a random subset in a random order. + recalled_mask = rng.random(list_length) < p_recall + recalled = itemnos[recalled_mask] + rng.shuffle(recalled) + recalled = list(recalled) + # Maybe insert an extra-list intrusion (itemno not in this list -> -1 + # at the matrix level; here use a pool item from another list). + if rng.random() < p_intrusion and pool_size > list_length: + outside = [i for i in range(1, pool_size + 1) + if i not in set(itemnos.tolist())] + recalled.insert(rng.integers(0, len(recalled) + 1), + int(rng.choice(outside))) + + rec_itemnos.append([int(x) for x in recalled]) + max_recs = max(max_recs, len(recalled)) + for pos, ino in enumerate(recalled): + rec_rows.append( + dict(subject="s0", session=0, list=li, type="REC_WORD", + itemno=int(ino), serialpos=pos + 1) + ) + + # Word-value rows (semantic features) — one per pool item, itemnos 1-based. + feat_cols = [f"f{j}" for j in range(n_features)] + wv_rows = [] + for ino in range(1, pool_size + 1): + row: dict[str, object] = dict( + subject="s0", session=0, list=-1, type="WORD_VALS", + itemno=ino, serialpos=0) + for j, c in enumerate(feat_cols): + row[c] = float(features[ino - 1, j]) + wv_rows.append(row) + + events = pd.DataFrame(pres_rows + rec_rows + wv_rows) + + # Rectangular matrices padded with zeros (the matrix convention). + pres_mat = np.array(pres_itemnos, dtype=int) + rec_mat = np.zeros((n_lists, max(max_recs, 1)), dtype=int) + for i, r in enumerate(rec_itemnos): + rec_mat[i, : len(r)] = r + + return dict( + events=events, + pres_itemnos=pres_mat, + rec_itemnos=rec_mat, + features=features, + feat_cols=feat_cols, + list_length=list_length, + n_lists=n_lists, + ) diff --git a/tests/real_data.py b/tests/real_data.py new file mode 100644 index 0000000..2aec5fe --- /dev/null +++ b/tests/real_data.py @@ -0,0 +1,85 @@ +"""Loader for the committed ltpFR2 real-data fixture. + +5 sessions of subject LTP093 from the ltpFR2 scalp-EEG free-recall experiment +(an open dataset) are committed as ``tests/data/ltpfr2_5sessions.csv`` so the +real-data integration tests run anywhere — no rhino mount and no cmlreaders +needed. The long-format schema matches what the ``pybeh_pd`` ``pd_*`` functions +expect: + + columns: subject, session, list, type ('WORD'|'REC_WORD'), item_num, serialpos + +``item_num`` is the 1-based wordpool id (the ``itemno`` pybeh_pd uses); encoding +events are ``WORD`` and retrieval events are ``REC_WORD``. To refresh the fixture +from rhino, re-run ``tests/data/_regenerate_ltpfr2.py`` (needs cmlreaders). +""" +from __future__ import annotations + +import functools +import os + +import numpy as np +import pandas as pd + +_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +FIXTURE_CSV = os.path.join(_DATA_DIR, "ltpfr2_5sessions.csv") +GOLDEN_NPZ = os.path.join(_DATA_DIR, "ltpfr2_golden.npz") + + +@functools.lru_cache(maxsize=1) +def load_ltpfr2_long() -> pd.DataFrame: + """Load the committed 5-session ltpFR2 fixture as a long DataFrame.""" + return pd.read_csv(FIXTURE_CSV) + + +@functools.lru_cache(maxsize=1) +def load_ltpfr2_golden() -> dict: + """Load the frozen pybeh_pd outputs captured on the fixture.""" + with np.load(GOLDEN_NPZ) as data: + return {k: data[k] for k in data.files} + + +ITEMNO = "item_num" + + +def capture_outputs(df) -> dict: + """Every pybeh_pd analysis applicable to plain free-recall events, as a + {name: float-ndarray} dict. Single source of truth shared by the golden + regeneration script and the regression test, so they cannot drift. + + Semantic CRP / distance-factor are intentionally absent: ltpFR2 events carry + no semantic feature space (those are locked on the synthetic suite instead). + """ + import pybeh_pd as pb + + def a(x): + return np.asarray(x, dtype=float) + + out = {} + pres, rec, recalls = pb.get_all_matrices(df, itemno_column=ITEMNO) + out["pres_itemnos"] = a(pres) + out["rec_itemnos"] = a(rec) + out["recalls"] = a(recalls) + out["poss_recalls"] = a(pb.make_poss_recalls_matrix(pres)) + out["clean_mask"] = a(pb.make_clean_recalls_mask2d(recalls)) + subs = ["_"] * pres.shape[0] + out["crp_matrix"] = a(pb.crp(recalls=recalls, subjects=subs, + listLength=pres.shape[1], lag_num=5)) + out["temp_fact_matrix"] = a(pb.temp_fact(recalls=recalls, subjects=subs, + listLength=pres.shape[1])) + crp = pb.pd_crp(df, lag_num=5, itemno_column=ITEMNO).sort_values("lag") + out["pd_crp_prob"] = a(crp["prob"].values) + out["pd_crp_lag"] = a(crp["lag"].values) + out["pd_min_crp_prob"] = a(pb.pd_min_crp(df, lag_num=5, itemno_column=ITEMNO) + .sort_values("lag")["prob"].values) + out["pd_temp_fact"] = a([pb.pd_temp_fact(df, itemno_column=ITEMNO)]) + out["pd_min_temp_fact_r2"] = a([pb.pd_min_temp_fact(df, itemno_column=ITEMNO, + max_n_reps=2)]) + sess_ids = sorted(df["session"].unique()) + out["session_ids"] = a(sess_ids) + out["pd_temp_fact_per_session"] = a( + [pb.pd_temp_fact(df[df["session"] == s], itemno_column=ITEMNO) + for s in sess_ids]) + out["pd_crp_prob_per_session"] = a( + [pb.pd_crp(df[df["session"] == s], lag_num=5, itemno_column=ITEMNO) + .sort_values("lag")["prob"].values for s in sess_ids]) + return out diff --git a/tests/regression/capture_golden.py b/tests/regression/capture_golden.py new file mode 100644 index 0000000..599389c --- /dev/null +++ b/tests/regression/capture_golden.py @@ -0,0 +1,31 @@ +"""Capture golden-master outputs for the behavior-lock regression suite. + +Run ONCE to freeze current behavior: + + python tests/regression/capture_golden.py + +Writes ``golden.npz`` next to this file. The replay test compares live output +against it. Re-run only when an *intended* behavior change has been reviewed and +approved (it should not happen during this renovation). +""" +from __future__ import annotations + +import os + +import numpy as np + +from cases import build_cases + + +def main(): + cases = build_cases() + out = os.path.join(os.path.dirname(__file__), "golden.npz") + np.savez(out, **{k: np.asarray(v, dtype=float) for k, v in cases.items()}) + print(f"wrote {len(cases)} golden cases -> {out}") + for k in sorted(cases): + v = np.asarray(cases[k], dtype=float) + print(f" {k:34s} shape={v.shape}") + + +if __name__ == "__main__": + main() diff --git a/tests/regression/cases.py b/tests/regression/cases.py new file mode 100644 index 0000000..1d1149b --- /dev/null +++ b/tests/regression/cases.py @@ -0,0 +1,154 @@ +"""Single source of truth for behavior-lock regression cases. + +Both the golden-capture script (``capture_golden.py``) and the replay test +(``test_regression.py``) import :func:`build_cases`, so the set of cases and how +each result is reduced to a numpy array can never drift between capture and check. + +Each case maps a string name to a float ndarray (NaNs allowed; compared with +``np.isclose(..., equal_nan=True)``). Results that are DataFrames or tuples are +reduced here to arrays. The known-broken +``loftus_masson_unequal_variance_kahana`` is intentionally excluded (it raises; +that behavior is locked separately in the test via ``pytest.raises``). +""" +from __future__ import annotations + +import os +import sys + +import numpy as np + +_HERE = os.path.dirname(__file__) +sys.path.insert(0, os.path.join(_HERE, "..")) # tests/ (factories) +sys.path.insert(0, os.path.join(_HERE, "..", "..", "src")) # src/ (pybeh_pd pkg) +import pybeh_pd as pb # noqa: E402 +from factories import make_experiment # noqa: E402 + + +def _arr(x): + return np.asarray(x, dtype=float) + + +def _repeated_pres_case(): + """A hand-built repeated-presentation case for the max_n_reps>1 / 3D paths. + + Two trials, list length 4, item 2 presented twice (serial positions 2 and 4). + """ + pres = np.array([[1, 2, 3, 2], [1, 2, 3, 2]]) + rec = np.array([[2, 1, 3, 0], [3, 2, 1, 0]]) + return pres, rec + + +def build_cases(): + cases: dict[str, np.ndarray] = {} + + # --- matrix-level functions over several seeded experiments ------------- + for seed in (0, 1, 2): + ex = make_experiment(seed=seed) + pres, rec, ll = ex["pres_itemnos"], ex["rec_itemnos"], ex["list_length"] + subs = ["_"] * pres.shape[0] + recalls = pb.make_recalls_matrix(pres, rec) + poss = pb.make_poss_recalls_matrix(pres) + sims = pb.get_sim_mat(ex["events"], ex["feat_cols"]) + + p = f"seed{seed}" + cases[f"make_recalls_matrix__{p}"] = _arr(recalls) + cases[f"make_poss_recalls_matrix__{p}"] = _arr(poss) + cases[f"clean_mask__{p}"] = _arr(pb.make_clean_recalls_mask2d(recalls)) + cases[f"crp__{p}"] = _arr(pb.crp(recalls=recalls, subjects=subs, + listLength=ll, lag_num=4)) + cases[f"temp_fact__{p}"] = _arr(pb.temp_fact(recalls=recalls, + subjects=subs, listLength=ll)) + bin_means, crp = pb.sem_crp(recalls=recalls, recalls_itemnos=rec, + pres_itemnos=pres, subjects=subs, + sem_sims=sims, n_bins=5, listLength=ll) + cases[f"sem_crp_binmeans__{p}"] = _arr(bin_means) + cases[f"sem_crp_crp__{p}"] = _arr(crp) + cases[f"dist_fact__{p}"] = _arr(pb.dist_fact(rec_itemnos=rec, + pres_itemnos=pres, + subjects=subs, dist_mat=sims)) + cases[f"get_sim_mat__{p}"] = _arr(sims) + + # --- repeated-presentation (3D) paths: min_crp / min_temp_fact ---------- + pres_r, rec_r = _repeated_pres_case() + subs_r = ["_", "_"] + recalls3d = pb.make_recalls_matrix(pres_r, rec_r, max_n_reps=2) + poss3d = pb.make_poss_recalls_matrix(pres_r, max_n_reps=2) + cases["make_recalls_matrix__rep3d"] = _arr(recalls3d) + cases["make_poss_recalls_matrix__rep3d"] = _arr(poss3d) + cases["min_crp__rep"] = _arr(pb.min_crp(recalls=recalls3d, poss_recalls=poss3d, + subjects=subs_r, listLength=4, lag_num=3)) + cases["min_temp_fact__rep"] = _arr(pb.min_temp_fact( + recalls=recalls3d, poss_recalls=poss3d, subjects=subs_r, listLength=4)) + + # --- helpers ------------------------------------------------------------ + cases["get_min_trans"] = _arr([pb.get_min_trans([3, 1], [2]), + pb.get_min_trans([5], [2, 4]), + pb.get_min_trans([1, 4], [4])]) + # Helpers are called internally with numpy scalars + numpy arrays; mirror that + # (a Python-int `actual` takes a different, numpy-2-incompatible 0-d path). + cases["temp_percentile_rank"] = _arr([ + pb.temp_percentile_rank(np.int64(2), np.array([1, 2, 3, 4])), + pb.temp_percentile_rank(np.int64(3), np.array([3, 3, 1])), + np.nan if pb.temp_percentile_rank(np.int64(1), np.array([1])) is None else 0.0, + ]) + cases["dist_percentile_rank"] = _arr([ + pb.dist_percentile_rank(np.float64(0.5), np.array([0.1, 0.5, 0.9])), + pb.dist_percentile_rank(np.float64(0.5), np.array([0.1, 0.5, 0.9]), + is_similarity=True), + ]) + + # --- pandas wrappers ---------------------------------------------------- + for seed in (0, 1, 2): + ex = make_experiment(seed=seed) + df, fc = ex["events"], ex["feat_cols"] + p = f"seed{seed}" + cases[f"pd_crp__{p}"] = _arr(pb.pd_crp(df)["prob"].values) + cases[f"pd_min_crp__{p}"] = _arr(pb.pd_min_crp(df)["prob"].values) + cases[f"pd_temp_fact__{p}"] = _arr([pb.pd_temp_fact(df)]) + # pd_min_temp_fact(df) with the default max_n_reps=1 raises (bug B4); the + # raise is locked in the test. Capture the working max_n_reps=2 path here. + cases[f"pd_min_temp_fact_r2__{p}"] = _arr([pb.pd_min_temp_fact(df, max_n_reps=2)]) + cases[f"pd_dist_fact__{p}"] = _arr([pb.pd_dist_fact(df, sim_columns=fc)]) + cases[f"pd_sem_crp__{p}"] = _arr(pb.pd_sem_crp(df, sim_columns=fc)["prob"].values) + cases[f"get_itemno_matrices__{p}"] = _arr( + pb.get_itemno_matrices(df.query("type == 'WORD'").copy())) + pres_m, rec_m, recalls_m = pb.get_all_matrices(df) + cases[f"get_all_matrices_pres__{p}"] = _arr(pres_m) + cases[f"get_all_matrices_recalls__{p}"] = _arr(recalls_m) + + # --- per-list / per-subject aggregating variants (groupby.apply paths) --- + # These read semantic features off the WORD rows directly (serialpos-indexed). + bins = [0.0, 2.0, 3.0, 4.0, 5.0] + for seed in (0, 1): + ex = make_experiment(seed=seed) + df, fc = ex["events"], ex["feat_cols"] + li = f"seed{seed}" + one_list = df.query("list == 0").copy() + dfl = pb.pd_dist_fact_list(one_list, sim_columns=fc) + cases[f"pd_dist_fact_list_df__{li}"] = _arr(dfl["dist_fact"].values) + cases[f"pd_dist_fact_list_tc__{li}"] = _arr( + [dfl["total"].values[0], dfl["count"].values[0]]) + sl = pb.pd_sem_crp_list(one_list, sim_columns=fc, bins=bins) + cases[f"pd_sem_crp_list__{li}"] = _arr(sl["prob"].values) + + li_idx = ["subject", "session", "list"] + dsub = pb.pd_dist_fact_list_sub(df, sim_columns=fc, list_index=li_idx) + cases[f"pd_dist_fact_list_sub__{li}"] = _arr(dsub["dist_fact"].values) + ssub = pb.pd_sem_crp_list_sub(df, sim_columns=fc, bins=bins, list_index=li_idx) + cases[f"pd_sem_crp_list_sub__{li}"] = _arr(ssub["prob"].values) + + # --- Loftus-Masson / Cousineau CI helpers ------------------------------- + rng = np.random.default_rng(7) + mat = rng.normal(size=(10, 4)) + np.arange(4) + cases["lm_equal_kahana"] = _arr(pb.loftus_masson_equal_variance_kahana(mat)) + + long = make_experiment(seed=3)["events"].query("type == 'REC_WORD'").copy() + long["val"] = rng.normal(size=len(long)) + lm = pb.loftus_masson_analytic(long, "subject", "list", "val") + cases["lm_analytic_unequal"] = _arr(lm["CI_unequal"].values) + cases["lm_analytic_equal"] = _arr(lm["CI_equal"].values) + cous = pb.cousineau(long, "subject", "list", "val") + cases["cousineau_adj"] = _arr(cous["adj_val"].values) + cases["cousineau_cmo"] = _arr(cous["cmo_adj_val"].values) + + return cases diff --git a/tests/regression/golden.npz b/tests/regression/golden.npz new file mode 100644 index 0000000..fd18674 Binary files /dev/null and b/tests/regression/golden.npz differ diff --git a/tests/regression/test_regression.py b/tests/regression/test_regression.py new file mode 100644 index 0000000..2aa7c9d --- /dev/null +++ b/tests/regression/test_regression.py @@ -0,0 +1,74 @@ +"""Behavior-lock regression test (Phase 4 GATE). + +Replays the golden-master values captured in ``golden.npz`` against live output +from :func:`cases.build_cases`. Any divergence beyond tolerance fails — this is +what proves the modernization edits (Phases 6-11) changed no observable output. + +Golden was captured on the baseline env (numpy 1.24 / pandas 2.3). The same +assertions are expected to hold on newer stacks (numpy 2 / pandas 3) within +tolerance; a cross-env failure here is itself a finding to report. + +Re-capture with ``python tests/regression/capture_golden.py`` only after an +*approved, intentional* behavior change. +""" +from __future__ import annotations + +import os + +import numpy as np +import pytest + +from cases import build_cases + +GOLDEN = os.path.join(os.path.dirname(__file__), "golden.npz") +RTOL = 1e-9 +ATOL = 1e-12 + + +@pytest.fixture(scope="module") +def golden(): + assert os.path.exists(GOLDEN), "run capture_golden.py first" + with np.load(GOLDEN) as data: + return {k: data[k] for k in data.files} + + +@pytest.fixture(scope="module") +def live(): + return build_cases() + + +def test_case_set_matches(golden, live): + """The set of case names must not drift (no silently dropped coverage).""" + assert set(golden) == set(live), ( + f"only in golden: {set(golden) - set(live)}; " + f"only in live: {set(live) - set(golden)}" + ) + + +@pytest.mark.parametrize("name", sorted(build_cases().keys())) +def test_regression(name, golden, live): + g = golden[name] + x = np.asarray(live[name], dtype=float) + assert x.shape == g.shape, f"{name}: shape {x.shape} != golden {g.shape}" + assert np.allclose(x, g, rtol=RTOL, atol=ATOL, equal_nan=True), ( + f"{name}: values diverged from golden\n live={x}\n gold={g}" + ) + + +# --- Locked known-broken behaviors (bugs B1, B4 in bug_report.md) ----------- +# These are pinned so a later fix is a *deliberate* change, not silent. + +def test_pd_min_temp_fact_default_raises(): + """B4: default max_n_reps=1 path raises IndexError (broken).""" + import pybeh_pd as pb + from factories import make_experiment + df = make_experiment(seed=0)["events"] + with pytest.raises(IndexError): + pb.pd_min_temp_fact(df) + + +def test_loftus_masson_unequal_kahana_raises(): + """B1: function references undefined global `mat` -> NameError.""" + import pybeh_pd as pb + with pytest.raises(NameError): + pb.loftus_masson_unequal_variance_kahana(np.ones((4, 3))) diff --git a/tests/test_ci.py b/tests/test_ci.py new file mode 100644 index 0000000..1df7257 --- /dev/null +++ b/tests/test_ci.py @@ -0,0 +1,330 @@ +"""Skeptical unit tests for the within-subject confidence-interval helpers in +``pybeh_pd``: ``loftus_masson_analytic``, ``cousineau``, +``loftus_masson_equal_variance_kahana`` and ``loftus_masson_unequal_variance_kahana``. + +These are ports of ``LoftusMasson.m`` (Loftus & Masson, 1994, PB&R) and the +Cousineau-Morey-O'Brien within-subject normalization. + +Tests only PIN current behavior; source is never modified. Known/discovered bugs +are pinned with ``@pytest.mark.pins_bug`` and a comment, and reported back to the +caller. +""" +import math +import warnings + +import numpy as np +import pandas as pd +import pytest +import scipy as sp +from scipy import stats + +import pybeh_pd as pb + + +# --------------------------------------------------------------------------- +# Shared hand-computed fixture: 3 subjects x 2 conditions. +# +# dat = [[1, 3], +# [2, 5], +# [4, 6]] +# grand mean = 21/6 = 3.5, grand total = 21 +# SS_T = sum((x-3.5)^2) = 6.25+0.25+2.25+2.25+0.25+6.25 = 17.5 +# Srow = [4, 7, 10]; SSrow = (16+49+100)/2 - 21^2/6 = 82.5 - 73.5 = 9.0 +# Scol = [7, 14]; SScol = (49+196)/3 - 73.5 = 81.6667 - 73.5 = 8.16667 +# SSint = 17.5 - 9.0 - 8.16667 = 0.33333 +# df_int = (3*2-1)-(3-1)-(2-1) = 5-2-1 = 2 +# MSint = 0.33333/2 = 0.16667 +# SE = sqrt(0.16667/3) = 0.235702 +# t.975(df=2) = 4.302653 +# CI = 0.235702 * 4.302653 = 1.0141449740863315 (same for both columns) +# --------------------------------------------------------------------------- +DAT_3x2 = np.array([[1.0, 3.0], [2.0, 5.0], [4.0, 6.0]]) +CI_3x2_EQUAL = 1.0141449740863315 + + +def _long_from_matrix(dat, sub_col="sub", cond_col="cond", value_col="val"): + """Reshape a subjects x conditions matrix to a tidy long DataFrame.""" + n_subs, n_conds = dat.shape + rows = [ + {sub_col: s, cond_col: c, value_col: float(dat[s, c])} + for s in range(n_subs) + for c in range(n_conds) + ] + return pd.DataFrame(rows) + + +# =========================================================================== +# 1. HAND-CODED VALUE TESTS +# =========================================================================== + +@pytest.mark.hand_coded +def test_equal_variance_kahana_hand_value(): + """loftus_masson_equal_variance_kahana on the 3x2 fixture matches the + by-hand Loftus-Masson Eq. (2) CI, is constant across conditions, and has + length == n_conditions.""" + ci = pb.loftus_masson_equal_variance_kahana(DAT_3x2) + + # Reconstruct the expected value from hand-computed ANOVA pieces. + SS_int = 0.3333333333333428 # 17.5 - 9.0 - 8.166666... + df_int = 2 + MS_int = SS_int / df_int + crit = sp.stats.t.ppf(0.975, df_int) + expected = np.sqrt(MS_int / 3) * crit + + assert ci.shape == (2,) # one entry per condition + assert np.allclose(ci, expected) + assert np.allclose(ci, CI_3x2_EQUAL) + # equal-variance assumption => identical half-width for every condition + assert ci[0] == ci[1] + + +@pytest.mark.hand_coded +def test_analytic_CI_equal_matches_kahana_same_data(): + """HIGH-VALUE CHECK: loftus_masson_analytic's 'CI_equal' column is a second + implementation of the *same* Loftus-Masson Eq. (2). On identical data it + must agree with loftus_masson_equal_variance_kahana. (They do agree.)""" + df = _long_from_matrix(DAT_3x2) + with warnings.catch_warnings(): + # CI_unequal has a NaN (negative estimator -> sqrt warning) for this + # tiny matrix; irrelevant to the CI_equal comparison. + warnings.simplefilter("ignore", RuntimeWarning) + out = pb.loftus_masson_analytic(df, "sub", "cond", "val") + + ci_kahana = pb.loftus_masson_equal_variance_kahana(DAT_3x2) + + assert list(out.columns) == ["cond", "M_C", "CI_unequal", "CI_equal"] + assert len(out) == 2 + # CI_equal constant across conditions and equal to the hand value. + assert np.allclose(out["CI_equal"].values, CI_3x2_EQUAL) + # The two independent implementations of Eq. (2) agree. + assert np.allclose(out["CI_equal"].values, ci_kahana) + # condition means + assert np.allclose( + sorted(out["M_C"].values), sorted([7 / 3, 14 / 3]) + ) + + +@pytest.mark.hand_coded +def test_analytic_CI_equal_matches_kahana_balanced_random(): + """Same cross-implementation agreement on a larger, well-behaved balanced + design (so CI_unequal is real, not NaN).""" + rng = np.random.default_rng(0) + n, k = 8, 4 + dat = rng.normal(5, 2, (n, k)) + np.arange(k) + df = _long_from_matrix(dat) + + out = pb.loftus_masson_analytic(df, "sub", "cond", "val") + ci_kahana = pb.loftus_masson_equal_variance_kahana(dat) + + assert len(out) == k + assert np.allclose(out["CI_equal"].values, ci_kahana) + assert (out["CI_unequal"] >= 0).all() + + +@pytest.mark.hand_coded +def test_cousineau_adj_and_cmo_hand_values(): + """cousineau: + adj_ = v + grand_mean - subject_mean (exact) + cmo_adj_ = sqrt(K/(K-1))*(v - subject_mean) + grand_mean (K=n_conds) + Hand-checked on the 3x2 fixture (grand mean 3.5, K=2).""" + df = _long_from_matrix(DAT_3x2) + out = pb.cousineau(df, "sub", "cond", "val") + + grand = 3.5 + # subject means: sub0 -> (1+3)/2=2.0, sub1 -> (2+5)/2=3.5, sub2 -> (4+6)/2=5.0 + sub_means = {0: 2.0, 1: 3.5, 2: 5.0} + K = 2 + factor = math.sqrt(K / (K - 1)) + + for _, r in out.iterrows(): + sm = sub_means[r["sub"]] + exp_adj = r["val"] + grand - sm + exp_cmo = factor * (r["val"] - sm) + grand + assert r["adj_val"] == pytest.approx(exp_adj) + assert r["cmo_adj_val"] == pytest.approx(exp_cmo) + + # spot-check the exact emitted values (regression-grade) + row0 = out[(out["sub"] == 0) & (out["cond"] == 0)].iloc[0] + assert row0["adj_val"] == pytest.approx(2.5) + assert row0["cmo_adj_val"] == pytest.approx(math.sqrt(2) * (1.0 - 2.0) + 3.5) + + # required output columns are present + for col in ["M", "M_S", "adj_val", "cmo_adj_val"]: + assert col in out.columns + + +@pytest.mark.hand_coded +def test_cousineau_within_cols_path_hand_values(): + """The within_cols branch groups the grand mean M by within_cols and the + subject mean M_S by (sub_cols + within_cols). Hand-checked. + + Design: 2 subjects x 2 conds x 2 within-levels, val = sub*10 + cond + wc. + For wc=0 group values are {0,1,10,11} -> M=5.5; sub0/wc0 subject mean is + mean(0,1)=0.5; so adj for (sub0,cond0,wc0,val=0) = 0 + 5.5 - 0.5 = 5.0. + """ + rows = [ + {"sub": s, "cond": c, "wc": w, "val": float(s * 10 + c + w)} + for s in range(2) + for c in range(2) + for w in range(2) + ] + df = pd.DataFrame(rows) + out = pb.cousineau(df, "sub", "cond", "val", within_cols=["wc"]) + + r0 = out[(out["sub"] == 0) & (out["cond"] == 0) & (out["wc"] == 0)].iloc[0] + assert r0["M"] == pytest.approx(5.5) + assert r0["M_S"] == pytest.approx(0.5) + assert r0["adj_val"] == pytest.approx(5.0) + + # adj is val + (within-group grand mean) - (within-subject mean) throughout + expected_adj = df["val"].values + out["M"].values - out["M_S"].values + assert np.allclose(out["adj_val"].values, expected_adj) + + +# =========================================================================== +# 2. MATHEMATICAL PROPERTIES +# =========================================================================== + +@pytest.mark.property +def test_cousineau_removes_between_subject_variance(): + """Cousineau normalization: the per-subject mean of adj_ is constant + across subjects (between-subject variance removed).""" + rng = np.random.default_rng(7) + n, k = 6, 3 + # add large per-subject offsets so between-subject variance dominates + dat = rng.normal(0, 1, (n, k)) + (np.arange(n) * 100)[:, None] + df = _long_from_matrix(dat) + out = pb.cousineau(df, "sub", "cond", "val") + + per_sub = out.groupby("sub")["adj_val"].mean().values + assert np.allclose(per_sub, per_sub[0]) + # and it equals the grand mean + assert per_sub[0] == pytest.approx(df["val"].mean()) + + +@pytest.mark.property +def test_cmo_preserves_grand_mean(): + """The Cousineau-Morey-O'Brien adjustment preserves the grand mean.""" + rng = np.random.default_rng(11) + n, k = 5, 4 + dat = rng.normal(3, 2, (n, k)) + np.arange(k) + df = _long_from_matrix(dat) + out = pb.cousineau(df, "sub", "cond", "val") + assert out["cmo_adj_val"].mean() == pytest.approx(df["val"].mean()) + + +@pytest.mark.property +def test_equal_variance_basic_properties(): + """loftus_masson_equal_variance_kahana: non-negative, all entries equal, + length == n_conditions.""" + rng = np.random.default_rng(3) + for n, k in [(4, 2), (6, 3), (10, 5)]: + dat = rng.normal(0, 1, (n, k)) + ci = pb.loftus_masson_equal_variance_kahana(dat) + assert ci.shape == (k,) + assert np.all(ci >= 0) + assert np.allclose(ci, ci[0]) + + +@pytest.mark.property +def test_equal_variance_shrinks_with_more_subjects(): + """Adding subjects (replicating the design) shrinks the CI. + + NOTE: the rubric's '~1/sqrt(2)' expectation does NOT hold exactly for this + estimator, and we deliberately do NOT assert it. Replicating the rows keeps + SS_int proportional but df_int = (n-1)(k-1) more-than-doubles when n -> 2n, + so both MS_int and the t-criterion change. The robust, true property is that + the CI strictly shrinks. (Measured ratio ~0.378, not 0.707, for the 3x2 + fixture -- reported as a methodological note, not a code bug.)""" + dat = DAT_3x2 + ci_n = pb.loftus_masson_equal_variance_kahana(dat)[0] + ci_2n = pb.loftus_masson_equal_variance_kahana(np.vstack([dat, dat]))[0] + ci_3n = pb.loftus_masson_equal_variance_kahana(np.vstack([dat, dat, dat]))[0] + + assert ci_2n < ci_n + assert ci_3n < ci_2n + assert ci_3n > 0 + + # The *SE* part alone (sqrt(MS_int/n)) is what the 1/sqrt(n) intuition refers + # to; pin that it too fails to scale as 1/sqrt(2) here because MS_int is not + # replication-invariant (df grows). + assert not np.isclose(ci_2n / ci_n, 1 / math.sqrt(2), atol=0.05) + + +@pytest.mark.property +def test_analytic_shape_and_signs(): + """loftus_masson_analytic returns one row per condition; CI_equal is + constant across conditions; CI_unequal >= 0 on well-behaved data.""" + rng = np.random.default_rng(5) + n, k = 12, 4 + dat = rng.normal(5, 1.5, (n, k)) + np.arange(k) + df = _long_from_matrix(dat) + out = pb.loftus_masson_analytic(df, "sub", "cond", "val") + + assert len(out) == k + assert out["cond"].nunique() == k + assert np.allclose(out["CI_equal"].values, out["CI_equal"].iloc[0]) + assert (out["CI_unequal"] >= 0).all() + assert (out["CI_equal"] >= 0).all() + + +# =========================================================================== +# 3. EDGE CASES +# =========================================================================== + +@pytest.mark.edge_case +@pytest.mark.pins_bug +def test_cousineau_single_condition_raises_zerodivision(): + """EDGE/BUG: with a single condition (K=1) the CMO factor is + sqrt(K/(K-1)) = sqrt(1/0). Because n_conds is a Python int from + DataFrame.nunique(), '1/0' is a *Python* division and raises + ZeroDivisionError (not inf/nan). Pinned as current behavior. + + Source: pybeh_pd.py:818-819 (cousineau). + """ + df = pd.DataFrame( + [{"sub": s, "cond": 0, "val": float(s)} for s in range(3)] + ) + with pytest.raises(ZeroDivisionError): + pb.cousineau(df, "sub", "cond", "val") + + +@pytest.mark.edge_case +def test_cousineau_single_subject(): + """Single subject: M_S equals each value within the (only) subject, so the + Cousineau adj reduces to the condition value (adj = v + grand - subj_mean, + with subj_mean == grand here only if balanced).""" + df = pd.DataFrame( + [{"sub": 0, "cond": c, "val": float(c + 1)} for c in range(3)] + ) + out = pb.cousineau(df, "sub", "cond", "val") + grand = df["val"].mean() # = 2.0 + # one subject => M_S == grand mean for every row + assert np.allclose(out["M_S"].values, grand) + # therefore adj == original value + assert np.allclose(out["adj_val"].values, df["val"].values) + + +@pytest.mark.edge_case +def test_equal_variance_single_subject_is_nan(): + """Single subject (1 x k): df_int = (1*k-1)-(1-1)-(k-1) = 0, so MS_int and + the CI are undefined. Pin current behavior: returns NaNs.""" + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + ci = pb.loftus_masson_equal_variance_kahana(np.array([[1.0, 2.0]])) + assert ci.shape == (2,) + assert np.all(np.isnan(ci)) + + +@pytest.mark.edge_case +@pytest.mark.pins_bug +def test_unequal_variance_kahana_raises_nameerror(): + """KNOWN BUG B1: loftus_masson_unequal_variance_kahana's first statement is + `dat = mat`, referencing an undefined global `mat`, so EVERY call raises + NameError before any computation. Pinned. + + Source: pybeh_pd.py:852 (`dat = mat`). + """ + for d in (DAT_3x2, np.ones((4, 3)), np.array([[1.0, 2.0, 3.0]])): + with pytest.raises(NameError): + pb.loftus_masson_unequal_variance_kahana(d) diff --git a/tests/test_clustering.py b/tests/test_clustering.py new file mode 100644 index 0000000..fd815b6 --- /dev/null +++ b/tests/test_clustering.py @@ -0,0 +1,432 @@ +"""Skeptical unit tests for the clustering-factor family in ``pybeh_pd``. + +Covered functions +----------------- +- ``temp_percentile_rank`` / ``dist_percentile_rank`` (from ``pybeh_copy``) +- ``temp_fact`` (from ``pybeh_copy``) +- ``min_temp_fact`` (3D repeated-pres path) +- ``dist_fact`` +- ``pd_temp_fact`` / ``pd_min_temp_fact`` / ``pd_dist_fact`` +- ``pd_dist_fact_list`` / ``pd_dist_fact_list_sub`` + +Behavior-preservation rules: where the math disagrees with the code, or a code +path raises, the test PINS the current behavior (``@pytest.mark.pins_bug``) and +the divergence is reported separately -- nothing in the source is changed. + +Numpy note: this repo runs on numpy 1.24 (numpy1). The percentile helpers are +invoked internally with numpy scalars + numpy arrays; the tests mirror that +(passing ``np.int64`` / ``np.float64`` scalars) both to match real call sites +and to avoid the numpy-2 0-d ``nonzero`` path (known bug B5), which would only +fire under numpy>=2. +""" +from __future__ import annotations + +import os +import warnings + +import numpy as np +import pytest + +import pybeh_pd as pb +from factories import make_experiment + +# The package resolves to the src/ layout (conftest puts src/ on the path). +assert pb.__file__ is not None and pb.__file__.endswith( + os.path.join("pybeh_pd", "__init__.py")), pb.__file__ + + +# Several pandas wrappers emit SettingWithCopyWarning / FutureWarning that are +# orthogonal to the math under test. +pytestmark = pytest.mark.filterwarnings("ignore::FutureWarning") + + +# --------------------------------------------------------------------------- +# 1. HAND-CODED VALUE TESTS +# --------------------------------------------------------------------------- + +@pytest.mark.hand_coded +def test_temp_percentile_rank_hand(): + # possible sorted DESC = [3, 2, 1]; actual=1 is the smallest distance -> + # it occupies rank index 2 of 3 -> 2 / (3-1) = 1.0 (max clustering). + assert pb.temp_percentile_rank(np.int64(1), np.array([3, 2, 1])) == 1.0 + # actual=3 is the largest distance -> index 0 -> 0 / 2 = 0.0 (anti-cluster). + assert pb.temp_percentile_rank(np.int64(3), np.array([3, 2, 1])) == 0.0 + # actual=2 is the median -> index 1 -> 1 / 2 = 0.5 (chance). + assert pb.temp_percentile_rank(np.int64(2), np.array([3, 2, 1])) == 0.5 + + +@pytest.mark.hand_coded +def test_temp_percentile_rank_ties_and_none(): + # Ties: sorted DESC [3, 3, 1]; actual=3 matches indices 0 and 1 -> + # mean(0,1)=0.5 -> 0.5 / (3-1) = 0.25. + assert pb.temp_percentile_rank(np.int64(3), np.array([3, 3, 1])) == 0.25 + # No match -> None. + assert pb.temp_percentile_rank(np.int64(5), np.array([3, 2, 1])) is None + # Fewer than 2 possibles -> None. + assert pb.temp_percentile_rank(np.int64(1), np.array([1])) is None + # Exactly 0 possibles -> None. + assert pb.temp_percentile_rank(np.int64(1), np.array([])) is None + + +@pytest.mark.hand_coded +def test_dist_percentile_rank_is_similarity_flips_sort(): + poss = np.array([0.1, 0.5, 0.9]) + # Distance mode: sorted DESC [0.9, 0.5, 0.1]. actual=0.1 (smallest distance, + # i.e. closest) -> index 2 -> 2/2 = 1.0. + assert pb.dist_percentile_rank(np.float64(0.1), poss) == 1.0 + # Largest distance -> index 0 -> 0.0. + assert pb.dist_percentile_rank(np.float64(0.9), poss) == 0.0 + # Median -> 0.5. + assert pb.dist_percentile_rank(np.float64(0.5), poss) == 0.5 + # Similarity mode flips the sort to ASC [0.1, 0.5, 0.9]; actual=0.1 (lowest + # similarity -> most distant) -> index 0 -> 0.0. + assert pb.dist_percentile_rank(np.float64(0.1), poss, is_similarity=True) == 0.0 + # Highest similarity -> index 2 -> 1.0. + assert pb.dist_percentile_rank(np.float64(0.9), poss, is_similarity=True) == 1.0 + + +@pytest.mark.hand_coded +def test_temp_fact_perfect_and_anticlustered(): + # Perfectly clustered recall (adjacent serial positions) -> factor 1.0. + assert pb.temp_fact(recalls=np.array([[1, 2, 3, 4]]), + subjects=["_"], listLength=4)[0] == 1.0 + # Recall [1,4,2,3]: each made transition is the *largest* available -> + # 1->4: poss |i-1| for i in {2,3,4} = {1,2,3}; actual=3 -> 0.0 + # 4->2: poss |i-4| for i in {2,3} = {2,1}; actual=2 -> 0.0 + # 2->3: only one possible -> None (skipped) + # mean = 0.0. + assert pb.temp_fact(recalls=np.array([[1, 4, 2, 3]]), + subjects=["_"], listLength=4)[0] == 0.0 + + +@pytest.mark.hand_coded +def test_temp_fact_partial_by_hand(): + # Recall [2,1,3] on listLength 4, fully worked out: + # 2->1: seen={2}; poss |i-2| for i in {1,3,4} = {1,1,2}; actual=1 -> + # sorted DESC [2,1,1], matches idx 1,2 -> mean 1.5 / 2 = 0.75 + # 1->3: seen={2,1}; poss |i-1| for i in {3,4} = {2,3}; actual=2 -> + # sorted DESC [3,2], match idx 1 -> 1/1 = 1.0 + # mean = (0.75 + 1.0) / 2 = 0.875. + got = pb.temp_fact(recalls=np.array([[2, 1, 3, 0]]), + subjects=["_"], listLength=4)[0] + assert got == pytest.approx(0.875) + + +@pytest.mark.hand_coded +def test_dist_fact_hand_and_similarity_flip(): + dist = np.array([[0., 1., 2.], [1., 0., 1.], [2., 1., 0.]]) + pres = np.array([[1, 2, 3]]) + # Recall 1->2 (the closest neighbour). poss dist from item1 to {2,3} = + # [1, 2]; actual = 1 -> sorted DESC [2,1] match idx 1 -> 1.0 (max + # distance-clustering for the *shortest* hop). + rec = np.array([[1, 2, 0]]) + assert pb.dist_fact(rec_itemnos=rec, pres_itemnos=pres, + subjects=["_"], dist_mat=dist)[0] == 1.0 + # Flipping is_similarity on the same matrix sends a short hop to the + # opposite end -> 0.0. + assert pb.dist_fact(rec_itemnos=rec, pres_itemnos=pres, subjects=["_"], + dist_mat=dist, is_similarity=True)[0] == 0.0 + + +# --------------------------------------------------------------------------- +# 2. MATHEMATICAL PROPERTIES +# --------------------------------------------------------------------------- + +def _in_unit_or_nan(x): + x = float(x) + return np.isnan(x) or (0.0 - 1e-12 <= x <= 1.0 + 1e-12) + + +@pytest.mark.property +def test_temp_fact_in_unit_interval(): + for seed in range(6): + ex = make_experiment(seed=seed) + recalls = pb.make_recalls_matrix(ex["pres_itemnos"], ex["rec_itemnos"]) + vals = pb.temp_fact(recalls=recalls, + subjects=["_"] * recalls.shape[0], + listLength=ex["list_length"]) + assert all(_in_unit_or_nan(v) for v in vals) + + +@pytest.mark.property +def test_dist_fact_in_unit_interval(): + for seed in range(6): + ex = make_experiment(seed=seed) + sims = pb.get_sim_mat(ex["events"], ex["feat_cols"]) + recalls = pb.make_recalls_matrix(ex["pres_itemnos"], ex["rec_itemnos"]) + # get_sim_mat returns Euclidean *distances* -> is_similarity defaults + # to False, which is correct here. Diagonal is 0, so the B2 warning + # heuristic is not tripped. + vals = pb.dist_fact(rec_itemnos=ex["rec_itemnos"], + pres_itemnos=ex["pres_itemnos"], + subjects=["_"] * recalls.shape[0], dist_mat=sims) + assert all(_in_unit_or_nan(v) for v in vals) + + +@pytest.mark.property +def test_dist_fact_random_matrices_stay_in_unit(): + rng = np.random.default_rng(7) + for _ in range(30): + d = rng.random((8, 8)) + d = (d + d.T) / 2.0 + np.fill_diagonal(d, 0.0) + pres = np.array([np.arange(1, 9)]) + rec = np.zeros((1, 8), dtype=int) + rec[0, :5] = rng.permutation(np.arange(1, 9))[:5] + v = pb.dist_fact(rec_itemnos=rec, pres_itemnos=pres, + subjects=["_"], dist_mat=d)[0] + assert _in_unit_or_nan(v) + + +@pytest.mark.property +def test_min_temp_fact_3d_in_unit_interval(): + # Repeated-presentation case (item 2 presented twice). max_n_reps=2 path. + pres = np.array([[1, 2, 3, 4, 2], [1, 2, 3, 4, 2]]) + rec = np.array([[2, 4, 1, 3, 0], [3, 1, 4, 2, 0]]) + recalls3d = pb.make_recalls_matrix(pres, rec, max_n_reps=2) + poss3d = pb.make_poss_recalls_matrix(pres, max_n_reps=2) + vals = pb.min_temp_fact(recalls=recalls3d, poss_recalls=poss3d, + subjects=["_", "_"], listLength=5) + assert all(_in_unit_or_nan(v) for v in vals) + + +@pytest.mark.property +def test_pd_temp_fact_and_pd_dist_fact_in_unit_interval(): + for seed in range(4): + ex = make_experiment(seed=seed) + df, fc = ex["events"], ex["feat_cols"] + assert _in_unit_or_nan(pb.pd_temp_fact(df)) + assert _in_unit_or_nan(pb.pd_dist_fact(df, sim_columns=fc)) + + +@pytest.mark.property +def test_percentile_rank_always_in_unit_or_none(): + rng = np.random.default_rng(11) + for _ in range(200): + poss = rng.integers(0, 6, size=rng.integers(0, 6)) + # actual drawn from the possible set or an out-of-range value. + if len(poss) and rng.random() < 0.7: + actual = np.int64(rng.choice(poss)) + else: + actual = np.int64(99) + r = pb.temp_percentile_rank(actual, poss) + assert r is None or _in_unit_or_nan(r) + if len(poss) < 2: + assert r is None + + +@pytest.mark.property +def test_dist_fact_is_similarity_direction_on_hand_example(): + # On the SAME distance matrix, the shortest hop scores high in distance + # mode (clustering) and low in similarity mode -- opposite ends. + dist = np.array([[0., 1., 2.], [1., 0., 1.], [2., 1., 0.]]) + pres = np.array([[1, 2, 3]]) + rec = np.array([[1, 2, 0]]) # closest possible hop + f = pb.dist_fact(rec_itemnos=rec, pres_itemnos=pres, + subjects=["_"], dist_mat=dist, is_similarity=False)[0] + t = pb.dist_fact(rec_itemnos=rec, pres_itemnos=pres, + subjects=["_"], dist_mat=dist, is_similarity=True)[0] + assert f == 1.0 and t == 0.0 + # With a single binary transition (no ties), the two modes are + # complementary. + assert f + t == pytest.approx(1.0) + + +@pytest.mark.property +def test_dist_fact_negated_distance_matches_similarity_flag(): + # is_similarity=True on a distance matrix should equal is_similarity=False + # on (max - distance), provided we reset the diagonal so the B2 heuristic + # (large diagonal => warn) does not fire. + dist = np.array([[0., 1., 2., 3.], + [1., 0., 1., 2.], + [2., 1., 0., 1.], + [3., 2., 1., 0.]]) + pres = np.array([[1, 2, 3, 4]]) + rec = np.array([[1, 3, 2, 0]]) + negdist = dist.max() - dist + np.fill_diagonal(negdist, 0.0) + a = pb.dist_fact(rec_itemnos=rec, pres_itemnos=pres, subjects=["_"], + dist_mat=dist, is_similarity=True)[0] + b = pb.dist_fact(rec_itemnos=rec, pres_itemnos=pres, subjects=["_"], + dist_mat=negdist, is_similarity=False)[0] + assert a == pytest.approx(b) + + +@pytest.mark.pins_bug +def test_dist_fact_diagonal_heavy_matrix_raises_nameerror_B2(): + # KNOWN BUG B2: dist_fact() calls warnings.warn(...) but ``warnings`` is + # never imported (pybeh_pd.py:640). A similarity-looking matrix (large + # diagonal) with is_similarity=False reaches that branch and raises + # NameError instead of warning. Pin the current behavior. + diag_heavy = np.array([[10., 0., 0.], [0., 10., 0.], [0., 0., 10.]]) + pres = np.array([[1, 2, 3]]) + rec = np.array([[1, 2, 0]]) + with pytest.raises(NameError): + pb.dist_fact(rec_itemnos=rec, pres_itemnos=pres, + subjects=["_"], dist_mat=diag_heavy, is_similarity=False) + + +@pytest.mark.pins_bug +def test_dist_fact_diagonal_heavy_ok_when_is_similarity_true(): + # The same diagonal-heavy matrix does NOT raise when is_similarity=True, + # because that branch is guarded by ``not is_similarity``. Confirms the B2 + # trigger is specifically the (diagonal-heavy AND is_similarity=False) + # combination, and that a similarity matrix is handled normally otherwise. + diag_heavy = np.array([[10., 1., 2.], [1., 10., 1.], [2., 1., 10.]]) + pres = np.array([[1, 2, 3]]) + rec = np.array([[1, 2, 0]]) + v = pb.dist_fact(rec_itemnos=rec, pres_itemnos=pres, + subjects=["_"], dist_mat=diag_heavy, is_similarity=True)[0] + assert _in_unit_or_nan(v) + + +@pytest.mark.property +def test_temp_fact_random_recalls_average_near_chance(): + # Statistical consistency: uniformly random full recall orders should give + # a temporal factor that averages to ~0.5 (chance). Use one subject with + # many trials and assert the aggregate lands in a loose chance band. + rng = np.random.default_rng(42) + ll, n_trials = 10, 400 + recalls = np.stack([rng.permutation(np.arange(1, ll + 1)) + for _ in range(n_trials)]) + val = pb.temp_fact(recalls=recalls, + subjects=["_"] * n_trials, listLength=ll)[0] + assert 0.3 <= val <= 0.7 + # Tighter sanity: this seed is deterministic and very close to 0.5. + assert val == pytest.approx(0.5, abs=0.05) + + +# --------------------------------------------------------------------------- +# 3. EDGE CASES +# --------------------------------------------------------------------------- + +@pytest.mark.edge_case +def test_temp_fact_empty_and_single_recall_nan(): + # No recalls at all -> NaN (count stays 0 -> 0/0). + assert np.isnan(pb.temp_fact(recalls=np.array([[0, 0, 0, 0]]), + subjects=["_"], listLength=4)[0]) + # A single recall -> no transition possible -> NaN. + assert np.isnan(pb.temp_fact(recalls=np.array([[1, 0, 0, 0]]), + subjects=["_"], listLength=4)[0]) + + +@pytest.mark.edge_case +def test_temp_fact_all_intrusions_nan(): + # All extra-list intrusions (-1) -> no clean transitions -> NaN. + assert np.isnan(pb.temp_fact(recalls=np.array([[-1, -1, -1, -1]]), + subjects=["_"], listLength=4)[0]) + + +@pytest.mark.edge_case +def test_dist_fact_empty_and_single_recall_nan(): + dist = np.array([[0., 1., 2.], [1., 0., 1.], [2., 1., 0.]]) + pres = np.array([[1, 2, 3]]) + assert np.isnan(pb.dist_fact(rec_itemnos=np.array([[0, 0, 0]]), + pres_itemnos=pres, subjects=["_"], + dist_mat=dist)[0]) + assert np.isnan(pb.dist_fact(rec_itemnos=np.array([[1, 0, 0]]), + pres_itemnos=pres, subjects=["_"], + dist_mat=dist)[0]) + + +@pytest.mark.edge_case +def test_dist_fact_ret_counts_shape(): + dist = np.array([[0., 1., 2.], [1., 0., 1.], [2., 1., 0.]]) + pres = np.array([[1, 2, 3]]) + rec = np.array([[1, 2, 0]]) + final, total, count = pb.dist_fact(rec_itemnos=rec, pres_itemnos=pres, + subjects=["_"], dist_mat=dist, + ret_counts=True) + assert final[0] == 1.0 + assert total[0] == 1.0 + assert count[0] == 1.0 + + +@pytest.mark.pins_bug +def test_pd_min_temp_fact_default_raises_indexerror_B4(): + # KNOWN BUG B4: pd_min_temp_fact() defaults max_n_reps=1, which makes + # make_poss_recalls_matrix squeeze to 2D; min_temp_fact then indexes + # serialpos[0] on a scalar -> IndexError. Pin the raise. + ex = make_experiment(seed=0) + with pytest.raises(IndexError): + pb.pd_min_temp_fact(ex["events"]) + + +@pytest.mark.edge_case +def test_pd_min_temp_fact_max_n_reps_2_returns_unit_float(): + ex = make_experiment(seed=0) + val = pb.pd_min_temp_fact(ex["events"], max_n_reps=2) + assert isinstance(val, float) + assert _in_unit_or_nan(val) + + +@pytest.mark.edge_case +def test_pd_dist_fact_no_presentations_returns_nan(): + # A subject DataFrame with no WORD/REC_WORD rows (only WORD_VALS) yields an + # empty presentation matrix -> pres_itemnos.shape[1] == 0 -> np.nan. + ex = make_experiment(seed=0) + only_vals = ex["events"].query("type == 'WORD_VALS'").copy() + assert np.isnan(pb.pd_dist_fact(only_vals, sim_columns=ex["feat_cols"])) + + +@pytest.mark.edge_case +def test_pd_dist_fact_list_one_list(): + # pd_dist_fact_list reads features off the WORD rows of a single list and + # returns a one-row frame with dist_fact/total/count; dist_fact in [0,1]. + ex = make_experiment(seed=0) + list0 = ex["events"].query("list == 0") + out = pb.pd_dist_fact_list(list0, sim_columns=ex["feat_cols"]) + assert list(out.columns) == ["dist_fact", "total", "count"] + assert len(out) == 1 + assert _in_unit_or_nan(out["dist_fact"].iloc[0]) + # count is the number of scored transitions; total/count == dist_fact. + c = out["count"].iloc[0] + if c > 0: + assert out["dist_fact"].iloc[0] == pytest.approx( + out["total"].iloc[0] / c) + + +@pytest.mark.edge_case +def test_pd_dist_fact_list_sub_aggregates_to_unit(): + # Aggregate across this subject's lists. Default list_index uses 'trial'; + # the factory uses 'list', so pass list_index explicitly (per the harness). + ex = make_experiment(seed=0) + df = ex["events"].query("type != 'WORD_VALS'").copy() + out = pb.pd_dist_fact_list_sub( + df, sim_columns=ex["feat_cols"], + list_index=["subject", "session", "list"]) + assert "dist_fact" in out.columns + assert len(out) == 1 + assert _in_unit_or_nan(out["dist_fact"].iloc[0]) + # Subject-level factor is the transition-weighted pool of all lists. + assert out["dist_fact"].iloc[0] == pytest.approx( + out["total"].iloc[0] / out["count"].iloc[0]) + + +# --------------------------------------------------------------------------- +# Scrutiny: does temp_fact handle the seen-set across repeated serial positions? +# --------------------------------------------------------------------------- + +@pytest.mark.property +def test_temp_fact_repeat_recall_severs_transition_chain(): + # Scrutiny of the seen-set / clean-mask interaction across a repeated serial + # position. For [2,2,1] the clean mask is [1,0,1]: the *repeated* '2' is + # masked at position 1. A transition is scored only when BOTH the current + # and next positions are clean, so: + # j=0 (2->2): mask[1]=0 -> not scored + # j=1 (2->1): mask[1]=0 -> not scored + # The repeat therefore severs the chain on BOTH sides and NO transition is + # counted -> NaN. (It does NOT silently collapse to the plain [2,1] case.) + assert np.isnan(pb.temp_fact(recalls=np.array([[2, 2, 1, 0]]), + subjects=["_"], listLength=3)[0]) + # By contrast the un-repeated [2,1] scores a single clean transition: + # 2->1 on LL=3: seen={2}; poss |i-2| for i in {1,3} = {1,1}; actual=1 -> + # sorted DESC [1,1] matches idx 0,1 -> mean 0.5 / 1 = 0.5. + assert pb.temp_fact(recalls=np.array([[2, 1, 0, 0]]), + subjects=["_"], listLength=3)[0] == pytest.approx(0.5) + # A repeat that does NOT sit between the two scored items leaves a later + # clean transition intact: [1,2,1,3] -> mask [1,1,0,1]; only the 1->2 + # transition (positions 0,1) is scored. seen={1}; poss |i-1| for i in {2,3} + # = {1,2}; actual=1 -> sorted DESC [2,1] match idx1 -> 1.0. + assert pb.temp_fact(recalls=np.array([[1, 2, 1, 3]]), + subjects=["_"], listLength=3)[0] == pytest.approx(1.0) diff --git a/tests/test_crp.py b/tests/test_crp.py new file mode 100644 index 0000000..2bb3202 --- /dev/null +++ b/tests/test_crp.py @@ -0,0 +1,569 @@ +"""Skeptical unit tests for the lag-CRP / sem-CRP family in pybeh_pd. + +Covers (importable as ``pb.``): + crp, min_crp, sem_crp, pd_crp, pd_min_crp, pd_sem_crp, + pd_sem_crp_list, pd_sem_crp_list_sub, and the helper + make_poss_recalls_matrix / make_recalls_matrix used to build inputs. + +Rules: tests only; current behavior is pinned, not "fixed". Where the math +deviates from the code, a ``pins_bug`` marker + comment explains why. +""" +from __future__ import annotations + +import warnings + +import numpy as np +import pandas as pd +import pytest +from scipy.spatial import distance_matrix + +import pybeh_pd as pb +from pybeh_pd import ( + crp, + min_crp, + sem_crp, + make_recalls_matrix, + make_poss_recalls_matrix, +) +from factories import make_experiment + + +# --------------------------------------------------------------------------- +# helpers +# --------------------------------------------------------------------------- +def _in_unit_or_nan(a): + """True iff every element is NaN or within [0, 1].""" + a = np.asarray(a, dtype=float) + finite = a[~np.isnan(a)] + return np.all((finite >= 0.0) & (finite <= 1.0)) + + +def _matrices_no_repeats(seed): + """Build (pres, rec, recalls) matrices with no repeated presentations.""" + exp = make_experiment(n_lists=6, list_length=8, p_intrusion=0.0, seed=seed) + pres = exp["pres_itemnos"] + rec = exp["rec_itemnos"] + recalls = make_recalls_matrix(pres, rec) + return pres, rec, recalls, exp + + +# =========================================================================== +# crp (pybeh_copy lag-CRP) +# =========================================================================== +@pytest.mark.hand_coded +def test_crp_forward_recall_all_plus_one(): + # Perfect forward recall [1,2,3,4]: every transition is +1. + # listLength=4, lag_num=3 -> width 2*3+1 = 7, center (lag 0) NaN. + out = crp(recalls=np.array([[1, 2, 3, 4]]), subjects=["a"], + listLength=4, lag_num=3) + assert out.shape == (1, 7) + # columns map to lags -3..+3; center index 3 is lag 0 and MUST be NaN. + assert np.isnan(out[0, 3]) + # Hand accounting (already-recalled lower positions are NOT possible): + # 1->2: from sp1, poss lags {+1,+2,+3}, actual +1 + # 2->3: from sp2 seen{1,2}, poss lags {+1,+2}, actual +1 + # 3->4: from sp3 seen{1,2,3}, poss lags {+1}, actual +1 + # lag +1 (index 4): actual 3 / poss 3 = 1.0 + assert out[0, 4] == 1.0 + # lag +2 (index 5): actual 0 / poss 2 = 0.0; lag +3 (index 6): 0/1 = 0.0 + assert out[0, 5] == 0.0 + assert out[0, 6] == 0.0 + # negative lags were never possible (lower sps already recalled) -> NaN + assert np.isnan(out[0, 2]) # lag -1 + assert np.isnan(out[0, 1]) # lag -2 + assert np.isnan(out[0, 0]) # lag -3 + + +@pytest.mark.hand_coded +def test_crp_single_transition(): + # Only one valid transition 2->3 (then padding zeros). + # listLength=4, lag_num=3. Transition is +1. + out = crp(recalls=np.array([[2, 3, 0, 0]]), subjects=["a"], + listLength=4, lag_num=3) + assert out.shape == (1, 7) + assert np.isnan(out[0, 3]) # center NaN + # From sp 2, possible lags with sp not yet seen: {-1, +1, +2}; actual +1. + # lag +1 idx4 -> 1/1 = 1.0; lag -1 idx2 -> 0/1 = 0.0; lag +2 idx5 -> 0/1. + assert out[0, 4] == 1.0 + assert out[0, 2] == 0.0 + assert out[0, 5] == 0.0 + # lags with no possible transition stay NaN (e.g. +3 idx6, -2 idx1, -3 idx0) + assert np.isnan(out[0, 6]) + assert np.isnan(out[0, 1]) + assert np.isnan(out[0, 0]) + + +@pytest.mark.hand_coded +def test_crp_backward_then_forward(): + # recalls [2,1,3]: transitions 2->1 (lag -1), 1->3 (lag +2). + # listLength=3, lag_num=2 -> width 5 (lags -2..+2), center idx2 NaN. + out = crp(recalls=np.array([[2, 1, 3]]), subjects=["a"], + listLength=3, lag_num=2) + assert out.shape == (1, 5) + assert np.isnan(out[0, 2]) + # 2->1: from sp2 poss lags {-1,+1}, actual -1. + # 1->3: from sp1 (seen {1,2}) poss lags {+2}, actual +2. + # lag -1 idx1: 1/1 = 1.0 ; lag +1 idx3: 0/1 = 0.0 ; lag +2 idx4: 1/1 = 1.0 + assert out[0, 1] == 1.0 + assert out[0, 3] == 0.0 + assert out[0, 4] == 1.0 + # lag -2 idx0 never possible -> NaN + assert np.isnan(out[0, 0]) + + +@pytest.mark.property +def test_crp_probabilities_in_unit_and_center_nan(): + _, _, recalls, exp = _matrices_no_repeats(seed=1) + out = crp(recalls=recalls, subjects=["s"] * len(recalls), + listLength=exp["list_length"], lag_num=5) + assert out.shape == (1, 11) + assert _in_unit_or_nan(out) + # center column (lag 0) is always NaN + assert np.isnan(out[0, 5]) + + +@pytest.mark.property +def test_crp_width_is_2lagnum_plus_1(): + _, _, recalls, exp = _matrices_no_repeats(seed=2) + for lag_num in (1, 3, 5, 7): + out = crp(recalls=recalls, subjects=["s"] * len(recalls), + listLength=exp["list_length"], lag_num=lag_num) + assert out.shape[1] == 2 * lag_num + 1 + + +@pytest.mark.property +def test_crp_lag_num_guards_raise(): + r = np.array([[1, 2, 3]]) + # lag_num < 1 + with pytest.raises(ValueError): + crp(recalls=r, subjects=["a"], listLength=4, lag_num=0) + # lag_num >= listLength + with pytest.raises(ValueError): + crp(recalls=r, subjects=["a"], listLength=4, lag_num=4) + # non-int lag_num + with pytest.raises(ValueError): + crp(recalls=r, subjects=["a"], listLength=4, lag_num=2.5) + # non-int skip_first_n + with pytest.raises(ValueError): + crp(recalls=r, subjects=["a"], listLength=4, lag_num=2, + skip_first_n=1.5) + + +@pytest.mark.edge_case +def test_crp_empty_recalls_all_nan(): + # No correct recalls -> no transitions -> every column NaN. + out = crp(recalls=np.zeros((1, 4), dtype=int), subjects=["a"], + listLength=4, lag_num=3) + assert out.shape == (1, 7) + assert np.all(np.isnan(out)) + + +@pytest.mark.edge_case +def test_crp_skip_first_n_removes_only_transition(): + # Single transition 2->3 but skip_first_n=1 skips it -> all NaN. + out = crp(recalls=np.array([[2, 3, 0, 0]]), subjects=["a"], + listLength=4, lag_num=3, skip_first_n=1) + assert np.all(np.isnan(out)) + + +# =========================================================================== +# min_crp (repeated-presentation lag-CRP) +# =========================================================================== +@pytest.mark.hand_coded +def test_min_crp_forward_matches_hand(): + # No repeats, forward recall: identical to crp by hand. + pres = np.array([[1, 2, 3, 4]]) + recalls = np.array([[1, 2, 3, 4]]) + poss = make_poss_recalls_matrix(pres_itemnos=pres, max_n_reps=1) + out = min_crp(recalls=recalls, poss_recalls=poss, subjects=["a"], + listLength=4, lag_num=3) + assert out.shape == (1, 7) + assert np.isnan(out[0, 3]) # center NaN + assert out[0, 4] == 1.0 # lag +1 + assert out[0, 5] == 0.0 # lag +2 + assert out[0, 6] == 0.0 # lag +3 + + +@pytest.mark.hand_coded +def test_min_crp_backward_then_forward(): + pres = np.array([[1, 2, 3]]) + recalls = np.array([[2, 1, 3]]) + poss = make_poss_recalls_matrix(pres_itemnos=pres, max_n_reps=1) + out = min_crp(recalls=recalls, poss_recalls=poss, subjects=["a"], + listLength=3, lag_num=2) + assert out.shape == (1, 5) + assert np.isnan(out[0, 2]) + assert out[0, 1] == 1.0 # lag -1 + assert out[0, 3] == 0.0 # lag +1 + assert out[0, 4] == 1.0 # lag +2 + + +@pytest.mark.hand_coded +def test_min_crp_single_transition(): + pres = np.array([[1, 2, 3, 4]]) + recalls = np.array([[2, 3, 0, 0]]) + poss = make_poss_recalls_matrix(pres_itemnos=pres, max_n_reps=1) + out = min_crp(recalls=recalls, poss_recalls=poss, subjects=["a"], + listLength=4, lag_num=3) + assert np.isnan(out[0, 3]) + assert out[0, 4] == 1.0 # lag +1 + assert out[0, 2] == 0.0 # lag -1 + assert out[0, 5] == 0.0 # lag +2 + + +@pytest.mark.property +def test_min_crp_probabilities_in_unit_and_center_nan(): + pres, _, recalls, exp = _matrices_no_repeats(seed=3) + poss = make_poss_recalls_matrix(pres_itemnos=pres, max_n_reps=1) + out = min_crp(recalls=recalls, poss_recalls=poss, + subjects=["s"] * len(recalls), + listLength=exp["list_length"], lag_num=5) + assert _in_unit_or_nan(out) + assert np.isnan(out[0, 5]) + + +@pytest.mark.property +@pytest.mark.parametrize("seed", [1, 2, 3, 4, 5]) +def test_min_crp_agrees_with_crp_on_unique_items(seed): + # KEY CLAIM under scrutiny: with NO repeated presentations, the + # repeated-presentation min_crp must reduce to plain crp. Verified here + # element-wise (equal_nan) over the shared lag window for several seeds. + pres, _, recalls, exp = _matrices_no_repeats(seed=seed) + ll = exp["list_length"] + poss = make_poss_recalls_matrix(pres_itemnos=pres, max_n_reps=1) + subj = ["s"] * len(recalls) + c = crp(recalls=recalls, subjects=subj, listLength=ll, lag_num=5) + m = min_crp(recalls=recalls, poss_recalls=poss, subjects=subj, + listLength=ll, lag_num=5) + assert np.allclose(c, m, equal_nan=True) + + +@pytest.mark.property +def test_min_crp_3d_repeated_path_in_unit(): + # Exercise the with_repeats (3D) branch; result still bounded. + exp = make_experiment(n_lists=4, list_length=8, p_intrusion=0.0, seed=6) + pres, rec = exp["pres_itemnos"], exp["rec_itemnos"] + rc3 = make_recalls_matrix(pres, rec, max_n_reps=2) + poss3 = make_poss_recalls_matrix(pres, max_n_reps=2) + assert rc3.ndim == 3 and poss3.ndim == 3 + out = min_crp(recalls=rc3, poss_recalls=poss3, + subjects=["s"] * len(rc3), listLength=8, lag_num=5) + assert _in_unit_or_nan(out) + assert np.isnan(out[0, 5]) + + +@pytest.mark.property +def test_min_crp_lag_num_guards_raise(): + pres = np.array([[1, 2, 3]]) + recalls = np.array([[1, 2, 3]]) + poss = make_poss_recalls_matrix(pres_itemnos=pres, max_n_reps=1) + with pytest.raises(ValueError): + min_crp(recalls=recalls, poss_recalls=poss, subjects=["a"], + listLength=3, lag_num=0) + with pytest.raises(ValueError): + min_crp(recalls=recalls, poss_recalls=poss, subjects=["a"], + listLength=3, lag_num=3) + with pytest.raises(ValueError): + min_crp(recalls=recalls, poss_recalls=poss, subjects=["a"], + listLength=3, lag_num=2, skip_first_n=0.5) + + +@pytest.mark.edge_case +def test_min_crp_empty_recalls_all_nan(): + pres = np.array([[1, 2, 3, 4]]) + recalls = np.zeros((1, 4), dtype=int) + poss = make_poss_recalls_matrix(pres_itemnos=pres, max_n_reps=1) + out = min_crp(recalls=recalls, poss_recalls=poss, subjects=["a"], + listLength=4, lag_num=3) + assert np.all(np.isnan(out)) + + +# =========================================================================== +# sem_crp +# =========================================================================== +@pytest.mark.hand_coded +def test_sem_crp_explicit_bins_hand(): + # listLength=3, forward recall [1,2,3]; sym sem matrix. + pres = np.array([[1, 2, 3]]) + rec = np.array([[1, 2, 3]]) + recalls = np.array([[1, 2, 3]]) + sem = np.array([[0., 1., 2.], [1., 0., 3.], [2., 3., 0.]]) + bins = [0.5, 1.5, 2.5] # 3 bins + with warnings.catch_warnings(): + warnings.simplefilter("ignore") # 0/0 in an empty bin_means cell + bm, c = sem_crp(recalls=recalls, recalls_itemnos=rec, + pres_itemnos=pres, subjects=["a"], sem_sims=sem, + bins=bins, listLength=3) + # Hand accounting (see analysis): actual=[1,0,1], poss=[1,1,1]. + assert c.shape == (1, 3) # crp row length == n_bins + assert bm.shape == c.shape # bin_means same shape as crp + np.testing.assert_array_equal(c[0], np.array([1.0, 0.0, 1.0])) + # bin_means: bin0 = 1/1 = 1, bin1 = 0/0 = nan, bin2 = 3/1 = 3. + assert bm[0, 0] == 1.0 + assert np.isnan(bm[0, 1]) + assert bm[0, 2] == 3.0 + + +@pytest.mark.hand_coded +def test_sem_crp_returns_two_tuple_and_four_tuple(): + pres = np.array([[1, 2, 3]]) + rec = np.array([[1, 2, 3]]) + recalls = np.array([[1, 2, 3]]) + sem = np.array([[0., 1., 2.], [1., 0., 3.], [2., 3., 0.]]) + bins = [0.5, 1.5, 2.5] + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + two = sem_crp(recalls=recalls, recalls_itemnos=rec, pres_itemnos=pres, + subjects=["a"], sem_sims=sem, bins=bins, listLength=3) + four = sem_crp(recalls=recalls, recalls_itemnos=rec, pres_itemnos=pres, + subjects=["a"], sem_sims=sem, bins=bins, listLength=3, + ret_counts=True) + assert len(two) == 2 + assert len(four) == 4 + _, _, actual, poss = four + np.testing.assert_array_equal(actual, np.array([1.0, 0.0, 1.0])) + np.testing.assert_array_equal(poss, np.array([1.0, 1.0, 1.0])) + + +@pytest.mark.hand_coded +def test_sem_crp_n_bins_controls_row_length(): + # With n_bins (no explicit bins) crp row length == n_bins. + pres, rec, recalls, exp = _matrices_no_repeats(seed=7) + sem = distance_matrix(exp["features"], exp["features"], p=2) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + bm, c = sem_crp(recalls=recalls, recalls_itemnos=rec, + pres_itemnos=pres, subjects=["s"] * len(recalls), + sem_sims=sem, n_bins=7, + listLength=exp["list_length"]) + assert c.shape == (1, 7) + assert bm.shape == (1, 7) + + +@pytest.mark.property +def test_sem_crp_bounds_and_shapes(): + pres, rec, recalls, exp = _matrices_no_repeats(seed=8) + sem = distance_matrix(exp["features"], exp["features"], p=2) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + bm, c = sem_crp(recalls=recalls, recalls_itemnos=rec, + pres_itemnos=pres, subjects=["s"] * len(recalls), + sem_sims=sem, n_bins=10, + listLength=exp["list_length"]) + # crp probabilities in [0,1] or NaN: the actual transition's bin is always + # among the possible-transition bins, so actual<=poss per bin (no >1). + assert _in_unit_or_nan(c) + assert bm.shape == c.shape + + +@pytest.mark.property +def test_sem_crp_poss_ge_actual_never_exceeds_one(): + # Directly assert the accounting invariant: for every populated bin, + # actual <= poss (so crp = actual/poss is never > 1). + pres, rec, recalls, exp = _matrices_no_repeats(seed=9) + sem = distance_matrix(exp["features"], exp["features"], p=2) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + _, _, actual, poss = sem_crp( + recalls=recalls, recalls_itemnos=rec, pres_itemnos=pres, + subjects=["s"] * len(recalls), sem_sims=sem, n_bins=10, + listLength=exp["list_length"], ret_counts=True) + assert np.all(actual <= poss) + + +# =========================================================================== +# pd_crp +# =========================================================================== +@pytest.mark.property +def test_pd_crp_lag0_nan_and_bounds(): + exp = make_experiment(p_intrusion=0.0, seed=2) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + out = pb.pd_crp(exp["events"], lag_num=5) + assert list(out.columns) == ["prob", "lag"] + # lag==0 row is NaN + assert np.isnan(out.loc[out["lag"] == 0, "prob"]).all() + # 2*lag_num+1 rows + assert len(out) == 11 + assert _in_unit_or_nan(out["prob"].values) + + +@pytest.mark.property +def test_pd_crp_matches_matrix_crp(): + # pd_crp should equal crp computed off the same matrices. + pres, rec, recalls, exp = _matrices_no_repeats(seed=2) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + out = pb.pd_crp(exp["events"], lag_num=5) + direct = crp(recalls=recalls, subjects=["s"] * len(recalls), + listLength=exp["list_length"], lag_num=5)[0] + np.testing.assert_allclose(out["prob"].values, direct, equal_nan=True) + + +@pytest.mark.edge_case +def test_pd_crp_no_valid_lists_returns_empty_df(): + # WORD-only df: no list has both presentations and recalls. + df = pd.DataFrame([ + dict(subject="s", session=0, list=0, type="WORD", itemno=1, + serialpos=1), + dict(subject="s", session=0, list=0, type="WORD", itemno=2, + serialpos=2), + ]) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + out = pb.pd_crp(df, lag_num=3) + assert isinstance(out, pd.DataFrame) + assert out.empty + + +# =========================================================================== +# pd_min_crp +# =========================================================================== +@pytest.mark.property +def test_pd_min_crp_default_max_n_reps_bounds(): + exp = make_experiment(p_intrusion=0.0, seed=4) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + out = pb.pd_min_crp(exp["events"], lag_num=5, max_n_reps=1) + assert list(out.columns) == ["prob", "lag"] + assert np.isnan(out.loc[out["lag"] == 0, "prob"]).all() + assert _in_unit_or_nan(out["prob"].values) + + +@pytest.mark.property +def test_pd_min_crp_agrees_with_pd_crp_no_repeats(): + # No repeated presentations -> pd_min_crp == pd_crp. + exp = make_experiment(p_intrusion=0.0, seed=5) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + a = pb.pd_crp(exp["events"], lag_num=5) + b = pb.pd_min_crp(exp["events"], lag_num=5, max_n_reps=1) + np.testing.assert_allclose(a["prob"].values, b["prob"].values, + equal_nan=True) + + +@pytest.mark.property +def test_pd_min_crp_max_n_reps_2_bounds(): + exp = make_experiment(n_lists=4, list_length=8, p_intrusion=0.0, seed=6) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + out = pb.pd_min_crp(exp["events"], lag_num=5, max_n_reps=2) + assert np.isnan(out.loc[out["lag"] == 0, "prob"]).all() + assert _in_unit_or_nan(out["prob"].values) + + +# =========================================================================== +# pd_sem_crp +# =========================================================================== +@pytest.mark.property +def test_pd_sem_crp_bounds_and_columns(): + exp = make_experiment(p_intrusion=0.0, seed=3) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + out = pb.pd_sem_crp(exp["events"], sim_columns=exp["feat_cols"], + n_bins=10) + assert {"prob", "sem_bin_mean", "sem_bin"}.issubset(out.columns) + # at most n_bins rows (rows with no data are dropped by `prob == prob`) + assert len(out) <= 10 + assert _in_unit_or_nan(out["prob"].values) + # no dropped row is NaN (the query removes them) + assert not out["prob"].isna().any() + + +@pytest.mark.property +def test_pd_sem_crp_ret_counts_actual_le_poss(): + exp = make_experiment(p_intrusion=0.0, seed=3) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + out = pb.pd_sem_crp(exp["events"], sim_columns=exp["feat_cols"], + n_bins=10, ret_counts=True) + assert {"actual", "poss"}.issubset(out.columns) + assert np.all(out["actual"].values <= out["poss"].values) + + +@pytest.mark.edge_case +def test_pd_sem_crp_no_valid_lists_returns_empty_df(): + df = pd.DataFrame([ + dict(subject="s", session=0, list=0, type="WORD", itemno=1, + serialpos=1, f0=0.1), + dict(subject="s", session=0, list=0, type="WORD_VALS", itemno=1, + serialpos=0, f0=0.1), + ]) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + out = pb.pd_sem_crp(df, sim_columns=["f0"], n_bins=3) + assert isinstance(out, pd.DataFrame) + assert out.empty + + +# =========================================================================== +# pd_sem_crp_list (one list; features off WORD rows; bins REQUIRED) +# =========================================================================== +def _one_list_df(exp, list_id=0): + df = exp["events"] + return df[(df["list"] == list_id) + & (df["type"].isin(["WORD", "REC_WORD"]))].copy() + + +@pytest.mark.property +def test_pd_sem_crp_list_bounds(): + exp = make_experiment(n_lists=4, list_length=8, p_intrusion=0.0, seed=5) + bins = list(np.linspace(0, 8, 6))[:-1] + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + out = pb.pd_sem_crp_list(_one_list_df(exp), sim_columns=exp["feat_cols"], + bins=bins) + assert {"prob", "sem_bin_mean", "sem_bin"}.issubset(out.columns) + assert _in_unit_or_nan(out["prob"].values) + # rows with data are kept; at most len(bins) of them + assert len(out) <= len(bins) + + +@pytest.mark.property +def test_pd_sem_crp_list_ret_counts_actual_le_poss(): + exp = make_experiment(n_lists=4, list_length=8, p_intrusion=0.0, seed=5) + bins = list(np.linspace(0, 8, 6))[:-1] + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + out = pb.pd_sem_crp_list(_one_list_df(exp), sim_columns=exp["feat_cols"], + bins=bins, ret_counts=True) + assert {"actual", "poss"}.issubset(out.columns) + assert np.all(out["actual"].values <= out["poss"].values) + + +# =========================================================================== +# pd_sem_crp_list_sub (groupby(list_index).apply); factory uses 'list' +# =========================================================================== +@pytest.mark.integration +def test_pd_sem_crp_list_sub_aggregates_bounds(): + exp = make_experiment(n_lists=4, list_length=8, p_intrusion=0.0, seed=5) + bins = list(np.linspace(0, 8, 6))[:-1] + sub_df = exp["events"][exp["events"]["type"].isin(["WORD", "REC_WORD"])] + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + out = pb.pd_sem_crp_list_sub( + sub_df, sim_columns=exp["feat_cols"], bins=bins, + list_index=["subject", "session", "list"]) + assert {"sem_bin", "actual", "poss", "prob"}.issubset(out.columns) + # subject-level prob = summed actual / summed poss; bounded. + assert _in_unit_or_nan(out["prob"].values) + assert np.all(out["actual"].values <= out["poss"].values) + + +@pytest.mark.integration +def test_pd_sem_crp_list_sub_matches_manual_pool_sum(): + # The sub-level prob should equal sum(actual)/sum(poss) across that + # subject's lists, computed bin by bin. + exp = make_experiment(n_lists=4, list_length=8, p_intrusion=0.0, seed=5) + bins = list(np.linspace(0, 8, 6))[:-1] + sub_df = exp["events"][exp["events"]["type"].isin(["WORD", "REC_WORD"])] + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + out = pb.pd_sem_crp_list_sub( + sub_df, sim_columns=exp["feat_cols"], bins=bins, + list_index=["subject", "session", "list"]) + # prob == actual/poss exactly for each aggregated bin row + np.testing.assert_allclose( + out["prob"].values, + out["actual"].values / out["poss"].values, + equal_nan=True) diff --git a/tests/test_matrix.py b/tests/test_matrix.py new file mode 100644 index 0000000..5604b81 --- /dev/null +++ b/tests/test_matrix.py @@ -0,0 +1,451 @@ +"""Skeptical unit tests for the matrix-level helpers in ``pybeh_pd``. + +Functions under test (all ``pb.``): + get_itemno_matrices, get_all_matrices, make_recalls_matrix, + make_poss_recalls_matrix, get_min_trans, get_sim_mat, + make_clean_recalls_mask2d (from pybeh_copy, re-exported on pb). + +Conventions tested: + pres/rec itemnos are positive ints; rec rows 0-padded; extra-list + intrusions become -1 in the recalls matrix; correct recalls become the + 1-based serial position of the item in its presentation row. + +Markers: hand_coded, property, edge_case, pins_bug (see pytest.ini). +""" +from __future__ import annotations + +import numpy as np +import pandas as pd +import pytest + +import pybeh_pd as pb +from factories import make_experiment + + +# --------------------------------------------------------------------------- +# make_recalls_matrix +# --------------------------------------------------------------------------- + +@pytest.mark.hand_coded +def test_make_recalls_matrix_basic_serialpos(): + # pres item k is at serial position k+1; recalled items map to those sps, + # padding 0 stays 0. + pres = np.array([[1, 2, 3, 4]]) + rec = np.array([[3, 1, 4, 0]]) + out = pb.make_recalls_matrix(pres, rec) + assert out.shape == (1, 4) + np.testing.assert_array_equal(out, np.array([[3, 1, 4, 0]])) + + +@pytest.mark.hand_coded +def test_make_recalls_matrix_extra_list_intrusion_maps_to_minus_one(): + # itemno 5 is not in the presentation row -> -1. + pres = np.array([[1, 2, 3, 4]]) + rec = np.array([[5, 2, 0, 0]]) + out = pb.make_recalls_matrix(pres, rec) + np.testing.assert_array_equal(out, np.array([[-1, 2, 0, 0]])) + + +@pytest.mark.hand_coded +def test_make_recalls_matrix_zero_in_middle_stays_zero(): + # A 0 anywhere in the recall row is "no recall" -> stays 0, surrounding + # correct recalls still map to serial positions. + pres = np.array([[10, 20, 30]]) + rec = np.array([[20, 0, 30]]) + out = pb.make_recalls_matrix(pres, rec) + np.testing.assert_array_equal(out, np.array([[2, 0, 3]])) + + +@pytest.mark.property +def test_make_recalls_matrix_correct_entries_are_valid_serialpos(): + exp = make_experiment(seed=11) + pres = exp["pres_itemnos"] + rec = exp["rec_itemnos"] + L = exp["list_length"] + out = pb.make_recalls_matrix(pres, rec) + assert out.shape == rec.shape + for t in range(out.shape[0]): + for r in range(out.shape[1]): + v = out[t, r] + if v > 0: + # valid serial position in 1..L + assert 1 <= v <= L + # and the item at that position equals the recalled itemno + assert pres[t, v - 1] == rec[t, r] + elif v == 0: + # padding only where the recall itemno was 0 padding + assert rec[t, r] == 0 + else: + # intrusion: -1, and the recalled itemno is genuinely absent + assert v == -1 + assert rec[t, r] not in set(pres[t].tolist()) + + +@pytest.mark.property +def test_make_recalls_matrix_max_n_reps_2_returns_3d(): + # Item 5 presented at positions 1 and 3 (doubled). With max_n_reps=2 the + # output is 3D (trials, recalls, 2) and a correct recall of 5 records both + # serial positions [1, 3]. + pres = np.array([[5, 6, 5, 7]]) + rec = np.array([[5, 7, 0]]) + out = pb.make_recalls_matrix(pres, rec, max_n_reps=2) + assert out.shape == (1, 3, 2) + np.testing.assert_array_equal(out[0, 0], np.array([1, 3])) # both sps of "5" + np.testing.assert_array_equal(out[0, 1], np.array([4, 0])) # "7" single sp + np.testing.assert_array_equal(out[0, 2], np.array([0, 0])) # padding + + +@pytest.mark.edge_case +def test_make_recalls_matrix_doubled_item_raises_with_default_max_n_reps(): + # Item 1 presented twice; with max_n_reps=1 recalling it finds 2 serial + # positions -> Exception. + pres = np.array([[1, 1, 2]]) + rec = np.array([[1, 0, 0]]) + with pytest.raises(Exception, match="more than max_n_reps"): + pb.make_recalls_matrix(pres, rec, max_n_reps=1) + + +@pytest.mark.edge_case +def test_make_recalls_matrix_empty_recall_row_all_zero(): + pres = np.array([[1, 2, 3]]) + rec = np.array([[0, 0, 0]]) + out = pb.make_recalls_matrix(pres, rec) + np.testing.assert_array_equal(out, np.zeros((1, 3), dtype=int)) + + +@pytest.mark.edge_case +def test_make_recalls_matrix_all_intrusions(): + pres = np.array([[1, 2, 3]]) + rec = np.array([[7, 8, 9]]) # none presented + out = pb.make_recalls_matrix(pres, rec) + np.testing.assert_array_equal(out, np.array([[-1, -1, -1]])) + + +@pytest.mark.edge_case +def test_make_recalls_matrix_single_item_list(): + pres = np.array([[42]]) + rec = np.array([[42, 0]]) + out = pb.make_recalls_matrix(pres, rec) + np.testing.assert_array_equal(out, np.array([[1, 0]])) + + +@pytest.mark.edge_case +def test_make_recalls_matrix_rec_longer_than_pres(): + # rec row may have more columns than the list length; trailing zeros pad. + pres = np.array([[1, 2]]) + rec = np.array([[2, 1, 0, 0, 0]]) + out = pb.make_recalls_matrix(pres, rec) + np.testing.assert_array_equal(out, np.array([[2, 1, 0, 0, 0]])) + + +@pytest.mark.pins_bug +def test_make_recalls_matrix_nan_padding_misclassified_as_intrusion(): + # BUG (B-NEW, pybeh_pd.py:74): the docstring says recall rows "may be padded + # with zeros or NaNs", but the zero/NaN guard is written + # (rec_itemnos[t,r]) == 0 | (np.isnan(rec_itemnos[t,r])) + # which, due to Python operator precedence (| binds tighter than ==), + # evaluates as rec == (0 | isnan) instead of (rec == 0) | isnan. + # For a NaN entry: 0 | True == 1, and nan == 1 is False, so the NaN falls + # through to the else branch and is recorded as an INTRUSION (-1) rather + # than padding (0). + # + # CORRECT behavior would be 0 (padding). We pin the current buggy -1. + pres = np.array([[1.0, 2.0, 3.0]]) + rec = np.array([[2.0, np.nan, 3.0]]) + out = pb.make_recalls_matrix(pres, rec) + np.testing.assert_array_equal(out, np.array([[2, -1, 3]])) # bug: nan -> -1 + + +# --------------------------------------------------------------------------- +# make_poss_recalls_matrix +# --------------------------------------------------------------------------- + +@pytest.mark.hand_coded +def test_make_poss_recalls_unique_items_identity(): + # On a unique-item list every item maps to its own serial position. + pres = np.array([[10, 20, 30, 40]]) + out = pb.make_poss_recalls_matrix(pres) + np.testing.assert_array_equal(out, np.array([[1, 2, 3, 4]])) + + +@pytest.mark.hand_coded +def test_make_poss_recalls_zero_padding_stays_zero(): + pres = np.array([[5, 6, 0]]) + out = pb.make_poss_recalls_matrix(pres) + np.testing.assert_array_equal(out, np.array([[1, 2, 0]])) + + +@pytest.mark.hand_coded +def test_make_poss_recalls_repeated_item_max_n_reps_2(): + # Item 5 at positions 1 and 3 -> both rows for that item record [1, 3]. + pres = np.array([[5, 6, 5, 7]]) + out = pb.make_poss_recalls_matrix(pres, max_n_reps=2) + assert out.shape == (1, 4, 2) + np.testing.assert_array_equal(out[0, 0], np.array([1, 3])) + np.testing.assert_array_equal(out[0, 1], np.array([2, 0])) + np.testing.assert_array_equal(out[0, 2], np.array([1, 3])) + np.testing.assert_array_equal(out[0, 3], np.array([4, 0])) + + +@pytest.mark.property +def test_make_poss_recalls_unique_lists_is_arange(): + exp = make_experiment(seed=7) + pres = exp["pres_itemnos"] + L = exp["list_length"] + out = pb.make_poss_recalls_matrix(pres) + assert out.shape == pres.shape + expected = np.tile(np.arange(1, L + 1), (pres.shape[0], 1)) + np.testing.assert_array_equal(out, expected) + + +@pytest.mark.edge_case +def test_make_poss_recalls_doubled_item_raises_max_n_reps_1(): + pres = np.array([[3, 3, 4]]) + with pytest.raises(Exception, match="more than max_n_reps"): + pb.make_poss_recalls_matrix(pres, max_n_reps=1) + + +# --------------------------------------------------------------------------- +# get_min_trans +# --------------------------------------------------------------------------- + +@pytest.mark.hand_coded +def test_get_min_trans_positive_wins_tie(): + # candidates serialpos [1, 3], current recall rec=[2]: + # diffs sp - r = {1-2=-1, 3-2=+1}. abs equal -> Howard&Kahana rule: + # sort descending [+1, -1] then argmin(|.|) picks the first, +1. + assert pb.get_min_trans([1, 3], [2]) == 1 + + +@pytest.mark.hand_coded +def test_get_min_trans_zero_when_same_position(): + # rec position equals a candidate -> lag 0 (smallest abs). + assert pb.get_min_trans([1, 2, 3], [2]) == 0 + + +@pytest.mark.hand_coded +def test_get_min_trans_picks_smallest_abs_lag(): + # candidates [1, 10], rec=[3]: diffs {-2, +7} -> smallest abs is -2. + assert pb.get_min_trans([1, 10], [3]) == -2 + + +@pytest.mark.property +def test_get_min_trans_result_is_a_realizable_lag(): + rng = np.random.default_rng(0) + for _ in range(50): + sp = sorted(rng.choice(np.arange(1, 12), size=rng.integers(1, 5), replace=False).tolist()) + rec = [int(rng.integers(1, 12))] + res = pb.get_min_trans(sp, rec) + all_lags = [s - r for s in sp for r in rec] + # result is one of the candidate lags and has minimal abs value + assert res in all_lags + assert abs(res) == min(abs(x) for x in all_lags) + # tie-break: among minimal-abs lags, the most positive one is chosen + min_abs = min(abs(x) for x in all_lags) + tied = [x for x in all_lags if abs(x) == min_abs] + assert res == max(tied) + + +# --------------------------------------------------------------------------- +# make_clean_recalls_mask2d (from pybeh_copy) +# --------------------------------------------------------------------------- + +@pytest.mark.hand_coded +def test_clean_mask_first_correct_only(): + # 1 only for the first occurrence of each positive value; repeats/0/neg -> 0. + data = np.array([[3, 1, 1, 0, -1]]) + out = np.array(pb.make_clean_recalls_mask2d(data)) + np.testing.assert_array_equal(out, np.array([[1, 1, 0, 0, 0]])) + + +@pytest.mark.hand_coded +def test_clean_mask_repeat_then_new(): + data = np.array([[2, 2, -1, 3, 0]]) + out = np.array(pb.make_clean_recalls_mask2d(data)) + np.testing.assert_array_equal(out, np.array([[1, 0, 0, 1, 0]])) + + +@pytest.mark.hand_coded +def test_clean_mask_all_zero_and_all_negative(): + data = np.array([[0, 0, 0], [-1, -2, -1]]) + out = np.array(pb.make_clean_recalls_mask2d(data)) + np.testing.assert_array_equal(out, np.zeros((2, 3), dtype=int)) + + +@pytest.mark.property +def test_clean_mask_is_binary_and_marks_first_occurrence(): + exp = make_experiment(seed=5) + recalls = pb.make_recalls_matrix(exp["pres_itemnos"], exp["rec_itemnos"]) + out = np.array(pb.make_clean_recalls_mask2d(recalls)) + assert out.shape == recalls.shape + # strictly 0/1 + assert set(np.unique(out).tolist()).issubset({0, 1}) + # mask == 1 exactly at first occurrence of each positive serial position + for row_in, row_out in zip(recalls, out): + seen: set[int] = set() + for v_in, v_out in zip(row_in, row_out): + expected = 1 if (v_in > 0 and v_in not in seen) else 0 + assert v_out == expected + if v_in > 0: + seen.add(int(v_in)) + + +# --------------------------------------------------------------------------- +# get_sim_mat +# --------------------------------------------------------------------------- + +@pytest.mark.hand_coded +def test_get_sim_mat_euclidean_hand_values(): + # Two WORD_VALS items at (0,0) and (3,4) -> distance 5, symmetric, 0 diag. + df = pd.DataFrame({ + "type": ["WORD_VALS", "WORD_VALS"], + "itemno": [1, 2], + "f0": [0.0, 3.0], + "f1": [0.0, 4.0], + }) + sm = pb.get_sim_mat(df, ["f0", "f1"]) + assert sm.shape == (2, 2) + np.testing.assert_allclose(sm, np.array([[0.0, 5.0], [5.0, 0.0]])) + + +@pytest.mark.hand_coded +def test_get_sim_mat_p1_manhattan(): + df = pd.DataFrame({ + "type": ["WORD_VALS", "WORD_VALS"], + "itemno": [1, 2], + "f0": [0.0, 3.0], + "f1": [0.0, 4.0], + }) + sm = pb.get_sim_mat(df, ["f0", "f1"], p=1) + np.testing.assert_allclose(sm, np.array([[0.0, 7.0], [7.0, 0.0]])) + + +@pytest.mark.hand_coded +def test_get_sim_mat_sorts_by_itemno(): + # Rows given out of itemno order; output should be sorted so item 1 is row 0. + df = pd.DataFrame({ + "type": ["WORD_VALS", "WORD_VALS"], + "itemno": [2, 1], + "f0": [10.0, 0.0], + "f1": [0.0, 0.0], + }) + sm = pb.get_sim_mat(df, ["f0", "f1"]) + # both off-diagonals equal the distance 10, diagonal 0 + np.testing.assert_allclose(sm, np.array([[0.0, 10.0], [10.0, 0.0]])) + + +@pytest.mark.property +def test_get_sim_mat_symmetric_zero_diag_shape(): + exp = make_experiment(seed=2) + sm = pb.get_sim_mat(exp["events"], exp["feat_cols"]) + pool = exp["n_lists"] * exp["list_length"] + assert sm.shape == (pool, pool) + # Minkowski self-distance is 0 on the diagonal + np.testing.assert_allclose(np.diag(sm), np.zeros(pool), atol=1e-12) + # symmetric + np.testing.assert_allclose(sm, sm.T, atol=1e-12) + # non-negative + assert (sm >= 0).all() + + +# --------------------------------------------------------------------------- +# get_itemno_matrices +# --------------------------------------------------------------------------- + +@pytest.mark.hand_coded +def test_get_itemno_matrices_pivots_to_serialpos_order(): + df = pd.DataFrame({ + "subject": ["s0"] * 4, + "session": [0] * 4, + "list": [0, 0, 1, 1], + "itemno": [11, 12, 21, 22], + }) + out = pb.get_itemno_matrices(df) + assert out.shape == (2, 2) + np.testing.assert_array_equal(out, np.array([[11, 12], [21, 22]])) + + +@pytest.mark.property +def test_get_itemno_matrices_padding_for_ragged_lists(): + # List 0 has 3 items, list 1 has 2 -> ragged; shorter row padded with 0. + df = pd.DataFrame({ + "subject": ["s0"] * 5, + "session": [0] * 5, + "list": [0, 0, 0, 1, 1], + "itemno": [1, 2, 3, 4, 5], + }) + out = pb.get_itemno_matrices(df) + assert out.shape == (2, 3) + np.testing.assert_array_equal(out, np.array([[1, 2, 3], [4, 5, 0]])) + + +@pytest.mark.property +def test_get_itemno_matrices_matches_factory_pres(): + exp = make_experiment(seed=9) + word_df = exp["events"].query("type == 'WORD'").copy() + out = pb.get_itemno_matrices(word_df) + np.testing.assert_array_equal(out, exp["pres_itemnos"]) + + +# --------------------------------------------------------------------------- +# get_all_matrices +# --------------------------------------------------------------------------- + +@pytest.mark.property +def test_get_all_matrices_shapes_and_consistency(): + exp = make_experiment(seed=4) + pres, rec, recalls = pb.get_all_matrices(exp["events"].copy()) + L = exp["list_length"] + assert pres.shape[1] == L + assert recalls.shape == rec.shape + assert pres.shape[0] == exp["n_lists"] + # recalls derived consistently with pres/rec + for t in range(recalls.shape[0]): + for r in range(recalls.shape[1]): + v = recalls[t, r] + if v > 0: + assert pres[t, v - 1] == rec[t, r] + elif v == 0: + assert rec[t, r] == 0 + + +@pytest.mark.property +def test_get_all_matrices_matches_factory_pres_matrix(): + exp = make_experiment(seed=8) + pres, rec, recalls = pb.get_all_matrices(exp["events"].copy()) + np.testing.assert_array_equal(pres, exp["pres_itemnos"]) + + +@pytest.mark.edge_case +def test_get_all_matrices_drops_lists_missing_a_type(): + # A list with WORD rows but no REC_WORD rows (and vice versa) is excluded, + # because get_all_matrices keeps only lists where both types are present. + rows = [] + # list 0: complete (pres + rec) + for pos, ino in enumerate([1, 2, 3]): + rows.append(dict(subject="s0", session=0, list=0, type="WORD", itemno=ino)) + for ino in [2, 1]: + rows.append(dict(subject="s0", session=0, list=0, type="REC_WORD", itemno=ino)) + # list 1: presentations only (no recalls) -> should be dropped + for ino in [4, 5, 6]: + rows.append(dict(subject="s0", session=0, list=1, type="WORD", itemno=ino)) + df = pd.DataFrame(rows) + pres, rec, recalls = pb.get_all_matrices(df) + # only one list survives + assert pres.shape[0] == 1 + assert rec.shape[0] == 1 + np.testing.assert_array_equal(pres, np.array([[1, 2, 3]])) + np.testing.assert_array_equal(recalls[0], np.array([2, 1])) + + +@pytest.mark.edge_case +def test_get_all_matrices_intrusion_in_events_maps_to_minus_one(): + rows = [] + for ino in [1, 2, 3]: + rows.append(dict(subject="s0", session=0, list=0, type="WORD", itemno=ino)) + # recall includes itemno 99 which was never presented on this list + for ino in [2, 99, 1]: + rows.append(dict(subject="s0", session=0, list=0, type="REC_WORD", itemno=ino)) + df = pd.DataFrame(rows) + pres, rec, recalls = pb.get_all_matrices(df) + np.testing.assert_array_equal(recalls[0], np.array([2, -1, 1])) diff --git a/tests/test_readme.py b/tests/test_readme.py new file mode 100644 index 0000000..6aa0f44 --- /dev/null +++ b/tests/test_readme.py @@ -0,0 +1,37 @@ +"""Execute the README quickstart so documentation can't silently drift. + +Extracts the first ```python fenced block from README.md, runs it, and checks the +documented results (lag-0 CRP is NaN, the +/-1 lag-CRP values, the temporal +clustering factor). If the README example or the API changes incompatibly, this +test fails. +""" +from __future__ import annotations + +import os +import re + +import numpy as np + +README = os.path.join(os.path.dirname(__file__), "..", "README.md") + + +def _first_python_block(md_text: str) -> str: + m = re.search(r"```python\n(.*?)```", md_text, re.DOTALL) + assert m, "no ```python block found in README.md" + return m.group(1) + + +def test_readme_quickstart_runs(): + with open(README) as fh: + code = _first_python_block(fh.read()) + ns: dict = {} + exec(compile(code, "README.md", "exec"), ns) + + crp = ns["crp"].set_index("lag")["prob"] + assert np.isnan(crp.loc[0]) + assert np.isclose(crp.loc[1], 2 / 3) + assert np.isclose(crp.loc[-1], 2 / 3) + + import pybeh_pd as pb + tf = pb.pd_temp_fact(ns["events"]) + assert np.isclose(tf, 0.875) diff --git a/tests/test_real_data.py b/tests/test_real_data.py new file mode 100644 index 0000000..ada2b8e --- /dev/null +++ b/tests/test_real_data.py @@ -0,0 +1,129 @@ +"""Real-data integration tests on 5 ltpFR2 sessions (committed fixture). + +Runs the actual ``pybeh_pd`` pipeline on real scalp-EEG free-recall data and +asserts both (a) the well-established empirical signatures of free recall — the +*temporal contiguity effect* (Kahana 1996) and above-chance *temporal clustering* +(Polyn, Norman & Kahana 2009) — and (b) that the outputs exactly match the frozen +golden values captured on this fixture, so the real-data behavior is locked too. + +Data + golden live in tests/data/ and are loaded from disk; neither cmlreaders nor +rhino is required. The synthetic fixture factory is also checked against the real +event schema, which is what licenses the synthetic-data unit tests elsewhere. +""" +from __future__ import annotations + +import numpy as np + +import pytest + +import pybeh_pd as pb +from real_data import load_ltpfr2_long, load_ltpfr2_golden, capture_outputs +from factories import make_experiment + +ITEMNO = "item_num" +LIST_LENGTH = 24 # ltpFR2 lists are 24 items +RTOL, ATOL = 1e-9, 1e-12 + + +# --- sanity: 5 sessions of valid free-recall data --------------------------- + +def test_loaded_five_sessions(): + df = load_ltpfr2_long() + assert df["session"].nunique() == 5 + assert set(df["type"].unique()) == {"WORD", "REC_WORD"} + study = df.query("type == 'WORD'") + assert (study.groupby("session")["list"].nunique() == 24).all() + assert (study.groupby(["session", "list"]).size() == LIST_LENGTH).all() + + +# --- the synthetic factory mirrors the real schema (validates fixtures) ------ + +def test_synthetic_fixture_mirrors_real_schema(): + df = load_ltpfr2_long() + syn = make_experiment(seed=0)["events"] + for col in ["subject", "session", "list", "type"]: + assert col in df.columns and col in syn.columns + assert {"WORD", "REC_WORD"}.issubset(set(syn["type"].unique())) + assert {"WORD", "REC_WORD"} == set(df["type"].unique()) + assert (df.query("type=='WORD'")[ITEMNO] > 0).all() + assert (syn.query("type=='WORD'")["itemno"] > 0).all() + + +# --- lag-CRP: temporal contiguity effect (properties) ----------------------- + +def test_pd_crp_contiguity_and_asymmetry(): + crp = pb.pd_crp(load_ltpfr2_long(), lag_num=5, itemno_column=ITEMNO).set_index("lag") + prob = crp["prob"] + assert np.isnan(prob.loc[0]) # lag 0 undefined + valid = prob.drop(index=0) + assert ((valid >= 0) & (valid <= 1)).all() # valid probabilities + assert prob.loc[1] > prob.loc[5] # forward contiguity + assert prob.loc[-1] > prob.loc[-5] # backward contiguity + assert prob.loc[1] == valid.max() # +1 most likely + assert prob.loc[1] > prob.loc[-1] # forward asymmetry + + +def test_pd_crp_matches_per_subject_decomposition(): + """Canonical usage (FR1_analyses.ipynb cell 10) is pd_crp applied per subject: + ``events.groupby('subject').apply(pb.pd_crp, itemno_column='item_num')``. For + one subject the direct call must equal that decomposition. (The literal + groupby-apply idiom itself breaks on pandas 3 — grouping column dropped — see + bug_report.md; we express the decomposition by explicit slicing here.)""" + df = load_ltpfr2_long() + direct = pb.pd_crp(df, itemno_column=ITEMNO).sort_values("lag")["prob"].values + per_subject = [ + pb.pd_crp(g, itemno_column=ITEMNO).sort_values("lag")["prob"].values + for _, g in df.groupby("subject") + ] + assert len(per_subject) == 1 + assert np.allclose(direct, per_subject[0], equal_nan=True) + + +# --- temporal clustering factor: above chance (properties) ------------------ + +def test_pd_temp_fact_above_chance(): + tf = pb.pd_temp_fact(load_ltpfr2_long(), itemno_column=ITEMNO) + assert 0.5 < tf <= 1.0 + assert tf > 0.6 + + +def test_temp_fact_per_session_above_chance(): + df = load_ltpfr2_long() + for sess, g in df.groupby("session"): + tf = pb.pd_temp_fact(g, itemno_column=ITEMNO) + assert 0.5 < tf <= 1.0, f"session {sess}: temp_fact={tf}" + + +# --- matrix builders produce valid serial positions on real data ------------ + +def test_get_all_matrices_valid_serialpos(): + pres, rec, recalls = pb.get_all_matrices(load_ltpfr2_long(), itemno_column=ITEMNO) + assert pres.shape[1] == LIST_LENGTH + assert recalls.shape == rec.shape + assert recalls.min() >= -1 + assert recalls.max() <= LIST_LENGTH + correct = recalls[recalls > 0] + assert ((correct >= 1) & (correct <= LIST_LENGTH)).all() + + +# --- behavior lock: every applicable analysis matches frozen golden ---------- +# capture_outputs() is the single source of truth shared with the golden +# regeneration script, so this checks the full real-data output set, not a subset. + +GOLDEN_KEYS = sorted(load_ltpfr2_golden().keys()) + + +def test_golden_case_set_is_complete(): + """Live capture and the committed golden must cover the same analyses.""" + live = capture_outputs(load_ltpfr2_long()) + assert set(live) == set(load_ltpfr2_golden()) + + +@pytest.mark.parametrize("name", GOLDEN_KEYS) +def test_real_data_output_matches_golden(name): + live = capture_outputs(load_ltpfr2_long()) + g = load_ltpfr2_golden() + x = np.asarray(live[name], dtype=float) + assert x.shape == g[name].shape, f"{name}: {x.shape} != {g[name].shape}" + assert np.allclose(x, g[name], rtol=RTOL, atol=ATOL, equal_nan=True), ( + f"{name} diverged from golden")