Skip to main content

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.

PyPI version

Package Documentation

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 of samples 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
)
info

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

Multiple resources?

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)

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.2"
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)