Skip to main content

planteo

PyPI License

A typed representation for a problem after it has been read and before it is solved.

A narrative problem statement is ambiguous, under-specified and unit-bearing. Turning it into a solver model is usually done by writing code straight from the text, and that loses the two things that decide whether the result is right: which words produced which symbol, and what the words did not say.

planteo holds the object in between. It does not read natural language and it does not solve anything. It defines the object, checks it, and emits it.

Why this exists

Across four different fields, the reported check is that the artifact ran, and the measured faithfulness is much lower:

Field What gets reported What was measured
Optimization modelling the solver reached the reference objective objective correctness does not imply a correct model; compensating errors pass (survey)
Statement formalization it compiles a 3.0 to 29.0 point compile-to-faithfulness gap; the strongest agent compiled 89.5% and was faithful 60.5% (study)
Experiment design the plan looks complete every model tested is weak at datasets, baselines and metrics (benchmark)
Simulation modelling the model runs tools do well at qualitative work, badly at causal reasoning and quantitative fixes (benchmark)

A model that runs is not a model that is right. This package makes the difference inspectable.

The two design commitments

Every quantity carries a dimension. Not optional, and dimensionless is a dimension that must be stated. Adding metres to seconds is rejected before a solver ever sees it, and a unit is compared by its exponent vector rather than by its label, so tonnes and kilograms are compatible while a string comparison would say otherwise.

Every element carries its provenance. A Span records the offsets and the text it covered, so a document that has been stored or moved can be checked against its narrative rather than trusted. An element with no span states that it was inferred, and why.

And one thing no surveyed representation carries: open_questions. When the narrative does not determine something, the question is recorded with the span that raised it and the choice that was taken, instead of being silently resolved.

Install

pip install planteo               # the representation, zero dependencies
pip install "planteo[pyomo]"      # plus the Pyomo emitter

Use

from planteo import (
    Comparator, Compare, Dimension, Domain, Family, Narrative,
    Objective, Problem, Product, Quantity, Ref, Role, Sense, Span, Sum, validate,
)

text = Narrative(
    "A plant blends ore from two pits. Pit A costs 12 USD per tonne and pit B "
    "costs 9 USD per tonne. Together they must deliver at least 100 tonnes. "
    "Minimise the total cost."
)

TONNE = Dimension.of("t", mass=1)
PER_TONNE = Dimension.of("USD/t", currency=1, mass=-1)

problem = Problem(
    narrative=text,
    family=Family.OPTIMIZATION,
    quantities=(
        Quantity("x_a", Role.VARIABLE, TONNE, lower=0.0, span=Span.find(text, "Pit A")),
        Quantity("x_b", Role.VARIABLE, TONNE, lower=0.0, span=Span.find(text, "pit B")),
        Quantity("c_a", Role.PARAMETER, PER_TONNE, value=12.0),
        Quantity("c_b", Role.PARAMETER, PER_TONNE, value=9.0),
        Quantity("demand", Role.PARAMETER, TONNE, value=100.0),
    ),
    relations=(
        Compare(Sum((Ref("x_a"), Ref("x_b"))), Comparator.GE, Ref("demand"), name="meet_demand"),
    ),
    objectives=(
        Objective(Sense.MINIMISE, Sum((
            Product((Ref("c_a"), Ref("x_a"))),
            Product((Ref("c_b"), Ref("x_b"))),
        )), name="total_cost"),
    ),
)

report = validate(problem)
assert report.ok, report

Emit it:

from planteo.emit import pyomo as emit

print(emit.emit_source(problem))   # a runnable Pyomo script, with provenance comments
model = emit.build_model(problem)  # or a live ConcreteModel

The emitted source carries the narrative alongside the code, so a reader can check the formalization against the words:

# tonnes taken from pit A
# from the narrative: "Pit A"
model.x_a = pyo.Var(domain=pyo.Reals, bounds=(0.0, None))  # t

Compare two formalizations:

from planteo import compare

compare(mine, yours).verdict   # Verdict.EQUIVALENT | Verdict.NOT_PROVEN_EQUIVALENT

There is deliberately no DIFFERENT verdict. Equal canonical form proves equivalence; unequal canonical form proves nothing, and a vocabulary that pretended otherwise would produce confident false negatives.

Dynamics

