Strangeworks Optimization
strangeworks-optimization
Strangeworks Optimization SDK delivers an optimization platform designed for developers, harnessing the power of quantum inspired algorithms across diverse backend systems. The abstraction layer ensures a hassle-free experience for users while retaining the freedom to pick the most appropriate backend for any given task.
Installation
To get started, make sure you have Python 3.10 or 3.11 (installation) and are familiar with setting up and using virtual environments. You can easily install the Strangeworks SDK from PyPI with pip:
pip install -U pip && pip install strangeworks-optimization
Authentication
To access the Strangeworks API, you'll need your API key, which you can find in the Strangeworks Portal on the home page. Once you have it, simply authenticate like so:
import strangeworks as sw
from strangeworks_optimization import StrangeworksOptimizer
sw.authenticate(api_key)
Optimizer
Initialize the StrangeworksOptimizer
using a problem model
, a solver
backend, and parameter options
from strangeworks_optimization import StrangeworksOptimizer
so = StrangeworksOptimizer(model=model,
solver=solver
options=options)
Model: This is the problem you want to solve. It is represented as a QUBO, Ising, or other optimization problem such as MPS, CQM or DQM.
Solver: These are backend providers. Strangeworks provides a unified interface to interact with them, but each has its own unique characteristics and advantages. D-Wave, Gurobi, Quantagonia, etc.
Options: The solver
options
are parameters that are specific to the solver. For example, the number ofsamples
to take when solving the problem. See the Optimization Providers page for the required backend for more details.
Notebook
Here is an example notebook solving a QUBO problem using the Strangeworks Optimizer
Running a Job
When solving an Optimization Model, an Optimization Job is generated that spawns a Sub Job that runs on your selected solver.
- The Optimization Job aggregates the results of the Sub Jobs and provides the best solution found.
- The Sub Job is a single execution of the solver on a specific model configuration and will contain product specific execution data and specific error logs.
The StrangeworksOptimizer
class employs the run()
method to initiate a job. This method requires model
, solver
, and options
as arguments.
from strangeworks_optimization import StrangeworksOptimizer
so = StrangeworksOptimizer(model=model,
solver=solver
options=options)
sw_job = so.run()
These jobs can be viewed on the portal
Model
For all available models see here.
For this example we will use dimod
to create a simple QUBO problem. The QUBO problem is defined by linear and quadratic terms. The linear terms are a dictionary where the keys are the variables and the values are the linear coefficients. The quadratic terms are a dictionary where the keys are the pairs of variables and the values are the quadratic coefficients. The BinaryQuadraticModel
class is used to create the QUBO problem.
from dimod import BinaryQuadraticModel
linear = {1: -2, 2: -2, 3: -3, 4: -3, 5: -2}
quadratic = {(1, 2): 2, (1, 3): 2, (2, 4): 2, (3, 4): 2, (3, 5): 2, (4, 5): 2}
model = BinaryQuadraticModel(linear, quadratic, "BINARY")
Solver
To check available backends we will create a StrangeworksOptimizer
object and call the backends
method. We can print the names of the available backends with the following code:
from strangeworks_optimization import StrangeworksOptimizer
so = StrangeworksOptimizer()
backends = so.backends()
for backend in backends:
print(backend.name)
To see all available solvers and backends see Providers.
Notice we place the provider name before the backend name to specify the full solver. For example, dwave.Advantage_system6.4
is a D-Wave solver provided by the dwave
provider:
solver = "dwave.Advantage_system6.4"
Options
Each solver has its own Parameter Model available for import from strangeworks_optimization_models.parameter_models
from strangeworks_optimization_models.parameter_models import DwaveSamplerParameterModel
options = DwaveSamplerParameterModel(
num_reads=100,
chain_strength=50
)
Under each Provider page in the Optimization Providers section you can find details of the correct Parameter Model and options.
Tags
To enhance organization and searchability, you can assign tags to jobs.
so = StrangeworksOptimizer(model=model,
solver=solver,
options=options,
tags=["tag1", "tag2"])
To find jobs by tag, use the jobs_by_tag
method. You can filter by multiple tags and specify whether to use AND
or OR
for the search to combine the tags.
so = StrangeworksOptimizer()
jobs = so.jobs_by_tag("tag1", "tag2", and_or="AND")
Tags can also be added to jobs after they have been created.
import strangeworks as sw
sw.add_tags(job_slug, ["tag1", "tag2"])
Resources
If you have multiple optimization resources, simply specify the resource_slug
when creating the optimizer
This may be useful if you are managing multiple resources or if you are using the SDK in a multi-user environment.
from strangeworks_optimization import StrangeworksOptimizer
so = StrangeworksOptimizer(
model=model, solver=solver, resource_slug=resource_slug
)
Throughout the rest of this tutorial, we will assume that you have a single optimization resource.
Run
The StrangeworksOptimizer
class uses the run()
method to execute the job with model
, solver
, and options
as arguments.
so = StrangeworksOptimizer(
model=model,
solver=solver,
options=options
)
sw_job = so.run()
The slug is a unique identifier. You can use it to fetch the job later:
print(f"Job slug: {sw_job.slug}")
🥳 Result! You have successfully used the Strangeworks Optimization Service to solve a QUBO problem.
😅 Something went wrong? Find us in Slack!
Fetching a Run
If you want to fetch a previously submitted run for resubmission or to inspect the contents, you can use the download_input_file
method to recreate the optimizer used to run the job:
so = StrangeworksOptimizer()
optimization_job = so.download_input_file(sw_job.slug)
Status
You can see check progress on the portal as well as from the SDK:
so = StrangeworksOptimizer()
print(f"Job status: {so.status(sw_job.slug)}")
The statuses include:
QUEUED
: The job is waiting to be executed.RUNNING
: The job is currently being executed.COMPLETED
: The job has been executed and the results are available.FAILED
: The job has failed to execute.
💡 Failed Jobs:
If the job status comes back as failed you can view the job page in the portal or use the Strangeworks SDK to retrieve the errors:
import strangeworks as sw
sw.get_error_messages(sw_job.slug)
Results
Solutions are returned in the dimod.sampleset
format, see here for detailed documentation. This is so that results from different solvers can be easily analyzed and benchmarked together without too much extra work from the user.
When the job is completed you can retrieve the solution.
so = StrangeworksOptimizer()
results = so.results(sw_job.slug)
print(f"Best solution:\n{results.solution.first}")
This will print the best most optimal solution. But there are a variety of ways to extract the full information, the one to use depends on how you plan to use it. You can print the samples:
for sample in results.solution.samples():
print(sample)
Or you can use records to get the results in standard python numpy.ndarray
format:
sample_ndarray = results.solution.record.sample
energy_ndarray = results.solution.record.energy
Aggregated results are also available:
aggregated_results = results.solution.aggregate()
Cost
The cost
attribute of the results object provides the cost of the best solution found.
cost = results.cost
Runtime
The runtime
attribute of the results object provides the runtime of the best solution found.
runtime = results.runtime
Raw Results
Also note that, you can access the raw results in the original format for each solver with this code:
so = StrangeworksOptimizer()
results = so.results(sw_job.slug)
results.solution_options["raw_results"]
Files
Download Job
If you want to fetch a previously submitted run for resubmission or to inspect the contents, you can use the download_input_file
method to recreate the optimizer used to run the job:
so = StrangeworksOptimizer()
optimization_job = so.download_input_file(sw_job.slug)
When a model is uploaded to the platform, it is saved in the workspace and becomes accessible to users. Each model is stored as a RemoteFile
, allowing users to download it for further inspection or reuse.
Download Model
To access the model associated with a previous job, use the download_input_model
method from the StrangeworksOptimizer
class:
so = StrangeworksOptimizer()
model = so.download_input_model(job_slug)
print(type(model))
By default, download_input_model
retrieves a reference to the remote model file rather than downloading the actual file. This reference includes a URL pointing to the model's location. If you need to download the physical model file, set the download_remote=True
parameter:
so = StrangeworksOptimizer()
model = so.download_input_model(job_slug, download_remote=True)
print(type(model))
Remote Files
A RemoteFile
can be submitted directly to the platform.
from strangeworks_optimization_models.problem_models import RemoteFile
model_url = model.model_url
model_type = model.model_type
model = RemoteFile(model_url, model_type)
solver = 'your.solver'
so = StrangeworksOptimizer(model=model, solver=solver)
sw_job = so.run()
If you want to submit a model file you must first retrieve the model_url
and model_type
. Accepted model_type
values are (see Models for more information):
BinaryQuadraticModel
ConstrainedQuadraticModel
DiscreteQuadraticModel
AquilaNDArray
QuboDict
MPSFile
HitachiModel
FujitsuModel
MatrixMarket
QplibFile
Upload Models
To upload a model to the platform, use the upload_model
method from the StrangeworksOptimizer
class:
import strangeworks as sw
from strangeworks_optimizer import StrangeworksOptimizer
from dimod import BinaryQuadraticModel
sw.authenticate(api_key)
linear = {1: -2, 2: -2, 3: -3, 4: -3, 5: -2}
quadratic = {(1, 2): 2, (1, 3): 2, (2, 4): 2, (3, 4): 2, (3, 5): 2, (4, 5): 2}
bqm = BinaryQuadraticModel(linear, quadratic, "BINARY")
model = StrangeworksOptimizer().upload_model(bqm)
# model_url = model.model_url
# model_type = model.model_type
Download Remote
Once you have obtained the url of the model, you can retrieve it using:
so = StrangeworksOptimizer()
model = so.download_input_model_from_url(file_url).model
print(type(model))
Batching
You can run multiple jobs in parallel by using a batch.yaml
file (see Remote Files for how to produce a model_url
):
job_name: BatchJob
runs:
- run: "rundwave1"
problem_parameters:
model_url: "https://api.strangeworks.com/workspace//files/"
model_type: "BinaryQuadraticModel"
solver: "dwave.Advantage_system6.4"
solver_options:
num_reads: 50
chain_strength: 30
- run: "rundwave2"
problem_parameters:
model_url: "https://api.strangeworks.com/workspace//files/"
model_type: "BinaryQuadraticModel"
solver: "dwave.Advantage2_prototype2.6"
solver_options:
num_reads: 50
chain_strength: 30
- run: "rungurobi1"
problem_parameters:
model_url: "https://api.strangeworks.com/workspace//files/"
model_type: "BinaryQuadraticModel"
solver: "gurobi.qubo"
solver_options:
TimeLimit: 100
WorkLimit: 1000
Cutoff: 10
BarIterLimit: 1000
BestBdStop: 10
BestObjStop: 10
and the run_batch
method to retrieve a list of Jobs.
batch_file = "batch.yaml"
so = StrangeworksOptimizer()
sw_jobs = so.run_batch(batch_file=batch_file)