pin-derive
Python bindings for pin-derive, a small bidirectional interval constraint engine. Declare cells and multidirectional relations, pin whichever facts you know, and the remaining cells narrow to derived points or still-open ranges. The same network solves in every direction: pin price and margin to derive profit, or pin profit and price to derive margin from the same relation.
Four runtimes share one JSON contract and one conformance corpus: the TypeScript
reference engine (pin-derive on npm), the native Rust crate
(pin-derive-core), the JavaScript wasm bindings (pin-derive-wasm), and this
package. It runs the Rust core as a bundled wasm binary through
wasmtime, so it is a pure-Python wheel
that works on every platform wasmtime supports, with no Rust toolchain and no
compilation at install time.
Install
uv add pin-derive # or: pip install pin-derive
Requires Python 3.11+. The only dependency is wasmtime.
Quickstart
from pin_derive import PinDerive
engine = PinDerive() # loads the bundled wasm binary
# A network is a spec: cells with an initial interval, relations, pins.
structure = {
"cells": [
{"id": "price", "init": {"lo": 0, "hi": None}, "label": "price ($)"},
{"id": "margin", "init": {"lo": 0, "hi": None}, "label": "margin"},
{"id": "profit", "init": {"lo": 0, "hi": None}, "label": "profit ($)"},
],
"relations": [
{"type": "product", "z": "profit", "x": "price", "y": "margin"}, # profit = price × margin
],
}
# Forward: pin price and margin, profit derives.
forward = engine.solve({
**structure,
"pins": [
{"cell": "price", "value": {"lo": 50, "hi": 50}},
{"cell": "margin", "value": {"lo": 0.2, "hi": 0.2}},
],
})
profit = next(c for c in forward["cells"] if c["id"] == "profit")
print(profit["provenance"], profit["value"]["lo"]) # derived 10.0
# Backward: pin profit and price instead, margin derives from the same relation.
backward = engine.solve({
**structure,
"pins": [
{"cell": "profit", "value": {"lo": 10, "hi": 10}},
{"cell": "price", "value": {"lo": 50, "hi": 50}},
],
})
margin = next(c for c in backward["cells"] if c["id"] == "margin")
print(margin["provenance"], margin["value"]["lo"]) # derived 0.2
Pin fewer cells than the relation needs and the under-determined cells come
back with boundsStatus: "bounded" and a {lo, hi} range to choose from
instead of an error. Pin values that cannot all hold and the one cell that
broke reports boundsStatus: "conflicting", the snapshot's conflicts list
names where two facts collided, and every other cell keeps its derived value.
The spec
| key | what it holds |
|---|---|
cells |
{id, init?: {lo, hi}, label?, meta?}; None on either side means unbounded |
relations |
a tagged union: equal, sum, product, affine, quantize, lte, within, min, max, clamp, lookup, curve |
pins |
{cell, value: {lo, hi}, id?, meta?} held assumptions, replayed in order |
commits |
same shape as pins; choices made inside a decision range |
options |
trace: True to record the narrowing DAG on a solve; the search knobs repairs reads |
Every spec, result, and relation shape is a TypedDict exported from
pin_derive, and the package ships py.typed, so mypy and an editor show the
full contract. The relation library is documented in the repository's
docs/REFERENCE.md; the spec format is identical across all four runtimes.
API
Every method on PinDerive takes a spec as a dict or a JSON string and
returns parsed JSON. Engine-reported errors raise PinDeriveError (a
RuntimeError) carrying the contractual, cross-runtime message.
| method | what it does |
|---|---|
solve(spec) |
solve a spec; truth only: values, decisions, conflicts |
explain(spec, options=None) |
why each conflict is there and what would relax it; a clean spec returns the empty envelope |
repairs(spec) |
which pin relaxations (drop, widen, shift) clear which conflicts, under spec["options"] search knobs |
preview(spec, moves, options=None) |
the snapshot as if moves were applied; mutates nothing |
commit(spec, moves, accept=None, options=None, expect_base=None) |
atomic move batch under an acceptance predicate, with compare-and-swap on the base fingerprint |
fill(request) |
proportional interior allocation across the solved network's ranges |
lp(program) |
two-phase simplex over a domain-neutral linear program |
apply_patches(spec, patches) / preview_patches(spec, patches, options=None) |
executable decisions: add, remove, or replace pins and commits, all-or-nothing |
solve_fingerprint(spec) / snapshot_fingerprint(snapshot) |
SHA-256 identity of an input state / a solved state |
canonicalize_solve_state(spec) |
the canonical bytes solve_fingerprint digests |
decision_delta(spec, next_spec) |
structured diff of two states' decisions |
prepare(spec) |
compile a structure once; returns a PreparedRef for the batch calls |
solve_many / explain_many / preview_many / commit_many |
one result per move set or batch on a prepared handle, in input order |
free_prepared(handle) |
drop a prepared handle (PreparedRef.free() does the same) |
Dependency traces (options["trace"]) come back as the raw trace list on the
snapshot. The traceForCell and conflictingFacts readers over that list
exist in the TypeScript package and the Rust crate, not in the wasm bindings.
Bundled wasm
pin_derive/_pin_derive_wasm.wasm ships inside the wheel and is built from the
repository's Rust core with wasm-opt -Oz. Pass an explicit path only when
testing a freshly built development artifact:
engine = PinDerive("../target/wasm32-unknown-unknown/release/pin_derive_wasm.wasm")
To rebuild it from a repository checkout:
node scripts/wasm-build.mjs # from the repo root; cargo + pinned wasm-opt, needs `pnpm install` once
cp rust/target/wasm32-unknown-unknown/release/pin_derive_wasm.wasm \
rust/python/src/pin_derive/_pin_derive_wasm.wasm
Use the build script rather than cargo build directly: the bundled binary is
expected to be the optimized one.
Stability
Pre-1.0. The JSON spec, snapshot, and error strings are the stable contract: they are locked by conformance fixtures shared with the other three runtimes, and a change to them is a minor-version release with notes. The method names above follow that contract one-to-one. The C ABI of the wasm binary itself is internal to these bindings.
License
Apache-2.0. Source, design notes, and the full reference live in the pin-derive repository.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pin_derive-0.10.1.tar.gz.
File metadata
- Download URL: pin_derive-0.10.1.tar.gz
- Upload date:
- Size: 181.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c7b0c77d8dd1e43c025b93f4af0e8e6c16bc9bc618052d88c7424fb487860d3
|
|
| MD5 |
c0038be3cf7f6b1112761cd2f9a4cad8
|
|
| BLAKE2b-256 |
ad3317dbcdf92f90cb896a94fc77960f7bd629515f20009df0fec970fd197c30
|
File details
Details for the file pin_derive-0.10.1-py3-none-any.whl.
File metadata
- Download URL: pin_derive-0.10.1-py3-none-any.whl
- Upload date:
- Size: 168.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c583df2f719ad9561cb5a7b49c48f2eefe65b2d47339db5ab1ff47830d332d5
|
|
| MD5 |
be63c92f2330a523ab343605204919bd
|
|
| BLAKE2b-256 |
79786bb912e5d6ef26deb9e1b534143d5fbc8ea15351bd4fb1abd1373d702e3f
|