A dynamics problem is a system of first-order ODEs read from a statement: states with initial values, one independent variable with the range to simulate, a rate per state, and the queries the statement asks.

from fractions import Fraction
from planteo import Constant, Dimension, Family, Narrative, Power, Problem, Product, Quantity, Query
from planteo import Rate, Ref, Role, Sum, system

KG, LITRE, MINUTE = Dimension.of("kg", mass=1), Dimension.of("L", length=3), Dimension.of("min", time=1)
tank = Problem(
    narrative=Narrative("A tank holds 100 L of brine with 2 kg of salt. Brine at 0.4 kg/L flows in "
                        "at 5 L/min and drains at the same rate. How much salt after 20 minutes?"),
    family=Family.DYNAMICS,
    quantities=(
        Quantity("t", Role.INDEPENDENT, MINUTE, lower=0.0, upper=30.0),
        Quantity("x", Role.STATE, KG, value=2.0),                       # value = initial value
        Quantity("V", Role.PARAMETER, LITRE, value=100.0),
        Quantity("c_in", Role.PARAMETER, Dimension.of("kg/L", mass=1, length=-3), value=0.4),
        Quantity("q", Role.PARAMETER, Dimension.of("L/min", length=3, time=-1), value=5.0),
    ),
    relations=(Rate("x", "t", Sum((
        Product((Ref("c_in"), Ref("q"))),
        Product((Constant(-1.0, Dimension.dimensionless()), Ref("x"), Power(Ref("V"), Fraction(-1)), Ref("q"))),
    ))),),
    queries=(Query(Ref("x"), 20.0, name="salt_after_20_min"),),
)
built = system(tank)       # states, y0, t_span, rhs(t, y), one function per query

system needs nothing numerical; integrate it with any ODE solver, for example scipy.integrate.solve_ivp(built.rhs, built.t_span, built.y0). planteo.emit.scipy.emit_source writes the same system as a readable Python module. The rate's dimension is checked against the independent variable: a rate in kilograms where kilograms per minute are due is rejected, which is the unit-conversion trap in its dynamics form.

What it validates

  1. Dimensions. Every relation is checked term by term; both sides of a comparator must agree.
  2. Closure. No free symbol.
  3. Determinacy. Every quantity is given, chosen, derived by exactly one relation, or observed.
  4. Span integrity. Every span still covers the text it recorded.
  5. Family completeness. The family's required structure is present: an objective or a feasibility declaration for optimization; for dynamics, one bounded independent variable, a rate and an initial value for every state, and queries inside the range. The families do not mix.

The validator reports every finding rather than stopping at the first, and each finding names the element it is about.

What it is not

  • Not a translator. Producing a Problem from text is the job of a harness; this defines the target.
  • Not a solver or a solver wrapper.
  • Not a modelling language. It emits to Pyomo for optimization and to a SciPy module for dynamics, rather than competing with either. A MiniZinc emitter is designed and not built.
  • Not an equivalence oracle. See the verdict vocabulary above.

Documentation

The wiki is in docs/. The design document that governs this package, written before any code, is docs/design/SDD.md; every requirement in it names the test that verifies it.

License

MIT. See LICENSE.

Release files for planteo 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for planteo 0.2.0
File Size Uploaded
planteo-0.2.0.tar.gz 45.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for planteo 0.2.0
File Interpreter ABI Platform
planteo-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 86.0 kB

Release files / planteo-0.2.0.tar.gz

Download URL planteo-0.2.0.tar.gz
Size 45.7 kB
Tags Source
SHA-256 checksum
How to use checksums
c9efad05c2161395a57c36876a53d5430a2aebd110a0ef5419cad67e2810f2dc
BLAKE2b-256 checksum
How to use checksums
c1879c6e6cd3dff5786ce35beec7f8cb9242789128f6b212d30720006e5fd147
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / planteo-0.2.0-py3-none-any.whl

Download URL planteo-0.2.0-py3-none-any.whl
Size 40.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f2ad79849a686af0b5e6440529e3574a7974d04c7d264108b33ad41aaa43c0a8
BLAKE2b-256 checksum
How to use checksums
58d4f54d36fef40d809b06657ca8c803f05cebfd67f4ef5bdbc805b4d14f499e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.1

2 release files

This release

0.2.0 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release 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