Skip to content

Commit

Permalink
Split the remaining utils into submodules, update code/tests
Browse files Browse the repository at this point in the history
  • Loading branch information
randomir committed Jun 13, 2024
1 parent f1bbe45 commit f577411
Show file tree
Hide file tree
Showing 26 changed files with 128 additions and 103 deletions.
2 changes: 1 addition & 1 deletion dwave/cloud/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from dwave.cloud.api import constants, exceptions
from dwave.cloud.config import load_config, validate_config_v1
from dwave.cloud.config.models import ClientConfig
from dwave.cloud.utils import is_caused_by
from dwave.cloud.utils.exception import is_caused_by
from dwave.cloud.utils.http import PretimedHTTPAdapter, BaseUrlSession, default_user_agent

__all__ = ['DWaveAPIClient', 'SolverAPIClient', 'MetadataAPIClient', 'LeapAPIClient']
Expand Down
2 changes: 1 addition & 1 deletion dwave/cloud/auth/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from dwave.cloud.config.models import ClientConfig
from dwave.cloud.regions import resolve_endpoints
from dwave.cloud.utils.http import default_user_agent
from dwave.cloud.utils import pretty_argvalues
from dwave.cloud.utils.logging import pretty_argvalues

__all__ = ['AuthFlow', 'LeapAuthFlow', 'OAuthError']

Expand Down
8 changes: 4 additions & 4 deletions dwave/cloud/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@
from dwave.cloud import Client
from dwave.cloud import api
from dwave.cloud.solver import StructuredSolver, BaseUnstructuredSolver
from dwave.cloud.utils.logging import configure_logging
from dwave.cloud.utils.qubo import generate_random_ising_problem
from dwave.cloud.utils.http import user_agent
from dwave.cloud.utils.time import datetime_to_timestamp, utcnow, epochnow
from dwave.cloud.utils.cli import default_text_input, strtrunc, CLIError
from dwave.cloud.utils.dist import (
get_contrib_packages, get_distribution, PackageNotFoundError, VersionNotFoundError)
from dwave.cloud.utils.http import user_agent
from dwave.cloud.utils.logging import configure_logging
from dwave.cloud.utils.qubo import generate_random_ising_problem
from dwave.cloud.utils.time import datetime_to_timestamp, utcnow, epochnow
from dwave.cloud.coders import bqm_as_file
from dwave.cloud.package_info import __title__, __version__
from dwave.cloud.exceptions import (
Expand Down
3 changes: 2 additions & 1 deletion dwave/cloud/client/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@
from dwave.cloud.events import dispatches_events
from dwave.cloud.utils.http import PretimedHTTPAdapter, BaseUrlSession, default_user_agent
from dwave.cloud.utils.time import datetime_to_timestamp, utcnow
from dwave.cloud.utils import cached, retried, is_caused_by
from dwave.cloud.utils.decorators import cached, retried
from dwave.cloud.utils.exception import is_caused_by

__all__ = ['Client']

Expand Down
6 changes: 3 additions & 3 deletions dwave/cloud/computation.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
from dateutil.parser import parse
from operator import itemgetter

from dwave.cloud.utils.time import utcnow, datetime_to_timestamp
from dwave.cloud.utils.decorators import aliasdict, deprecated
from dwave.cloud.exceptions import InvalidAPIResponseError
from dwave.cloud.utils.decorators import aliasdict, deprecated
from dwave.cloud.utils.time import utcnow, datetime_to_timestamp

# Use numpy if available for fast decoding
try:
Expand Down Expand Up @@ -578,7 +578,7 @@ def result(self):
Instead of adding copies of ``solutions`` and ``num_occurrences``
keys (as ``samples`` and ``occurrences``), we alias them using
:class:`~dwave.cloud.utils.aliasdict`. Values are available under
:class:`~dwave.cloud.utils.decorators.aliasdict`. Values are available under
alias keys, but the keys themselves are not stored or visible.
Examples:
Expand Down
2 changes: 1 addition & 1 deletion dwave/cloud/regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
DEFAULT_REGION, DEFAULT_SOLVER_API_ENDPOINT, DEFAULT_LEAP_API_ENDPOINT,
DEFAULT_METADATA_API_ENDPOINT)
from dwave.cloud.config.models import ClientConfig, validate_config_v1
from dwave.cloud.utils import cached
from dwave.cloud.utils.decorators import cached

