Stochastic authoring - #9
Merged
Merged
Conversation
A stochastic model is an ordinary JuMP model with two decorations. A random source is a JuMP variable marked with set_source — the importer emits it as a named declaration and rewrites its uses into source references, so it never becomes a decision variable, and using it twice means the same random variable (JuMP's own by-reference semantics is exactly the wire's identity-by-name). Scenario aggregators (smean, scvar, sfreq_leq, sfreq_geq) are plain functions returning nonlinear expressions with stochastic operator heads: JuMP stores them symbolically and validates heads only when a solver evaluates them, which this client never does — models ship as data and solve through the service. No new modeling surface, no second front door: JuMP algebra composes the aggregators like any expression, and a model with no stochastic marks emits byte-identical output (the existing goldens do not move). The schema gains the stochastic layer (Program fields 9-11 + the SourceRef expression case), regenerated into src/proto/; the operator catalog mirror grows the aggregator heads plus max/min, which the service now serves. Guardrails: bounds or domains on a source are rejected (a source carries a distribution, not a domain), source names must be unique and non-anonymous, scenario counts and seeds must be positive (0 is reserved on the wire for the server default). Version 0.3.0: new public API (set_source, set_scenarios, four aggregators), documented under Modeling with a newsvendor quickstart. Suite: 72 green.
The client pinned HTTP 2.5 while the service pins 1.11.0 — disjoint, so the two could not co-resolve, and any environment holding both was unresolvable. Widening the client is the only open direction. The service cannot move up: Oxygen 1.10.2, its newest release, caps HTTP at "1.8.0 - 1", so the whole Oxygen-based server stack is confined to the 1.x line until upstream moves. The widening is safe rather than merely convenient. The client touches exactly three HTTP symbols — request, header, URIs.escapeuri — none of which changed across the major, and the suite is green on 1.11.0 including the transport test, which runs a mock server over real requests. Nothing that resolves today stops resolving: the bound only adds. Patch bump, since relaxing a compat is additive.
The README and the docs home opened on "convert it to Quicopt's versioned wire schema" and "serialize the Program to the versioned, language-neutral wire bytes (proto3)". That describes the plumbing to someone who already knows it and tells a reader deciding whether this package solves their problem nothing: it never says a model goes out and an answer comes back, which is the whole product. The vocabulary was never load-bearing. What travels is a Program — variables, expressions and constraints as data, with a published protobuf schema — so the docs say that once, where the reader meets it, and drop "wire X" as a modifier everywhere else. The decode-equivalence note stays but now states the fact (two encodings of one model can differ byte for byte and mean the same thing) before naming the consequence. The API page called "Transport" becomes "Solving", which is what a caller is doing on it. The docs build was also failing, and had been: sfreq_geq carries sfreq_leq's docstring but appeared in no @docs block, so checkdocs flagged it and its @ref could not resolve, and KEY_PATH_ENV was cross-referenced from solve without being on any page. Both are now included — KEY_PATH_ENV alongside DEFAULT_BASE_URL, since "which server, and where the key is cached" is worth a section of its own. `make.jl` runs clean; the suite is unchanged at 72 tests.
The repository moved from the personal account to the Quicopt organization, so every badge, the Documenter canonical URL and the deploydocs target were resolving against a namespace that no longer serves this package.
The four authoring functions were the catalog's operator names surfaced
verbatim: `smean`, `scvar`, `sfreq_leq`, `sfreq_geq`, `set_source`. Those names
were never chosen for a reader — they were inherited from what travels on the
wire, and they leak the pipeline's vocabulary into the one surface a user
actually types.
`sfreq_leq` was the worst of them, and not merely for being ugly: its qualifier
binds to the wrong noun. In the only place it appears,
@constraint(m, sfreq_leq(demand - stock, 0) >= 0.9)
there is a probability being bounded, so "freq leq" competes with the bound the
constraint already carries and the reader cannot rule out the wrong parse from
context. Making the relation an argument fixes it structurally rather than
cosmetically — `prob(demand - stock, ≤, 0)` can only attach the comparison to
the quantity:
set_source -> set_distribution
smean -> expectation
scvar -> cvar
sfreq_leq/geq -> prob(x, ≤/≥, τ)
`set_` also promised far less than it delivered: it does not adjust an
attribute the way `set_lower_bound` does, it changes the variable's kind, and a
variable that *has a distribution* is self-evidently not one the solver picks.
Since `≤` and `<=` are the same function object in Julia, one method per
relation accepts both spellings; a fallback method names the two valid relations
instead of leaving a bare MethodError. Only `≤` and `≥` have scenario
counterparts.
None of this reaches the wire. The heads emitted are still `:smean`, `:scvar`,
`:sfreq_leq`, `:sfreq_geq`, so the byte goldens are unchanged — the proof that
the rename is confined to the public surface. The internals keep the wire's
vocabulary (`_register_source!`, the `SourceRef` declarations) deliberately, and
the comment at the head of the layer records the split so it is not "tidied"
into agreement later.
No released version carried the old names: 0.3.1 is neither deployed nor
registered, so this needs no deprecation shim and no further version bump.
The layer shipped in 0.3.0 but was reachable only from the Modeling API page. A reader landing on the README or the docs home saw three deterministic bullets and a linear quickstart, so the layer was invisible unless they already knew to go looking for it. Both now carry the newsvendor, which is the right example precisely because its answer is counter-intuitive: stock the mean and you are wrong, because running out costs more than overstocking. That asymmetry is the argument for modelling the uncertainty at all, and it lands in one line of output rather than a paragraph of motivation. The chance constraint comes along to show `prob`'s shape, since the relation-as-argument is unusual enough to be worth seeing before the API reference explains it. The snippets were run: `demand` imports to a named source declaration and drops out of the decision set, leaving `stock` alone. Also corrects `prob`'s docstring, which rendered the example constraint as "the probability that demand exceeds stock in no scenario is at least 0.9" — wrong twice over: the event is demand *not* exceeding stock, and the bound is on a per-scenario probability rather than on a count of scenarios.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.