Source code for dwave.system.utilities

# Copyright 2019 D-Wave
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.

"""Utility functions."""

import os
import json
import networkx as nx
import numpy as np
import warnings

from typing import Union

__all__ = [
    'anneal_schedule_with_offset',
    'classproperty',
    'common_working_graph',
    'energy_scales_custom_schedule',
    ]


def _asarray(
    argname: str,
    array_like: np.typing.ArrayLike,
    num_columns: int = 1,
    ) -> np.ndarray:
    "Coerce array-like input into a NumPy array."
    try:
        array = np.asarray_chkfinite(array_like)
    except (ValueError, TypeError) as err:
        raise ValueError(f"{argname!r}: {err}") from err

    if not np.issubdtype(array.dtype, np.number):
        raise TypeError(f"{argname!r} must be an array-like of numbers")

    if (array.ndim > 1 and array.shape[1] != num_columns
        or array.ndim == 1 and array.ndim != num_columns):

        raise ValueError(f"{argname!r} must be a {num_columns}D array-like")

    return array

# taken from https://stackoverflow.com/a/39542816, licensed under CC BY-SA 3.0
# not needed in py39+
class classproperty(property):
    def __get__(self, obj, objtype=None):
        return super(classproperty, self).__get__(objtype)


[docs] def common_working_graph(graph0, graph1): """Creates a graph using the common nodes and edges of two given graphs. This function finds the edges and nodes with common labels. Note that this not the same as finding the greatest common subgraph with isomorphisms. Args: graph0: (dict[dict]/:obj:`~networkx.Graph`) A NetworkX graph or a dictionary of dictionaries adjacency representation. graph1: (dict[dict]/:obj:`~networkx.Graph`) A NetworkX graph or a dictionary of dictionaries adjacency representation. Returns: :obj:`~networkx.Graph`: A graph with the nodes and edges common to both input graphs. Examples: This example creates a graph that represents a part of a particular Advantage quantum computer's working graph. >>> import dwave_networkx as dnx >>> from dwave.system import DWaveSampler, common_working_graph ... >>> sampler = DWaveSampler(solver={'topology__type': 'pegasus'}) >>> P3 = dnx.pegasus_graph(3) >>> p3_working_graph = common_working_graph(P3, sampler.adjacency) """ warnings.warn("dwave.system.common_working_graph() is deprecated as of dwave-system 1.23.0 " "and will be removed in dwave-system 2.0. Use networkx.intersection() instead.", DeprecationWarning, stacklevel=2) G = nx.Graph() G.add_nodes_from(v for v in graph0 if v in graph1) G.add_edges_from((u, v) for u in graph0 for v in graph0[u] if v in graph1 and u in graph1[v]) return(G)
class FeatureFlags: """User environment-level Ocean feature flags pertinent to dwave-system.""" # NOTE: This is an experimental feature. If we decide to keep it, we'll want # to move this machinery level up to Ocean-common. @staticmethod def get(name, default=False): try: return json.loads(os.getenv('DWAVE_FEATURE_FLAGS')).get(name, default) except: return default @classproperty def hss_solver_config_override(cls): return cls.get('hss_solver_config_override')
[docs] def anneal_schedule_with_offset( anneal_offset: float = 0.0, anneal_schedule: Union[np.typing.ArrayLike, list, list[list[float]], None] = None, s: Union[np.typing.ArrayLike, list, None] = None, A: Union[np.typing.ArrayLike, list, None] = None, B: Union[np.typing.ArrayLike, list, None] = None, c: Union[np.typing.ArrayLike, list, None] = None ) -> np.ndarray: r"""Calculates the anneal schedule for a given anneal offset. The standard annealing trajectory, published for each quantum computer on :ref:`this <qpu_solver_properties_specific>` page, lowers :math:`A(s)`, the tunneling energy, and raises :math:`B(s)`, the problem energy, identically for all qubits. :ref:`Anneal offsets <qpu_qa_anneal_offsets>` enable you to adjust the standard annealing path per qubit. This function accepts a quantum computer's anneal schedule and an offset value, and returns the advanced or delayed schedule. Args: anneal_offset: Anneal-offset value for a single qubit. anneal_schedule: Anneal schedule, as a 4-column |array-like|_, with column values for :math:`s, A, B, c` as provided by (and typically taken from) the spreadsheet columns of the published :ref:`Per-QPU Solver Properties and Schedules <qpu_solver_properties_specific>` page. If set, ``anneal_offset`` is the only additional parameter allowed. s: Normalized anneal fraction, :math:`\frac{t}{t_a}`, which ranges from 0 to 1, where :math:`t_a` is the annealing duration, as a 1-dimensional |array-like|_. If set ``anneal_schedule`` must be ``None`` and values must be provided for ``A``, ``B``, and ``c``. A: Transverse or tunneling energy, :math:`A(s)`, as a 1-dimensional |array-like|_. If set ``anneal_schedule`` must be ``None`` and values must be provided for ``s``, ``B``, and ``c``. B: Energy applied to the problem Hamiltonian, :math:`B(s)`, as a 1-dimensional |array-like|_. If set ``anneal_schedule`` must be ``None`` and values must be provided for ``s``, ``A``, and ``c``. c: Normalized annealing bias, :math:`c(s)`, as a 1-dimensional |array-like|_. If set ``anneal_schedule`` must be ``None`` and values must be provided for ``s``, ``A``, and ``B``. Returns: Offset schedules :math:`A(s), B(s)`, and :math:`c(s)`, as a :std:doc:`NumPy <numpy:index>` array with columns :math:`s, A, B, c`. Note: You can prepare the input schedule by downloading the schedule for your selected quantum computer on the :ref:`Per-QPU Solver Properties and Schedules <qpu_solver_properties_specific>` page, saving the schedule tab in CSV format, and using NumPy's :func:`~numpy.loadtxt` function, as here: .. doctest:: :skipif: True >>> import numpy as np >>> schedule = np.loadtxt(schedule_csv_filename, delimiter=",", skiprows=1) Examples: For a schedule provided as array :code:`schedule`, this example returns the schedule with an offset of 0.2. >>> from dwave.system import anneal_schedule_with_offset ... >>> offset = 0.2 >>> schedule_offset = anneal_schedule_with_offset(offset, schedule) # doctest: +SKIP """ if anneal_schedule is not None and ( s is not None or A is not None or B is not None or c is not None): raise ValueError("Either `anneal_schedule` or `s, A, B, c`" f" can be specified. Got both inputs.") if anneal_schedule is None and ( s is None or A is None or B is None or c is None): raise ValueError("If `anneal_schedule` is unspecified, you must" f" specify all of `s, A, B, c`. Not all were specified.") if anneal_schedule is not None: schedule = _asarray('anneal_schedule', anneal_schedule, 4) s = schedule[:, 0] A = schedule[:, 1] B = schedule[:, 2] c = schedule[:, 3] else: s = _asarray('s', s, 1) A = _asarray('A', A, 1) B = _asarray('B', B, 1) c = _asarray('c', c, 1) A_offset = np.interp(c + anneal_offset, c, A) B_offset = np.interp(c + anneal_offset, c, B) c_offset= c + anneal_offset return np.column_stack((s, A_offset, B_offset, c_offset))
[docs] def energy_scales_custom_schedule( default_schedule: Union[np.typing.ArrayLike, list[list[float]], None] = None, s: Union[np.typing.ArrayLike, list[float], None] = None, A: Union[np.typing.ArrayLike, list[float], None] = None, B: Union[np.typing.ArrayLike, list[float], None] = None, c: Union[np.typing.ArrayLike, list[float], None] = None, custom_schedule: Union[np.typing.ArrayLike, list[list[float]], None] = None, custom_t: Union[np.typing.ArrayLike, list[float], None] = None, custom_s: Union[np.typing.ArrayLike, list[float], None] = None, ) -> np.ndarray: r"""Generates the energy scales for a custom anneal schedule. The standard annealing trajectory, published for each quantum computer on :ref:`this <qpu_solver_properties_specific>` page, lowers :math:`A(s)`, the tunneling energy, and raises :math:`B(s)`, the problem energy, according to a default schedule. You can customize that schedule as described in the :ref:`qpu_qa_anneal_sched` section of the :ref:`qpu_annealing` page. This function accepts a quantum computer's default anneal schedule and your custom schedule (as defined by the :ref:`parameter_qpu_anneal_schedule` parameter), and returns the energy scales as a function of time. Args: default_schedule: Anneal schedule, as a 4-column |array-like|_, with column values for :math:`s, A, B, c` as provided by (and typically taken from) the spreadsheet columns of the published :ref:`Per-QPU Solver Properties and Schedules <qpu_solver_properties_specific>` page. If set, do not set parameters ``s``, ``A``, ``B``, and ``c``. s: Normalized anneal fraction, :math:`\frac{t}{t_a}`, which ranges from 0 to 1, where :math:`t_a` is the annealing duration, as a 1-dimensional |array-like|_. If set ``anneal_schedule`` must be ``None`` and values must be provided for ``A``, ``B``, and ``c``. A: Transverse or tunneling energy, :math:`A(s)`, as a 1-dimensional |array-like|_. If set ``anneal_schedule`` must be ``None`` and values must be provided for ``s``, ``B``, and ``c``. B: Energy applied to the problem Hamiltonian, :math:`B(s)`, as a 1-dimensional |array-like|_. If set ``anneal_schedule`` must be ``None`` and values must be provided for ``s``, ``A``, and ``c``. c: Normalized annealing bias, :math:`c(s)`, as a 1-dimensional |array-like|_. If set ``anneal_schedule`` must be ``None`` and values must be provided for ``s``, ``A``, and ``B``. custom_schedule: Your custom anneal schedule, as a 2-column |array-like|_, with column values for time, :math:`t`, and the normalized anneal fraction, :math:`s`. Must meet the rules described for the :ref:`parameter_qpu_anneal_schedule` parameter. If set, do not set parameters ``custom_t`` or ``custom_s``. custom_t: Time, :math:`t`, as a 1-dimensional |array-like|_, compliant with the rules described for the :ref:`parameter_qpu_anneal_schedule` parameter. If set ``anneal_schedule`` must be ``None`` and ``custom_s`` must be provided too. custom_s: Normalized anneal fraction, :math:`\frac{t}{t_a}`, as a 1-dimensional |array-like|_, compliant with the rules described for the :ref:`parameter_qpu_anneal_schedule` parameter.. If set ``anneal_schedule`` must be ``None`` and ``custom_t`` must be provided too. Returns: Energy scales :math:`A(s), B(s)`, and :math:`c(s)`, as a :std:doc:`NumPy <numpy:index>` array with columns :math:`t, s, A, B, c`. Note: You can prepare the input schedule by downloading the schedule for your selected quantum computer on the :ref:`Per-QPU Solver Properties and Schedules <qpu_solver_properties_specific>` page, saving the schedule tab in CSV format, and using NumPy's :func:`~numpy.loadtxt` function, as here: .. doctest:: :skipif: True >>> import numpy as np >>> schedule = np.loadtxt(schedule_csv_filename, delimiter=",", skiprows=1) Examples: For a default schedule provided as array :code:`schedule_qpu3`, this example returns the energy scales for a reverse anneal. >>> from dwave.system import energy_scales_custom_schedule ... >>> anneal_schedule = [[0.0, 1.0], [5, 0.45], [99, 0.45], [100, 1.0]] >>> energy_schedule = energy_scales_custom_schedule( ... schedule_qpu3, ... custom_schedule=anneal_schedule) # doctest: +SKIP """ if default_schedule is not None and ( s is not None or A is not None or B is not None or c is not None): raise ValueError("Either `default_schedule` or `s, A, B, c`" f" can be specified. Got both inputs.") if default_schedule is None and ( s is None or A is None or B is None or c is None): raise ValueError("If `default_schedule` is unspecified, you must" f" specify all of `s, A, B, c`. Not all were specified.") if custom_schedule is not None and ( custom_t is not None or custom_s is not None): raise ValueError("Either `custom_schedule` or `custom_t, custom_s`" f" can be specified. Got both inputs.") if custom_schedule is None and ( custom_t is None or custom_s is None): raise ValueError("If `custom_schedule` is unspecified, you must" f" specify `custom_t and custom_s`. Both were not specified.") if default_schedule is not None: schedule = _asarray('default_schedule', default_schedule, 4) s = schedule[:, 0] A = schedule[:, 1] B = schedule[:, 2] c = schedule[:, 3] else: s = _asarray('s', s, 1) A = _asarray('A', A, 1) B = _asarray('B', B, 1) c = _asarray('c', c, 1) if custom_schedule is not None: schedule = _asarray('custom_schedule', custom_schedule, 2) custom_t = schedule[:, 0] custom_s = schedule[:, 1] else: custom_t = _asarray('custom_t', custom_t, 1) custom_s = _asarray('custom_s', custom_s, 1) precision_s = -np.log10(np.median(np.diff(s))) custom_s = np.round(custom_s, decimals=int(precision_s)) out = np.empty((0, 5)) for index in range(1, len(custom_s)): if custom_s[index] == custom_s[index - 1]: # This is a pause interval s_index = np.where(s == custom_s[index]) out_interval = np.vstack(( custom_t[index - 1], s[s_index], A[s_index], B[s_index], c[s_index])).T else: # This is a sloped interval forward_anneal = custom_s[index] > custom_s[index - 1] if forward_anneal: interval = (s <= custom_s[index]) & (s >= custom_s[index - 1]) else: interval = (s >= custom_s[index]) & (s <= custom_s[index - 1]) t_interp = np.interp( s[interval], sorted([custom_s[index - 1], custom_s[index]]), [custom_t[index - 1], custom_t[index]]) s_scales = np.stack(( s[interval], A[interval], B[interval], c[interval])) out_interval = np.vstack(( t_interp, s_scales if forward_anneal else np.flip(s_scales, axis=1))).T # Cut overlapped interval seams (except last interval) if index < len(custom_s) - 1: out_interval = out_interval[:-1,:] out = np.append(out, out_interval, axis=0) return out