Source code for dimod.response

"""
dimod samplers respond with a consistent :class:`.Response` object that is
:class:`~collections.Iterable` (over samples, from lowest energy to highest) and
:class:`~collections.Sized` (number of samples).

Examples
--------
    This example shows the response of the dimod ExactSolver sampler.

>>> import dimod
>>> response = dimod.ExactSolver().sample_ising({'a': -0.5}, {})
>>> len(response)
2
>>> for sample in response:
...     print(sample)
{'a': 1}
{'a': -1}

"""
from collections import Mapping, Iterable, Sized, namedtuple, ValuesView, ItemsView
import itertools
import concurrent.futures

import numpy as np
from six import itervalues, iteritems

from dimod.decorators import vartype_argument
from dimod.utilities import resolve_label_conflict
from dimod.vartypes import Vartype

__all__ = ['Response']


[docs]class Response(Iterable, Sized): """A container for samples and any other data returned by dimod samplers. Args: samples_matrix (:obj:`numpy.matrix`): Samples as a NumPy matrix where each row is a sample. data_vectors (dict[field, :obj:`numpy.array`/list]): Additional per-sample data as a dict of vectors. Each vector is of the same length as `samples_matrix`. The key 'energy' and its vector are required. vartype (:class:`.Vartype`): Vartype of the samples. info (dict, optional, default=None): Information about the response as a whole formatted as a dict. variable_labels (list, optional, default=None): Variable labels mappped by index to columns of the samples matrix. Attributes: vartype (:class:`.Vartype`): Vartype of the samples. info (dict): Information about the response as a whole formatted as a dict. variable_labels (list/None): Variable labels. Each column in the samples matrix is the values returned for one variable. If None, column indices are the labels. label_to_idx (dict): Map of variable labels to columns in samples matrix. Examples: This example shows some attributes of the response for the sampler of dimod package's random_sampler.py reference example. >>> from dimod.reference.samplers.random_sampler import RandomSampler >>> sampler = RandomSampler() >>> bqm = dimod.BinaryQuadraticModel({0: 0.0, 1: 1.0}, {(0, 1): 0.5}, -0.5, dimod.SPIN) >>> response = sampler.sample(bqm) >>> response.vartype # doctest: +SKIP <Vartype.SPIN: frozenset([1, -1])> # doctest: +SKIP >>> response.variable_labels [0, 1] """ @vartype_argument('vartype') def __init__(self, samples_matrix, data_vectors, vartype, info=None, variable_labels=None): # Constructor is opinionated about the samples_matrix type, it should be a numpy matrix if not isinstance(samples_matrix, np.matrix): raise TypeError("expected 'samples_matrix' to be a NumPy matrix") elif samples_matrix.dtype != np.int8: # cast to int8 samples_matrix = samples_matrix.astype(np.int8) self._samples_matrix = samples_matrix num_samples, num_variables = samples_matrix.shape if not isinstance(data_vectors, Mapping): raise TypeError("expected 'data_vectors' to be a dict") if 'energy' not in data_vectors: raise ValueError("energy must be provided") else: data_vectors = dict(data_vectors) # shallow copy for key, vector in iteritems(data_vectors): try: data_vectors[key] = vector = np.asarray(vector) except (ValueError, TypeError): raise TypeError("expected data vector {} to be array-like".format(key)) shape = vector.shape if not shape or shape[0] != num_samples: raise ValueError(("expected data vector {} (shape {}) to have {} rows" "").format(key, vector.shape, num_samples)) self._data_vectors = data_vectors # vartype is checked by the decorator self.vartype = vartype if info is None: info = {} elif not isinstance(info, dict): raise TypeError("expected 'info' to be a dict.") else: info = dict(info) # make a shallow copy self.info = info if variable_labels is None: self.variable_labels = None self.label_to_idx = None else: self.variable_labels = variable_labels = list(variable_labels) if len(variable_labels) != num_variables: msg = ("variable_labels' length must match the number of columns in " "samples_matrix, {} labels, matrix has {} columns".format(len(variable_labels), num_variables)) raise ValueError(msg) self.label_to_idx = {v: idx for idx, v in enumerate(variable_labels)} # will store any pending Future objects and data about them self._futures = {} def __len__(self): """The number of samples.""" num_samples, num_variables = self.samples_matrix.shape return num_samples def __iter__(self): """Iterate over the samples, low energy to high.""" return self.samples(sorted_by='energy') def __str__(self): # developer note: it would be nice if the variable labels (if present could be printed) return self.samples_matrix.__str__() ############################################################################################## # Properties ############################################################################################## @property def samples_matrix(self): """:obj:`numpy.matrix`: Samples as a NumPy matrix of data type int8. Examples: This example shows the samples of dimod package's ExactSolver reference sampler formatted as a NumPy matrix. >>> import dimod >>> response = dimod.ExactSolver().sample_ising({'a': -0.5, 'b': 1.0}, {('a', 'b'): -1}) >>> response.samples_matrix matrix([[-1, -1], [ 1, -1], [ 1, 1], [-1, 1]]) """ if self._futures: self._resolve_futures(**self._futures) return self._samples_matrix @samples_matrix.setter def samples_matrix(self, mat): self._samples_matrix = mat @property def data_vectors(self): """dict[field, :obj:`numpy.array`/list]: Per-sample data as a dict, where keys are the data labels and values are each a vector of the same length as sample_matrix. Examples: This example shows the returned energies of dimod package's ExactSolver reference sampler. >>> import dimod >>> response = dimod.ExactSolver().sample_ising({'a': -0.5, 'b': 1.0}, {('a', 'b'): -1}) >>> response.data_vectors['energy'] array([-1.5, -0.5, -0.5, 2.5]) """ if self._futures: self._resolve_futures(**self._futures) return self._data_vectors
[docs] def done(self): """True if all loaded futures are done or if there are no futures. Only relevant when the response is constructed with :meth:`Response.from_futures`. Examples: This example checks whether futures are done before and after a `set_result` call. >>> from concurrent.futures import Future >>> future = Future() >>> response = dimod.Response.from_futures((future,), dimod.BINARY, 3) >>> future.done() False >>> future.set_result({'samples': [0, 1, 0], 'energy': [1]}) >>> future.done() True """ return all(future.done() for future in self._futures)
############################################################################################## # Construction and updates ##############################################################################################
[docs] @classmethod def from_matrix(cls, samples, data_vectors, vartype=None, info=None, variable_labels=None): """Build a response from a NumPy array-like object. Args: samples (array_like/str): Samples as a :class:`numpy.matrix` or NumPy array-like object. See Notes. data_vectors (dict[field, :obj:`numpy.array`/list]): Additional per-sample data as a dict of vectors. Each vector is the same length as `samples_matrix`. The key 'energy' and its vector are required. vartype (:class:`.Vartype`, optional, default=None): Vartype of the response. If not provided, vartype is inferred from the samples matrix if possible or a ValueError is raised. info (dict, optional, default=None): Information about the response as a whole formatted as a dict. variable_labels (list, optional, default=None): Maps (by index) variable labels to the columns of the samples matrix. Returns: :obj:`.Response`: A `dimod` :obj:`.Response` object based on the input NumPy array-like object. Raises: :exc:`ValueError`: If vartype is not provided and samples are all 1s, have more than two unique values, or have values of an unknown vartype. Examples: This example code snippet builds a response from a NumPy matrix. .. code-block:: python samples = np.matrix([[0, 1], [1, 0]]) energies = [0.0, 1.0] response = Response.from_matrix(samples, {'energy': energies}) This example code snippet builds a response from a NumPy array-like object (a Python list). .. code-block:: python samples = [[0, 1], [1, 0]] energies = [0.0, 1.0] response = Response.from_matrix(samples, {'energy': energies}) Notes: SciPy defines array_like in the following way: "In general, numerical data arranged in an array-like structure in Python can be converted to arrays through the use of the array() function. The most obvious examples are lists and tuples. See the documentation for array() for details for its use. Some objects may support the array-protocol and allow conversion to arrays this way. A simple way to find out if the object can be converted to a numpy array using array() is simply to try it interactively and see if it works! (The Python Way)." [array_like]_ References: .. [array_like] Docs.scipy.org. (2018). Array creation - NumPy v1.14 Manual. [online] Available at: https://docs.scipy.org/doc/numpy/user/basics.creation.html [Accessed 16 Feb. 2018]. """ samples_matrix = np.matrix(samples, dtype=np.int8) if vartype is None: vartype = infer_vartype(samples_matrix) response = cls(samples_matrix, data_vectors=data_vectors, vartype=vartype, info=info, variable_labels=variable_labels) return response
[docs] @classmethod def from_dicts(cls, samples, data_vectors, vartype=None, info=None): """Build a response from an iterable of dicts. Args: samples (iterable[dict]): Iterable of samples where each sample is a dictionary (or Mapping). data_vectors (dict[field, :obj:`numpy.array`/list]): Additional per-sample data as a dict of vectors. Each vector is the same length as `samples_matrix`. The key 'energy' and its vector are required. vartype (:class:`.Vartype`, optional, default=None): Vartype of the response. If not provided, vartype is inferred from the samples matrix if possible or a ValueError is raised. info (dict, optional, default=None): Information about the response as a whole formatted as a dict. Returns: :obj:`.Response`: A `dimod` :obj:`.Response` object based on the input dicts. Raises: :exc:`ValueError`: If vartype is not provided and samples are all 1s, have more than two unique values, or have values of an unknown vartype. Examples: This example code snippet builds a response from an interable of samples and its corresponding dict of energies. .. code-block:: python samples = [{'a': -1, 'b': +1}, {'a': +1, 'b': -1}] energies = [-1.0, -1.0] response = Response.from_dicts(samples, {'energy': energies}) """ samples = iter(samples) # get the first sample first_sample = next(samples) try: variable_labels = sorted(first_sample) except TypeError: # unlike types cannot be sorted in python3 variable_labels = list(first_sample) num_variables = len(variable_labels) def _iter_samples(): yield np.fromiter((first_sample[v] for v in variable_labels), count=num_variables, dtype=np.int8) try: for sample in samples: yield np.fromiter((sample[v] for v in variable_labels), count=num_variables, dtype=np.int8) except KeyError: msg = ("Each dict in 'samples' must have the same keys.") raise ValueError(msg) samples_matrix = np.matrix(np.stack(list(_iter_samples()))) return cls.from_matrix(samples_matrix, data_vectors=data_vectors, vartype=vartype, info=info, variable_labels=variable_labels)
[docs] @classmethod def from_pandas(cls, samples_df, data_vectors, vartype=None, info=None): """Build a response from a pandas DataFrame. Args: samples (:obj:`pandas.DataFrame`): A pandas DataFrame of samples where each row is a sample. data_vectors (dict[field, :obj:`numpy.array`/list]): Additional per-sample data as a dict of vectors. Each vector is the same length as `samples_matrix`. The key 'energy' and its vector are required. vartype (:class:`.Vartype`, optional, default=None): Vartype of the response. If not provided, vartype is inferred from the samples matrix if possible or a ValueError is raised. info (dict, optional, default=None): Information about the response as a whole formatted as a dict. Returns: :obj:`.Response`: A `dimod` :obj:`.Response` object based on the input DataFrame. Raises: :exc:`ValueError`: If vartype is not provided and samples are all 1s, have more than two unique values, or have values of an unknown vartype. Examples: These example code snippets build a response from a pandas DataFrame. .. code-block:: python import pandas as pd samples = pd.DataFrame([{'a': 1, 'b': 0}, {'a': 0, 'b': 0}], dtype='int8') response = Response.from_pandas(samples, {energy: [1, 0]}) .. code-block:: python import pandas as pd samples = pd.DataFrame([[+1, -1]], dtype='int8', columns=['v1', 'v2']) response = Response.from_pandas(samples, {energy: [1]}) """ import pandas as pd variable_labels = list(samples_df.columns) samples_matrix = samples_df.as_matrix(columns=variable_labels) if isinstance(data_vectors, pd.DataFrame): raise NotImplementedError("support for DataFrame data_vectors is forthcoming") return cls.from_matrix(samples_matrix, data_vectors, vartype=vartype, info=info, variable_labels=variable_labels)
@classmethod def empty(cls, vartype): empty_samples_matrix = np.matrix(np.empty((0, 0), dtype=np.int8)) empty_data_vectors = {'energy': []} return cls(samples_matrix=empty_samples_matrix, data_vectors=empty_data_vectors, vartype=vartype)
[docs] @classmethod def from_futures(cls, futures, vartype, num_variables, samples_key='samples', data_vector_keys=None, info_keys=None, variable_labels=None, active_variables=None, ignore_extra_keys=True): """Build a response from :obj:`~concurrent.futures.Future`-like objects. Args: futures (iterable): Iterable :obj:`~concurrent.futures.Future` or :obj:`~concurrent.futures.Future`-like objects (Python objects with similar structure). :meth:`~concurrent.futures.Future.result` returns a dict. vartype (:class:`.Vartype`): Vartype of the response. num_variables (int): Number of variables for each sample. samples_key (hashable, optional, default='samples'): Key of the result dict containing the samples. Samples are array-like. data_vector_keys (iterable/mapping, optional, default=None): A mapping from the keys of the result dict to :attr:`Response.data_vectors`. If None, ['energy'] is assumed to be a key in the result dict and the 'energy' data vector is mapped. info_keys (iterable/mapping, optional, default=None): A mapping from the keys of the result dict to :attr:`Response.info`. If None, info is empty. variable_labels (list, optional, default=None): Maps (by index) variable labels to columns of the samples matrix. active_variables (array-like, optional, default=None): Selects which columns of the result's samples are used. If `variable_labels` is not provided, `variable_labels` is set to match `active_variables`. ignore_extra_keys (bool, optional, default=True): If True, keys given in `data_vector_keys` and `info_keys` but that are not in :meth:`~concurrent.futures.Future.result` are ignored. If False, extra keys cause a ValueError. Returns: :obj:`.Response`: A `dimod` :obj:`.Response` object based on the input Future-like objects. Notes: :obj:`~concurrent.futures.Future` objects are read on the first read of :attr:`.Response.samples_matrix` or :attr:`.Response.data_vectors`. Examples: These example code snippets build responses from :obj:`~concurrent.futures.Future` objects. .. code-block:: python from concurrent.futures import Future future = Future() # load the future into response response = dimod.Response.from_futures((future,), dimod.BINARY, 3) future.set_result({'samples': [0, 1, 0], 'energy': [1]}) # now read from the response matrix = response.samples_matrix .. code-block:: python from concurrent.futures import Future future = Future() # load the future into response response = dimod.Response.from_futures((future,), dimod.BINARY, 3, active_variables=[0, 1, 3]) future.set_result({'samples': [0, 1, 3, 0], 'energy': [1]}) # now read from the response, this matrix matrix = response.samples_matrix .. code-block:: python from concurrent.futures import Future future = Future() # load the future into response response = dimod.Response.from_futures((future,), dimod.BINARY, 3, data_vector_keys={'en': 'energy'}) future.set_result({'samples': [0, 1, 0], 'en': [1]}) # now read from the response matrix = response.samples_matrix """ if data_vector_keys is None: data_vector_keys = {'energy': 'energy'} elif isinstance(data_vector_keys, Mapping): data_vector_keys = dict(data_vector_keys) else: data_vector_keys = {key: key for key in data_vector_keys} # identity mapping if info_keys is None: info_keys = {} elif isinstance(info_keys, Mapping): info_keys = dict(info_keys) else: info_keys = {key: key for key in info_keys} if active_variables is not None: if variable_labels is None: variable_labels = active_variables elif len(variable_labels) != len(active_variables): raise ValueError("active_variables and variable_labels should have the same length") response = cls.empty(vartype) # now dump all of the remaining information into the _futures response._futures = {'futures': futures, 'samples_key': samples_key, 'data_vector_keys': data_vector_keys, 'info_keys': info_keys, 'variable_labels': variable_labels, 'active_variables': active_variables, 'ignore_extra_keys': ignore_extra_keys} return response
def _resolve_futures(self, futures, samples_key, data_vector_keys, info_keys, variable_labels, active_variables, ignore_extra_keys): # first reset the futures to avoid recursion errors self._futures = {} # `dwave.cloud.qpu.computation.Future` is not yet interchangeable with # `concurrent.futures.Future`, so we need to detect the kind of future # we're dealing with. futures = list(futures) # if generator if hasattr(futures[0], 'as_completed'): as_completed = futures[0].as_completed else: as_completed = concurrent.futures.as_completed # combine all samples from all futures into a single response for future in as_completed(futures): result = dict(future.result()) # create a shallow copy # first get the samples matrix and filter out any inactive variables samples = np.matrix(result.pop(samples_key), dtype=np.int8) if active_variables is not None: samples = samples[:, active_variables] # next get the data vectors if ignore_extra_keys: data_vectors = {} for source_key, key in iteritems(data_vector_keys): try: data_vectors[key] = result[source_key] except KeyError: pass info = {} for source_key, key in iteritems(info_keys): try: info[key] = result[source_key] except KeyError: pass else: data_vectors = {} for source_key, key in iteritems(data_vector_keys): try: data_vectors[key] = result[source_key] except KeyError: raise ValueError("data vector key '{}' not in Future.result()".format(key)) info = {} for source_key, key in iteritems(info_keys): try: info[key] = result[source_key] except KeyError: raise ValueError("info key '{}' not in Future.result()".format(key)) # now get the appropriate response response = self.__class__.from_matrix(samples, data_vectors=data_vectors, info=info, vartype=self.vartype, variable_labels=variable_labels) self.update(response)
[docs] def update(self, *other_responses): """Add values of other responses to the response. Args: *other_responses: (:obj:`.Response`): Additional responses from which to add values. Any number of additional response objects, separated by commas, can be specified. Responses must have matching `sample_matrix` dimensions, `data_vector` keys, and variable labels. Examples: This example updates a response with values from two other responses. >>> import dimod >>> samples = [[0, 1], [1, 0]] >>> energies = [0.0, 1.0] >>> response = dimod.Response.from_matrix(samples, {'energy': energies}) >>> samples1 = [[0, 0], [1, 1]] >>> energies1 = [0.25, 1.25] >>> response1 = dimod.Response.from_matrix(samples1, {'energy': energies1}) >>> samples2 = [[1, 0], [0, 1]] >>> energies2 = [0.5, 1.75] >>> response2 = dimod.Response.from_matrix(samples2, {'energy': energies2}) >>> len(response) 2 >>> for i in response.data(): # doctest: +SKIP ... print(i) ... Sample(sample={0: 0, 1: 1}, energy=0.0) Sample(sample={0: 1, 1: 0}, energy=1.0) >>> response.update(response1, response2) >>> len(response) 6 >>> for energy in response.data(fields=['energy'], name='UpdatedEnergy'): ... print(energy) ... UpdatedEnergy(energy=0.0) UpdatedEnergy(energy=0.25) UpdatedEnergy(energy=0.5) UpdatedEnergy(energy=1.0) UpdatedEnergy(energy=1.25) UpdatedEnergy(energy=1.75) """ # make sure all of the other responses are the appropriate vartype. We could cast them but # that would effect the energies so it is best to happen outside of this function. vartype = self.vartype for response in other_responses: if vartype is not response.vartype: raise ValueError("can only update with responses of matching vartype base") # if self is empty, then we are done if not self: other_responses = list(other_responses) response = other_responses.pop() self.samples_matrix = response.samples_matrix self.data_vectors.update(response.data_vectors) self.info.update(response.info) self.variable_labels = response.variable_labels self.label_to_idx = response.label_to_idx if other_responses: self.update(*other_responses) return # make sure that the variable labels are consistent variable_labels = self.variable_labels if variable_labels is None: __, num_variables = self.samples_matrix.shape variable_labels = list(range(num_variables)) # in this case we need to allow for either None or variable_labels if not all(response.variable_labels is None or response.variable_labels == variable_labels for response in other_responses): raise ValueError("cannot update responses with unlike variable labels") else: if not all(response.variable_labels == variable_labels for response in other_responses): raise ValueError("cannot update responses with unlike variable labels") # concatenate all of the matrices matrices = [self.samples_matrix] matrices.extend([response.samples_matrix for response in other_responses]) self.samples_matrix = np.concatenate(matrices) # group all of the data vectors for key in self.data_vectors: vectors = [self.data_vectors[key]] vectors.extend(response.data_vectors[key] for response in other_responses) self.data_vectors[key] = np.concatenate(vectors) # finally update the response info for response in other_responses: self.info.update(response.info)
############################################################################################### # Transformations and Copies ###############################################################################################
[docs] def copy(self): """Creates a shallow copy of a response. Examples: This example copies a response. >>> import dimod >>> samples = [[0, 1], [1, 0]] >>> energies = [0.0, 1.0] >>> response = dimod.Response.from_matrix(samples, {'energy': energies}) >>> copied_response = response.copy() """ return self.from_matrix(self.samples_matrix, self.data_vectors, vartype=self.vartype, info=self.info, variable_labels=self.variable_labels)
[docs] @vartype_argument('vartype') def change_vartype(self, vartype, data_vector_offsets=None, inplace=True): """Create a new response with the given vartype. Args: vartype (:class:`.Vartype`/str/set): Variable type to use for the new response. Accepted input values: * :class:`.Vartype.SPIN`, ``'SPIN'``, ``{-1, 1}`` * :class:`.Vartype.BINARY`, ``'BINARY'``, ``{0, 1}`` data_vector_offsets (dict[field, :obj:`numpy.array`/list], optional, default=None): Offsets to add to `data_vectors` of the response formatted as a dict containing per-sample offsets in vectors. Each vector is the same length as `samples_matrix`. inplace (bool, optional, default=True): If True, the response is updated in-place, otherwise a new response is returned. Returns: :obj:`.Response`. New response with vartype matching input 'vartype'. Examples: This example converts the response of the dimod package's ExactSolver sampler to binary and adds offsets. >>> import dimod >>> response = dimod.ExactSolver().sample_ising({'a': -0.5, 'b': 1.0}, {('a', 'b'): -1}) >>> response_binary = response.change_vartype('BINARY', ... data_vector_offsets={'energy': [0, 0.1, 0.2, 0.3]}, ... inplace=False) >>> response_binary.vartype <Vartype.BINARY: frozenset([0, 1])> >>> for datum in response_binary.data(): # doctest: +SKIP ... print(datum) ... Sample(sample={'a': 0, 'b': 0}, energy=-1.5) Sample(sample={'a': 1, 'b': 0}, energy=-0.4) Sample(sample={'a': 1, 'b': 1}, energy=-0.3) Sample(sample={'a': 0, 'b': 1}, energy=2.8) This example code snippet creates a response with spin variables from a response with binary variables while adding energy offsets to the new response. .. code-block:: python import pandas as pd samples = [[0, 1], [1, 0]] energies = [0.0, 1.0] response = dimod.Response.from_matrix(samples, {'energy': energies}) offsets = {'energy': [0.25, 0.75]} response.change_vartype('SPIN', data_vector_offsets = offsets) """ if not inplace: return self.copy().change_vartype(vartype, data_vector_offsets=data_vector_offsets, inplace=True) if data_vector_offsets is not None: for key in data_vector_offsets: self.data_vectors[key] += data_vector_offsets[key] if vartype is self.vartype: return self if vartype is Vartype.SPIN and self.vartype is Vartype.BINARY: self.samples_matrix = 2 * self.samples_matrix - 1 self.vartype = vartype elif vartype is Vartype.BINARY and self.vartype is Vartype.SPIN: self.samples_matrix = (self.samples_matrix + 1) // 2 self.vartype = vartype else: raise ValueError("Cannot convert from {} to {}".format(self.vartype, vartype)) return self
[docs] def relabel_variables(self, mapping, inplace=True): """Relabel a response's variables as per a given mapping. Args: mapping (dict): Dict mapping current variable labels to new. If an incomplete mapping is provided, unmapped variables keep their original labels inplace (bool, optional, default=True): If True, the original response is updated; otherwise a new response is returned. Returns: :class:`.Response`: Response with relabeled variables. If inplace=True, returns itself. Examples: This example relabels the response of the dimod package's ExactSolver sampler and saves it as a new response. >>> import dimod >>> response = dimod.ExactSolver().sample_ising({'a': -0.5, 'b': 1.0}, {('a', 'b'): -1}) >>> new_response = response.relabel_variables({'a': 0, 'b': 1}, inplace=False) >>> [next(new_response.samples())[x] for x in [0, 1]] [-1, -1] This example code snippet relabels variables in a response. .. code-block:: python response = dimod.Response.from_dicts([{'a': -1}, {'a': +1}], {'energy': [-1, 1]}) response.relabel_variables({'a': 0}) """ if not inplace: return self.copy().relabel_variables(mapping, inplace=True) # we need labels if self.variable_labels is None: __, num_variables = self.samples_matrix.shape self.variable_labels = list(range(num_variables)) try: old_labels = set(mapping) new_labels = set(itervalues(mapping)) except TypeError: raise ValueError("mapping targets must be hashable objects") for v in new_labels: if v in self.variable_labels and v not in old_labels: raise ValueError(('A variable cannot be relabeled "{}" without also relabeling ' "the existing variable of the same name").format(v)) shared = old_labels & new_labels if shared: old_to_intermediate, intermediate_to_new = resolve_label_conflict(mapping, old_labels, new_labels) self.relabel_variables(old_to_intermediate, inplace=True) self.relabel_variables(intermediate_to_new, inplace=True) return self self.variable_labels = variable_labels = [mapping.get(v, v) for v in self.variable_labels] self.label_to_idx = {v: idx for idx, v in enumerate(variable_labels)} return self
############################################################################################### # Viewing a Response ###############################################################################################
[docs] def samples(self, n=None, sorted_by='energy'): """Iterate over the samples in the response. Args: n (int, optional, default=None): The maximum number of samples to provide. If None, all are provided. sorted_by (str/None, optional, default='energy'): Selects the `data_vector` used to sort the samples. If None, the samples are yielded in the order given by the samples matrix. Yields: :obj:`.SampleView`: A view object mapping the variable labels to their values. Acts like a read-only dict. Examples: This example iterates over the response samples of the dimod ExactSolver sampler. >>> import dimod >>> response = dimod.ExactSolver().sample_ising({'a': -0.5, 'b': 1.0}, {('a', 'b'): -1}) >>> for sample in response.samples(): # sorted_by='energy' ... print(sample['a']==sample['b']) ... True False True False >>> for sample in response.samples(sorted_by=None): # doctest: +SKIP ... print(sample) ... {'a': -1, 'b': -1} {'a': 1, 'b': -1} {'a': 1, 'b': 1} {'a': -1, 'b': 1} """ num_samples = len(self) if n is not None: for sample in itertools.islice(self.samples(n=None, sorted_by=sorted_by), n): yield sample return if sorted_by is None: order = np.arange(num_samples) else: order = np.argsort(self.data_vectors[sorted_by]) samples = self.samples_matrix label_mapping = self.label_to_idx for idx in order: yield SampleView(idx, self)
[docs] def data(self, fields=None, sorted_by='energy', name='Sample'): """Iterate over the data in the response. Args: fields (list, optional, default=None): If specified, only these fields' values are included in the yielded tuples. The special field name 'sample' can be used to view the samples. sorted_by (str/None, optional, default='energy'): Selects the data_vector used to sort the samples. If None, the samples are yielded in the order given by the samples matrix. name (str/None, optional, default='Sample'): Name of the yielded namedtuples or None to yield regular tuples. Yields: namedtuple/tuple: The data in the response, in the order specified by the input `fields`. Examples: This example iterates over the response data of the dimod ExactSolver sampler. >>> import dimod >>> response = dimod.ExactSolver().sample_ising({'a': -0.5, 'b': 1.0}, {('a', 'b'): -1}) >>> for datum in response.data(): # doctest: +SKIP ... print(datum) ... Sample(sample={'a': -1, 'b': -1}, energy=-1.5) Sample(sample={'a': 1, 'b': -1}, energy=-0.5) Sample(sample={'a': 1, 'b': 1}, energy=-0.5) Sample(sample={'a': -1, 'b': 1}, energy=2.5) >>> for energy, in response.data(fields=['energy'], sorted_by='energy'): ... print(energy) ... -1.5 -0.5 -0.5 2.5 >>> print(next(response.data(fields=['energy'], name='ExactSolverSample'))) ExactSolverSample(energy=-1.5) """ if fields is None: fields = ['sample'] fields.extend(self.data_vectors) if sorted_by is None: order = np.arange(len(self)) else: order = np.argsort(self.data_vectors[sorted_by]) if name is None: # yielding a tuple def _pack(values): return tuple(values) else: # yielding a named tuple SampleTuple = namedtuple(name, fields) def _pack(values): return SampleTuple(*values) samples = self.samples_matrix label_mapping = self.label_to_idx data_vectors = self.data_vectors def _values(idx): for field in fields: if field == 'sample': yield SampleView(idx, self) else: yield data_vectors[field][idx] for idx in order: yield _pack(_values(idx))
class SampleView(Mapping): """View each row of the samples matrix as if it was a dict.""" def __init__(self, idx, response): self._idx = idx # row of response._samples_matrix self._response = response def __getitem__(self, key): label_mapping = self._response.label_to_idx if label_mapping is not None: key = label_mapping[key] return int(self._response.samples_matrix[self._idx, key]) def __iter__(self): # iterate over the variables label_mapping = self._response.label_to_idx if label_mapping is None: __, num_variables = self._response.samples_matrix.shape return iter(range(num_variables)) return label_mapping.__iter__() def __len__(self): __, num_variables = self._response.samples_matrix.shape return num_variables def __repr__(self): """Represents itself as as a dictionary""" return dict(self).__repr__() def values(self): return SampleValuesView(self) def items(self): return SampleItemsView(self) class SampleItemsView(ItemsView): """Faster read access to the numpy matrix""" __slots__ = () def __iter__(self): # Inherited __init__ puts the Mapping into self._mapping variable_labels = self._mapping._response.variable_labels samples_matrix = self._mapping._response.samples_matrix idx = self._mapping._idx if variable_labels is None: for v, val in enumerate(np.nditer(samples_matrix[idx, :], order='C', op_flags=['readonly'])): yield (v, int(val)) else: for v, val in zip(variable_labels, np.nditer(samples_matrix[idx, :], order='C', op_flags=['readonly'])): yield (v, int(val)) class SampleValuesView(ValuesView): """Faster read access to the numpy matrix""" __slots__ = () def __iter__(self): # Inherited __init__ puts the Mapping into self._mapping samples_matrix = self._mapping._response.samples_matrix for val in np.nditer(samples_matrix[self._mapping._idx, :], op_flags=['readonly']): yield int(val) def infer_vartype(samples_matrix): """Try to determine the Vartype of the samples matrix based on its values. Args: samples_matrix (:object:`numpy.ndarray`): An array or matrix of samples. Returns: :class:`.Vartype` Raises: ValueError: If the matrix is all ones, contains values other than -1, 1, 0 or contains more than two unique values. """ ones_matrix = samples_matrix == 1 if np.all(ones_matrix): msg = ("ambiguous vartype - an empty samples_matrix or one where all the values " "are all 1 must have the vartype specified by setting vartype=dimod.SPIN or " "vartype=dimod.BINARY.") raise ValueError(msg) if np.all(ones_matrix + (samples_matrix == 0)): return Vartype.BINARY elif np.all(ones_matrix + (samples_matrix == -1)): return Vartype.SPIN else: sample_vals = set(int(v) for v in np.nditer(samples_matrix)) - {-1, 1, 0} if sample_vals: msg = ("samples_matrix includes unknown values {}").format(sample_vals) else: msg = ("samples_matrix includes both -1 and 0 values") raise ValueError(msg)