Skip to content

Who may emit math from Python: composition and decomposition force the same seam #381

Description

@FBumann

Rule 6 refuses a Python modeling API. Two separate features now need something that emits math from Python, and docs/design/ceiling.md has been carrying the question as an aside in the Composition section:

Should anything ever be blessed, it is the schema level and not the plan level — that much is settled; what is not is whether to bless the seam at all.

One feature depending on an unanswered question is a footnote. Two arriving independently is the signal to answer it.

The two callers

Composition (#29, #30). Component libraries need namespacing and schema merge. Whatever genuinely is not data — variable port counts, runtime-unknown component types — belongs in a layer emitting more rows or more templates. That layer has nothing supported to call. A seam already exists in practice: api.load_schema accepts dict | MathSchema, so a programmatically built model already goes through validation, expansion, resolution and dim checking. It is undocumented and unversioned, which means it will be depended on and then broken.

Decomposition (ROADMAP Track 2d, #204, #39). Benders needs someone to write the cut. The cut is generated from duals by an algorithm; it is not sayable in YAML and should not be.

The distinction that may split them

These look like the same request and might not be, which is the first thing to settle:

Caller Emits At what level Needs a name in the language?
Composition declarations — variables, constraints schema (MathSchema) yes — they are the model
Decomposition affine rows — (row, col, coeff) matrix, post-lowering no — a cut is anonymous by nature

If that holds, decomposition does not force the schema seam. It forces a much narrower one: append COO rows to a live session, with no declaration, no name, no read-back obligation. That is a smaller thing to bless and it does not touch the language at all — arguably it is a sink capability rather than a seam.

Settling this is most of the work. It is possible the answer is "bless the narrow one, keep refusing the wide one".

The options, for the wide case

A — never bless. Ship a driver per use case: a decomposition driver, a component-library builder, each reading and writing internal structures with no public contract. Keeps rule 6 absolutely. Costs us a new shipped feature every time someone has a shape we did not anticipate, which is the failure mode rule 6 exists to avoid in the other direction.

B — bless a narrow, versioned schema emitter. Not a modeling API: no expression building, no fluent interface, no Model object. A documented way to hand load_schema a structure, versioned so it can change. The thing that already works, made real.

C — bless nothing, document the refusal. State that programmatic declaration is out and that generated YAML is the supported answer despite ceiling.md forbidding it. Honest but contradicts the current text; listed for completeness.

What decides it

Rule 6's purpose is that the model is the file — one artifact, readable, diffable, validated. Option B does not violate that if what it emits is a MathSchema that could have been written as YAML. It violates it if what it emits could not. That test — is the emitted thing sayable? — is probably the actual line, and it is checkable rather than a matter of taste.

Related: #38 (islands are the other thing that runs user Python, and are bounded rather than blessed), #67 (a version: key, which is the same versioning problem one level up).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:apiNative entry points and CLIarea:languageSchema, grammar, AST, macros, primitivesdecisionA question to be answered, not work to be done; closes by resolution

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions