xeos 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. xeos-0.1.0/.github/workflows/ci.yml +134 -0
  2. xeos-0.1.0/.github/workflows/docs.yml +49 -0
  3. xeos-0.1.0/.github/workflows/python-publish.yml +99 -0
  4. xeos-0.1.0/.gitignore +23 -0
  5. xeos-0.1.0/CLAUDE.md +122 -0
  6. xeos-0.1.0/LICENSE +21 -0
  7. xeos-0.1.0/PKG-INFO +135 -0
  8. xeos-0.1.0/README.md +103 -0
  9. xeos-0.1.0/ci/environment.yml +14 -0
  10. xeos-0.1.0/conda/README.md +67 -0
  11. xeos-0.1.0/conda/meta.yaml +66 -0
  12. xeos-0.1.0/pyproject.toml +59 -0
  13. xeos-0.1.0/xeos/__init__.py +37 -0
  14. xeos-0.1.0/xeos/api.py +34 -0
  15. xeos-0.1.0/xeos/backends/__init__.py +17 -0
  16. xeos-0.1.0/xeos/backends/_jmd95.py +86 -0
  17. xeos-0.1.0/xeos/backends/_linear.py +52 -0
  18. xeos-0.1.0/xeos/backends/_mdjwf.py +59 -0
  19. xeos-0.1.0/xeos/backends/_mpas.py +103 -0
  20. xeos-0.1.0/xeos/backends/_roquet.py +162 -0
  21. xeos-0.1.0/xeos/backends/_roquet_idealized.py +81 -0
  22. xeos-0.1.0/xeos/backends/_roquet_spv.py +175 -0
  23. xeos-0.1.0/xeos/backends/_teos10.py +61 -0
  24. xeos-0.1.0/xeos/backends/_unesco.py +68 -0
  25. xeos-0.1.0/xeos/backends/_wright.py +87 -0
  26. xeos-0.1.0/xeos/conventions.py +104 -0
  27. xeos-0.1.0/xeos/eos.py +147 -0
  28. xeos-0.1.0/xeos/models.py +123 -0
  29. xeos-0.1.0/xeos/registry.py +64 -0
  30. xeos-0.1.0/xeos/tests/reference/.gitignore +18 -0
  31. xeos-0.1.0/xeos/tests/reference/README.md +84 -0
  32. xeos-0.1.0/xeos/tests/reference/_build_mitgcm_fortran.py +281 -0
  33. xeos-0.1.0/xeos/tests/reference/_build_mpas_eos_fortran.py +224 -0
  34. xeos-0.1.0/xeos/tests/reference/_build_roquet_spv_fortran.py +130 -0
  35. xeos-0.1.0/xeos/tests/reference/_build_seawaterpolynomials_julia.py +128 -0
  36. xeos-0.1.0/xeos/tests/reference/_build_unesco_fortran.py +147 -0
  37. xeos-0.1.0/xeos/tests/reference/_build_wright_fortran.py +157 -0
  38. xeos-0.1.0/xeos/tests/reference/environment.yml +31 -0
  39. xeos-0.1.0/xeos/tests/reference/generate_truth.py +199 -0
  40. xeos-0.1.0/xeos/tests/reference/truth.json +2243 -0
  41. xeos-0.1.0/xeos/tests/test_api.py +177 -0
  42. xeos-0.1.0/xeos/tests/test_backends.py +69 -0
  43. xeos-0.1.0/xeos/tests/test_models.py +66 -0
  44. xeos-0.1.0/xeos/version.py +3 -0
  45. xeos-0.1.0/xeos/xarray_utils.py +38 -0
