Skip to content

quicopt.wire

wire

quicopt.wire — a Program → the bytes the service reads.

Encoding is deterministic: the same model always produces the same bytes, and they are the bytes the service produces for that model too — checked against committed goldens in tests/test_wire_golden.py. The encoder uses nothing outside the standard library, like the rest of the package.

Example
import pulp
from quicopt import Client
from quicopt.pulp import import_model
from quicopt.wire import encode

prob = pulp.LpProblem("mix", pulp.LpMaximize)
x = pulp.LpVariable("x", lowBound=0, upBound=4)
y = pulp.LpVariable("y", cat="Binary")
prob += 3 * x + 5 * y
prob += x + 2 * y <= 5

body = encode(import_model(prob))         # 209 bytes
result = Client().solve(body)             # bytes solve like a model
print(result.status, result.objective)    # optimal 14.0

Encoding is normally invisible: Client.solve does it for you, and what it POSTs is exactly these bytes. Reach for encode to send them yourself, store them, or check them — and note that solve takes the bytes back, so an encoded model is a portable artifact, not an internal step.

body is the whole request: no session, no dialect, no order the encoder gets to choose. That is what makes "the bytes are identical" a testable claim rather than a hope, and it is what tests/test_wire_golden.py checks against committed goldens.

encode

encode(prog: Program) -> bytes

Encode a Program to the (v1) bytes the service reads.

Two equal Programs always encode to equal bytes, whichever order their tables happened to be built in. A model that declares no uncertainty encodes to exactly the bytes it would have before the stochastic layer existed, so adding that layer costs an ordinary model nothing.

Parameters:

Name Type Description Default
prog Program

The :class:~quicopt.ir.Program to encode.

required

Returns:

Name Type Description
bytes bytes

The message, deterministic and identical byte for byte to what the

bytes

Quicopt service produces for the same model.

Source code in src/quicopt/wire.py
def encode(prog: Program) -> bytes:
    """Encode a ``Program`` to the (v1) bytes the service reads.

    Two equal Programs always encode to equal bytes, whichever order their tables
    happened to be built in. A model that declares no uncertainty encodes to exactly
    the bytes it would have before the stochastic layer existed, so adding that
    layer costs an ordinary model nothing.

    Args:
        prog: The :class:`~quicopt.ir.Program` to encode.

    Returns:
        bytes: The message, deterministic and identical byte for byte to what the
        Quicopt service produces for the same model.
    """
    # Fields in schema order (1–8 deterministic, 9–11 stochastic); the order-free
    # tables are emitted sorted, which is what makes equal Programs equal bytes. The
    # two scenario scalars are omitted at their default of 1, which is also why the
    # default is 1 and not 0: protobuf cannot tell a zero from an absent field, so a
    # 0 here would reach the service as "use your default".
    io = BytesIO()
    for s in prog.sets:
        _wmsg(io, 1, _msg(lambda b, s=s: _enc_index_set(b, s)))
    for name in sorted(prog.indexed_sets.keys()):
        _wmsg(io, 2, _msg(lambda b, name=name: _enc_indexed_set(b, name, prog.indexed_sets[name])))
    for name in sorted(prog.params.keys()):
        _wmsg(io, 3, _msg(lambda b, name=name: _enc_param_table(b, name, prog.params[name])))
    for vd in prog.vars:
        _wmsg(io, 4, _msg(lambda b, vd=vd: _enc_var_decl(b, vd)))
    _wmsg(io, 5, _expr_msg(prog.objective))
    _wstr(io, 6, prog.sense)
    for c in prog.constraints:
        _wmsg(io, 7, _msg(lambda b, c=c: _enc_constraint(b, c)))
    for key in sorted(prog.fix.keys(), key=lambda k: (k[0], _idxkey(k[1]))):
        var, idx = key
        def build(b, var=var, idx=idx, key=key):
            _wstr(b, 1, var)
            _wmsg(b, 2, _idx_msg(idx))
            _wdouble(b, 3, prog.fix[key])
        _wmsg(io, 8, _msg(build))
    if prog.scenarios != 1:
        _wvarint(io, 9, prog.scenarios)
    if prog.scenario_seed != 1:
        _wvarint(io, 10, prog.scenario_seed)
    for name in sorted(prog.sources.keys()):
        _wmsg(io, 11, _msg(lambda b, name=name: _enc_source(b, name, prog.sources[name])))
    return io.getvalue()

encode_params

encode_params(params: dict) -> bytes

Encode just the Param tables as a standalone ParamData message.

This is how data is rebound without resending the model: send the Program once, then one ParamData per instance. Tables are written in sorted-key order, so the same data always encodes to the same bytes.

Parameters:

Name Type Description Default
params dict

A mapping table name → (index tuple → double) of the parameter values to send.

required

Returns:

Name Type Description
bytes bytes

The encoded ParamData message.

Source code in src/quicopt/wire.py
def encode_params(params: dict) -> bytes:
    """Encode just the ``Param`` tables as a standalone ``ParamData`` message.

    This is how data is rebound without resending the model: send the ``Program``
    once, then one ``ParamData`` per instance. Tables are written in sorted-key
    order, so the same data always encodes to the same bytes.

    Args:
        params: A mapping ``table name → (index tuple → double)`` of the parameter
            values to send.

    Returns:
        bytes: The encoded ``ParamData`` message.
    """
    io = BytesIO()
    for name in sorted(params.keys()):
        _wmsg(io, 1, _msg(lambda b, name=name: _enc_param_table(b, name, params[name])))
    return io.getvalue()