Skip to main content

quicopt

PyPI Python 3.9+ Docs License: Apache 2.0

The Python client for the Quicopt optimization service.

You describe a decision: what you get to choose, what has to hold, and what you want as much (or as little) of as possible. Quicopt finds the best choice there is.

Write the model with the Python modeling library you already use — Pyomo, OR-Tools MathOpt or PuLP — and solve encodes it, sends it to the service and hands back the answer. There is no solver on your machine; the service does the solving.

Full documentation: https://quicopt.github.io/quicopt-python/

Install

pip install quicopt              # the model and the encoder — standard library only
pip install "quicopt[pyomo]"     # + read models written in Pyomo
pip install "quicopt[mathopt]"   # + read models written in OR-Tools MathOpt
pip install "quicopt[pulp]"      # + read models written in PuLP

From source (contributors), an editable install into a virtual environment:

python3 -m venv .venv && . .venv/bin/activate
pip install -e '.[pyomo,mathopt,pulp]'

Use

import pyomo.environ as pyo
from quicopt import Client

m = pyo.ConcreteModel()
m.x = pyo.Var(bounds=(0.1, 10))
m.obj = pyo.Objective(expr=m.x**2 + 1.0 / m.x, sense=pyo.minimize)

client = Client()                             # defaults to the free-tier Quicopt server
result = client.solve(m)                      # read the model, encode it, solve it
print(result.status, result.objective, result.solution)
print(result.display)                         # the service's ready-to-print summary

solve takes the model as it stands (Pyomo, OR-Tools MathOpt, or PuLP) and reads it into Quicopt's own form on the way out. The first keyless call mints an API key, cached at $XDG_CACHE_HOME/quicopt/free_key (~/.cache/… by default) and replayed on every later call — including from later runs, so you keep one key without doing anything. Pass Client(api_key=…) to authenticate with a key you already hold (used as-is, never cached), or Client(cache=False) to keep the key in memory only. Point Client(base_url=…) at another server to override the default. For a long solve, client.submit(m) returns a job handle to poll — job.result().

Where the home directory does not survive the run (CI, containers, Colab), the cache is wiped between sessions and each run mints a new key. Set QUICOPT_KEY_PATH to a durable location — or Client(key_path=…) — to keep one key across sessions.

Tag a call with client.solve(m, project="my-project") to attribute it to a project — handy when one key serves several projects. Which modeling library you wrote in (Pyomo/MathOpt/PuLP) is recorded automatically.

The service decides how long a solve may run. client.solve(m, time_limit=30) sets it to 30 seconds instead: a solver that proves its answer sooner still stops sooner, and a search runs for all of it. The service grants at most its maximum (120 s on the free tier), and the client warns when a solve got less time than asked for.

Orders, groups, minimum loads and fees

quicopt.structured adds decisions that Pyomo has no word for: an order (a route, a schedule), a split of items into groups (people into teams), and a variable that is either 0 or within its bounds (a delivery of none, or of 10 to 25 units). active(x) is 1 when such a variable is not 0, which turns a fixed fee per delivery into one term, and Pyomo's own Expr_if over a comparison charges a fee once a total passes a threshold. A block names the variables of one subproblem, such as one customer's deliveries; every solution returned satisfies the constraints over one block's variables alone.

import pyomo.environ as pyo
from quicopt import Client
from quicopt.structured import semiinteger, active, block

demand = [40, 30, 20]                         # units, customers 0 to 2
price = [[1, 3, 4], [3, 1, 3], [4, 2, 1]]     # per unit, price[warehouse][customer]
opening = [60, 50, 40]                        # fee of warehouses 0 to 2

m = pyo.ConcreteModel()
m.ship = pyo.Var(range(3), range(3), within=pyo.NonNegativeIntegers, bounds=(10, 25))
semiinteger(m.ship)                           # every delivery: none, or 10 to 25 units
for c in range(3):
    block(m, f"customer_{c}", [m.ship[w, c] for w in range(3)])
m.served = pyo.Constraint(range(3), rule=lambda m, c:
                          sum(m.ship[w, c] for w in range(3)) >= demand[c])
m.cost = pyo.Objective(expr=
    sum(price[w][c] * m.ship[w, c] for w in range(3) for c in range(3))
    + sum(5 * active(m.ship[w, c]) for w in range(3) for c in range(3))      # 5 per delivery
    + sum(pyo.Expr_if(IF=sum(m.ship[w, c] for c in range(3)) >= 1,           # opening fee
                      THEN=opening[w], ELSE=0) for w in range(3)))

