Skip to main content

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

pin_derive-0.10.1.tar.gz (181.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pin_derive-0.10.1-py3-none-any.whl (168.0 kB view details)

Uploaded Python 3

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

Hashes for pin_derive-0.10.1.tar.gz
Algorithm Hash digest
SHA256 5c7b0c77d8dd1e43c025b93f4af0e8e6c16bc9bc618052d88c7424fb487860d3
MD5 c0038be3cf7f6b1112761cd2f9a4cad8
BLAKE2b-256 ad3317dbcdf92f90cb896a94fc77960f7bd629515f20009df0fec970fd197c30

See more details on using hashes here.

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

Hashes for pin_derive-0.10.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6c583df2f719ad9561cb5a7b49c48f2eefe65b2d47339db5ab1ff47830d332d5
MD5 be63c92f2330a523ab343605204919bd
BLAKE2b-256 79786bb912e5d6ef26deb9e1b534143d5fbc8ea15351bd4fb1abd1373d702e3f

See more details on using hashes here.

Release history Release notifications | RSS feed

0.11.4

2 files

0.11.3

2 files

0.11.2

2 files

0.11.1

2 files

0.11.0

2 files

0.10.2

2 files

This release

0.10.1 This release

2 files

0.10.0

2 files

0.4.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page