@@ -0,0 +1,134 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
10
+
11
+ # Modern replacement for styfle/cancel-workflow-action: cancel superseded runs
12
+ # of this workflow on the same ref (e.g. rapid pushes to a PR branch).
13
+ concurrency:
14
+ group: ${{ github.workflow }}-${{ github.ref }}
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ # ---------------------------------------------------------------------------
19
+ # Full cross-validation suite across the supported Python range, using the
20
+ # pinned conda environment (numpy/xarray + the optional gsw + dask extras) so
21
+ # the TEOS-10 backend and the dask-parallelized path are actually exercised.
22
+ # ---------------------------------------------------------------------------
23
+ test-conda:
24
+ name: test (conda, py${{ matrix.python-version }})
25
+ runs-on: ubuntu-latest
26
+ defaults:
27
+ run:
28
+ shell: bash -l {0}
29
+ strategy:
30
+ fail-fast: false
31
+ matrix:
32
+ python-version: ['3.11', '3.12', '3.13', '3.14']
33
+
34
+ steps:
35
+ - name: Checkout source
36
+ uses: actions/checkout@v4
37
+
38
+ - name: Set up conda (miniforge)
39
+ uses: conda-incubator/setup-miniconda@v3
40
+ with:
41
+ miniforge-version: latest
42
+ channels: conda-forge
43
+ channel-priority: strict
44
+ python-version: ${{ matrix.python-version }}
45
+ activate-environment: test_env_xeos
46
+ auto-activate-base: false
47
+
48
+ - name: Update environment from ci/environment.yml
49
+ run: conda env update -f ci/environment.yml
50
+
51
+ - name: Editable install
52
+ run: python -m pip install -e .
53
+
54
+ - name: Environment info
55
+ run: |
56
+ conda env list
57
+ conda list
58
+
59
+ - name: Test with pytest
60
+ run: pytest -ra
61
+
62
+ # ---------------------------------------------------------------------------
63
+ # Guard the "core install stays lightweight" invariant: install ONLY the
64
+ # core package (numpy + xarray) via pip, confirm no heavy optional deps leaked
65
+ # in, and run the suite. The gsw/dask-dependent cases skip themselves cleanly
66
+ # (pytest.importorskip / ImportError -> pytest.skip), so this also serves as a
67
+ # non-conda cross-check that the vendored numpy kernels stand on their own.
68
+ # ---------------------------------------------------------------------------
69
+ test-pip-core:
70
+ name: test (pip, core-only)
71
+ runs-on: ubuntu-latest
72
+ steps:
73
+ - name: Checkout source
74
+ uses: actions/checkout@v4
75
+
76
+ - name: Set up Python
77
+ uses: actions/setup-python@v5
78
+ with:
79
+ python-version: '3.12'
80
+
81
+ - name: Install core package only (numpy + xarray)
82
+ run: |
83
+ python -m pip install --upgrade pip
84
+ python -m pip install -e .
85
+
86
+ - name: Assert the core install did not pull heavy optional deps
87
+ run: |
88
+ python -c "import xeos; print('xeos import OK,', len(xeos.list_eos()), 'backends registered')"
89
+ python - <<'PY'
90
+ import importlib.util
91
+ for mod in ("gsw", "dask", "numba"):
92
+ assert importlib.util.find_spec(mod) is None, \
93
+ f"{mod} unexpectedly installed by the core `pip install -e .` (invariant broken)"
94
+ print("lightweight-core invariant holds: gsw/dask/numba absent")
95
+ PY
96
+
97
+ - name: Run tests (pytest added separately so it is not a core dep)
98
+ run: |
99
+ python -m pip install pytest
100
+ pytest -ra
101
+
102
+ # ---------------------------------------------------------------------------
103
+ # Style / static-analysis. Advisory (non-blocking): the vendored numeric
104
+ # kernels deliberately use compact, hand-aligned coefficient tables and
105
+ # oceanographic single-letter names (R100, A0, SA, CT, ...), which black would
106
+ # explode line-per-value and pylint flags as naming/format violations. The
107
+ # job still runs so reviewers see the output, but does not gate the workflow.
108
+ # Flip `continue-on-error` to false once the team adopts a [tool.black] /
109
+ # pylint config that codifies those exceptions.
110
+ # ---------------------------------------------------------------------------
111
+ lint:
112
+ name: lint / format (advisory)
113
+ runs-on: ubuntu-latest
114
+ steps:
115
+ - name: Checkout source
116
+ uses: actions/checkout@v4
117
+
118
+ - name: Set up Python
119
+ uses: actions/setup-python@v5
120
+ with:
121
+ python-version: '3.12'
122
+
123
+ - name: Install lint tools + package
124
+ run: |
125
+ python -m pip install --upgrade pip
126
+ python -m pip install -e . black pylint
127
+
128
+ - name: black --check
129
+ continue-on-error: true
130
+ run: black --check --diff xeos
131
+
132
+ - name: pylint
133
+ continue-on-error: true
134
+ run: pylint xeos
@@ -0,0 +1,49 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ # Allow the GITHUB_TOKEN to deploy to GitHub Pages.
9
+ permissions:
10
+ contents: read
11
+ pages: write
12
+ id-token: write
13
+
14
+ # Allow one concurrent deployment; cancel in-progress runs for the same ref.
15
+ concurrency:
16
+ group: pages
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ build:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.12"
28
+
29
+ - name: Install dependencies
30
+ run: pip install -r docs/requirements.txt
31
+
32
+ - name: Build HTML docs
33
+ run: sphinx-build -W --keep-going -b html docs docs/_build/html
34
+
35
+ - uses: actions/configure-pages@v5
36
+
37
+ - uses: actions/upload-pages-artifact@v3
38
+ with:
39
+ path: docs/_build/html
40
+
41
+ deploy:
42
+ needs: build
43
+ runs-on: ubuntu-latest
44
+ environment:
45
+ name: github-pages
46
+ url: ${{ steps.deployment.outputs.page_url }}
47
+ steps:
48
+ - id: deployment
49
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,99 @@
1
+ # Build and publish xeos to PyPI using PyPI Trusted Publishing (OIDC).
2
+ #
3
+ # No API token / password secret is used: the `publish` job authenticates to
4
+ # PyPI over OpenID Connect via `pypa/gh-action-pypi-publish`, gated by the
5
+ # GitHub `pypi` environment. See conda/README.md and PyPI's "Trusted Publishers"
6
+ # settings for the one-time configuration the maintainer must do on PyPI before
7
+ # the first publish succeeds.
8
+ #
9
+ # Triggers:
10
+ # * release: published -> build + publish to PyPI
11
+ # * workflow_dispatch -> build (+ optional TestPyPI publish) on demand
12
+
13
+ name: Publish to PyPI
14
+
15
+ on:
16
+ release:
17
+ types: [published]
18
+ workflow_dispatch:
19
+ inputs:
20
+ testpypi:
21
+ description: "Also publish the build to TestPyPI"
22
+ type: boolean
23
+ default: false
24
+
25
+ permissions:
26
+ contents: read
27
+
28
+ jobs:
29
+ build:
30
+ name: Build sdist + wheel
31
+ runs-on: ubuntu-latest
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+
35
+ - name: Set up Python
36
+ uses: actions/setup-python@v5
37
+ with:
38
+ python-version: "3.x"
39
+
40
+ - name: Install build tooling
41
+ run: python -m pip install --upgrade pip build twine
42
+
43
+ - name: Build sdist and wheel
44
+ run: python -m build
45
+
46
+ - name: Check distribution metadata
47
+ run: twine check --strict dist/*
48
+
49
+ - name: Upload distributions
50
+ uses: actions/upload-artifact@v4
51
+ with:
52
+ name: python-package-distributions
53
+ path: dist/
54
+
55
+ publish-pypi:
56
+ name: Publish to PyPI
57
+ # Only publish to PyPI on an actual GitHub Release.
58
+ if: github.event_name == 'release'
59
+ needs: [build]
60
+ runs-on: ubuntu-latest
61
+ environment:
62
+ name: pypi
63
+ url: https://pypi.org/p/xeos
64
+ permissions:
65
+ id-token: write # OIDC token for Trusted Publishing
66
+ steps:
67
+ - name: Download distributions
68
+ uses: actions/download-artifact@v4
69
+ with:
70
+ name: python-package-distributions
71
+ path: dist/
72
+
73
+ - name: Publish to PyPI
74
+ uses: pypa/gh-action-pypi-publish@release/v1
75
+
76
+ publish-testpypi:
77
+ name: Publish to TestPyPI
78
+ # Opt-in dry-run path: trigger via "Run workflow" and tick the `testpypi`
79
+ # box. Requires a separate Trusted Publisher configured on
80
+ # https://test.pypi.org and a GitHub environment named `testpypi`.
81
+ if: github.event_name == 'workflow_dispatch' && inputs.testpypi
82
+ needs: [build]
83
+ runs-on: ubuntu-latest
84
+ environment:
85
+ name: testpypi
86
+ url: https://test.pypi.org/p/xeos
87
+ permissions:
88
+ id-token: write # OIDC token for Trusted Publishing
89
+ steps:
90
+ - name: Download distributions
91
+ uses: actions/download-artifact@v4
92
+ with:
93
+ name: python-package-distributions
94
+ path: dist/
95
+
96
+ - name: Publish to TestPyPI
97
+ uses: pypa/gh-action-pypi-publish@release/v1
98
+ with:
99
+ repository-url: https://test.pypi.org/legacy/
xeos-0.1.0/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # OS / editor
17
+ .DS_Store
18
+
19
+ # Sphinx docs build output
20
+ docs/_build/
21
+
22
+ # Claude Code transient worktrees
23
+ .claude/worktrees/
xeos-0.1.0/CLAUDE.md ADDED
@@ -0,0 +1,122 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Overview
6
+
7
+ `xeos` provides lightweight, xarray/dask-aware wrappers over many seawater
8
+ Equations of State (EOS), so analysts can apply the *same* EOS their ocean-model
9
+ run used (MOM6, Oceananigans, MITgcm) — selected by the model's own selector
10
+ string. The polynomial/rational EOS are vendored as numpy kernels; core runtime
11
+ deps are **numpy + xarray only**. TEOS-10 is delegated to the optional `gsw` extra
12
+ (`pip install xeos[teos10]`) — note `gsw.rho` is itself the Roquet 75-term
13
+ polynomial, not the exact Gibbs function (which is `gsw.rho_t_exact`).
14
+
15
+ ## Commands
16
+
17
+ ```bash
18
+ pip install -e .[test] # editable install + gsw + pytest
19
+ pytest # full suite
20
+ pytest xeos/tests/test_backends.py # cross-validation vs frozen truth
21
+ pytest xeos/tests/test_models.py::test_selector_resolves # a single test
22
+ pylint xeos && black xeos # lint / format (declared in ci/environment.yml)
23
+ ```
24
+
25
+ CI (`.github/workflows/ci.yml`) builds a conda env from `ci/environment.yml`,
26
+ does an editable install, and runs `pytest` across Python 3.11–3.14.
27
+
28
+ ## Architecture
29
+
30
+ The package is a small registry-and-facade design. Data flows:
31
+ `from_model(model, selector)` → canonical EOS id → registered `EOSBackend`
32
+ → wrapped in an `EquationOfState` facade → kernels dispatched through xarray.
33
+
34
+ - **`registry.py`** — `EOSBackend` dataclass (a `density` kernel + optional
35
+ analytic `drho_dt`/`drho_ds` + convention metadata) and a global registry.
36
+ Backends self-register at import time.
37
+ - **`backends/`** — one module per EOS; importing `backends/__init__.py`
38
+ registers them all. Vendored kernels: `_linear`, `_wright` (full + reduced
39
+ coefficient sets, native pressure **Pa**), `_jmd95` and `_unesco` (native
40
+ **dbar**; `_unesco` reuses `_jmd95._rho_surface`), `_mdjwf` (rational fit,
41
+ **dbar**), `_roquet` (55-term TEOS-10 density polynomial, **dbar**≈depth),
42
+ `_roquet_spv` (55-term specific-volume form, MOM6 `ROQUET_SPV`), and
43
+ `_roquet_idealized` (6 second-order Roquet forms via one factory, conservative
44
+ temp / absolute salinity, Z = −p), and `_mpas` (MPAS-Ocean: `mpas-linear` plus
45
+ `mpas-jm`/`mpas-wright`, which **reuse** the `_jmd95`/`_wright`-reduced kernels —
46
+ byte-identical EOS). `_teos10` is a thin lazy-`gsw` wrapper.
47
+ - **`eos.py`** — `EquationOfState` facade. Converts user pressure (default dbar)
48
+ to each backend's native unit, dispatches via `xarray_utils.apply_eos`, and
49
+ computes `alpha = -drho_dt/rho`, `beta = drho_ds/rho` from analytic derivatives,
50
+ falling back to centred finite differences when a backend supplies none.
51
+ - **`models.py`** — `MODEL_SELECTORS` alias table mapping each model's selector
52
+ strings (MOM6 `EQN_OF_STATE`, MITgcm `eosType`, Oceananigans EOS types, MPAS-O
53
+ `config_eos_type`) to canonical ids; `from_model()` and `equation_of_state()`
54
+ (the latter handles parameterised schemes like `linear`).
55
+ - **`conventions.py`** — `TemperatureKind`/`SalinityKind`/`PressureUnit` enums
56
+ and optional gsw-backed conversion helpers. **xeos never silently converts
57
+ inputs**; backends declare the kinds they expect. TEOS-10 + Roquet take
58
+ conservative temperature / absolute salinity; everything else potential
59
+ temperature / practical salinity.
60
+ - **`xarray_utils.py`** — `apply_eos` wraps kernels with
61
+ `xr.apply_ufunc(dask="parallelized")` when any input is a DataArray (preserving
62
+ labels/dask, attaching CF attrs); otherwise calls the kernel on numpy arrays.
63
+
64
+ ### Adding an EOS
65
+ Drop a `backends/_name.py` that builds an `EOSBackend` and calls `register(...)`,
66
+ import it in `backends/__init__.py`, and add its model selector strings to
67
+ `MODEL_SELECTORS`. Tests iterating the registry / truth fixtures pick it up.
68
+
69
+ ### Critical gotchas
70
+ - **Pressure units differ per backend** (Wright = Pa, JMD95/Roquet/gsw = dbar);
71
+ the facade converts, so a kernel always receives its declared native unit.
72
+ - **Wright variants share one formula, differ only in coefficients.** Both
73
+ `wright97-reduced` (MOM6 `WRIGHT`/`WRIGHT_RED`) and `wright97-full`
74
+ (`WRIGHT_FULL`) are validated against MOM6 Fortran (`MOM_EOS_Wright_{red,full}.F90`).
75
+ - **MOM6 `UNESCO`/`JACKETT_MCD` is the JMD95 fit, NOT EOS-80.** Despite the name,
76
+ `MOM_EOS_UNESCO.F90` is the Jackett & McDougall (1995) potential-temp fit —
77
+ byte-for-byte xeos's `jmd95` (verified to machine precision), differing from the
78
+ original Fofonoff & Millard EOS-80 (xeos's `unesco`, = MITgcm `UNESCO`) by up to
79
+ ~0.4 kg/m³. So those MOM6 selectors resolve to `jmd95` in `models.py`; the
80
+ `jmd95@mom6` truth case pins the equivalence.
81
+ - **`ROQUET_SPV` uses `deltaS=24`, NOT 32.** The widely-used `polyTEOS10.py`
82
+ reference has a typo in its `polyTEOS10_55t` routine (`deltaS=32`, copied from the
83
+ density form), making its specific-volume output disagree with its own published
84
+ check values. `_roquet_spv.py` uses the correct `24` and is validated against
85
+ MOM6's authoritative Fortran (`MOM_EOS_Roquet_SpV.F90`), not the buggy Python —
86
+ see `xeos/tests/reference/_build_roquet_spv_fortran.py`. (Bug reported upstream.)
87
+ - **MPAS-O `jm`/`wright` are the same EOS as `jmd95`/`wright97-reduced`.** MPAS-O's
88
+ `config_eos_type` offers `linear`, `jm` (Jackett-McDougall 1995) and `wright`
89
+ (Wright 1997, reduced coefficients); the `jm`/`wright` coefficients are
90
+ byte-for-byte identical to xeos's existing kernels, so `_mpas.py` reuses those
91
+ kernel functions rather than re-vendoring them, and the `mpas-jm`/`mpas-wright`
92
+ truth (from MPAS-O's own Fortran, `_build_mpas_eos_fortran.py`) confirms the reuse
93
+ is exact. MPAS-O's T/S clamping and depth→pressure parameterisations are
94
+ documented in `_mpas.py` but **not** applied (the facade takes pressure as input).
95
+ - **Not yet implemented:** MOM6 `JACKETT_06`, MOM6 `WRIGHT` legacy-buggy, MITgcm
96
+ `POLY3` (per-level runtime coefficients), MITgcm `IDEALGAS`. Add as new
97
+ `backends/_*.py` + selector entries when a trustworthy reference is available.
98
+
99
+ ## Testing & reference truth
100
+
101
+ `test_backends.py` validates each vendored kernel against frozen values in
102
+ `xeos/tests/reference/truth.json`. **Preferred truth source is the model's own
103
+ source code**, not a third-party Python port: MITgcm Fortran (`jmd95`, `unesco`,
104
+ `mdjwf` — coefficients parsed from `ini_eos.F`, formulas from `find_rho.F`), MOM6
105
+ Fortran (`wright97-*`, `jmd95@mom6`, `roquet-spv`) and MPAS-Ocean Fortran
106
+ (`mpas-linear`/`-jm`/`-wright`, from E3SM) all compiled with gfortran, plus
107
+ Oceananigans' `SeawaterPolynomials.jl` run via Julia (the six idealized `roquet-*`
108
+ forms); `gsw` is the accepted exception for `teos10`. The `_build_*.py` generators
109
+ download/compile/run those sources on demand (each self-checks a value first) and
110
+ emit only numbers, so the committed `truth.json` stays toolchain-free to consume.
111
+ A truth case may set a `"backend"` field to validate one kernel against multiple
112
+ model sources (e.g. `jmd95@mom6` → the `jmd95` backend, which is *also* validated
113
+ against MITgcm's own Fortran; `mpas-jm`/`mpas-wright` reuse the `jmd95`/
114
+ `wright97-reduced` kernels and confirm MPAS-O's `jm`/`wright` are the same EOS).
115
+ Only `polyTEOS10.py` (→ `teos10-poly55`) remains a Python-port reference; `gsw` is
116
+ canonical. All run in a **separate pinned env** (`xeos/tests/reference/environment.yml`:
117
+ gfortran + julia + gsw); the regular suite reads only `truth.json`. Regenerate via
118
+ `xeos/tests/reference/README.md` and commit the updated JSON.
119
+
120
+ ## Versioning
121
+
122
+ Single-sourced in `xeos/version.py`, read by hatchling (`[tool.hatch.version]`).
xeos-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Henri F. Drake
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
xeos-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.4
2
+ Name: xeos
3
+ Version: 0.1.0
4
+ Summary: xarray-enabled wrappers for various Equations of State of seawater
5
+ Project-URL: Homepage, https://github.com/hdrake/xeos
6
+ Project-URL: Bugs/Issues/Features, https://github.com/hdrake/xeos/issues
7
+ Author-email: "Henri F. Drake" <hfdrake@uci.edu>
8
+ License-File: LICENSE
9
+ Keywords: ocean modeling,oceanography,water mass transformation
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: numpy
21
+ Requires-Dist: xarray
22
+ Provides-Extra: complete
23
+ Requires-Dist: gsw; extra == 'complete'
24
+ Requires-Dist: numba; extra == 'complete'
25
+ Provides-Extra: teos10
26
+ Requires-Dist: gsw; extra == 'teos10'
27
+ Provides-Extra: test
28
+ Requires-Dist: dask; extra == 'test'
29
+ Requires-Dist: gsw; extra == 'test'
30
+ Requires-Dist: pytest; extra == 'test'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # xeos
34
+
35
+ **Lightweight, xarray-enabled wrappers for seawater equations of state.**
36
+
37
+ Ocean models (MOM6, MITgcm, MPAS-Ocean, Oceananigans) differ in the equation of
38
+ state (EOS) they use, and many let you change it at run time. Python post-processing then
39
+ often applies a *different* EOS than the simulation did, silently corrupting
40
+ derived quantities like density, thermal expansion, and water-mass transformation
41
+ diagnostics. `xeos` lets you pick the EOS that matches your run — **by the model's
42
+ own selector string** — and apply it to xarray/dask data through one uniform API.
43
+
44
+ It stays lightweight on purpose: the polynomial/rational equations of state are
45
+ vendored as small numpy kernels, so the core install needs only **numpy + xarray**.
46
+ TEOS-10 (via `gsw`) is an optional extra.
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ pip install xeos # core (numpy + xarray): all vendored EOS
52
+ pip install xeos[teos10] # adds TEOS-10 via gsw
53
+ pip install xeos[complete] # gsw + numba acceleration
54
+ ```
55
+
56
+ ## Usage
57
+
58
+ Match your model run by its selector string:
59
+
60
+ ```python
61
+ import xeos
62
+
63
+ # MOM6 run with EQN_OF_STATE = "WRIGHT_FULL"
64
+ eos = xeos.from_model("MOM6", "WRIGHT_FULL")
65
+ rho = eos.rho(theta, salt, pressure) # xarray DataArrays in, labeled DataArray out
66
+ a = eos.alpha(theta, salt, pressure) # thermal expansion
67
+ b = eos.beta(theta, salt, pressure) # haline contraction
68
+
69
+ # MITgcm eosType = 'JMD95Z'
70
+ xeos.from_model("MITgcm", "JMD95Z").rho(theta, salt, p)
71
+
72
+ # MPAS-Ocean config_eos_type = 'jm'
73
+ xeos.from_model("MPAS-Ocean", "jm").rho(theta, salt, p)
74
+
75
+ # Oceananigans TEOS10EquationOfState
76
+ xeos.from_model("Oceananigans", "TEOS10EquationOfState").rho(CT, SA, p)
77
+ ```
78
+
79
+ Or address an EOS directly:
80
+
81
+ ```python
82
+ xeos.equation_of_state("jmd95").rho(t, s, p)
83
+ xeos.rho(t, s, p, eos="wright97-full") # one-off functional form
84
+ xeos.list_eos() # what's available
85
+ ```
86
+
87
+ Inputs may be scalars, numpy arrays, or xarray `DataArray`s (dask-backed arrays
88
+ stay lazy). Pressure is sea pressure in **dbar** by default.
89
+
90
+ ## Conventions
91
+
92
+ `xeos` does **not** silently convert inputs. Each EOS declares the temperature and
93
+ salinity it expects: TEOS-10 and the Roquet polynomials use **conservative
94
+ temperature** + **absolute salinity**; the others use **potential temperature** +
95
+ **practical salinity** (see `eos.temperature` / `eos.salinity`). Explicit
96
+ conversion helpers live in `xeos.conventions` (these need the `gsw` extra).
97
+
98
+ ## Supported equations of state
99
+
100
+ `xeos.list_eos()` returns the current set. As of now:
101
+
102
+ - **linear** — configurable (MOM6/MITgcm/Oceananigans `LINEAR`)
103
+ - **wright97-full**, **wright97-reduced** — Wright 1997 (MOM6 `WRIGHT_FULL`, `WRIGHT`/`WRIGHT_RED`)
104
+ - **jmd95** — Jackett & McDougall 1995 (MITgcm `JMD95Z`/`JMD95P`; **also** MOM6
105
+ `UNESCO`/`JACKETT_MCD`, which are this fit — *not* EOS-80)
106
+ - **unesco** — UNESCO/EOS-80, Fofonoff & Millard 1983 (MITgcm `UNESCO`)
107
+ - **mdjwf** — McDougall et al. 2003 (MITgcm `MDJWF`)
108
+ - **teos10-poly55** — Roquet 55-term polynomial / TEOS-10 density form
109
+ (Oceananigans `TEOS10EquationOfState`, MOM6 `ROQUET_RHO`/`NEMO`)
110
+ - **roquet-spv** — Roquet 55-term specific-volume form (MOM6 `ROQUET_SPV`)
111
+ - **roquet-{linear,cabbeling,cabbeling-thermobaricity,freezing,second-order,simplest-realistic}**
112
+ — idealized second-order Roquet forms (Oceananigans `RoquetSeawaterPolynomial(:…)`)
113
+ - **mpas-linear**, **mpas-jm**, **mpas-wright** — MPAS-Ocean / E3SM
114
+ `config_eos_type` = `linear`/`jm`/`wright`; `mpas-jm` and `mpas-wright` reuse the
115
+ `jmd95` and `wright97-reduced` kernels (MPAS-O's `jm`/`wright` are the same EOS)
116
+ - **teos10** — TEOS-10 via `gsw` (its 75-term Roquet polynomial, not the exact
117
+ Gibbs function; MOM6/MITgcm `TEOS10`)
118
+
119
+ Not yet implemented (planned, slot into the same registry): MOM6 `JACKETT_06` and
120
+ `WRIGHT` legacy-buggy, and MITgcm `POLY3` (per-level runtime coefficients).
121
+
122
+ Full literature references with DOIs are in the
123
+ [usage docs](docs/usage.md#references).
124
+
125
+ ## Development
126
+
127
+ ```bash
128
+ pip install -e .[test]
129
+ pytest # validates vendored kernels against frozen fixtures
130
+ ```
131
+
132
+ Test "truth" values are generated from authoritative reference packages in a
133
+ pinned, separate environment and frozen into `xeos/tests/reference/truth.json`;
134
+ the test suite reads that file and stays lightweight. See
135
+ [`xeos/tests/reference/README.md`](xeos/tests/reference/README.md) to regenerate.