Skip to content

Commit

Permalink
0.2.0
Browse files Browse the repository at this point in the history
* implemented composition for spin variables, no docs yet

* broke everything but now better documented

* sped up ising_to_qubo

* sped up qubo_to_ising

* fixed important bug where data field was replicated

* composition version implemented, might try decorator now...

* layer -> composite

* dynamic inheritance to instantiated object wrapping

* added to unittests

* install instructions include pip

* docstrings and html stuff, not complete

* first pass finished documentation for spin_transform

* clean up, unittest update

* finished first draft documentation

* updated ignore

* changed generic sampler test name so it hopefully does not get caught by pytest

* removed unnecessary import

* samples -> num_samples

* removed structured_ising and structured_qubo methods, added composite functionality

* Added Travis CI status badge to README

* sublime in .gitignore

* removing structure

* building on travis and appveyor for pypi

* changed install

* reverted. added using pip to install

* changed order

* Update .travis.yml

changed test runner

* added deployment details for deployment to pypi

* added deployment details for deployment to pypi

* Merge branch 'master' of https://github.com/oneklc/dimod

* added linux and mac os testing

* fixed order

* added appveyor.yml from demo

* modified to work with our build process

* testing a hunch

* hunch number 2

* osx fixes for travis-ci

* fixes for mac builds

* fixed pip install

* made executable

* more updates for osx testing

* fix for python 2.7

* changed pypi user

* more fixes for python2 on mac

* clean up

* clean up

* added appveyor helper scripts

* added appveyor helper scripts

* typo fix

* installing/upgrading setuptools and wheel in python env

* removed previous fail. installing/upgrading setuptools and wheel in python env

* removing ci support for python 2.6

* added wheel to travis pypi build

* does not support python 2.7.0

* removed structured methods with references

* updated documenation to reflect removal of structured methods

* removed structured, changed the way the structure property was added and added inits to the example samplers
  • Loading branch information
arcondello committed Oct 10, 2017
1 parent 200d57e commit f76fdc2
Show file tree
Hide file tree
Showing 29 changed files with 653 additions and 185 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
*.pyc
*.odp

build
dimod.egg-info
dist

docs/generated
docs/generated

*.sublime-project
*.sublime-workspace
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ See documentation for more examples.

Compatible with Python 2 and 3.

`pip install dimod`

To install from source:

`python setup.py install`

## License
Expand Down
8 changes: 7 additions & 1 deletion dimod/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
__version__ = '0.1.1'
from __future__ import absolute_import

__version__ = '0.2.0'
__author__ = 'D-Wave Systems Inc.'
__authoremail__ = 'acondello@dwavesys.com'
__description__ = 'A shared API for binary quadratic model samplers.'
Expand All @@ -14,3 +16,7 @@
from dimod.sampler_template import *

from dimod.samplers import *

from dimod.composites import *

