Metadata-Version: 2.5
Name: yld.run
Version: 0.2.0
Summary: yld: programmatic problem authoring, solve requests and canonical `.yld` results.
License: MIT License
        
        Copyright (c) 2026 Sam Stanier
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Provides-Extra: notebook
Requires-Dist: jupyterlab>=4.0; extra == 'notebook'
Requires-Dist: matplotlib>=3.8; extra == 'notebook'
Requires-Dist: numpy>=1.24; extra == 'notebook'
Requires-Dist: pyvista>=0.43; extra == 'notebook'
Provides-Extra: view
Requires-Dist: pyvista>=0.43; extra == 'view'
Description-Content-Type: text/markdown

# yld

`yld` is pronounced “yield”.

This is a private research project for a programmatic computational mechanics toolkit. The name
reflects both mechanical yielding and the idea that the toolkit yields results.

## Two packages

`yld` is delivered as two independently installable Python packages, and the split is a licence and
distribution boundary, not a convenience (`architecture/PLATFORM.md` §9):

| package | licence | distributed | contents |
|---|---|---|---|
| **`yld.run`** (import: `yld`) | MIT | yes | problem authoring, materials and spatial fields as declarations, the solve-request document, the execution client, the archive reader and the PyVista viewer. Pure Python: no native module, no mandatory dependency. |
| **`yld_core`** | proprietary | **no** | constitutive binding, meshing, assembly, the conic solve, adaptation, independent verification, field evaluation and archive writing, through one compiled extension (`yld_core._native`). |

A problem is authored and a solve is requested with `yld` alone.  **Execution is remote by default**:
point an installation at a yld server once, and `solve()` needs nothing else:

```bash
uv pip install "yld.run[notebook]"
yld configure https://your-yld-server
yld examples
jupyter lab examples/
```

The endpoint is saved in the platform's user configuration directory — on Linux
`~/.config/yld/config.toml`, honouring `XDG_CONFIG_HOME`.  `YLD_EXECUTOR_URL` still overrides it for
one process, and `yld.use_executor(...)` overrides both for an explicit local or test executor.

```python
import yld

result = yld.solve(problem, yld.FELA_LB_Solver(order=3),
                   target=yld.Target.strength(), adapt=[yld.adapt.p(order=3)])
result.multiplier, result.verified                   # from the canonical `.yld` archive
result.save("analysis.yld"); result.show("cohesion")
```

`yld.use_executor(...)` replaces that default for the process — which is how a numerical installation
runs analyses locally (`yld_core.NativeExecutor()`) — and never falls back to it on its own.
`solve()` always submits a canonical `yld.solve-request/1` document to the configured executor and
always returns the same archive-backed result, so a local run and a service run are indistinguishable
to the caller.  With no endpoint and no registered executor it raises `yld.NoExecutorError` rather
than choosing a backend.  `yld.RemoteExecutor` speaks the service contract directly
(`notes/yld-service.md` §8).

Build both artefacts (the private one only for a local numerical installation):

```bash
tools/yld-package public   --out-dir build/dist   # yld_run-<version>-py3-none-any.whl + .tar.gz
tools/yld-package private  --out-dir build/dist   # yld_core-<version>-<platform>.whl
```

`tests/python/distribution_content_tests.py` builds and inspects the public artefacts (a positive
allowlist: no C++ sources, no native binaries, no experiments, no private modules), and
`tests/python/distribution_install_tests.py` installs each into a clean environment and checks what
it can and cannot do.

## Running analyses

A public-only installation authors problems, encodes requests, reads archives and executes against
the configured yld server.  A numerical installation can replace that remote default explicitly:

```python
import yld, yld_core
yld.use_executor(yld_core.NativeExecutor())
```

With no endpoint and no registered executor, `yld.solve()` raises `yld.NoExecutorError` and says so;
there is no discovery and no fallback between execution locations.  `YLD_EXECUTOR_TOKEN` and
`YLD_EXECUTOR_TIMEOUT` continue to apply when an endpoint is configured.

The private solve service for this workstation is installed and controlled by `tools/yld-service`
(`install`, `start`, `stop`, `status`, `logs`, `probe`).  It listens on `127.0.0.1:8765` only, has no
authentication, and must not be exposed: `notes/yld-service.md` has the HTTP contract, the resource
limits and the trust boundary.

Three complete worked examples ship with the public package.  Each is an authoritative `.py`
definition plus a JupyterLab notebook that imports it:

```bash
python -m yld.examples.getting_started
python -m yld.examples.footing_on_slope
python -m yld.examples.slope_stability
```

* `yld.examples.getting_started` — the weightless Prandtl strip footing, verified lower and upper
  bounds against `Nc = 2 + pi`;