__all__ = ['get_regions']

Expand Down
37 changes: 30 additions & 7 deletions dwave/cloud/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@
from dwave.cloud.coders import (
encode_problem_as_qp, encode_problem_as_ref, decode_binary_ref,
decode_qp_numpy, decode_qp, decode_bq, bqm_as_file)
from dwave.cloud.utils.coders import NumpyEncoder
from dwave.cloud.utils.qubo import reformat_qubo_as_ising
from dwave.cloud.computation import Future
from dwave.cloud.concurrency import Present
from dwave.cloud.events import dispatches_events
from dwave.cloud.utils.coders import NumpyEncoder
from dwave.cloud.utils.qubo import reformat_qubo_as_ising

# Use numpy if available for fast encoding/decoding
try:
import numpy as np
import numpy
_numpy = True
except ImportError:
_numpy = False
Expand Down Expand Up @@ -594,6 +594,30 @@ class DQMSolver(BaseUnstructuredSolver):
_handled_problem_types = {"dqm"}
_handled_encoding_formats = {"bq"}

@staticmethod
def _bqm_to_dqm(bqm):
"""Represent a :class:`dimod.BQM` as a :class:`dimod.DQM`."""
try:
from dimod import DiscreteQuadraticModel
except ImportError: # pragma: no cover
raise RuntimeError(
"dimod package with support for DiscreteQuadraticModel required."
"Re-install the library with 'dqm' support.")

dqm = DiscreteQuadraticModel()

ising = bqm.spin

for v, bias in ising.linear.items():
dqm.add_variable(2, label=v)
dqm.set_linear(v, [-bias, bias])

for (u, v), bias in ising.quadratic.items():
biases = numpy.array([[bias, -bias], [-bias, bias]], dtype=numpy.float64)
dqm.set_quadratic(u, v, biases)

return dqm

def _encode_problem_for_upload(self, dqm):
try:
data = dqm.to_file()
Expand All @@ -608,10 +632,9 @@ def _encode_problem_for_upload(self, dqm):

def sample_bqm(self, bqm, label=None, **params):
"""Use for testing."""
from dwave.cloud.utils import bqm_to_dqm

# to sample BQM problems, we need to convert them to DQM
dqm = bqm_to_dqm(bqm)
dqm = self.bqm_to_dqm(bqm)

