Overview

The SDK’s functionality is available via Client instances, which provide a high-level interface to the underlying Opvious API.

Creating a client

First, make sure you’ve installed the SDK. Once that’s done, the recommended way to create clients is via Client.default(). This will automatically authenticate the client using the OPVIOUS_TOKEN environment variable, if available:

client = opvious.Client.default()  # Using environment defaults
client = opvious.Client.default(token="...")  # Using an explicit token

By default clients connect to the Opvious cloud API, you can pass in an explicit endpoint argument to use a different one. For example when using a self-hosted API server:

client = opvious.Client.default(endpoint="http://localhost:8080")

Formulating problems

To solve an optimization problem, you will need to formulate it first. Opvious enforces a clear separation between a problem’s specification (its abstract definition with variable types, constraints, objectives, etc.) and the data needed to solve it.

Specifications can be created:

  • via this SDK’s declarative modeling API, which will automatically generate the problem’s mathematical representation;

  • or directly from a problem’s mathematical representation in LaTeX, written separately from this SDK.

Model instances

Note

Refer to the modeling page for information on how to create Model instances.

Calling any model’s specification() method will return its specification:

import opvious.modeling as om

class SetCover(Model):
  """Sample set cover specification"""

  sets = Dimension()
  vertices = Dimension()
  covers = Parameter.indicator(sets, vertices)
  used = Variable.indicator(sets)

  @constraint
  def all_covered(self):
    for v in self.vertices:
      count = total(self.used(s) * self.covers(s, v)for s in self.sets)
      yield count >= 1

  @objective
  def minimize_used(self):
    return total(self.used(s) for s in self.sets)

model = SetCover()
specification = model.specification()

The returned LocalSpecification instances are integrated with IPython’s rich display capabilities and will be pretty-printed within notebooks. For example the above specification will be displayed as:

\[\begin{split}\begin{align*} \S^d_\mathrm{sets}&: S \\ \S^d_\mathrm{vertices}&: V \\ \S^p_\mathrm{covers}&: c \in \{0, 1\}^{S \times V} \\ \S^v_\mathrm{used}&: \psi \in \{0, 1\}^{S} \\ \S^c_\mathrm{allCovered}&: \forall v \in V, \sum_{s \in S} \psi_{s} c_{s,v} \geq 1 \\ \S^o_\mathrm{minimizeUsed}&: \min \sum_{s \in S} \psi_{s} \\ \end{align*}\end{split}\]

We recommend also using the client’s annotate_specification() method to validate specifications and highlight any errors:

annotated = await client.annotate_specification(specification)

Specifications

Note

Refer to the platform documentation for information on how to write a specification directly. You can also find various sample sources in our example repository.

This SDK provides utilities for loading specifications from various locations, listed below.

class opvious.LocalSpecification(sources: Sequence[LocalSpecificationSource], description: str | None = None, annotation: LocalSpecificationAnnotation | None = None)

A local model specification

Instances of this class are integrated with IPython’s rich display capabilities and will automatically render their LaTeX sources when output in notebooks.

Note that this type of specification cannot be used to queue solves.

annotation: LocalSpecificationAnnotation | None = None

API-issued annotation

This field is generated automatically by clients’ annotate_specification() method. When any issues are detected, the specification’s pretty-printed representation will highlight any errors.

description: str | None = None

Optional description

classmethod globs(*likes: str, root: str | None = None) LocalSpecification

Creates a local specification from LaTeX definition files

Parameters:
  • *likes – File names or globs

  • root – Optional root folder used for matching file names and globs. As a convenience this argument can also be an existing file’s name, in which case it will be interpreted as its parent directory (this is typically handy when used with __file__).

classmethod inline(*texts: str) LocalSpecification

Creates a local specification from inline LaTeX definitions

source(title: str | None = None) LocalSpecificationSource

Returns the first source, optionally matching a given title

sources: Sequence[LocalSpecificationSource]

The model’s mathematical source definitions

class opvious.FormulationSpecification(formulation_name: str, tag_name: str | None = None)

A specification from an Optimization Hub formulation

This type of specification allows queueing solves and is recommended for production use as it provides history and reproducibility when combined with tag names.