* `yld.examples.footing_on_slope` — the OPTUM G2 introductory rigid-footing-on-slope benchmark,
  lower and upper bounds and the published comparison;
* `yld.examples.slope_stability` — the validated 9 m cutting slope, gravity multiplier, strength
  reduction and the layered section.

Retrieve the bundled notebooks into a writable directory for JupyterLab:

```bash
uv pip install "yld.run[notebook]"
yld examples
jupyter lab examples/
```

The notebooks use the saved configuration automatically; no environment variable or executor code is
needed in the kernel.  They are package resources: they are delivered in both the wheel and the
sdist, and the copy command refuses to overwrite an edited local copy unless `--force` is given.

While a remote analysis runs, `solve()` shows a single terminal or notebook spinner and then reports
the elapsed time and whether the result was independently verified.  Output is quiet when redirected
or non-interactive; disable it for a call with `solve(..., spinner=False)` or for a process with
`YLD_SPINNER=0`.

## Default production build

Use the CMake preset so build artefacts stay under `build/dev`. The `dev` preset is intentionally the default production-solver build: `Release`, Python enabled, experiments disabled. CMake obtains Eigen 3.4.0 and Catch2 v3.8.1 through pinned `FetchContent`; no system Eigen/Catch2 path is required for the normal development build.

System prerequisites: a C++20 compiler, CMake ≥ 3.20, Ninja or Make, **clang-format 21.1.8** (the pinned
version CI installs, because `code_format` is a test and its verdict must not depend on the image), Python 3 with development headers and `uv` (the test suite runs Python oracles
through `uv`), a Rust toolchain (Clarabel.cpp builds its Rust backend), and **header-only Boost**
(`libboost-dev`) for the exact geometric predicates in `yld_mesh`.

Canonical verification goes through the shared build/test wrappers, which bound memory on the
workstation (see `AGENTS.md`, "Build discipline"):

```bash
tools/yld-build --preset dev
tools/yld-test  --preset dev
```

For a stale or missing configure, refresh the cache first — `cmake --fresh --preset dev` — then
build and test through the wrappers again.

If dependency discovery or cache state gets stale, refresh the cache with `cmake --fresh --preset dev` rather than manually deleting the build tree.

Debug builds are opt-in only:

```bash
cmake --fresh --preset debug
tools/yld-build --preset debug
tools/yld-test  --preset debug
```

The compiled extension is written **inside the private package** (`yld_core/_native*.so`) rather than
into a build directory, because that is where an extension module has to be to be importable: a
package's `__path__` is its own directory.  One checkout therefore has one native extension — rebuild
for the preset you are testing.  No `PYTHONPATH` entry is needed to import it.

Do not use the debug preset for solver performance, FELA regression timing, or ordinary commit validation.

## Examples

The public examples live in `yld/examples/`: `footing_on_slope.py` and `slope_stability.py` are the
authoritative problem definitions, and `yld/examples/notebooks/` carries their executed JupyterLab
notebooks.  `examples/slope_stability.ipynb` remains a longer research notebook developed in this
repository: gravity and strength-reduction targets, Bernstein lower and upper bounds with
independent verification, adaptation recipes, and deterministic and random spatial strength
fields. See `examples/README.md` for its launch instructions.

## Testing

Routine development tests only the area a change touches. Catch2 tags and `ctest -R` are the
normal entry points:

```bash
# one whole test binary
./build/dev/tests/yld_mechanics_tests

# a Catch2 tag, e.g. the model just changed
./build/dev/tests/yld_mechanics_tests "[mohr_coulomb]"

# ctest tests selected by name
tools/yld-test --preset dev -R "mohr_coulomb|code_format"

# the Python suite only
tools/yld-test --preset dev -R "^python_|^bezier_member_vtk$|^parameter_archive_visualisation$"
```

The complete `tools/yld-test --preset dev` suite includes MFEM/Python integration tests and takes
about four minutes. It is **not** the commit gate: it runs in CI (`.github/workflows/ci.yml`) on every
push to `main` and on pull requests, against the production Release solver path.

The local commit hook is deliberately fast, because a slow hook teaches people to use
`--no-verify`:

```bash
git config core.hooksPath .githooks   # one-time setup
```

`.githooks/pre-commit` checks only what needs the working tree or is commit-specific —
`clang-format` on staged C++ files, a 5 MB per-file size guard against bulk artefacts, validation of
any staged experiment record, and a private-key scan. Bypass for work-in-progress commits with
`YLD_SKIP_HOOKS=1 git commit ...`.

Run the full suite locally before a substantial push, or when CI is unavailable:

```bash
tools/yld-test --preset dev
```
