Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,13 @@ when one breaks.
`STILT_TEST_FETCH_MET=1 uv run pytest tests/transport/hysplit/test_release_assignment.py::test_hysplit_multipoint_release_points_follow_control_order --setup-only -q`.
A receptor in the page must stay inside that time window. `docs.yml` restores
the same cache the tests use.
- **The guides that open a project start from a sample project**
(`docs/_ext/docs_examples.py`): four receptors an hour apart on 15 January
2021, run once per docs build (about 40 s) and copied into a temporary folder
for each page. A page starts it from a hidden block
(`docs_examples.temp_workdir(sample=True)`) and goes back with
`os.chdir(docs_cwd)` at its end, so the page's files land in the temporary
folder.
- **Optional dependencies:** the `dev` group installs `pystilt[complete]`, so the
plotting extra (`visualization`) is there in CI. If an example needs another
extra, add `pystilt[extra]` the same way.
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
14 and 15 January 2021 that the tests use, so its output and figures are
what the code makes. Its receptor moved from 15 July 2023 to 15 January
2021 to fit that met.
- The outputs, checking, and receptors guides run when the docs are built too,
and show their plots. The outputs and checking guides open a small sample
project that the docs build makes first (about 40 s).

### Fixed

- `receptor.plot.map()`, and so `sim.plot.map()`, puts its legend in the upper
right corner. It searched for the emptiest spot, which took over a second
with a simulation's particles on the map, and matplotlib warned.
- `receptor.plot.map()` labels a column's heights, and a multipoint receptor's
height colorbar, with the reference the receptor was given (`AGL` or `MSL`).
It said `AGL` for both.

## [0.1.0a24] - 2026-10-09

Expand Down
100 changes: 100 additions & 0 deletions docs/_ext/docs_examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""
Helpers for the docs pages that run real PYSTILT code when the docs are built.

The pages run on the HRRR files the tests cache: 14 and 15 January 2021,
cropped around Salt Lake. A page that works with a project on disk starts from
a small sample project, which is built once per docs build (about 40 s) and
copied into a temporary folder for each page that uses it.

A page uses these from a block that is run but not shown (``:suppress:``)::

import docs_examples

docs_cwd = docs_examples.temp_workdir(sample=True)

and returns to the docs folder at its end with ``os.chdir(docs_cwd)``.
"""

import atexit
import os
import shutil
import tempfile
from pathlib import Path

DOCS = Path(__file__).resolve().parents[1]

#: Where the sample project is built. ``just build-docs`` clears ``docs/_build``,
#: so every clean build makes a new one; ``just docs-serve`` reuses it.
SAMPLE = DOCS / "_build" / "sample_project"

#: The sample project's receptors: one site, the four hours before 10:00 UTC.
SAMPLE_HOURS = (6, 7, 8, 9)

_temporary_folders: list[Path] = []


def met() -> dict:
"""Return the met config of the files the tests cache."""
default = DOCS.parent / "tests" / "met_cache"
directory = Path(os.environ.get("STILT_TEST_MET_DIR", default)).resolve()
if not directory.is_dir():
raise FileNotFoundError(
f"{directory} is missing. The docs run on the met files the tests "
"cache: see 'Examples in the docs' in AGENTS.md."
)
return {"directory": str(directory), "file_format": "%Y%m%d_%H", "file_tres": "6h"}


def sample_project() -> Path:
"""Return the folder of the sample project, building it the first time."""
import stilt

project = SAMPLE / "my_project"
marker = SAMPLE / "complete"
if not marker.exists():
shutil.rmtree(SAMPLE, ignore_errors=True)
receptors = [
stilt.PointReceptor(
time=f"2021-01-15 {hour:02d}:00",
longitude=-111.848,
latitude=40.766,
altitude=10,
)
for hour in SAMPLE_HOURS
]
grid = stilt.Grid(
xmin=-113.0, xmax=-110.5, ymin=40.0, ymax=42.0, xres=0.01, yres=0.01
)
stilt.Project.init(
str(project),
receptors=receptors,
mets={"hrrr": met()},
variants={"hrrr": {}},
n_hours=-24,
numpar=200,
grid=grid,
).run()
marker.touch()
return project


def temp_workdir(sample: bool = False) -> str:
"""
Change to a new temporary folder and return the folder you left.

With ``sample``, the new folder holds a copy of the sample project as
``my_project``, so a page can open it the way a reader would.
"""
previous = os.getcwd()
work = Path(tempfile.mkdtemp())
_temporary_folders.append(work)
if sample:
shutil.copytree(sample_project(), work / "my_project")
os.chdir(work)
return previous


@atexit.register
def _remove_temporary_folders() -> None:
for folder in _temporary_folders:
shutil.rmtree(folder, ignore_errors=True)
16 changes: 7 additions & 9 deletions docs/getting_started/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,18 @@ the ground on the University of Utah campus, at 08:00 UTC on 15 January 2021:

.. This page runs when the docs are built. It uses the met files the tests
cache, which cover 14 and 15 January 2021 around Salt Lake, and works in a
temporary folder so the project it makes is not left in the repository.
temporary folder so the project it makes is not left in the repository
(docs/_ext/docs_examples.py).

.. ipython:: python
:suppress:

import os
import tempfile
from pathlib import Path

docs_cwd = os.getcwd()
met_dir = Path(os.environ.get("STILT_TEST_MET_DIR", "tests/met_cache")).resolve()
assert met_dir.is_dir(), f"{met_dir} is missing: see 'Examples in the docs' in AGENTS.md"
met = {"directory": str(met_dir), "file_format": "%Y%m%d_%H", "file_tres": "6h"}
os.chdir(tempfile.mkdtemp())

import docs_examples

met = docs_examples.met()
docs_cwd = docs_examples.temp_workdir()

Step 2: Follow the particles
----------------------------
Expand Down
35 changes: 31 additions & 4 deletions docs/guides/checking.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,29 @@ failed ones by reason. Then it lists the output's settings folders

In Python, ``project.status()`` is the same, one row per simulation:

.. code-block:: python
.. This page runs when the docs are built, on the sample project of
docs/_ext/docs_examples.py (four receptors, all complete).

.. ipython:: python
:suppress:

import os

import docs_examples

docs_cwd = docs_examples.temp_workdir(sample=True)

.. ipython:: python

import stilt

project = stilt.Project("./my_project")
status = project.status()
status.state.value_counts()
status[status.state == "failed"][["receptor", "variant", "reason", "message"]]

The sample project has no failures, so the last table is empty.

Each simulation is in one of four states. The state comes from the files in
the output directory, so it is the same whoever asks and whenever.

Expand Down Expand Up @@ -176,10 +193,20 @@ My footprint looks wrong
Start from the particles. A map of them shows where the air came from,
whatever the footprint grid:

.. code-block:: python
.. ipython:: python

sim = project.simulation("202101150800_-111.848_40.766_10", "hrrr")

@savefig checking_particles.png width=6in
sim.particles.stilt.plot.map();

@savefig checking_footprint.png width=6in
sim.footprint.stilt.plot.map();

.. ipython:: python
:suppress:

sim.particles.stilt.plot.map()
sim.footprint.stilt.plot.map()
os.chdir(docs_cwd)

Then check, in order:

Expand Down
Loading
Loading