formulation_name: str

The corresponding formulation’s name

tag_name: str | None = None

The matching tag’s name

If absent, the formulation’s default will be used.

class opvious.RemoteSpecification(url: str)

A model specification from a remote URL

url: str

The specification’s http(s) URL

Finding a solution

Once you have a problem’s specification, the client exposes two distinct ways of solving it:

Live solves

Solves can be run in real time with the client’s Client.solve() method.

async Client.solve(problem: Problem, assert_feasible=False, prefer_streaming=True) Solution

Solves an optimization problem remotely

Inputs will be validated before being sent to the API for solving.

Parameters:
  • problemProblem instance to solve

  • assert_feasible – Throw if the final outcome was not feasible

  • prefer_streaming – Show real time progress notifications when possible

The returned solution exposes both metadata (status, objective value, etc.) and solution data (if the solve was feasible):

solution = await client.solve(
    opvious.Problem(
        specification=opvious.FormulationSpecification(
            "porfolio-selection"
        ),
        parameters={
            "covariance": {
                ("AAPL", "AAPL"): 0.2,
                ("AAPL", "MSFT"): 0.1,
                ("MSFT", "AAPL"): 0.1,
                ("MSFT", "MSFT"): 0.25,
            },
            "expectedReturn": {
                "AAPL": 0.15,
                "MSFT": 0.2,
            },
            "desiredReturn": 0.1,
        },
    ),
)

# Metadata is available on `outcome`
print(f"Objective value: {solution.outcome.objective_value}")

# Variable and constraint data are available via `outputs`
optimal_allocation = solution.outputs.variable("allocation")

See also Client.queue_solve() for an alternative for long-running solves.

Note

In many environments, clients can stream solve progress notifications back to the client. This allows for real-time updates on the ongoing solve (current optimality gap, latest epsilon constraint added, etc.). You can view them by enabling INFO or DEBUG logging, for example:

import logging

logging.basicConfig(level=logging.INFO)

Queued solves

Solves are queued via the client’s Client.queue_solve() method.

async Client.queue_solve(problem: Problem) QueuedSolve

Queues a solve for asynchronous processing

Inputs will be validated locally before the request is sent to the API. From then on, the solve will be queued and begin solving start as soon as enough capacity is available.

Parameters:

problemProblem instance to solve

The returned QueuedSolve instance can be used to:

As a convenience, Client.wait_for_solve_outcome() allows polling an solve until until it completes, backing off exponentially between each poll:

# Queue a new Sudoku solve
solve = await client.queue_solve(
    opvious.Problem(
        specification=opvious.FormulationSpecification("sudoku"),
        parameters={"hints": [(0, 0, 3), (1, 1, 5)]},
    )
)

# Wait for the solve to complete
await client.wait_for_solve_outcome(solve, assert_feasible=True)

# Fetch the solution's data
output_data = await client.fetch_solve_outputs(solve)

# Get a parsed variable as a dataframe
decisions = output_data.variable("decisions")

See also Client.solve() for an alternative for solving problems live.

Debugging problems

async Client.summarize_problem(problem: Problem) ProblemSummary

Returns summary statistics about a problem without solving it

Parameters:

problemProblem instance to summarize

async Client.format_problem(problem: Problem, include_line_comments=False) str

Returns the problem’s annotated representation in LP format

Parameters:
  • problemProblem instance to format

  • include_line_comments – Include comment lines in the output. By default these lines are only logged as DEBUG messages.

The LP formatted output will be fully annotated with matching keys and labels:

minimize
  +1 inventory$1 \ [day=0]
  +1 inventory$2 \ [day=1]
  +1 inventory$3 \ [day=2]
  \ ...
subject to
 inventoryPropagation$1: \ [day=1]
  +1 inventory$1 \ [day=1]
  -1 inventory$2 \ [day=0]
  -1 production$1 \ [day=1]
  = -29
 inventoryPropagation$2: \ [day=2]
  -1 inventory$1 \ [day=1]
  +1 inventory$3 \ [day=2]
  -1 production$2 \ [day=2]
  = -36
 \ ...