# TODO: convert sampleset back
return self.sample_dqm(dqm, label=label, **params)
Expand Down Expand Up @@ -1503,9 +1526,9 @@ def estimate_qpu_access_time(self,
q = readout_time_model_parameters[:n//2]
t = readout_time_model_parameters[n//2:]
if readout_time_model == 'pwl_log_log':
readout_time = pow(10, np.interp(np.emath.log10(num_qubits), q, t))
readout_time = pow(10, numpy.interp(numpy.emath.log10(num_qubits), q, t))
elif readout_time_model == 'pwl_linear':
readout_time = np.interp(num_qubits, q, t)
readout_time = numpy.interp(num_qubits, q, t)
else:
raise ValueError("``estimate_qpu_access_time`` does not support "
f"``readout_time_model`` value {readout_time_model} "
Expand Down
6 changes: 3 additions & 3 deletions dwave/cloud/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 D-Wave Systems Inc.
# Copyright 2024 D-Wave Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -15,12 +15,12 @@
"""Utilities for private and Ocean-internal use."""

# imports for backward-compat
from dwave.cloud.utils.logging import set_loglevel
from dwave.cloud.utils.logging import set_loglevel, configure_logging

from dwave.cloud.utils.qubo import (
uniform_get, reformat_qubo_as_ising, active_qubits,
)

from dwave.cloud.utils.time import utcnow

from dwave.cloud.utils.decorators import retried
from dwave.cloud.utils.decorators import cached, retried
2 changes: 1 addition & 1 deletion dwave/cloud/utils/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 D-Wave Systems Inc.
# Copyright 2024 D-Wave Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
7 changes: 5 additions & 2 deletions dwave/cloud/utils/coders.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 D-Wave Systems Inc.
# Copyright 2024 D-Wave Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -20,7 +20,10 @@

import json

import numpy
try:
import numpy
except ImportError: # pragma: no cover
pass # numpy not required for all cloud-client functionality

__all__ = ['NumpyEncoder', 'coerce_numpy_to_python']

Expand Down
4 changes: 2 additions & 2 deletions dwave/cloud/utils/decorators.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 D-Wave Systems Inc.
# Copyright 2024 D-Wave Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -53,7 +53,7 @@ class aliasdict(dict):
Example:
>>> from operator import itemgetter
>>> from dwave.cloud.utils import aliasdict
>>> from dwave.cloud.utils.decorators import aliasdict
>>> d = aliasdict(a=1, b=2)
>>> d.alias(c=itemgetter('a'))
Expand Down
9 changes: 4 additions & 5 deletions dwave/cloud/utils/dist.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 D-Wave Systems Inc.
# Copyright 2024 D-Wave Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -18,10 +18,9 @@
These functions previously lived under ``dwave.cloud.utils``.
"""

from collections import OrderedDict
from importlib.metadata import (
entry_points, EntryPoint, Distribution, PackageNotFoundError)
from typing import Dict, List, Union
from typing import Dict, List, OrderedDict, Union

from packaging.requirements import Requirement

Expand All @@ -38,7 +37,7 @@ def get_contrib_config() -> List[EntryPoint]:
return contrib


def get_contrib_packages() -> Dict[str, Distribution]:
def get_contrib_packages() -> OrderedDict[str, dict]:
"""Combine all contrib packages in an ordered dict. Assumes package names
are unique.
"""
Expand Down Expand Up @@ -76,7 +75,7 @@ def get_distribution(requirement: Union[str, Requirement],
Raises:
:class:`~importlib.metadata.PackageNotFoundError`:
Package by name not found.
:class:`~dwave.cloud.utils.VersionNotFoundError`:
:class:`~dwave.cloud.utils.dist.VersionNotFoundError`:
Version of the package found (distribution) does not match the
requirement.
"""
Expand Down
52 changes: 13 additions & 39 deletions dwave/cloud/utils.py → dwave/cloud/utils/exception.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2017 D-Wave Systems Inc.
# Copyright 2024 D-Wave Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -12,23 +12,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import inspect
"""Exception handling utilities for private and Ocean-internal use.
.. versionchanged:: 0.12.0
These functions previously lived under ``dwave.cloud.utils``.
"""

__all__ = ['hasinstance', 'exception_chain', 'is_caused_by']

from typing import Any, Iterable, Tuple, Union

__all__ = ['hasinstance', 'exception_chain', 'is_caused_by']


def hasinstance(iterable, class_or_tuple):
def hasinstance(objs: Iterable[Any], class_or_tuple: Union[Any, Tuple[Any]]):
"""Extension of ``isinstance`` to iterables/sequences. Returns True iff the
sequence contains at least one object which is instance of ``class_or_tuple``.
"""

return any(isinstance(e, class_or_tuple) for e in iterable)
return any(isinstance(e, class_or_tuple) for e in objs)


def exception_chain(exception):
def exception_chain(exception: Exception):
"""Traverse the chain of embedded exceptions, yielding one at the time.
Args:
Expand Down Expand Up @@ -67,7 +70,9 @@ def f():
return


def is_caused_by(exception, exception_types):
def is_caused_by(exception: Exception,
exception_types: Union[Exception, Tuple[Exception]]
) -> bool:
"""Check if any of ``exception_types`` is causing the ``exception``.
Equivalently, check if any of ``exception_types`` is contained in the
exception chain rooted at ``exception``.
Expand All @@ -86,34 +91,3 @@ def is_caused_by(exception, exception_types):
"""

return hasinstance(exception_chain(exception), exception_types)



def pretty_argvalues():
"""Pretty-formatted function call arguments, from the caller's frame."""
return inspect.formatargvalues(*inspect.getargvalues(inspect.currentframe().f_back))



def bqm_to_dqm(bqm):
"""Represent a :class:`dimod.BQM` as a :class:`dimod.DQM`."""
try:
from dimod import DiscreteQuadraticModel
except ImportError: # pragma: no cover
raise RuntimeError(
"dimod package with support for DiscreteQuadraticModel required."
"Re-install the library with 'dqm' support.")

dqm = DiscreteQuadraticModel()

ising = bqm.spin

for v, bias in ising.linear.items():
dqm.add_variable(2, label=v)
dqm.set_linear(v, [-bias, bias])

for (u, v), bias in ising.quadratic.items():
biases = numpy.array([[bias, -bias], [-bias, bias]], dtype=numpy.float64)
dqm.set_quadratic(u, v, biases)

return dqm
2 changes: 1 addition & 1 deletion dwave/cloud/utils/http.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 D-Wave Systems Inc.
# Copyright 2024 D-Wave Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
8 changes: 7 additions & 1 deletion dwave/cloud/utils/logging.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 D-Wave Systems Inc.
# Copyright 2024 D-Wave Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -15,6 +15,7 @@
"""Logging utilities for private and Ocean-internal use."""

import datetime
import inspect
import io
import json
import logging
Expand Down Expand Up @@ -208,3 +209,8 @@ def configure_logging_from_env(logger: logging.Logger) -> bool:
structured_output=(log_format.strip().lower() == 'json'))

return True


def pretty_argvalues():
"""Pretty-formatted function call arguments, from the caller's frame."""
return inspect.formatargvalues(*inspect.getargvalues(inspect.currentframe().f_back))
2 changes: 1 addition & 1 deletion dwave/cloud/utils/qubo.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 D-Wave Systems Inc.
# Copyright 2024 D-Wave Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion dwave/cloud/utils/time.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 D-Wave Systems Inc.
# Copyright 2024 D-Wave Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
17 changes: 17 additions & 0 deletions releasenotes/notes/refactor-utils-560b18c32f12feee.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
upgrade:
- |
``dwave.cloud.utils`` module has been split into the following submodules:
``dwave.cloud.utils.cli``, ``dwave.cloud.utils.coders``,
``dwave.cloud.utils.decorators``, ``dwave.cloud.utils.dist``,
``dwave.cloud.utils.exception``, ``dwave.cloud.utils.http``,
``dwave.cloud.utils.logging``, ``dwave.cloud.utils.qubo``, and
``dwave.cloud.utils.time``.
First reason for doing this was code clean-up (including adding type annotations),
but a more important reason is that this will enable import time optimization
in the future. And that's why we break backwards compatibility by not importing
everything back into the top-level utils namespace.
The utils module is considered Ocean-internal as is, so this move shouldn't
affect user's code.
3 changes: 2 additions & 1 deletion tests/api/mocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
from typing import Any, Optional, Tuple, Union

from dwave.cloud.coders import encode_problem_as_qp, encode_problem_as_ref
from dwave.cloud.utils import utcrel, generate_const_ising_problem
from dwave.cloud.utils.qubo import generate_const_ising_problem
from dwave.cloud.utils.time import utcrel
from dwave.cloud.solver import StructuredSolver, UnstructuredSolver
from dwave.cloud.testing.mocks import qpu_clique_solver_data, hybrid_bqm_solver_data

Expand Down
Loading

0 comments on commit f577411

Please sign in to comment.