print(Client().solve(m).objective)            # 300.0: warehouse 1 stays closed

The API reference walks through this example, a team split and a route.

If you want the encoded model yourself — to inspect it, store it, or send it by another route — the importers and the encoder are public too:

from quicopt import encode
from quicopt.pyomo import import_model

payload = encode(import_model(m))   # Pyomo model → a Quicopt model → bytes

Layout

quicopt/ir.py         a model as plain data: variables, expressions, constraints
quicopt/wire.py       a model → the bytes the service reads (standard library only)
quicopt/pyomo.py      a Pyomo model → a Quicopt model
quicopt/mathopt.py    an OR-Tools MathOpt model → a Quicopt model
quicopt/pulp.py       a PuLP model → a Quicopt model
quicopt/stochastic.py writing a Pyomo model whose data is not known yet
quicopt/structured.py orders, groups, minimum loads, fees and blocks in a Pyomo model
quicopt/client.py     POST those bytes to the service, read the result (HTTP, stdlib)

Between client and service, a model is a Program: variables, expressions and constraints as data, with a published protobuf schema for it, which wire.py encodes exactly. Each importer is an independent module — pyomo.py, mathopt.py, pulp.py, and further modeling libraries slot in the same way — pulls in only its own optional extra, and builds the model through the shared forms in _terms.py, so the same model comes out the same bytes whichever library wrote it.

Test

The encoder is checked against committed golden bytes, with no dependencies:

python3 tests/test_wire_golden.py        # or: pytest tests/

The importers are pinned to each other by byte equality: the same model written in Pyomo and in PuLP must encode to identical bytes, which carries the goldens' authority across (tests/test_frontend_equivalence.py; needs the [pyomo,pulp] extras, skips without them).

Status

  • the model + the encoder (ir, wire) — stable; the bytes are exactly what the service decodes.
  • pyomo importer — affine / quadratic / nonlinear (+ - * / ^ sin cos exp log sqrt abs), variable bounds (incl. unbounded) + integrality, == / <= / >= / ranged constraints, min / max. A fixed variable pins to [val, val]; one fixed without a value raises rather than importing as free.
  • mathopt importer — OR-Tools MathOpt ModelProto: linear / quadratic objective, linear constraints (incl. ranged and one-sided), variable bounds (incl. unbounded) + integrality, min / max.
  • pulp importer — PuLP LpProblem: linear objective (with offset) and linear <= / == / >= constraints, variable bounds (incl. unbounded) + integrality, min / max. PuLP is linear by construction, so this is exactly LP / MILP; a problem with no objective is a feasibility problem (a constant 0).
  • orders, groups and lookup tables (structured, Pyomo) — a permutation of items over slots, read by item_at / slot_of, with precedences and a starting arrangement; a partition of items into parts, read by part_of / size_of; tables indexed by decisions. Counted from 0, like Python.
  • minimum loads, fees and blocks (structured, Pyomo) — variables that are 0 or within their bounds (semicontinuous / semiinteger), active, fees through Pyomo's Expr_if over <= / < / >= / > / ==, and named blocks of variables.
  • sending (HTTP) — Client.solve / Client.submit over /v1/solve and /v1/jobs: the encoded model up, result JSON (status / objective / solution / framed display / the arrangement of each permutation and the grouping of each partition) back; API-key minting on the first call, optional gzip, an optional time limit. Standard library only.

License

Apache License 2.0 — see LICENSE. (c) 2026 Tim Bode, PGI-12, Forschungszentrum Jülich.

Metadata

Release files for quicopt 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for quicopt 0.6.0
File Size Uploaded
quicopt-0.6.0.tar.gz 80.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for quicopt 0.6.0
File Interpreter ABI Platform
quicopt-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 144.6 kB

Release files / quicopt-0.6.0.tar.gz

Download URL quicopt-0.6.0.tar.gz
Size 80.0 kB
Tags Source
SHA-256 checksum
How to use checksums
146b14acbd2a8263043f761b1e744099d1cb52ba7b804c618eae466a19cd97a0
BLAKE2b-256 checksum
How to use checksums
6b8fd071aa714d24982a40b4adad0b2bbe0e9243b528e641b4dcd5bedd0adcfa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release files / quicopt-0.6.0-py3-none-any.whl

Download URL quicopt-0.6.0-py3-none-any.whl
Size 64.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3da2637dc51e4eca4dbd5a3c9841652dd960cccb6c9f27ade0672924d416ceaa
BLAKE2b-256 checksum
How to use checksums
7cc81982d70a84e4e5c2773066b0da3464c5b5009472fbf8cb70b1ee86ec5372
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page