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()
.
client = opvious.Client.default(opvious.DEMO_ENDPOINT) # Demo cloud endpoint
client = opvious.Client.default("http://localhost:8080") # Local API server
The first argument, endpoint
, determines the client’s underlying API. For
example the address of a self-hosted API server. The SDK also provides
Client.from_environment()
to create clients from the OPVIOUS_ENDPOINT
and OPVIOUS_TOKEN
environment variables:
client = opvious.Client.from_environment()
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:
We recommend also using the client’s
annotate_specification()
method to validate specifications
and highlight any errors:
annotated = await client.annotate_specification(specification)
Specifications
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, which find solutions in real-time
Queued solves, which support larger data sizes
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:
problem –
Problem
instance to solveassert_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, annotations: list[str | tuple[str, str]] | None = None) str
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:
problem –
Problem
instance to solve
The returned
QueuedSolve
instance can be used to:track progress via
Client.poll_solve()
,retrieve inputs via
Client.fetch_solve()
,retrieve outputs via
Client.fetch_solve_outputs()
(after successful completion).
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:
problem –
Problem
instance to summarize
- async Client.format_problem(problem: Problem, include_line_comments=False) str
Returns the problem’s annotated representation in LP format
- Parameters:
problem –
Problem
instance to formatinclude_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 \ ...