Modeling

Turn a JuMP model into a Quicopt Program and encode it to bytes.

QuicoptClient.import_modelFunction
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; EqualToZero, LessThan/GreaterThan/IntervalNonneg). 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.

source
QuicoptClient.encodeFunction
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.

source

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 scenarios
QuicoptClient.set_distributionFunction
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.

source
QuicoptClient.set_scenariosFunction
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).

source
QuicoptClient.expectationFunction
expectation(x) -> NonlinearExpr

The 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.

source
QuicoptClient.cvarFunction
cvar(x, α) -> NonlinearExpr

The 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.

source
QuicoptClient.probFunction
prob(x, ≤, τ) -> NonlinearExpr
prob(x, ≥, τ) -> NonlinearExpr

The 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.

source