Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up qp problem encoding #661

Merged
merged 5 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion benchmarks/benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from pathlib import Path
from typing import List

import dimod
import requests
import requests_mock
from pydantic import TypeAdapter
Expand All @@ -32,7 +33,7 @@
from dwave.cloud.solver import StructuredSolver, BQMSolver, CQMSolver, DQMSolver, NLSolver
from dwave.cloud.regions import resolve_endpoints, get_regions
from dwave.cloud.testing import isolated_environ, mocks
from dwave.cloud.utils.qubo import generate_random_ising_problem
from dwave.cloud.utils.qubo import generate_random_ising_problem, active_qubits
from dwave.cloud.utils.logging import get_caller_name


Expand Down Expand Up @@ -146,10 +147,29 @@ class ProblemEncoding:
def setup(self, key):
self.solver = StructuredSolver(client=None, data=InitQPUSolver.generators[key]())
self.problem = generate_random_ising_problem(self.solver)
self.bqm = dimod.BQM.from_ising(*self.problem)

def time_encode_qp(self, key):
encode_problem_as_qp(self.solver, *self.problem)

def time_encode_qp_from_bqm(self, key):
encode_problem_as_qp(self.solver, self.bqm.linear, self.bqm.quadratic)

def time_encode_qp_from_bqm_to_dict(self, key):
encode_problem_as_qp(self.solver, dict(self.bqm.linear), dict(self.bqm.quadratic))

def time_active_qubits(self, key):
active_qubits(*self.problem)

def time_active_qubits_from_bqm(self, key):
active_qubits(self.bqm.linear, self.bqm.quadratic)

def time_check_problem(self, key):
self.solver.check_problem(*self.problem)

def time_check_problem_from_bqm(self, key):
self.solver.check_problem(self.bqm.linear, self.bqm.quadratic)


# requires internet access
class RegionsMetadata:
Expand Down
8 changes: 6 additions & 2 deletions dwave/cloud/coders.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import struct
import base64
from typing import Callable, Dict, List, Tuple, Union
from typing import Callable, Dict, List, Sequence, Tuple, Union

try:
# note: TypedDict is available in py38+, but NotRequired only in py311+
Expand Down Expand Up @@ -73,6 +73,10 @@ def encode_problem_as_qp(solver: 'dwave.cloud.solver.StructuredSolver',
Returns:
Encoded submission dictionary.
"""
# convert legacy format (list) to dict for performance
if isinstance(linear, Sequence):
linear = dict(enumerate(linear))

active = active_qubits(linear, quadratic)

# Encode linear terms. The coefficients of the linear terms of the objective
Expand All @@ -82,7 +86,7 @@ def encode_problem_as_qp(solver: 'dwave.cloud.solver.StructuredSolver',
# specified by the server.
# Note: only active qubits are coded with double, inactive with NaN
nan = float('nan')
lin = [uniform_get(linear, qubit, 0 if qubit in active else nan)
lin = [linear.get(qubit, 0 if qubit in active else nan)
for qubit in solver._encoding_qubits]

lin = base64.b64encode(struct.pack('<' + ('d' * len(lin)), *lin))
Expand Down
6 changes: 5 additions & 1 deletion dwave/cloud/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -1177,7 +1177,11 @@ def sample_bqm(self, bqm, label=None, **params):
else:
raise TypeError("unknown/unsupported vartype")

return self._sample(problem_type, bqm.linear, bqm.quadratic, bqm.offset,
# convert to dicts once, to save multiple conversions later on
linear = dict(bqm.linear)
quadratic = dict(bqm.quadratic)

return self._sample(problem_type, linear, quadratic, bqm.offset,
params, label=label, undirected_biases=True)

@dispatches_events('sample')
Expand Down
8 changes: 5 additions & 3 deletions dwave/cloud/utils/qubo.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,11 @@ def active_qubits(linear: Union[Mapping, Sequence],
Active qubits' indices.
"""

active = {idx for idx, bias in uniform_iterator(linear)}
for edge, _ in quadratic.items():
active.update(edge)
if isinstance(linear, Sequence):
active = set(range(len(linear)))
else:
active = set(linear)
active.update(*quadratic)
return active


Expand Down
8 changes: 8 additions & 0 deletions releasenotes/notes/speed-up-qp-encoding-f8ae1d86fa1dfde2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
features:
- |
Speed-up ``dwave.cloud.utils.qubo.active_qubits`` utility function by ~50%,
and ``dwave.cloud.coders.encode_problem_as_qp`` by 20-30%.
- |
Speed-up QPU problem sampling when problem submitted as BQM by 40%. For best
performance, keep linear and quadratic biases in ``dict``s.