Modeling
Turn a JuMP model into a Quicopt Program and encode it to bytes.
QuicoptClient.import_model — Function
import_model(m::JuMP.Model) -> `Program`Convert a JuMP/MOI model into a Quicopt Program. Reads the model at the MOI level: each variable becomes a scalar VarDecl (:x{col}) with its bounds and domain; the objective and every constraint become Quicopt expressions (ScalarNonlinearFunction/affine/quadratic → the expression graph; EqualTo → Zero, LessThan/GreaterThan/Interval → Nonneg). The result is a flat Program (no index sets); ±Inf bounds pass through and mean unbounded in that direction.
Variables given a distribution with set_distribution are emitted as named source declarations instead of decision variables, their uses rewritten to source references; the scenario count and seed from set_scenarios ride along as model data. A model with no stochastic marks emits exactly what it always did.
QuicoptClient.encode — Function
encode(prog) -> Vector{UInt8}Encode a Program to the (v1) bytes the service reads. Two encodings of one model can differ byte for byte and still mean the same thing — compare decoded Programs, never raw bytes.
Stochastic models
A stochastic model is an ordinary JuMP model with two decorations: variables given a distribution, and aggregators closing every stochastic subexpression before it reaches the objective or a constraint root.
using QuicoptClient, JuMP
m = Model()
@variable(m, 0 <= x <= 200) # decision: how much to stock
@variable(m, demand) # random: what will be asked for
set_distribution(m, demand, :normal, 100.0, 15.0)
set_scenarios(m, 512; seed = 42)
@objective(m, Min, 3x + 10 * expectation(max(demand - x, 0)))
result = solve(m)Using demand twice refers to the same draw — one name is one random variable; declare independent ones as separate variables. These models solve through the service only: the aggregator heads are symbolic, so attaching a local optimizer fails with an unsupported-operator error.
A chance constraint bounds a probability, so the comparison against the threshold is an argument of prob rather than part of its name — that way it can only attach to the quantity, not to the probability the constraint is already bounding:
@constraint(m, prob(demand - x, ≤, 0) >= 0.9) # stock out in ≤ 10% of scenariosQuicoptClient.set_distribution — Function
set_distribution(model, v, head::Symbol, params...; name = Symbol(JuMP.name(v)))Give the JuMP variable v a distribution: a draw from head (an operator from the service catalog, e.g. :normal) with the given parameters, which may be numbers or deterministic JuMP expressions. A variable that has a distribution is no longer one the solver chooses — it becomes a named random variable, and every use of v in the objective or constraints refers to the same draw (per scenario). Declare independent random variables separately.
Do not put bounds or integrality on v — it carries a distribution, not a domain — and close every stochastic subexpression with an aggregator (expectation, cvar, prob) before it reaches the objective or a constraint root.
set_distribution(model, v, data::AbstractVector{<:Real}; name = …)The empirical distribution: v takes the given scenario column, one value per scenario (length(data) must equal the count passed to set_scenarios). Several empirical columns are aligned by scenario index, so jointly-drawn columns preserve their correlation.
QuicoptClient.set_scenarios — Function
set_scenarios(model, n; seed = nothing)Set the number of scenarios of the sampled instance (and optionally the draw seed, ≥ 1). Both are model data: they pin the instance, so two solves of the same model see the same draws. Unset values fall back to the server's defaults (one scenario; its documented default seed).
QuicoptClient.expectation — Function
expectation(x) -> NonlinearExprThe expected value E[x] over the scenarios — the basic stochastic → deterministic aggregator. Symbolic: JuMP stores the node and the service evaluates it; attaching a local optimizer to a model containing one will fail with an unsupported-operator error, which is expected.
QuicoptClient.cvar — Function
cvar(x, α) -> NonlinearExprThe conditional value-at-risk of x at level α ∈ (0, 1) — the expected value of x over its worst (1 − α) tail. α must be a plain number.
QuicoptClient.prob — Function
prob(x, ≤, τ) -> NonlinearExpr
prob(x, ≥, τ) -> NonlinearExprThe probability that x ≤ τ (resp. x ≥ τ): the fraction of scenarios in which the comparison holds. This is the aggregator behind a chance constraint —
@constraint(m, prob(demand - stock, ≤, 0) >= 0.9)reads as demand is met in at least 90% of scenarios. The relation is an argument rather than part of the name so that it can only bind to x: written as prob_atmost(x, τ) it would compete with the bound on the probability itself, which the constraint already carries.
Only ≤ and ≥ have scenario counterparts (<= and >= are the same functions in Julia, so either spelling works); τ must be a plain number.