from dimod.composite_template import *
57 changes: 57 additions & 0 deletions dimod/composite_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""
Samplers can be composed. This pattern allows pre- and post-processing
to be applied to binary quadratic programs without needing to change
the underlying sampler implementation.
We refer to these layers as `composites`. Each composed sampler must
include at least one `sampler`, and possibly many composites. See
'dimod sampler composition pattern' figure below.
Each composed sampler is itself a dimod sampler with all of the
included methods and parameters. In this way complex samplers
can be constructed.
Examples
--------
We will be using the included composite :class:`.SpinReversalTransform`
and included samplers :class:`.ExactSolver` and
:class:`.SimulatedAnnealingSampler` in our examples.
Building composed samplers are easy
>>> composed_sampler_es = dimod.SpinReversalTransform(dimod.ExactSolver())
A composite layer can be applied to any dimod sampler.
>>> composed_sampler_sa = dimod.SpinReversalTransform(dimod.SimulatedAnnealingSampler)
These composed samplers themselves behave exactly like samplers.
>>> h = {0: -1, 1: 1}
>>> response = composed_sampler_es.sample_ising(h, {})
>>> list(response.samples())
[{0: 1, 1: -1}, {0: -1, 1: -1}, {0: 1, 1: 1}, {0: -1, 1: 1}]
Composite layers can also be nested.
>>> composed_sampler_nested = dimod.SpinReversalTransform(composed_sampler_es)
"""
from dimod import TemplateSampler


class TemplateComposite(TemplateSampler):
"""Serves as a template for composites. Not intended to be used directly.
Args:
*samplers: child sampler(s) of the composite.
Attributes:
children (list): A list of child samplers or child composed samplers.
"""
def __init__(self, *samplers):
TemplateSampler.__init__(self)
self.children = list(samplers)
1 change: 1 addition & 0 deletions dimod/composites/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from dimod.composites.spin_transform import *
289 changes: 289 additions & 0 deletions dimod/composites/spin_transform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
from random import random
import sys
import time
import itertools

from dimod.composite_template import TemplateComposite
from dimod.responses import SpinResponse, BinaryResponse
from dimod.decorators import ising, qubo
from dimod.utilities import ising_to_qubo, qubo_to_ising

__all__ = ['SpinReversalTransform']

_PY2 = sys.version_info[0] == 2
if _PY2:
iteritems = lambda d: d.iteritems()
range = xrange
zip = itertools.izip
else:
iteritems = lambda d: d.items()


class SpinReversalTransform(TemplateComposite):
"""Composite for applying spin reversal transform preprocessing.
Spin reversal transforms (or "gauge transformations") are applied
by flipping the spin of variables in the Ising problem. We can
then sample using the transformed Ising problem and flip the same
bits in the resulting sample.
Args:
sampler: A dimod sampler object.
Examples:
Composing a sampler.
>>> base_sampler = dimod.ExactSolver()
>>> composed_sampler = dimod.SpinReversalTransform(base_sampler)
The composed sampler can now be used as a dimod sampler.
>>> h = {0: -1, 1: 1}
>>> response = composed_sampler.sample_ising(h, {})
>>> list(response.samples())
[{0: 1, 1: -1}, {0: -1, 1: -1}, {0: 1, 1: 1}, {0: -1, 1: 1}]
The base sampler is also in `children` attribute of the composed
sampler.
>>> base_sampler in composed_sampler.children
True
References
----------
.. [KM] Andrew D. King and Catherine C. McGeoch. Algorithm engineering
for a quantum annealing platform. https://arxiv.org/abs/1410.2628,
2014.
Attributes:
children (list): [`sampler`] where `sampler` is the input sampler.
structure: Inherited from input `sampler`.
"""
def __init__(self, sampler):
# puts sampler into self.children
TemplateComposite.__init__(self, sampler)

self._child = sampler # faster access than self.children[0]

# copy over the structure
self.structure = sampler.structure

@ising(1, 2)
def sample_ising(self, h, J,
num_spin_reversal_transforms=1, spin_reversal_variables=None,
**kwargs):
"""Applies spin reversal transforms to an Ising problem, then samples
using the child sampler's `sample_ising` method.
Args:
h (dict/list): The linear terms in the Ising problem. If a
dict, should be of the form {v: bias, ...} where v is
a variable in the Ising problem, and bias is the linear
bias associated with v. If a list, should be of the form
[bias, ...] where the indices of the biases are the
variables in the Ising problem.
J (dict): A dictionary of the quadratic terms in the Ising
problem. Should be of the form {(u, v): bias} where u,
v are variables in the Ising problem and bias is the
quadratic bias associated with u, v.
num_spin_reversal_transforms (int, optional): Default 1. The
number of different spin reversal transforms to apply to
the given Ising problem. Note that the child sampler will
be invoked for each spin reversal transform.
spin_reversal_variables (iterable, optional): An iterable of
variables in the Ising problem. These are the variables
that have their spins flipped. If set to None, each variable
has a 50% chance of having its bit flipped. Default None.
**kwargs: Any other keyword arguments are passed unchanged to
the child sampler's `sample_ising` method.
Notes:
As noted in the section defining the `num_spin_reversal_transforms`
parameter, the child sampler will be invoked for each different
spin reversal transform. So if the child sampler accepts a
`num_reads` keyword parameter, the total number of reads
performed will be `num_reads` * `num_spin_reversal_transforms`.
"""
if not isinstance(num_spin_reversal_transforms, int):
raise TypeError("input `num_spin_reversal_transforms` must be an 'int'")

sampler = self._child

# dispatch all of the jobs, in case the samples are resolved upon response read.
# keep track of which variables were transformed
dispatched = []
for __ in range(num_spin_reversal_transforms):
h_spin, J_spin, transform = \
apply_spin_reversal_transform(h, J, spin_reversal_variables)

response = sampler.sample_ising(h_spin, J_spin, **kwargs)

dispatched.append((response, transform))

# put all of the responses into one
st_response = SpinResponse()

for response, transform in dispatched:
samples, energies, sample_data = zip(*response.items(data=True))

# flip the bits in the samples
st_samples = (_apply_srt_sample_spin(sample, transform) for sample in samples)

# keep track of which bits were flipped in data
st_sample_data = (_apply_srt_sample_data(dat, transform) for dat in sample_data)

st_response.add_samples_from(st_samples, energies, st_sample_data)

st_response.data.update(response.data)

return st_response

@ising(1, 2)
def sample_structured_ising(self, h, J,
num_spin_reversal_transforms=1, spin_reversal_variables=None,
**kwargs):
"""Applies spin reversal transforms to an Ising problem, then samples
using the child sampler's `sample_structured_ising` method.
Args:
h (dict/list): The linear terms in the Ising problem. If a
dict, should be of the form {v: bias, ...} where v is
a variable in the Ising problem, and bias is the linear
bias associated with v. If a list, should be of the form
[bias, ...] where the indices of the biases are the
variables in the Ising problem.
J (dict): A dictionary of the quadratic terms in the Ising
problem. Should be of the form {(u, v): bias} where u,
v are variables in the Ising problem and bias is the
quadratic bias associated with u, v.
num_spin_reversal_transforms (int, optional): Default 1. The
number of different spin reversal transforms to apply to
the given Ising problem. Note that the child sampler will
be invoked for each spin reversal transform.
spin_reversal_variables (iterable, optional): An iterable of
variables in the Ising problem. These are the variables
that have their spins flipped. If set to None, each variable
has a 50% chance of having its bit flipped. Default None.
**kwargs: Any other keyword arguments are passed unchanged to
the child sampler's `sample_structured_ising` method.
Notes:
As noted in the section defining the `num_spin_reversal_transforms`
parameter, the child sampler will be invoked for each different
spin reversal transform. So if the child sampler accepts a
`num_reads` keyword parameter, the total number of reads
performed will be `num_reads` * `num_spin_reversal_transforms`.
"""
if not isinstance(num_spin_reversal_transforms, int):
raise TypeError("input `num_spin_reversal_transforms` must be an 'int'")

sampler = self._child

# dispatch all of the jobs, in case the samples are resolved upon response read.
# keep track of which variables were transformed
dispatched = []
for __ in range(num_spin_reversal_transforms):
h_spin, J_spin, transform = \
apply_spin_reversal_transform(h, J, spin_reversal_variables)

response = sampler.sample_structured_ising(h_spin, J_spin, **kwargs)

dispatched.append((response, transform))

# put all of the responses into one
st_response = SpinResponse()

for response, transform in dispatched:
samples, energies, sample_data = zip(*response.items(data=True))

# flip the bits in the samples
st_samples = (_apply_srt_sample_spin(sample, transform) for sample in samples)

# keep track of which bits were flipped in data
st_sample_data = (_apply_srt_sample_data(dat, transform) for dat in sample_data)

st_response.add_samples_from(st_samples, energies, st_sample_data)

st_response.data.update(response.data)

return st_response


def _apply_srt_sample_spin(sample, transform):
# flips the bits in a spin sample
return {v: -s if v in transform else s for v, s in iteritems(sample)}


def _apply_srt_sample_data(data, transform):
# stores information about the transform in the sample's data field
if 'spin_reversal_variables' in data:
data['spin_reversal_variables_{}'.format(time.time())] = transform
else:
data['spin_reversal_variables'] = transform
return data


def apply_spin_reversal_transform(h, J, spin_reversal_variables=None):
"""Applies spin reversal transforms to an Ising problem.
Spin reversal transforms (or "gauge transformations") are applied
by flipping the spin of variables in the Ising problem. We can
then sample using the transformed Ising problem and flip the same
bits in the resulting sample.
Args:
h (dict): The linear terms in the Ising problem. Should be of
the form {v: bias, ...} where v is a variable in the Ising
problem, and bias is the linear bias associated with v.
J (dict): A dictionary of the quadratic terms in the Ising
problem. Should be of the form {(u, v): bias} where u,
v are variables in the Ising problem and bias is the
quadratic bias associated with u, v.
spin_reversal_variables (iterable, optional): An iterable of
variables in the Ising problem. These are the variables
that have their spins flipped. If set to None, each variable
has a 50% chance of having its bit flipped. Default None.
Returns:
h_spin (dict): the transformed linear biases, in the same
form as `h`.
J_spin (dict): the transformed quadratic biases, in the same
form as `J`.
spin_reversal_variables (set): The variables which had their
spins flipped. If `spin_reversal_variables` were provided,
then this will be the same.
References
----------
.. [KM] Andrew D. King and Catherine C. McGeoch. Algorithm engineering
for a quantum annealing platform. https://arxiv.org/abs/1410.2628,
2014.
"""

if spin_reversal_variables is None:
# apply spin transform to each variable with 50% chance
transform = set(v for v in h if random() > .5)
else:
transform = set(spin_reversal_variables)

# apply spins transform to the linear biases
h_spin = {v: -bias if v in transform else bias for v, bias in iteritems(h)}

# apply spins transform to the quadratic biases
def quad_bias(edge):
u, v = edge
bias = J[edge]
if u in transform:
bias = -bias
if v in transform:
bias = -bias
return bias
J_spin = {edge: quad_bias(edge) for edge in J}

return h_spin, J_spin, transform
Empty file.
Loading

0 comments on commit f76fdc2

Please sign in to comment.