diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 00000000..a3ce161f
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,107 @@
+version: 2
+jobs:
+ test-2.7: &test-template
+ docker:
+ - image: circleci/python:2.7-jessie
+
+ working_directory: ~/repo
+
+ steps:
+ - checkout
+
+ - run:
+ name: create virtualenv
+ command: |
+ python -m virtualenv env
+
+ - run:
+ name: install python dependencies
+ command: |
+ . env/bin/activate
+ python --version
+ pip install -r requirements.txt
+
+ - run:
+ name: run unittests
+ command: |
+ . env/bin/activate
+ python --version
+ coverage run -m unittest discover
+
+ - run:
+ name: codecov
+ command: |
+ . env/bin/activate
+ # todo: uncomment below when the repository becomes public
+ #codecov
+
+ test-3.4:
+ <<: *test-template
+ docker:
+ - image: circleci/python:3.4-jessie
+
+ test-3.5:
+ <<: *test-template
+ docker:
+ - image: circleci/python:3.5-jessie
+
+ test-3.6:
+ <<: *test-template
+ docker:
+ - image: circleci/python:3.6-jessie
+
+ test-3.7:
+ <<: *test-template
+ docker:
+ - image: circleci/python:3.7-stretch
+
+ test-doc:
+ docker:
+ - image: circleci/python:3.6-jessie
+
+ working_directory: ~/repo
+
+ steps:
+ - run:
+ name: install graphviz and pandoc
+ command: |
+ sudo apt-get install graphviz
+ sudo apt-get install pandoc
+
+ - checkout
+
+ - run:
+ name: create virtualenv
+ command: |
+ python -m virtualenv env
+
+ - run:
+ name: install sphinx and dependencies
+ command: |
+ . env/bin/activate
+ pip install -r requirements.txt
+ pip install sphinx
+ pip install sphinx_rtd_theme
+
+ - run:
+ name: test doc build
+ command: |
+ . env/bin/activate
+ sphinx-build -W -b html docs docs/_build/html
+
+ - run:
+ name: run doctest
+ command: |
+ . env/bin/activate
+ make doctest
+
+workflows:
+ version: 2
+ tests:
+ jobs:
+ - test-2.7
+ - test-3.4
+ - test-3.5
+ - test-3.6
+ - test-3.7
+ - test-doc
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 00000000..9ec90b1e
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,4 @@
+[run]
+# omit virtualenv
+omit = venv/*
+source=pyqubo
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..5941bd46
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,126 @@
+# PyCharm
+.idea/
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+docs/.doctrees/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+
+### Python.VirtualEnv Stack ###
+# Virtualenv
+# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
+[Bb]in
+[Ii]nclude
+[Ll]ib
+[Ll]ib64
+[Ll]ocal
+[Ss]cripts
+pyvenv.cfg
+pip-selfcheck.json
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..25c0b419
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2018 Recruit Communications Co., Ltd.
+
+ 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.
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..e8da3bf6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+SPHINXPROJ = pyqubo
+SOURCEDIR = docs
+BUILDDIR = docs/_build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
diff --git a/README.rst b/README.rst
new file mode 100644
index 00000000..c48815c8
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,102 @@
+.. index-start-marker1
+
+PyQUBO
+======
+
+PyQUBO allows you to create QUBOs or Ising models from flexible mathematical expressions easily.
+Some of the features of PyQUBO are
+
+* **Python-based** (the entire code is written in Python).
+* **QUBO generation (compile) is fast** (due to the JIT like compile mechanism).
+* **Comes with plenty of useful expressions** such as binary integer variables, logical constraints, or matrix/vector variables.
+
+Example Usage
+-------------
+
+This example constructs a simple expression and compile it to ``model``.
+By calling ``model.to_qubo()``, we get the resulting QUBO.
+(This example solves a number partitioning problem with a set S = {4, 2, 7, 1})
+
+>>> from pyqubo import Spin
+>>> s1, s2, s3, s4 = Spin("s1"), Spin("s2"), Spin("s3"), Spin("s4")
+>>> H = (4*s1 + 2*s2 + 7*s3 + s4)**2
+>>> model = H.compile()
+>>> qubo, offset = model.to_qubo()
+>>> pprint(qubo)
+{('s1', 's1'): -160.0,
+ ('s1', 's2'): 64.0,
+ ('s1', 's3'): 224.0,
+ ('s1', 's4'): 32.0,
+ ('s2', 's2'): -96.0,
+ ('s2', 's3'): 112.0,
+ ('s2', 's4'): 16.0,
+ ('s3', 's3'): -196.0,
+ ('s3', 's4'): 56.0,
+ ('s4', 's4'): -52.0}
+
+
+
+More examples can be found in `readthedocs_example_page (not published yet) `_
+
+Installation
+------------
+
+.. code-block:: shell
+
+ pip install pyqubo
+
+or
+
+.. code-block:: shell
+
+ python setup.py install
+
+Supported Python Versions
+-------------------------
+
+Python 2.7, 3.4, 3.5, 3.6 and 3.7 are supported.
+
+.. index-end-marker1
+
+Test
+----
+
+Run all tests.
+
+.. code-block:: shell
+
+ python -m unittest discover test
+
+Show coverage report.
+
+.. code-block:: shell
+
+ coverage run -m unittest discover
+ coverage html
+
+Run test with circleci CLI.
+
+.. code-block:: shell
+
+ circleci build --job $JOBNAME
+
+Run doctest.
+
+.. code-block:: shell
+
+ make doctest
+
+Organization
+------------
+
+Recruit Communications Co., Ltd.
+
+Licence
+-------
+
+Released under the Apache License 2.0.
+
+Contribution
+------------
+
+We welcome contributions to this project.
\ No newline at end of file
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 00000000..3a8f5b94
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+SPHINXPROJ = pyqubo
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 00000000..8d03c5c2
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+#
+# Configuration file for the Sphinx documentation builder.
+#
+# This file does only contain a selection of the most common options. For a
+# full list see the documentation:
+# http://www.sphinx-doc.org/en/master/config
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import sys
+import doctest
+sys.path.insert(0, os.path.abspath('../'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = u'pyqubo'
+copyright = u'Recruit Communications Co., Ltd'
+author = u'Author'
+
+# The short X.Y version
+version = u'0.1'
+# The full version, including alpha/beta/rc tags
+release = u'0.0.1'
+
+
+# -- General configuration ---------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+#extensions = [
+# 'sphinx.ext.autodoc',
+# 'sphinx.ext.viewcode',
+# 'sphinx.ext.todo'
+#]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = 'en'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path .
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints']
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'sphinx_rtd_theme'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# The default sidebars (for documents that don't match any pattern) are
+# defined by theme itself. Builtin themes are using these templates by
+# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
+# 'searchbox.html']``.
+#
+# html_sidebars = {}
+
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'pyqubodoc'
+
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'pyqubo.tex', u'pyqubo Documentation',
+ u'Author', 'manual'),
+]
+
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ (master_doc, 'pyqubo', u'pyqubo Documentation',
+ [author], 1)
+]
+
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'pyqubo', u'pyqubo Documentation',
+ author, 'pyqubo', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+
+# -- Options for Epub output -------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+epub_author = author
+epub_publisher = author
+epub_copyright = copyright
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#
+# epub_identifier = ''
+
+# A unique identification for the text.
+#
+# epub_uid = ''
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+
+# -- Extension configuration -------------------------------------------------
+
+# -- Options for todo extension ----------------------------------------------
+
+
+# enable google style docstring
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.napoleon',
+ 'sphinx.ext.mathjax',
+ 'sphinx.ext.inheritance_diagram',
+ 'sphinx.ext.graphviz',
+ 'sphinx.ext.doctest',
+ 'sphinx.ext.autosummary',
+ 'sphinx.ext.viewcode',
+ 'nbsphinx'
+]
+
+# configure doctest
+doctest_default_flags = doctest.NORMALIZE_WHITESPACE
+doctest_global_setup = "from pprint import pprint"
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = True
+
+add_module_names = False
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 00000000..e55c7852
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,32 @@
+
+
+.. include:: ../README.rst
+ :start-after: index-start-marker1
+ :end-before: index-end-marker1
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Manual:
+
+ notebooks/getting_started.ipynb
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Reference Document:
+
+ reference/express
+ reference/model
+ reference/tensor
+ reference/constraint
+ reference/logic
+ reference/func
+ reference/utils
+ reference/internal/index
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 00000000..11a84537
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,36 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+set SPHINXPROJ=pyqubo
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+
+:end
+popd
diff --git a/docs/notebooks/getting_started.ipynb b/docs/notebooks/getting_started.ipynb
new file mode 100644
index 00000000..66a1c4c8
--- /dev/null
+++ b/docs/notebooks/getting_started.ipynb
@@ -0,0 +1,77 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Getting Started\n",
+ "\n",
+ "With PyQUBO, you can construct QUBOs with 3 steps:\n",
+ "\n",
+ "1. Define the hamiltonian with expressions\n",
+ "2. Compile the expression to get the model\n",
+ "3. Call 'to_qubo()' method of model."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "({('a', 'a'): 2.0, ('c', 'c'): 1.0, ('b', 'b'): 0.0, ('a', 'b'): 1.0}, 0.0)"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from pyqubo import Qbit\n",
+ "\n",
+ "# 1. Define the hamiltonian with expressions\n",
+ "a, b, c = Qbit('a'), Qbit('b'), Qbit('c')\n",
+ "H = a*b + 2*a + c\n",
+ "\n",
+ "# 2. Compile the expression to get the model\n",
+ "model = H.compile()\n",
+ "\n",
+ "# 3. Call 'to_qubo()' method of model\n",
+ "model.to_qubo()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ ""
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3.0
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.1"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
\ No newline at end of file
diff --git a/docs/reference/constraint.rst b/docs/reference/constraint.rst
new file mode 100644
index 00000000..44f59f74
--- /dev/null
+++ b/docs/reference/constraint.rst
@@ -0,0 +1,30 @@
+Logical Constraint
+==================
+
+
+.. automodule:: pyqubo
+.. currentmodule:: pyqubo
+
+NOT Constraint
+--------------
+
+.. autoclass:: NotConst
+ :members:
+
+AND Constraint
+--------------
+
+.. autoclass:: AndConst
+ :members:
+
+OR Constraint
+-------------
+
+.. autoclass:: OrConst
+ :members:
+
+XOR Constraint
+--------------
+
+.. autoclass:: XorConst
+ :members:
\ No newline at end of file
diff --git a/docs/reference/express.rst b/docs/reference/express.rst
new file mode 100644
index 00000000..cc9440f5
--- /dev/null
+++ b/docs/reference/express.rst
@@ -0,0 +1,51 @@
+Expression
+==========
+
+.. automodule:: pyqubo
+.. currentmodule:: pyqubo
+
+.. autoclass:: Express
+ :members:
+
+Qbit
+----
+.. autoclass:: Qbit
+
+Spin
+----
+.. autoclass:: Spin
+
+Param
+-----
+.. autoclass:: Param
+ :members:
+
+Constraint
+----------
+.. autoclass:: Constraint
+ :members:
+
+UserDefinedExpress
+------------------
+.. autoclass:: UserDefinedExpress
+ :members:
+
+Add
+---
+.. autoclass:: Add
+ :members:
+
+AddList
+-------
+.. autoclass:: AddList
+ :members:
+
+Mul
+---
+.. autoclass:: Mul
+ :members:
+
+Num
+---
+.. autoclass:: Num
+ :members:
\ No newline at end of file
diff --git a/docs/reference/func.rst b/docs/reference/func.rst
new file mode 100644
index 00000000..3721ce05
--- /dev/null
+++ b/docs/reference/func.rst
@@ -0,0 +1,11 @@
+Functions
+=========
+
+
+.. automodule:: pyqubo
+.. currentmodule:: pyqubo
+
+Sum over indices
+----------------
+.. autoclass:: Sum
+ :members:
diff --git a/docs/reference/internal/binaryprod.rst b/docs/reference/internal/binaryprod.rst
new file mode 100644
index 00000000..f57d4831
--- /dev/null
+++ b/docs/reference/internal/binaryprod.rst
@@ -0,0 +1,9 @@
+BinaryProd
+==========
+
+
+.. automodule:: pyqubo
+.. currentmodule:: pyqubo
+
+.. autoclass:: BinaryProd
+ :members:
\ No newline at end of file
diff --git a/docs/reference/internal/coefficient.rst b/docs/reference/internal/coefficient.rst
new file mode 100644
index 00000000..548b9969
--- /dev/null
+++ b/docs/reference/internal/coefficient.rst
@@ -0,0 +1,9 @@
+Coefficient
+===========
+
+
+.. automodule:: pyqubo
+.. currentmodule:: pyqubo
+
+.. autoclass:: Coefficient
+ :members:
diff --git a/docs/reference/internal/compiledqubo.rst b/docs/reference/internal/compiledqubo.rst
new file mode 100644
index 00000000..3857c151
--- /dev/null
+++ b/docs/reference/internal/compiledqubo.rst
@@ -0,0 +1,9 @@
+CompiledQubo
+============
+
+
+.. automodule:: pyqubo
+.. currentmodule:: pyqubo
+
+.. autoclass:: CompiledQubo
+ :members:
\ No newline at end of file
diff --git a/docs/reference/internal/index.rst b/docs/reference/internal/index.rst
new file mode 100644
index 00000000..c69e7360
--- /dev/null
+++ b/docs/reference/internal/index.rst
@@ -0,0 +1,13 @@
+.. _internal:
+
+Internal Class
+**************
+
+.. toctree::
+ :maxdepth: 2
+
+ binaryprod
+ paramprod
+ compiledqubo
+ coefficient
+
diff --git a/docs/reference/internal/paramprod.rst b/docs/reference/internal/paramprod.rst
new file mode 100644
index 00000000..164fe3d8
--- /dev/null
+++ b/docs/reference/internal/paramprod.rst
@@ -0,0 +1,9 @@
+ParamProd
+=========
+
+
+.. automodule:: pyqubo
+.. currentmodule:: pyqubo
+
+.. autoclass:: ParamProd
+ :members:
diff --git a/docs/reference/logic.rst b/docs/reference/logic.rst
new file mode 100644
index 00000000..a5c0016d
--- /dev/null
+++ b/docs/reference/logic.rst
@@ -0,0 +1,21 @@
+Logical Gate
+============
+
+
+.. automodule:: pyqubo
+.. currentmodule:: pyqubo
+
+Not
+---
+.. autoclass:: Not
+ :members:
+
+And
+---
+.. autoclass:: And
+ :members:
+
+Or
+---
+.. autoclass:: Or
+ :members:
\ No newline at end of file
diff --git a/docs/reference/model.rst b/docs/reference/model.rst
new file mode 100644
index 00000000..7eefce46
--- /dev/null
+++ b/docs/reference/model.rst
@@ -0,0 +1,9 @@
+Model
+=======
+
+
+.. automodule:: pyqubo
+.. currentmodule:: pyqubo
+
+.. autoclass:: Model
+ :members:
diff --git a/docs/reference/tensor.rst b/docs/reference/tensor.rst
new file mode 100644
index 00000000..dbf97675
--- /dev/null
+++ b/docs/reference/tensor.rst
@@ -0,0 +1,18 @@
+Tensor
+=======
+
+
+.. automodule:: pyqubo
+.. currentmodule:: pyqubo
+
+Vector
+------
+
+.. autoclass:: Vector
+ :members:
+
+Matrix
+------
+
+.. autoclass:: Matrix
+ :members:
\ No newline at end of file
diff --git a/docs/reference/utils.rst b/docs/reference/utils.rst
new file mode 100644
index 00000000..0814312b
--- /dev/null
+++ b/docs/reference/utils.rst
@@ -0,0 +1,19 @@
+Utils
+=====
+
+.. automodule:: pyqubo
+.. currentmodule:: pyqubo
+
+
+Solvers
+-------
+
+.. automodule:: pyqubo.utils.solver
+ :members:
+
+
+Asserts
+-------
+
+.. automodule:: pyqubo.utils.asserts
+ :members:
\ No newline at end of file
diff --git a/notebooks/japanese/TSP.ipynb b/notebooks/japanese/TSP.ipynb
new file mode 100644
index 00000000..d896fd31
--- /dev/null
+++ b/notebooks/japanese/TSP.ipynb
@@ -0,0 +1,247 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%matplotlib inline\n",
+ "from pyqubo import Spin, Matrix, Param, solve_qubo, Const, Sum\n",
+ "import matplotlib.pyplot as plt\n",
+ "import networkx as nx\n",
+ "import numpy as np"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Traveling Salesman Problem (TSP)\n",
+ "\n",
+ "全ての都市を一度だけ訪問し、元の都市に戻ってくる最短の経路を見つける。"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# utilメソッドを定義しておく\n",
+ "def plot_city(cities, sol = {}):\n",
+ " n_city = len(cities)\n",
+ " cities_dict = dict(cities)\n",
+ " G = nx.Graph()\n",
+ " for city in cities_dict:\n",
+ " G.add_node(city)\n",
+ " \n",
+ " # draw path\n",
+ " if sol:\n",
+ " city_order = []\n",
+ " for i, v in sol.items():\n",
+ " for j, v2 in v.items():\n",
+ " if v2 == 1:\n",
+ " city_order.append(j)\n",
+ " for i in range(n_city):\n",
+ " city_index1 = city_order[i]\n",
+ " city_index2 = city_order[(i+1) % n_city]\n",
+ " G.add_edge(cities[city_index1][0], cities[city_index2][0])\n",
+ "\n",
+ " plt.figure(figsize=(3,3))\n",
+ " pos = nx.spring_layout(G)\n",
+ " nx.draw_networkx(G, cities_dict)\n",
+ " plt.axis(\"off\")\n",
+ " plt.show()\n",
+ "\n",
+ "def dist(i, j, cities):\n",
+ " pos_i = cities[i][1]\n",
+ " pos_j = cities[j][1]\n",
+ " return np.sqrt((pos_i[0] - pos_j[0])**2 + (pos_i[1] - pos_j[1])**2)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# 都市の名前と座標のデータを用意 list[(\"name\", (x, y))]\n",
+ "cities = [\n",
+ " (\"a\", (0, 0)),\n",
+ " (\"b\", (1, 3)),\n",
+ " (\"c\", (3, 2)),\n",
+ " (\"d\", (2, 1)),\n",
+ " (\"e\", (0, 1))\n",
+ "]\n",
+ "plot_city(cities)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "バイナリベクトル$x$を用意。$x[i, j]=1$は時刻$i$に都市$j$にいることを表現する。"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "n_city = len(cities)\n",
+ "x = Matrix('c', n_city, n_city)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# ある時刻iに一つの都市にのみ存在できる制約を記述\n",
+ "time_const = 0.0\n",
+ "for i in range(n_city):\n",
+ " # Const(...)で数式を囲むと、その部分が制約として認識される。\n",
+ " time_const += Const((Sum(0, n_city, lambda j: x[i, j]) - 1)**2, label=\"time{}\".format(i))\n",
+ "\n",
+ "# 一つの都市を一度しか訪れない制約を記述\n",
+ "city_const = 0.0\n",
+ "for j in range(n_city):\n",
+ " city_const += Const((Sum(0, n_city, lambda i: x[i, j]) - 1)**2, label=\"city{}\".format(i))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# 経路の総距離を記述\n",
+ "distance = 0.0\n",
+ "for i in range(n_city):\n",
+ " for j in range(n_city):\n",
+ " for k in range(n_city):\n",
+ " # 時刻kに都市i, 時刻k+1に都市jにいた場合の都市i,j間の距離\n",
+ " d_ij = dist(i, j, cities)\n",
+ " distance += d_ij * x[k, i] * x[(k+1)%n_city, j]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# ハミルトニアンを構築\n",
+ "A = Param(\"A\")\n",
+ "H = distance + A * (time_const + city_const)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# モデルをコンパイル\n",
+ "model = H.compile()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# QUBOを作成\n",
+ "qubo, offset = model.to_qubo(params={'A': 4.0})"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "sol = solve_qubo(qubo)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "number of broken constarint = 0\n"
+ ]
+ }
+ ],
+ "source": [
+ "solution, broken = model.decode_solution(sol, var_type=\"binary\")\n",
+ "print(\"number of broken constarint = {}\".format(len(broken)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "if len(broken) == 0:\n",
+ " plot_city(cities, solution[\"c\"])"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.1"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/notebooks/japanese/graph_partition.ipynb b/notebooks/japanese/graph_partition.ipynb
new file mode 100644
index 00000000..d736289f
--- /dev/null
+++ b/notebooks/japanese/graph_partition.ipynb
@@ -0,0 +1,190 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%matplotlib inline\n",
+ "from pyqubo import Spin, Vector, Param, solve_ising, Const\n",
+ "import matplotlib.pyplot as plt\n",
+ "import networkx as nx"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## グラフ分割問題\n",
+ "\n",
+ "偶数の個数の頂点を持つグラフを2つに分割する。分割されるエッジが最小となる分割方法を見つけたい。\n",
+ "この問題はIsingモデルにより次のように定式化される。\n",
+ "\n",
+ "$$H(s) = H_{A}(s) + H_{B}(s)\\\\\n",
+ "H_{A}(s) = A \\left( \\sum_{i \\in V} s_{i}\\right )^2\\\\\n",
+ "H_{B}(s) = B \\sum_{(i, j) \\in E} \\frac{1-s_{i}s_{j}}{2}\n",
+ "$$\n",
+ "\n",
+ "$H_{A}(s)$は2つの集合の頂点数が同じになる制約。$H_{B}(s)$は切断されるエッジの個数である。"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def plot_graph(E, colors=None):\n",
+ " G = nx.Graph()\n",
+ " for (i, j) in E:\n",
+ " G.add_edge(i, j)\n",
+ " plt.figure(figsize=(4,4))\n",
+ " pos = nx.spring_layout(G)\n",
+ " nx.draw_networkx(G, pos, node_color=colors)\n",
+ " plt.axis(\"off\")\n",
+ " plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# エッジが以下のように与えられる\n",
+ "E = {(0, 6), (2, 4), (7, 5), (0, 4), (2, 0),\n",
+ " (5, 3), (2, 3), (2, 6), (4, 6), (1, 3),\n",
+ " (1, 5), (7, 1), (7, 3), (2, 5)}\n",
+ "plot_graph(E)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "ノード数と同じである$8$次元のスピンのスピンベクトル$s$を用意する。各スピンは対応するノードがどちらの集合に属するかを表している。"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# スピンベクトルの宣言\n",
+ "s = Vector(\"s\", 8, spin=True)\n",
+ "\n",
+ "# パラメータA, Bの宣言\n",
+ "A, B = Param(\"A\"), Param(\"B\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# ハミルトニアン H_{A}を定義\n",
+ "HA = A * Const(sum(s) ** 2, \"num_nodes\")\n",
+ "\n",
+ "# ハミルトニアン H_{B}を定義\n",
+ "HB = B * sum((1.0 - s[i]*s[j]) / 2.0 for (i, j) in E)\n",
+ "\n",
+ "H = HA + HB"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# モデルのコンパイル\n",
+ "model = H.compile()\n",
+ "\n",
+ "# A=1.0, B=1.0としてIsingモデルを得る\n",
+ "linear, quad, offset = model.to_ising(params={'A': 0.1, 'B':1.0})"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "#broken constraints: 1\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Isingモデルを解く\n",
+ "solution = solve_ising(linear, quad)\n",
+ "\n",
+ "# 解をデコードする\n",
+ "decoded_sol, broken = model.decode_solution(solution, var_type=\"binary\")\n",
+ "print(\"#broken constraints: {}\".format(len(broken)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAARYAAAD8CAYAAACy5YsfAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvIxREBQAAIABJREFUeJzt3Xl4VdXV+PHvOgkJQ0AGxQAqCBKc53keiKjYKsp1FrXqW20dXnFq+2udWvV1Hlq11hltqwbnATVqsTiLM04XUQHFCypzSEKSs35/7AOG5N7k3ptzbm6S9XmePA/mnrPPCshi7332XltUFWOMCZPX3gEYYzofSyzGmNBZYjHGhM4SizEmdJZYjDGhs8RijAmdJRZjTOgssRhjQmeJxRgTOkssxpjQWWIxxoTOEosxJnSWWIwxobPEYowJnSUWY0zoLLEYY0JnicUYEzpLLMaY0FliMcaErrC9A+gq/ERZEXA4cA6wIdAdqAY+A64HnvZK4w3tF6Ex4RErph0tP1FWCFwEnA0I0DvJZcuAWuAK4EavNG5/KKZDs8QSIT9R1gN4GtgZ6JnGLVXAU8DxXmm8PsrYjImSzbFExE+UFQCPALuQXlIB6AX8ErjdT5RJVLEZEzVLLNE5AdgT6NH4m7fcvZgdx8yhx9AvOensRLL7egJHAgdGH6Ix0bDEEoGgt/F7XA9kDYPWLeQP/9ufk47q01ITvYALIgrPmMhZYonGjsCgZB8cNraEQw8sYUC/Vn/rd/ITZRuGHpkxOWCJJRon0mQIlAUPNyQypsOxxBKNYbT997YIGNr2UIzJPUss0egeUjvpvk0yJq9YYonGTyG1syCkdozJKUss0XgRt9itmfp6pabGp6EBGhqgpsanvj7pIsVlwKtRBmlMVGzlbQT8RFlvYD5JJnAvvfYnLrtu4Rrfu+jc/lx83oCml/4ElNoKXNMRWWKJiJ8o+wdwEtlt9KwBrvBK438ONypjcsOGQtG5EliR6U3qMv1y4LbQIzImRyyxRMQrjX89/4f6w6prfD/de3xfdXmVcve/l0zwSuM/RhmfMVGyoVBERMQDHjxg3579n35g8HYiUgiUtHDLMmD5uBPn3fHk81XHAXuo6rycBGtMyCyxREREbgC2BcY0fD+yADgat/9nMNAAeCtXaoGqFhUXezOAq4BHvdJ4rYj8Djge2EtVrediOhxLLBEQkYnAycDuqrpo1feDzYlbA8OB3pWvVPW98M8/nvXBjNrhSdr4P2A/YD9VXZqj0I0JhSWWkInIkcC1wG6qOqeVawuBxcDgpslDRAS4FdgUOEBVqyMK2ZjQ2eRtiERkb+CvwNjWkgqAqtYDH+KGTE0/U+C3wLfAZBEpCjdaY6JjiSUkIrI58BBwlKp+lMGt04Edkn2gqj5up3QDMElECtoapzG5YIklBCKyHvAscI6qvpzh7dOB7VN9qKp1wBHAQOC2YIhkTF6zxNJGItIXmAL8TVX/lUUTLSYWAFWtAQ4BtgKutuRi8p1N3raBiBQDzwEfA2drFr+ZwfBmMTBUVRe2cm1/4BXgQVW9PIuQjckJ67FkKVgAdy+wEDcEyipDq2oD8B6wXRrXLgT2B04SkTOyeZ4xuWCJJXtXAesDxwXJoS1aHQ6toqrfA6OBC0VkQhufa0wk7IjVLIjIWcDBuLUqYawvmQ6MT/diVf1GRPYHXhaRZar6WAgxGBMa67FkSEQOxy3NP7C1OZEMpN1jWUVVPwPGAreLSHlIcRgTCpu8zYCI7IE73XB/Vf0gxHYFN1czSlUzKkcZxPQo8EtVfSOsmIxpC+uxpElENgUmA8eGmVRg9Srbd0ljAjfJvdNwGxYfF5GtwozLmGxZYkmDiAzGLYA7T1UrI3pMxsOhVVT1OeAMYIqIlIUalTFZsMnbVohIH9wCuNtV9f4IHzUd1/PIiqpWiEhv4AUR2TOdvUrGRMXmWFoQbPx7FogDv812rUqazxoGvKaqQ9rYzjnAacCeqjo/hNCMyViXSyx+oqwYOBw4FXe+cjfcytcXgFu90vhcWD2hOgnoDRwewlqVFgXP+wHYsq2V40TkUtwWgL1VdXEY8RmTiS6TWPxEWS/gIuD04Fu9m1xSCygwDbiwYNDMI4C9cYWWMi6KnQ0ReQ64RVWfamM7AtyA2zW9v6omPePImKh0iclbP1E2EHgbOAuXUJomFYBi3NGoo+vq9M0jDymZAPwiV0klkPUEbmPBkG0ibgj3WLCnyZic6fQ9Fj9RVoL7CzscN+xJ7z5fazxPDvZK4y9FFlwTInIocKqqjg2pvULgQdw/IEcEhaWMiVxXSCz3AEfR6KD2PiO+XOOa6hrl9BPX4ubLBza9fTkwyCuNL486Tlhd1+VdoDSsieKgt/Ik8D3wq6B4lDGR6tRDIT9R1pcmSQVg6ayNVn/N+2g4PboL4w9ONjoC4Nio42zkO9w8z/phNaiqtcBhwEbAjVbLxeRCp04suLKOLf4L/cgzyxm4dgF77Nw92cclwAVBdf3IBb2UUOZZmrRbhds0uQdwaZhtG5NMZ08svwV6tnTB/Q8v5fhYH1r4h3xdXOW2XAk9sQAEr53HAEeIyLlht29MY5195W1pSx/OnlvHK29Uc8f167Z0WT2wHhDq/qAWTMe9vQqdqi4IdkJPE5Elqnrnqs/KvZgA+wDH4H7eQty6mqeByZV+RW0UMZnOqbMnlhaPzHhg8jJ237EHG26Q+mVRfYMWzvqmbtCmg8TL0cTnu8D2IiJRrPRV1blBcpkqIstGy/jHcSt1zwP6Ar2Axt23scBt5V7sduDaSr/CVvOaVnXqt0J+omwR7i9LUhvv9g0XnNGPXx29Vso2li1vaDjm9MTyZ19c0R13xs+c4Gtuo1/PAeaqaihvj0TkW9yS/K/CaC/FM7YspOjF3ThgcTcpGkIrQ0ZgJbAE2LfSr5gRVVymc+jsiWUqsFeyz15/p5oxR37HvI+G07ukxammGmCjgkEzF+Le1qwPbNDoq/F/19BC4gHmpbOWREQeB/6lqg+3/lNmp9yL9azX+o88ZISX/nFFCiwFtqv0K2ZFFZvp+Dr7UOga3CmDzd4lT3p4KeMOKmktqSjwilca/y7Iv/Hgq5ngNe4AmieebRr9eqCIJEideOYAi/h5AjeyxAL8vVAKBzf95nSdylIWIsFoqJge7CoHrPpYcL+XL5R7sZGVfoWtiTFJdfYeSwFuYdg6WTaxHBjnlcZfDCMeEekGDCZ1j2cDXLL/Cfeq+1GaJ55vg3OGslbuxdYO2mv2jn26TmUQQxkiG7bUxDJgfKVf8UJb4jCdV6fusXil8QY/UfZH3Ia81uYQ1rCyTqmq8n/s17cg05MNUwpONZwdfCUV1H/ZArfb+l3cG5r9+DnxDBGRRaTu8cwBFrQy0XwyrazvaUUJcH4QozHNdOoeyyp+ouxvuMVyvdK8pW7lSl284Q5f1yUWNNwGXB5lLZZkRORrYIyqxpt838OtrUnV41kfWAu3ijdp4tmPw18UkUHJnjtdp1LFUgB60psRbEZ/abbVAdx80jB7S2SS6dQ9lkbOxM1dTMS9gm7p514OzC0qkn0SCxo84HFgSxE5sZ12Oq+RWIKeyPfB11vJbhSRHrieTuPEsyPuiJH1cXVokhrJFvSiDx4eCebyIa+zk46mp5Q0vbQWGApYYjHNdIkeyyp+omxr4Bwghlv4VoxbfVyHm6iN4w4ie9Qrja8EEJHuwD+AzYFDVHVuLmIVkQuBdVV1Ypjtlnux7qpaFfR8WvW+TmMAg9hANmr60RLg8Eq/Ime7v03H0VV6LAB4pfEPgBP8RNlZwC9wQ4oiXG/mNa80/nHTe1S1RkROwC0ge0tExqvq6zkIdzpwcQTt1ma+DzHlPz5L2xiL6aS6VI+lrUTkINx5zReo6r0RP6sfbk6kb9hlMcu92NfAsKbfr9OVLGUhfVkHQZjPt3zGu+zEaHpJszf2NcD6lX7Fj2HGZjqHLtVjaStVfVZE9gKeFJEtcQkmkuJJqrooWPMyCvg05OavB66kyWS2osziE6pYhiD0pDdbsWuypOIDL1hSMalYjyULItIfeAhoAI6KqmC1iPwbmKKqk8Jst9yLrYWb/O2RZRPLgYMq/Ypp4UVlOpPOXjYhEsGZzQfiJnvfEpFRET0qkhIKlX7FEuAuIJu3XHXAN8CrYcZkOhdLLFlS1XpVPQu4FleGYEwEj5mOq7QfhYm++h83aEPaXdZgrmcRMKbSr7CurknJhkIhCA5mfxi4GrgxxHq1fXBDlr7Bqt3QiEhxAYWV27P3BiWstbaItLh40NeGhnrqVxRJ8RaVfkXKlcPGgPVYQhEczL4zbnXvXWEdt6GqS3FvhjYNo71VgjUs9zVQv+AH5o0UkVOA93FDo8aT0YrbFzSvgYY/vcaUJS/q5I3DjMV0TtZjCZGIlAD34SrXHRbGEacicj8wVVXvamtbjdq8BpcIyxtvaCz3YpvjVueuhzsqJQFUAi9V+hUqIvvhfr4tg3kmY5KyxBKyoDdwEXAScKiqvt/G9s4GNlbV01u9OL32zsKdBrlbNslBRG4CBqrq0WHEYzonGwqFTFV9Vb0Et1L3BRGJtbHJ0N4MichhwIXAgW3ocfwe2EZEjgojJtM5WY8lQiKyDW4T433AJdnUzA0mVX8A+gVnBGUby25BLGNU9b1s2wna2gF4BthGVb9rS1umc7IeS4SCYdCOuHoqk4M5mEzbqAJm4TZBZiVYZ/MIcHxbk0oQ0zvALbiJajsAzTRjiSViwQTuvgQbHUVkWBbNvEOWwyERKQWmAL9X1eeyaSOFK3ClOEOZ+zGdiyWWHAiGMKcAdwNviMieGTaR1TxL0EN6GrhXVe/J9P6WBOtqjgcuE5GRYbZtOj5LLDmizk3ACUCFiJyawe0ZJ5agvu7DuIPW/pzJvelS1c9xR7beLyK2odWsZpO37UBEyoAncWtEzmlth3RQbGohMEBVq9NoX4A7cIW7Dwl71W6TZ3m42rdTVfUvUT3HdCzWY2kHQR3bnYGNgOeC3dItXV8DfA5smeYj/oQ7duSIKJMKrC6VeRJwtohsG+WzTMdhiaWdBKUWDsYNVd4WkdaW7U8voGDHci+2TrkX61fuxZL+2YnISbitBWPDOpmxNUG5znNwQ6JmR4qYrseGQnkgKH15DXCSqj7T+LNyL1YMjF+ptVd1o2iIiNTg/kHwcBOz1wKvB0vux+DWzOylql/k+GcQ3JzOHFU9N5fPNvnHEkueEJFdgMnATcA1qqrlXuw03I5pSHKaI66SWzWQmKMzL47z4Q24bQS5qMnbjIisDXwIHKuqU9sjBpMfLLHkERFZD3gC+HRfxv3gScGvSfOgtQat17l8edlM/fiSKGNsjYiMxS2e20pVl7RnLKb9WGLJMyLScyRbvLYeG21ZIAWZzoFVAbtX+hUfRBFbukTkdqBIVU9qzzhM+7G1B3lmtIwvUdWNk537M0PfZiELaKCeYrozlFFNz1juCdyDeyPUns4FPhSRQ1X18XaOxbQDeyuUf/5HRJJ2I4cxit05kH3kULZiV2Yxg6W6qPElApSVe7EtchJpCsHbqAnA30Vk3faMxbQPSyx5pNyLFQBnk6J6fomshScFwX8JIFTT7I1yEfC/kQWZJlV9Ddd7usM2KnY9lljyy9a4Y19T+lzf42V9jDd4nmK6M6D5McyFwOFRBZihS3DnO9tcSxdjiSW/rI17hZzSxrIt+3Ao27M36zAEL/kfYUm5F2v3XkKw+fI44CqRNSeDTOdmiSW/pPXnISL0lbWppZpvmZX0knDDyp6qfoxbi3OvyOpxnOnkLLHkl5/IICkoPtVUJfuoKs/O/bke93Od096BmNywxJJfPkz1wUqtIaFzqdd6VJWfNEGCufRnYNNLfeD5SKPMUHDQ2QnAhSKSdSU803FYYskjlX5FLfB3YGXzT4VvmcWrPMNUniDOR4xiK9aRwWtcFeyEvi4X8WZCVb8Gfgc8ICJF7R2PiZatvM0z5V5sKPAFrbwdSmWFLm94nefOAm4Pegp5I3jt/AQwQ1X/0N7xmOhYjyXPBMeX3gLJJ09aUb2UhacCMeBdEdk91ODaKDh69lTgVyKya3vHY6JjiSU/nY87XiOT5FINnPKxvnUPrnj3lcC/ReQBkSbjpXYUFBc/HZiUzakFpmOwxJKHKv0KHzgKuBmowSWNVJYBi4FxlX7Fv2B1fd2HgI2B2cBHInJ+vsxtqOpjwGu4WjKmE7I5ljxX7sXWAU4GJgK9+PnQ9mLgE+Aq4PFKvyLJhK8jIhsBN+JKYZ6tqu3+1khE1gI+Ak5T1SntHY8JlyWWDiLYR7QB0A+oAxZU+hUZHTovIgfjEszHwMTgTU27EZF9gftxh8z/1J6xmHBZYuligpq0E4OvW4CrVHVFO8ZzAzAEOFLtf8ZOw+ZYuhhVrVHVK4BtgU2AT0XksHbcgfwH3PGxR7fT800ErMfSxQXDkZuBecBZwSFkuY5hO9wxsNuq6re5fr4Jn/VYujhVfRlXce5ZYJqIXCsifXIcw7vAX4F7klXOMx2P/SEaVLVOVW/EDUn6A5+JyPE5Hh5dCfQBfpPDZ5qI2FDINCMiO+N6ECuBM1T1/Rw9twx4Hdgt1+cimXBZj8U0o6pvAjvhSktOEZHbRGRADp4bBy7GDpnv8CyxmKRU1VfVO3Fvjupxb49Oy0GxpttwK4ltk2IHZkMhkxYR2Qo3PCoBzgyKZUf1rCHA+8BBqjo9queY6FiPxaRFVT8E9sKdMf2QiEwSkWaVvEN61ne40wruF5GkJxaY/GaJxaQt2Nz4b9zmxnnAxyJyXkSbGx/E7SW6MoK2TcRsKGSyFrzFuQkYhltcVxly+/1xyWVCsN7GdBCWWEybBGtdfoHb3Pg+cK6qfhNi+wcAt+MOmV8cVrsmWjYUMm0SDI+eBDYFPsBVrrs4rLkRVX0Otyr45jDaM7lhicWEItjc+Gfc5sbNca+nDw1p9e55wC4iki8nPJpW2FDIREJE9sO9np6Lm39p00paEdkFeAzYWlUTIYRoImQ9FhMJVX0J2Ap4DnhNRK4Wkd5taO8N4C7gTjtkPv9ZYjGRCTY33oAbGg0EPheRY9uQGC7FFYU6OawYTTRsKGRyJhjO/A1YgVu9+0EWbWwOTAV2VNWvwo3QhMV6LCZnguHMjrg6t8+LyC3BWpVM2piBWzR3nx0yn78ssZicUtUGVf0HbnMjuNovv84wSdwANADnhh6gCYUNhUy7EpGtcW+PeuJqv7yR5n3DgHeA/VT1o8gCNFmxHotpV8E8y57A9cBkEblXRErTuO8b4ALcRsWszrk20bHEYtpdsHr3n7jNjQuAGSIyUUS6tXLrvcDXuLdFJo/YUMjkHREZhVvCvz7u7dFLLVw7EPgQiKnqqzkK0bTCEovJS8Fal0NwE7Xv4jY3zk5x7SG4odTWqroMwE+UCbAO7uTIeuAHrzS+NBexG0ssJs8FmxnPxxV+uhG4RlVrklx3N1Df8P3I84EJwT0DcQXBBXfW9ZvA1cAUrzTekJufoGuyxGI6hOAt0HW4M5D+F3iq8ZGs/foW9Jl4Wt/Zvzuzf8+CAqkDeqVoahlQDcS80vh/o42667LEYjoUESnHzb98A5ytqvFg2HNXQ4MeXVAg3dNsqho41iuNPxZRqF2aJRbT4QSlMM8Efg/cuXTWCHr19M4gdS8llWpgtFcafz3sGLs6e91sOhxVXamq1wFbjNqo24iCAi6kSVKprfU5ZeJ8Ntz+a9ba6Eu2HT2bKS9VNW2qB3BnjsLuUiyxmA5LVb//dNqwL7oVysqmn9U3wPqDC/nPY+uxKD6Cyy4cwFG//p5v5tY1vXSonyjbPjcRdx02FDIdlp8o64ZbUNc3neu33nc2f5rYn8MPXqMsTAPwkFcaPzaCELss67GYjmxr0vx/eP4P9cS/qmOzUc1W/xcAB4cdWFdnicV0ZAOAVrvcdXXK8b9NMCHWm41HJj0CqVfwZsmExBKL6dR8X5lwZoJu3YS/XjGwvcPpMgrbOwBj2uAnWvjHUVU5ZeJ8FvzQwNMPDKZbt+SdEt/X6sLBM22yMUTWYzEd2fu4fUBJ/ebCBXw+s44nJg2mR4/k/6s3NKhOfmp5oYjEReSvInKwiJREFXBXYW+FTIfmJ8ouxdVlWWPF7ey5dQzf8RuKi4XCRrXpbrt6IMce3qfxpStqavw9e204qwEYE3ztALwNPB98faT2FyUjllhMh+YnygYDs2iSWDIwwyuNb9H4G0GPZR9+TjQlwAu4JFOpqj9kH3HXYInFdHh+ouyPwO/IcEn/yjqt84Tdi9ab+XZL14nIcH5OMvsAM3FJ5jngTVVttuquq7PEYjq84FXxrcDxpJlcVLX62NMT8x56YvlzuJMa/XTuC/Yp7cLPiWYE8B+CYZOqfp3Fj9DpWGIxnUKQXM7FlalUkiQY31cUqgo8WQLECgbN/AR4CncM7InZ9DyCCnbluCSzP7CUn+dmpqrq8ix/pA7NEovpVPxEWQlwNHAhsAE/F3oq+uTz2u9uu2/Jq3+7cuAErzTuA4hIT6ACl4xiqlqd7bNFxMMdK7uqN7M97TQJXO7FtsTVrlkLd0DcXOClSr8i5Vu0MFliMZ1S0IPpgytNWQcsLBg0swx4AhjeeOgTFO2+D3d86y9VdUkYMQRnVe9NjiaBy71Yd2A8LqkOB3zcWrWG4KseV8vm9kq/IhHms5uyxGK6FBH5EDen8kqT73u44193Ag6I4s2PiIzg5ySzNxDn595MmyaBy73YCNzRs2sBvVu4tAaXcCZU+hWPZPu81lhiMV2KiJwLbKqqzQ6WDwp4/xn3r/7+qjonwjhCmwQOkso7uKSS7qLXFcDplX7FpEziTpclFtOliMgg4FNgiKquSHHNucBZuOTyRY7iWpc1J4GX4F5nr5oEblalCqDci/UCvgAGkflK+mpgdKVfEXoFPdsrZLoUVf1eRN4EDgX+leKa60RkETBVRMaq6ns5iGs+8ADwQJNJ4POAB0Uk1STwMbh6NEmTSkLn8hWfUsMKiunOpmxPP1ln1cc9gCtww7JQWY/FdDkicjRwgqoe0Mp1hwF/B8arartV9G80CXwALtn0IpgE3o/DLhfxhiW77yedz2e8yxbsRB/6U4s7NaW79Gh8WQ2waaVfEer6G9uEaLqiJ4CdRGRwSxep6qO4HsEjIjI2J5Elj2OZqj6lqr9V1Y2A3YG3BlB6mo8/LNV9X/EJG7IJa8kARITu0qNpUgGXA84IO2ZLLKbLCeZWViWN1q59EVdh7m4RafX6XFDVWap66zay+2SPgmaHtwXXsJRF1FHLazqFafoMn+v7NGizc9qKgL3CjtESi+mq7gNOCN4EtUhV3wL2A64Wkd9EHln61hKRZrU2AVZSg6LM5zu2Z292YjTLWMzXfJa0nbADs8RiuqpXcQvWtkrnYlWdAewJnCsif0wnIeVADSnq0Xi4WhHrM4Ji6UGRFLMBI/mRpOvikvZ62sISi+mSgpW39+POeU73nq9w8xtHAtfmQXL5jhRJoZsUUUwP3G4GR0gZ7tywA7PEYrqy+4FjgiX9aVHV73FzErsCd4lIey7ZeJIWlowMZhjf8iUrtYY6Xckc4qxDadPLluF2hofKEovpslR1JvAVbkFaJvctBEYD6wEPpZrniFqlX7Ect/Yl6XBoQzahD/14ned5g+fpTT+GsUnTy2qBKWHHZutYTJcmIqcB+6jqkVncW4z7i90POLQ9SiSUe7FNgem4xW6ZWgH8pdKvuDLcqKzHYszDwAEiktZpio2pai1wFPAN8KKIDAg5tlZV+hWfAn/MotxDLfAecG34UVliMV1cMKypBGJZ3t8AnApMA15pbdFdFF7UyffN5osVvvrNzrBOoRp4Fxhb6VdEUlbTEosxbk1L2m+Hmgr27VwA/BN4NSiPkBMi0h144ktm3OWJdyRuQ+IKXGmEppYBi3G9lH0q/YqlkcVlcyymqwveCn0H7KKqs9rY1mnAn4ADVfWjMOJr4Vke8G9c9btjVhWvKvdiOwATgR1Wau2GhXRLeOJ9BfwVeCyqXsoasVliMQZE5CZgkapeEkJbRwE34SZ032hrey085yrca+9yVU26nkVE5gK7RVlbJhkbChnjTAImhLHoTVUfBE4CnhSRjF5lp0tETseVfjg0VVJZdSmuR5NTlliMcd7DTWruFkZjqvosMA5XX2V8GG2uIiIHAxcBB6nqT61djiUWY9pHMAE7iTZM4iZp81Xc4rubRaRZKcxsiMh2wD24nko680GWWIxpZ/8Exos0L1qSLVX9AFek6U8icl5b2hKRYbhl/KcGO67Tug1LLMa0H1X9Fre+4xchtxvHbV48WUSuyGYeR0T6Ac8CV6vq45nciiUWY9pdm9a0pBIkrT1wQ6PbRKQg3XuDrQOPAi+o6k0ZPrpdEou9bjamERHpBXwLbBwUuA67/T644UwCmKCqLa6WDXo3k3B1bmPBSt9MnrcA2CKKn6Ul1mMxppHgmI0nSKNsZZbtLwUOxG0afDw44rUllwEjgeMyTSoBGwoZkydCfTvUVLBh8HDgR+CFVBsggzdJR+OOfU16BlIaLLEYkyemAgNEZMuoHqCq9cCJuPUzU4MDy1YTkTHA5bi1Kgva8ChLLMbkg2DPzQPA8Tl4ztnAY8A0ERkKICJbBc8fH7xRagtLLMbkkUnAsVGXnlTnUuAWXHLZG3gaOCNYYNdW7ZJY7IhVY5JQ1c+DDXz74Y41jfp5N4lINa42zK2q+lBITVuPxZg8E+kkbmNB6YbxuMRytIjsE1bTWGIxJq88BIwN1p5EJlircjuwEvgl7niRh0XkkDCaxxKLMflDVX8E/oPrSUTpj7iD045S1XpV/Q9wEHC7iLS1x2SJxZg8FOlwSESOB04GDm5c5V9V3wH2Af4iIme15RHYkn5j8kuwT+c7YHtV/SbktvfFlZbcR1U/TXHNUNy8yz+ByzTDv7AiUgWsm+ujSazHYkwLgiM+HgKOC7NdEdkMeBA3/EmaVILnz8ZtXhwH3BjUuc3oUdhQyJi8FFrZSgARGQQ8A0wM5lNaFGwg3BvYDrgnw7U1lliMyVNv447T2KmtDYlICW4B3J2q+kC696nqYlzJhYGPz21VAAAGjUlEQVTAI8GxH2k9EkssxuSfsMpWBj2Nh4D3cfuAMo1jBXAIrjbvs2m+BrfJW2PylYhsgNswOCSYd8n0fgFuBYbj3gBlfbZPUCTqFtzQ6MDgtTgAfqKsGPd6fCIwvLrG79u9WBaLyJfA9cAjXmk83RMTs2aJxZg0icjLwC2q+kgW916Iq/GyR1CTpa2xCK7XMw4ob/h+5PfAJbhNjQC9k9y2DNd7uR74i1caz6a+S3rxWWIxJj0iciIwTlUzWhEbHGB2NbBrUKIyzJjO795dzkh8PPzb3iXe1kBrhaPAHcH6CjDOK41n3PtKKy5LLMakR0R6A3OBkar6Q5r37AE8AoyO4shVP1Hmzfxq5fvrDS7cskf3jKZMq4EpwHivNB56ErDdzcakSVWXicjTwFHlXuxfwGBcD2EJMLvSr6hufL2IjAIm48pKRnWO89Ejhxc1O4R+4aIGTpk4n8pXVrB2/wIu/8MAjjlsjbneHsAYIAY8HHZQ1mMxJk3lXsybq19e0Ju+v+8raxcDtbg5Cw8oAO4Hbqz0Kz4PKsK9DlyuqndHFZOfKPsY2Lzp9485/Xt8H+68fl0+mFHLL46fx6tPrcdmo4qbXvqeVxrfLuy4LLEYk4ZyL7YJ8KyqDgB6p1grVwfU++pP+y9P9q+nfoqqXhRVTH6ibBvgVZrMq1St8Bmw8Sw++s9QykYUATDhjARDBhVy5f9bu2kz1cCOXml8Rpix2VDImFaUe7HtgZeBklZW33ZzXzp6Z8YsL6BwdMShHQc064LEZ62ksEBWJxWArTYr5pU3qpteCi7mY4A/hBmYLZAzpgXlXmzVJsDeuMVmrfKkwCume3E36Tal3ItF+Y/3UNwQbA3Lq5Q+vdf8q92nt8fy5X6yNgqDdkJlicWYll1G8jUhAKzQZbysjzJD317j+8Gu6C1whZtCJSLdRGRYYkH94GSfl/QSli5bM4ksW+5TUpLyr3u62wPSZkMhY1Io92J9gSNI0itY5XPepw/9Un1cAvwOdzxqWoKhVl9ggyZfQxv9eiCQePPdmuJDDyxp1kbZiCLqG5SZX61k5HA3HPrwk1o2G1XU7NpAW44XScoSizGpnYDbfJhUQudSSBG96E01Vaku27zci21S6Vd8Bqtr2w6heeJonEAUmA3MafT1YaNfz1PVej9RdixwG016VL16eow7qISLr/mJO65zb4WefL6KV59aL1l8y3BV8kJlicWY1A4nxUrWeq3jKz5hW/bkO75O2YCvfrc5zHxARGpwiWNdYD4uQaxKHh/hdjzPAeYEO5nTMRmXWJq55cqBnHzOfEo3/4oB/Qq45f/WSfaqGaABd65RqCyxGJPagFQfzOITBjOM7tKzxb3DghT2pu83wI383NvIegNiY15pvNZPlP0DOBNYY5zTv18Bj92bdAqmsVrgVq80Hko8a8QWdoPGdCJJN+kt08UsZAEbUNZqAyLiD5B1P1DVaao6O6yk0sj1kHoc1gIFlgM3hxuOYz0WY1JLOqm5iB+opopXeQYUGqhHUd7SpewkzZau1OAOf4+EVxqf5yfKRuM2FfYivVfiq5LKfl5pfH4UcVliMSa1+3FV49Z49TKEDVmX9Vf/92y+oIYVbMy2ydrwgKeiDNIrjb/nJ8p2AV7EzQmlfD2Om6xdlVQ+iyymqBo2phNIujmvQAoplu6rvwooxKOAImk2OarAfyv9ilBLJSQTLMnfAPgf4ANcaYQluESyBLd0/z3gFGBYlEkFbK+QMS0q92I3AqfTZHI0TVXAuEq/ojLcqFrnJ8o2Bkbiei9LgZleafyLXD3fhkLGtOwy4DBciYSUC+WSqAaexQ1Pcs4rjX8OfN4ezwbrsRjTqnIvtiGuBMIA3Ka91lQBbwBjK/2KyOvL5iObYzGmFZV+xde4s5Wn4d7ypEoWVbieyh3AAV01qYD1WIzJSLkXGwGcgZsE7QHU4+ZfvgWuBe6r9CuWtF+E+cESizFZKvdiPXCvd5dW+hWhr17tyCyxGGNCZ3MsxpjQWWIxxoTOEosxJnSWWIwxobPEYowJnSUWY0zoLLEYY0JnicUYEzpLLMaY0FliMcaEzhKLMSZ0lliMMaGzxGKMCZ0lFmNM6CyxGGNCZ4nFGBM6SyzGmNBZYjHGhM4SizEmdJZYjDGhs8RijAmdJRZjTOgssRhjQmeJxRgTOkssxpjQWWIxxoTOEosxJnSWWIwxobPEYowJnSUWY0zoLLEYY0JnicUYE7r/D88K8B6If00/AAAAAElFTkSuQmCC\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# グラフを色分けしてみる\n",
+ "plot_graph(E, [solution[k] for k in sorted(solution.keys())])"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.1"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/notebooks/japanese/integer_partition.ipynb b/notebooks/japanese/integer_partition.ipynb
new file mode 100644
index 00000000..e2d3a3a6
--- /dev/null
+++ b/notebooks/japanese/integer_partition.ipynb
@@ -0,0 +1,89 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pyqubo import Spin, solve_ising"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "整数集合が以下のように与えられる。値の和の差が0になるような二つの集合$A, B$に分割したい\n",
+ "\n",
+ "$S = \\{2, 4, 6\\}$\n",
+ "\n",
+ "スピン$s1,s2,s3 \\in \\{-1, 1\\}$を用意し、それぞれ、整数$2,4,6$がどちらの集合に属するかを表す。例えば、$s=1$のとき、その数は集合$A$に属し、$s=-1$のとき集合$B$に属する、と設定できる。"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# ハミルトニアンを記述\n",
+ "s1, s2, s3 = Spin(\"s1\"), Spin(\"s2\"), Spin(\"s3\")\n",
+ "H = (2*s1 + 4*s2 + 6*s3)**2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Isingモデルを得る\n",
+ "model = H.compile()\n",
+ "linear, quad, offset = model.to_ising()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'s1': -1, 's2': -1, 's3': 1}"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Isingモデルを解く\n",
+ "solution = solve_ising(linear, quad)\n",
+ "solution"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.1"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/pyqubo/__init__.py b/pyqubo/__init__.py
new file mode 100644
index 00000000..d4fea46d
--- /dev/null
+++ b/pyqubo/__init__.py
@@ -0,0 +1,28 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+from __future__ import absolute_import
+
+import pyqubo.constraint
+import pyqubo.core
+import pyqubo.func
+import pyqubo.logic
+import pyqubo.tensor
+import pyqubo.utils
+from pyqubo.constraint import *
+from pyqubo.core import *
+from pyqubo.func import *
+from pyqubo.logic import *
+from pyqubo.tensor import *
+from pyqubo.utils import *
diff --git a/pyqubo/constraint.py b/pyqubo/constraint.py
new file mode 100644
index 00000000..2380e1fd
--- /dev/null
+++ b/pyqubo/constraint.py
@@ -0,0 +1,142 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+from .core import Constraint, UserDefinedExpress, Qbit
+
+
+class NotConst(UserDefinedExpress):
+ """Constraint: Not(a) = b.
+
+ Args:
+ a (:class:`Express`): expression to be binary
+
+ b (:class:`Express`): expression to be binary
+
+ label (str): label to identify the constraint
+
+ Examples:
+ In this example, when the binary variables satisfy the constraint,
+ the energy is 0.0. On the other hand, when they break the constraint,
+ the energy is 1.0 > 0.0.
+
+ >>> from pyqubo import NotConst, Qbit
+ >>> a, b = Qbit('a'), Qbit('b')
+ >>> exp = NotConst(a, b, 'not')
+ >>> model = exp.compile()
+ >>> model.energy({'a': 1, 'b': 0}, var_type='binary')
+ 0.0
+ >>> model.energy({'a': 1, 'b': 1}, var_type='binary')
+ 1.0
+ """
+
+ def __init__(self, a, b, label):
+ express = Constraint(2 * a * b - a - b + 1, label=label)
+ super(NotConst, self).__init__(express)
+
+
+class AndConst(UserDefinedExpress):
+ """Constraint: AND(a, b) = c.
+
+ Args:
+ a (:class:`Express`): expression to be binary
+
+ b (:class:`Express`): expression to be binary
+
+ c (:class:`Express`): expression to be binary
+
+ label (str): label to identify the constraint
+
+ Examples:
+ In this example, when the binary variables satisfy the constraint,
+ the energy is 0.0. On the other hand, when they break the constraint,
+ the energy is 1.0 > 0.0.
+
+ >>> from pyqubo import AndConst, Qbit
+ >>> a, b, c = Qbit('a'), Qbit('b'), Qbit('c')
+ >>> exp = AndConst(a, b, c, 'and')
+ >>> model = exp.compile()
+ >>> model.energy({'a': 1, 'b': 0, 'c': 0}, var_type='binary')
+ 0.0
+ >>> model.energy({'a': 0, 'b': 1, 'c': 1}, var_type='binary')
+ 1.0
+ """
+
+ def __init__(self, a, b, c, label):
+ express = Constraint(a * b - 2 * (a + b) * c + 3 * c, label=label)
+ super(AndConst, self).__init__(express)
+
+
+class OrConst(UserDefinedExpress):
+ """Constraint: OR(a, b) = c.
+
+ Args:
+ a (:class:`Express`): expression to be binary
+
+ b (:class:`Express`): expression to be binary
+
+ c (:class:`Express`): expression to be binary
+
+ label (str): label to identify the constraint
+
+ Examples:
+ In this example, when the binary variables satisfy the constraint,
+ the energy is 0.0. On the other hand, when they break the constraint,
+ the energy is 1.0 > 0.0.
+
+ >>> from pyqubo import OrConst, Qbit
+ >>> a, b, c = Qbit('a'), Qbit('b'), Qbit('c')
+ >>> exp = OrConst(a, b, c, 'or')
+ >>> model = exp.compile()
+ >>> model.energy({'a': 1, 'b': 0, 'c': 1}, var_type='binary')
+ 0.0
+ >>> model.energy({'a': 0, 'b': 1, 'c': 0}, var_type='binary')
+ 1.0
+ """
+
+ def __init__(self, a, b, c, label):
+ express = Constraint(a * b + (a + b) * (1 - 2 * c) + c, label=label)
+ super(OrConst, self).__init__(express)
+
+
+class XorConst(UserDefinedExpress):
+ """Constraint: OR(a, b) = c.
+
+ Args:
+ a (:class:`Express`): expression to be binary
+
+ b (:class:`Express`): expression to be binary
+
+ c (:class:`Express`): expression to be binary
+
+ label (str): label to identify the constraint
+
+ Examples:
+ In this example, when the binary variables satisfy the constraint,
+ the energy is 0.0. On the other hand, when they break the constraint,
+ the energy is 1.0 > 0.0.
+
+ >>> from pyqubo import XorConst, Qbit
+ >>> a, b, c = Qbit('a'), Qbit('b'), Qbit('c')
+ >>> exp = XorConst(a, b, c, 'xor')
+ >>> model = exp.compile()
+ >>> model.energy({'a': 1, 'b': 0, 'c': 1, 'aux_xor': 0}, var_type='binary')
+ 0.0
+ >>> model.energy({'a': 0, 'b': 1, 'c': 0, 'aux_xor': 0}, var_type='binary')
+ 1.0
+ """
+
+ def __init__(self, a, b, c, label):
+ aux = Qbit("aux_"+label)
+ express = Constraint(2 * a * b - 2 * (a + b) * c - 4 * (a + b) * aux + 4 * aux * c + a + b + c + 4 * aux, label=label)
+ super(XorConst, self).__init__(express)
diff --git a/pyqubo/core/__init__.py b/pyqubo/core/__init__.py
new file mode 100644
index 00000000..61209a9a
--- /dev/null
+++ b/pyqubo/core/__init__.py
@@ -0,0 +1,19 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+from pyqubo.core.express import *
+from pyqubo.core.coefficient import *
+from pyqubo.core.model import *
+from pyqubo.core.binaryprod import *
+from pyqubo.core.paramprod import *
diff --git a/pyqubo/core/binaryprod.py b/pyqubo/core/binaryprod.py
new file mode 100644
index 00000000..afad632d
--- /dev/null
+++ b/pyqubo/core/binaryprod.py
@@ -0,0 +1,99 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+from operator import mul, xor
+from six.moves import reduce
+
+
+class BinaryProd(object):
+ """A product of binary variables.
+ This class is used as a key of dictionary when you represent a polynomial as a dictionary.
+
+ For example, a polynomial :math:`2ab + b + 2` is represented as
+
+ .. code-block:: python
+
+ {BinaryProd({'a', 'b'}): 2.0, BinaryProd({'b'}): 1.0, BinaryProd(set()): 2.0}
+
+ This class represents product of binary variables.
+ Since :math:`a=a^2=a^3=...` where :math:`a` is binary, a product of binary variables
+ can be represented by a set of unique variables.
+ For example, :math:`aabbc` can be simplified as :math:`abc`.
+ For this reason, this class contains unique binary variables as a set.
+
+ Note:
+ BinaryProd initialized with empty key corresponds to constant.
+
+ Args:
+ keys (set[label]): set of variable labels.
+ """
+
+ JOINT_SYMBOL = "*"
+ CONST_STRING = "const"
+
+ def __init__(self, keys):
+ assert isinstance(keys, set)
+ self.keys = keys
+ if keys:
+ self.cached_hash = reduce(xor, [hash(key) for key in self.keys])
+ self.string = self.JOINT_SYMBOL.join(sorted(self.keys))
+
+ # When :obj:`keys` is empty set, this object represents constant.
+ else:
+ self.cached_hash = 0
+ self.string = self.CONST_STRING
+
+ def __hash__(self):
+ return self.cached_hash
+
+ def __eq__(self, other):
+ if not isinstance(other, BinaryProd):
+ return False
+ else:
+ return self.keys == other.keys
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __repr__(self):
+ return self.string
+
+ def is_constant(self):
+ """Returns whether this is constant or not.
+
+ Returns:
+ bool
+ """
+ if self.string == self.CONST_STRING:
+ return True
+ else:
+ return False
+
+ def calc_product(self, dict_values):
+ """Returns the value of the product of binary variables.
+
+ Args:
+ dict_values (dict[label, float]): value of binary variable.
+
+ Returns:
+ float
+ """
+ if self.is_constant():
+ return 1.0
+ else:
+ return reduce(mul, [dict_values[key] for key in self.keys])
+
+ @staticmethod
+ def merge_term_key(term_key1, term_key2):
+ return BinaryProd(term_key1.keys | term_key2.keys)
diff --git a/pyqubo/core/coefficient.py b/pyqubo/core/coefficient.py
new file mode 100644
index 00000000..4ff99690
--- /dev/null
+++ b/pyqubo/core/coefficient.py
@@ -0,0 +1,68 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+
+class Coefficient:
+ """The value of QUBO as a function of :class:`Param`.
+
+ Energy of QUBO is defined as :math:`E(\\mathbf{x}) = \\sum_{ij} a_{ij}x_{i}x_{j}`.
+
+ If the expression contains :class:`Param`, QUBO in :class:`Model` is `half-compiled`.
+ `Half-compiled` means you need to specify the value of parameters to get the final QUBO.
+ Each coefficient :math:`a_{ij}` of half-compiled QUBO is defined as :class:`Coefficient`.
+ If you want to get the final value of :math:`a_{ij}`, you need to evaluate with `params`.
+
+ Args:
+ terms (dict[:class:`ParamProd`, float]): polynomial function of :class:`Param`.
+ The labels in :class:`ParamProd` corresponds to labels of :class:`Param`.
+
+ Example:
+
+ For example, a polynomial :math:`2ab+2` is represented as
+
+ >>> from pyqubo import Coefficient, ParamProd
+ >>> coeff = Coefficient({ParamProd({'a': 1, 'b': 1}): 2.0, ParamProd({}): 2.0})
+
+ If we specify the params as :math:`a=2, b=3`, then the evaluated value will be 14.0.
+
+ >>> coeff.eval(params={'a': 2, 'b': 3})
+ 14.0
+
+ """
+
+ def __init__(self, terms):
+ self.terms = terms
+
+ def eval(self, params):
+ """Returns evaluated value with `params`.
+
+ Args:
+ params (dict[str, float]): Parameters.
+
+ Returns:
+ float
+ """
+ if not params:
+ raise ValueError("No parameters are given. Specify the parameter.")
+ result = 0.0
+ for term, value in self.terms.items():
+ prod = value
+ for key, p in term.keys.items():
+ if key in params:
+ prod *= params[key] ** p
+ else:
+ raise ValueError("{key} is not specified in params. "
+ "Set the value of {key}".format(key=key))
+ result += prod
+ return result
diff --git a/pyqubo/core/express.py b/pyqubo/core/express.py
new file mode 100644
index 00000000..7ccbc0ef
--- /dev/null
+++ b/pyqubo/core/express.py
@@ -0,0 +1,818 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+import abc
+import copy
+from collections import defaultdict
+from operator import or_, xor
+from six.moves import reduce
+import dimod
+import six
+
+from .coefficient import Coefficient
+from .model import Model, CompiledQubo
+from .binaryprod import BinaryProd
+from .paramprod import ParamProd
+
+
+@six.add_metaclass(abc.ABCMeta)
+class Express:
+ """Abstract class of pyqubo expression.
+
+ All basic component class such as :class:`.Qbit`, :class:`.Spin` or :class:`.Add`
+ inherits :class:`.Express`.
+
+ .. graphviz::
+
+ digraph {
+ graph [size="2.5, 2.5"]
+ node [shape=rl]
+ add [label=AddList]
+ qbit_a [label="Qbit(a)"]
+ qbit_b [label="Qbit(b)"]
+ mul_1 [label="Mul"]
+ mul_2 [label="Mul"]
+ num_1 [label="Num(1)"]
+ num_2 [label="Num(2)"]
+ add -> num_1
+ add -> mul_1
+ mul_1 -> mul_2
+ mul_1 -> qbit_a
+ mul_2 -> qbit_b
+ mul_2 -> num_2
+ }
+
+ For example, an expression :math:`2ab+1` (where :math:`a, b` is :class:`Qbit` variable) is
+ represented by the binary tree above.
+
+ Note:
+ This class is an abstract class of all component of expressions.
+
+ Example:
+ We write mathematical expressions with objects such as :class:`Qbit` or :class:`Spin`
+ which inherit :class:`.Express`.
+
+ >>> from pyqubo import Qbit
+ >>> a, b = Qbit("a"), Qbit("b")
+ >>> 2*a*b + 1
+ (((Qbit(a)*Num(2))*Qbit(b))+Num(1))
+
+ """
+
+ CONST_TERM_KEY = BinaryProd(set())
+ PROD_SYM = '*'
+
+ def __init__(self):
+ pass
+
+ def __rmul__(self, other):
+ """It is called when `other(number) - self`"""
+ return self.__mul__(other)
+
+ def __mul__(self, other):
+ """It is called when `self * other(any object)`"""
+ return Mul(self, other)
+
+ def __rsub__(self, other):
+ """It is called when `other(number) - self`"""
+ return AddList([Mul(self, -1), other])
+
+ def __sub__(self, other):
+ """It is called when `self - other(any object)`"""
+ if other == 0.0:
+ return self
+ else:
+ if isinstance(other, Express):
+ return self.__add__(Mul(other, -1))
+ else:
+ return self.__add__(-other)
+
+ def __radd__(self, other):
+ """It is called when `other(number) + self`"""
+ return self.__add__(other)
+
+ def __add__(self, other):
+ """It is called when `self + other(any object)`"""
+ if other == 0.0:
+ return self
+ else:
+ return AddList([self, other])
+
+ def __pow__(self, order):
+ if int(order) == order and order >= 1:
+ return reduce(lambda a, b: Mul(a, b), order * [self])
+ else:
+ raise ValueError("Power of {} th order cannot be done.".format(order))
+
+ def __div__(self, other):
+ """It is called when `self / other(any object)`"""
+ if not isinstance(other, Express):
+ return Mul(self, other ** -1)
+ else:
+ raise ValueError("Expression cannot be divided by Expression.")
+
+ def __rdiv__(self, other):
+ """It is called when `other(number) / self`"""
+ raise ValueError("Expression cannot be divided by Expression.")
+
+ def __truediv__(self, other): # pragma: no cover
+ """division in Python3"""
+ return self.__div__(other)
+
+ def __rtruediv__(self, other): # pragma: no cover
+ """It is called when `other(number) / self`"""
+ return self.__rdiv__(other)
+
+ def __neg__(self):
+ """Negative value of expression."""
+ return Mul(self, Num(-1))
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ @abc.abstractmethod
+ def __repr__(self): # pragma: no cover
+ pass
+
+ @abc.abstractmethod
+ def __eq__(self, other): # pragma: no cover
+ pass
+
+ @abc.abstractmethod
+ def __hash__(self): # pragma: no cover
+ pass
+
+ def compile(self, strength=5.0):
+ """Returns the compiled :class:`Model`.
+
+ This method reduces the degree of the expression if the degree is higher than 2,
+ and convert it into :class:`.Model` which has information about QUBO.
+
+ Args:
+ strength (float): The strength of the reduction constraint.
+ Insufficient strength can result in the binary quadratic model
+ not having the same minimizations as the polynomial.
+
+ Returns:
+ :class:`Model`: The model compiled from the :class:`.Express`.
+
+ Examples:
+ In this example, there is a higher order term :math:`abcd`. It is decomposed as
+ [[``a*d``, ``c``], ``b``] hierarchically and converted into QUBO.
+ By calling :func:`to_qubo()` of the :obj:`model`, we get the resulting QUBO.
+
+ >>> from pyqubo import Qbit
+ >>> a, b, c, d = Qbit("a"), Qbit("b"), Qbit("c"), Qbit("d")
+ >>> model = (a*b*c + a*b*d).compile()
+ >>> pprint(model.to_qubo())
+ ({('a', 'a'): 0.0,
+ ('a', 'a*b'): -10.0,
+ ('a', 'b'): 5.0,
+ ('a*b', 'a*b'): 15.0,
+ ('a*b', 'b'): -10.0,
+ ('a*b', 'c'): 1.0,
+ ('a*b', 'd'): 1.0,
+ ('b', 'b'): 0.0,
+ ('c', 'c'): 0.0,
+ ('d', 'd'): 0.0},
+ 0.0)
+ """
+ def compile_param_if_express(val):
+ if isinstance(val, Express):
+ return val._compile_param()
+ else:
+ return val
+
+ # Constraint for AND(multiplier, multiplicand) = product
+ def binary_product(multiplier, multiplicand, product, weight):
+ return {
+ BinaryProd({product}): 3.0 * weight,
+ BinaryProd({multiplicand, product}): -2.0 * weight,
+ BinaryProd({multiplier, product}): -2.0 * weight,
+ BinaryProd({multiplier, multiplicand}): weight
+ }
+
+ # When the label contains PROD_SYM, elements of products should be sorted
+ # such that the resulting label is uniquely determined.
+ def normalize_label(label):
+ s = Express.PROD_SYM
+ if s in label:
+ return s.join(sorted(label.split(s)))
+ else:
+ return label
+
+ # Expand the expression to polynomial
+ expanded, const = Express._expand(self)
+
+ # Make polynomials quadratic
+ offset = 0.0
+ pubo = {}
+ for term_key, value in expanded.items():
+ if term_key.is_constant():
+ offset = value
+ else:
+ pubo[tuple(term_key.keys)] = value
+ bqm = dimod.make_quadratic(pubo, strength, dimod.BINARY)
+ bqm_qubo, bqm_offset = bqm.to_qubo()
+
+ # Extracts product constrains
+ product_consts = {}
+ for (a, b), v in bqm.info['reduction'].items():
+ prod = normalize_label(v['product'])
+ product_consts["AND({},{})={}".format(a, b, prod)]\
+ = binary_product(a, b, prod, strength)
+
+ # Normalize labels and compile values of the QUBO
+ compiled_qubo = {}
+ for (label1, label2), value in bqm_qubo.items():
+ norm_label1 = normalize_label(label1)
+ norm_label2 = normalize_label(label2)
+
+ # Sort the tuple of labels such that the created key is uniquely determined.
+ if norm_label2 > norm_label1:
+ label_key = (norm_label1, norm_label2)
+ else:
+ label_key = (norm_label2, norm_label1)
+ # Compile values of the QUBO
+ compiled_qubo[label_key] = compile_param_if_express(value)
+ compiled_qubo = CompiledQubo(compiled_qubo, compile_param_if_express(offset + bqm_offset))
+
+ # Merge structures
+ uniq_variables = Express._unique_vars(self)
+ structure = reduce(Express._merge_dict, [var.structure for var in uniq_variables])
+
+ return Model(compiled_qubo, structure, Express._merge_dict(const, product_consts))
+
+ def _compile_param(self):
+ expanded, _ = Express._expand_param(self)
+ return Coefficient(expanded)
+
+ @staticmethod
+ def _merge_dict(dict1, dict2):
+ dict1_copy = copy.copy(dict1)
+ dict1_copy.update(dict2)
+ return dict1_copy
+
+ @staticmethod
+ def _merge_dict_update(const1, const2):
+ const1.update(const2)
+ return const1
+
+ @staticmethod
+ def _unique_vars(exp):
+ if isinstance(exp, AddList):
+ return reduce(or_, [Express._unique_vars(term) for term in exp.terms])
+ elif isinstance(exp, Mul):
+ return Express._unique_vars(exp.left) | Express._unique_vars(exp.right)
+ elif isinstance(exp, Add):
+ return Express._unique_vars(exp.left) | Express._unique_vars(exp.right)
+ elif isinstance(exp, Param):
+ return set()
+ elif isinstance(exp, Num):
+ return set()
+ elif isinstance(exp, Constraint):
+ return Express._unique_vars(exp.child)
+ elif isinstance(exp, Qbit):
+ return {exp}
+ elif isinstance(exp, Spin):
+ return {exp}
+ elif isinstance(exp, UserDefinedExpress):
+ return Express._unique_vars(exp.express)
+ else:
+ raise TypeError("Unexpected input type {}.".format(type(exp))) # pragma: no cover
+
+ @staticmethod
+ def _merge_term(term1, term2):
+ if len(term1) < len(term2):
+ for k, v in term1.items():
+ term2[k] += v
+ r = term2
+ else:
+ for k, v in term2.items():
+ term1[k] += v
+ r = term1
+ return r
+
+ @staticmethod
+ def _expand_param(exp):
+ """Expand the parameter expression hierarchically into dict format."""
+
+ if isinstance(exp, AddList):
+ expanded, const = reduce(
+ lambda arg1, arg2:
+ (Express._merge_term(arg1[0], arg2[0]), Express._merge_dict_update(arg1[1], arg2[1])),
+ [Express._expand_param(term) for term in exp.terms])
+ return expanded, const
+
+ elif isinstance(exp, Mul):
+ left, left_const = Express._expand_param(exp.left)
+ right, right_const = Express._expand_param(exp.right)
+ expanded_terms = defaultdict(float)
+ for k1, v1 in left.items():
+ for k2, v2 in right.items():
+ merged_key = ParamProd.merge_term_key(k1, k2)
+ expanded_terms[merged_key] += v1 * v2
+ return expanded_terms, Express._merge_dict_update(left_const, right_const)
+
+ elif isinstance(exp, Param):
+ expanded_terms = defaultdict(float)
+ expanded_terms[ParamProd({exp.label: 1.0})] = 1.0
+ return expanded_terms, {}
+
+ elif isinstance(exp, Num):
+ terms = defaultdict(float)
+ terms[ParamProd({})] = exp.value
+ return terms, {}
+
+ else:
+ raise TypeError("Unexpected input type {}.".format(type(exp))) # pragma: no cover
+
+ @staticmethod
+ def _expand(exp):
+ """Expand the expression hierarchically into dict format.
+
+ For example, ``2*Qbit(a)*Qbit(b) + 1`` is represented as
+ dict format ``{BinaryProd(ab): 2.0, CONST_TERM_KEY: 1.0}``.
+
+ Let's see how this dict is created step by step.
+ First, focus on `2*Qbit(a)*Qbit(b)` in which each expression is expanded as
+ _expand(Num(2)) # => {CONST_TERM_KEY: 2.0}
+ _expand(Qbit("a")) # => {BinaryProd(a): 1.0}
+ _expand(Qbit("b")) # => {BinaryProd(b): 1.0}
+ respectively.
+
+ :class:`Mul` combines the expression in the following way
+ _expand(Mul(Num(2), Qbit("a"))) # => {BinaryProd(a): 2.0}
+ _expand(Mul(Qbit("b"), Mul(Num(2), Qbit("a")))) # => {BinaryProd(ab): 2.0}
+
+ Finally, :class:`Add` combines the ``{BinaryProd(ab): 2.0}`` and `{CONST_TERM_KEY: 1.0}`,
+ and we get the final form {BinaryProd(ab): 2.0, CONST_TERM_KEY: 1.0}
+
+ Args:
+ exp (:class:`Express`): Input expression.
+
+ Returns:
+ tuple(expanded_terms, constraints):
+ tuple of expanded_terms and constrains. Expanded expression takes the form
+ ``dict[:class:`BinaryProd`, value]``.
+ Constraints takes the form of ``dict[label, expanded_terms]``.
+
+ """
+
+ if isinstance(exp, Add):
+ left, left_const = Express._expand(exp.left)
+ right, right_const = Express._expand(exp.right)
+ result = Express._merge_term(right, left)
+ return result, Express._merge_dict_update(left_const, right_const)
+
+ elif isinstance(exp, AddList):
+ expanded, const = reduce(
+ lambda arg1, arg2:
+ (Express._merge_term(arg1[0], arg2[0]), Express._merge_dict_update(arg1[1], arg2[1])),
+ [Express._expand(term) for term in exp.terms])
+ return expanded, const
+
+ elif isinstance(exp, Mul):
+ left, left_const = Express._expand(exp.left)
+ right, right_const = Express._expand(exp.right)
+ expanded_terms = defaultdict(float)
+ for k1, v1 in left.items():
+ for k2, v2 in right.items():
+ merged_key = BinaryProd.merge_term_key(k1, k2)
+ expanded_terms[merged_key] += v1 * v2
+ return expanded_terms, Express._merge_dict_update(left_const, right_const)
+
+ elif isinstance(exp, Param):
+ expanded_terms = defaultdict(float)
+ expanded_terms[Express.CONST_TERM_KEY] = exp
+ return expanded_terms, {}
+
+ elif isinstance(exp, Constraint):
+ child, child_const = Express._expand(exp.child)
+ child_const[exp.label] = copy.copy(child)
+ return child, child_const
+
+ elif isinstance(exp, Num):
+ terms = defaultdict(float)
+ terms[Express.CONST_TERM_KEY] = exp.value
+ return terms, {}
+
+ elif isinstance(exp, Qbit):
+ terms = defaultdict(float)
+ terms[BinaryProd({exp.label})] = 1.0
+ return terms, {}
+
+ elif isinstance(exp, Spin):
+ terms = defaultdict(float)
+ terms[BinaryProd({exp.label})] = 2.0
+ terms[Express.CONST_TERM_KEY] = -1.0
+ return terms, {}
+
+ elif isinstance(exp, UserDefinedExpress):
+ return Express._expand(exp.express)
+
+ else:
+ raise TypeError("Unexpected input type {}.".format(type(exp))) # pragma: no cover
+
+
+class UserDefinedExpress(Express):
+ """User Defined Express.
+
+ User can define her/his own expression by inheriting :class:`UserDefinedExpress`.
+
+ Attributes:
+ express (Express): User can define an original expression by defining this member.
+
+ Example:
+ Define the :class:`LogicalAnd` class by inheriting :class:`UserDefinedExpress`.
+
+ >>> from pyqubo import UserDefinedExpress
+ >>> class LogicalAnd(UserDefinedExpress):
+ ... def __init__(self, bit_a, bit_b):
+ ... express = bit_a * bit_b
+ ... super(LogicalAnd, self).__init__(express)
+ """
+
+ def __init__(self, express):
+ assert isinstance(express, Express)
+ super(UserDefinedExpress, self).__init__()
+ self.express = express
+
+ def __hash__(self):
+ return hash(self.express)
+
+ def __eq__(self, other):
+ if not isinstance(other, self.__class__):
+ return False
+ else:
+ return self.express == other.express
+
+ def __repr__(self):
+ return "{}({})".format(self.__class__.__name__, self.express)
+
+
+class Param(Express):
+ """Parameter expression.
+
+ You can specify the value of the :class:`Param` when creating the QUBO.
+ By using :class:`Param`, you can change the value without compiling again.
+ This is useful when you need to update the strength of constraint gradually.
+
+ Args:
+ label (str): The label of the parameter.
+
+ Example:
+ The value of the parameter is specified when you call :func:`to_qubo`.
+
+ >>> from pyqubo import Qbit, Param
+ >>> x, y, a = Qbit('x'), Qbit('y'), Param('a')
+ >>> exp = a*x*y + 2*x
+ >>> pprint(exp.compile().to_qubo(params={'a': 3}))
+ ({('x', 'x'): 2.0, ('x', 'y'): 3.0, ('y', 'y'): 0.0}, 0.0)
+ >>> pprint(exp.compile().to_qubo(params={'a': 5}))
+ ({('x', 'x'): 2.0, ('x', 'y'): 5.0, ('y', 'y'): 0.0}, 0.0)
+ """
+
+ def __init__(self, label):
+ super(Param, self).__init__()
+ self.label = label
+
+ def __hash__(self):
+ return hash(self.label)
+
+ def __eq__(self, other):
+ if not isinstance(other, Param):
+ return False
+ else:
+ return self.label == other.label
+
+ def __repr__(self):
+ return "Param({})".format(self.label)
+
+
+class Constraint(Express):
+ """Constraint expression.
+
+ You can specify the constraint part in your expression.
+
+ Args:
+ child (:class:`Express`): The expression you want to specify as a constraint.
+
+ label (str): The label of the constraint. You can identify constraints by the label.
+
+ Example:
+ When the solution is broken, `decode_solution` can detect it.
+ In this example, we introduce a constraint :math:`a+b=1`.
+
+ >>> from pyqubo import Qbit, Constraint
+ >>> a, b = Qbit('a'), Qbit('b')
+ >>> exp = a + b + Constraint((a+b-1)**2, label="one_hot")
+ >>> model = exp.compile()
+ >>> sol, broken = model.decode_solution({'a': 1, 'b': 1}, var_type='binary')
+ >>> pprint(broken)
+ {'one_hot': {'penalty': 1.0, 'result': {'a': 1, 'b': 1}}}
+ >>> sol, broken = model.decode_solution({'a': 1, 'b': 0}, var_type='binary')
+ >>> pprint(broken)
+ {}
+ """
+
+ def __init__(self, child, label):
+ assert isinstance(label, str), "label should be string."
+ assert isinstance(child, Express), "child should be an Express instance."
+ super(Constraint, self).__init__()
+ self.child = child
+ self.label = label
+
+ def __hash__(self):
+ return hash(self.label) ^ hash(self.child)
+
+ def __eq__(self, other):
+ if not isinstance(other, Constraint):
+ return False
+ else:
+ return self.label == other.label and self.child == other.child
+
+ def __repr__(self):
+ return "Const({}, {})".format(self.label, repr(self.child))
+
+
+class Spin(Express):
+ """Spin variable i.e. {-1, 1}.
+
+ Args:
+ label (str): The label of a variable. A variable is identified by this label.
+
+ structure (dict/optional): Variable structure.
+
+ Example:
+ >>> from pyqubo import Spin
+ >>> a, b = Spin('a'), Spin('b')
+ >>> exp = 2*a*b + 3*a
+ >>> pprint(exp.compile().to_qubo())
+ ({('a', 'a'): 2.0, ('a', 'b'): 8.0, ('b', 'b'): -4.0}, -1.0)
+ """
+
+ def __init__(self, label, structure=None):
+ assert isinstance(label, str), "label should be string."
+ assert Express.PROD_SYM not in label, "label should not contain {}".format(Express.PROD_SYM)
+ super(Spin, self).__init__()
+ if not structure:
+ # if no structure is given
+ self.structure = {label: (label,)}
+ else:
+ self.structure = structure
+ self.label = label
+
+ def __repr__(self):
+ return "Spin({})".format(self.label)
+
+ def __hash__(self):
+ return hash(self.label)
+
+ def __eq__(self, other):
+ """Returns whether the label is same or not."""
+ if not isinstance(other, Spin):
+ return False
+ else:
+ return self.label == other.label
+
+
+class Qbit(Express):
+ """Binary variable i.e. {0, 1}.
+
+ Args:
+ label (str): The label of a variable. A variable is identified by this label.
+
+ structure (dict/optional): Variable structure.
+
+ Example:
+ >>> from pyqubo import Qbit
+ >>> a, b = Qbit('a'), Qbit('b')
+ >>> exp = 2*a*b + 3*a
+ >>> pprint(exp.compile().to_qubo())
+ ({('a', 'a'): 3.0, ('a', 'b'): 2.0, ('b', 'b'): 0.0}, 0.0)
+ """
+
+ def __init__(self, label, structure=None):
+ assert isinstance(label, str), "Label should be string."
+ assert Express.PROD_SYM not in label, "label should not contain {}".format(Express.PROD_SYM)
+ super(Qbit, self).__init__()
+ if not structure:
+ # if no structure is given
+ self.structure = {label: (label,)}
+ else:
+ self.structure = structure
+ self.label = label
+
+ def __repr__(self):
+ return "Qbit({})".format(self.label)
+
+ def __hash__(self):
+ return hash(self.label)
+
+ def __eq__(self, other):
+ """Returns whether the label is same or not."""
+ if not isinstance(other, Qbit):
+ return False
+ else:
+ return self.label == other.label
+
+
+class Mul(Express):
+ """Product of expressions.
+
+ Args:
+ left (:class:`Express`): An expression
+
+ right (:class:`Express`): An expression
+
+ Example:
+ You can multiply expressions with either the built-in operator or :class:`Mul`.
+
+ >>> from pyqubo import Qbit, Mul
+ >>> a, b = Qbit('a'), Qbit('b')
+ >>> a * b
+ (Qbit(a)*Qbit(b))
+ >>> Mul(a, b)
+ (Qbit(a)*Qbit(b))
+ """
+
+ def __init__(self, left, right):
+ super(Mul, self).__init__()
+ # When right is a number (non-Express). (Only right can be a number.)
+ if isinstance(left, Express) and not isinstance(right, Express):
+ self.left = left
+ self.right = Num(right)
+
+ # When both arguments are Express.
+ elif isinstance(left, Express) and isinstance(right, Express):
+ self.left = left
+ self.right = right
+ else:
+ raise ValueError("left should be an Express instance.")
+
+ def __repr__(self):
+ return "({}*{})".format(repr(self.left), repr(self.right))
+
+ def __hash__(self):
+ return hash(self.left) ^ hash(self.right)
+
+ def __eq__(self, other):
+ if not isinstance(other, Mul):
+ return False
+ elif self.left == other.left and self.right == other.right:
+ return True
+ elif self.right == other.left and self.left == other.right:
+ return True
+ else:
+ return False
+
+
+class Add(Express):
+ """Addition of expressions (deprecated).
+
+ Args:
+ left (:class:`Express`): An expression
+
+ right (:class:`Express`): An expression
+
+ Example:
+ You can add expressions with either the built-in operator or :class:`Add`.
+
+ >>> from pyqubo import Qbit, Add
+ >>> a, b = Qbit('a'), Qbit('b')
+ >>> a + b
+ (Qbit(a)+Qbit(b))
+ >>> Add(a, b)
+ (Qbit(a)+Qbit(b))
+
+ """
+
+ def __init__(self, left, right):
+ super(Add, self).__init__()
+ # When right is a number (non-Express). (Only right can be a number.)
+ if isinstance(left, Express) and not isinstance(right, Express):
+ self.left = left
+ self.right = Num(right)
+
+ # When both arguments are Express.
+ elif isinstance(left, Express) and isinstance(right, Express):
+ self.left = left
+ self.right = right
+ else:
+ raise ValueError("left should be an Express instance.")
+
+ def __hash__(self):
+ return hash(self.left) ^ hash(self.right)
+
+ def __repr__(self):
+ return "({}+{})".format(repr(self.left), repr(self.right))
+
+ def __eq__(self, other):
+ if not isinstance(other, Add):
+ return False
+ elif self.left == other.left and self.right == other.right:
+ return True
+ elif self.right == other.left and self.left == other.right:
+ return True
+ else:
+ return False
+
+
+class AddList(Express):
+ """Addition of a list of expressions.
+
+ Args:
+ terms (list[:class:`Express`]): a list of expressions
+
+ Example:
+ You can add expressions with either the built-in operator or :class:`AddList`.
+
+ >>> from pyqubo import Qbit, AddList
+ >>> a, b = Qbit('a'), Qbit('b')
+ >>> a + b
+ (Qbit(a)+Qbit(b))
+ >>> AddList([a, b])
+ (Qbit(a)+Qbit(b))
+ """
+
+ def __init__(self, terms):
+ super(AddList, self).__init__()
+ new_terms = []
+ for term in terms:
+ if isinstance(term, Express):
+ new_terms.append(term)
+ else:
+ new_terms.append(Num(term))
+ self.terms = new_terms
+
+ def __add__(self, other):
+ """ Override __add__().
+
+ To prevent a deep nested expression, __add__() returns AddList object
+ where the added expression object is appended to :obj:`terms`.
+
+ Returns:
+ :class:`AddList`
+ """
+ if other == 0.0:
+ return self
+ else:
+ if not isinstance(other, Express):
+ other = Num(other)
+ return AddList(self.terms + [other])
+
+ def __hash__(self):
+ return reduce(xor, [hash(term) for term in self.terms])
+
+ def __repr__(self):
+ return "({})".format("+".join([repr(e) for e in self.terms]))
+
+ def __eq__(self, other):
+ if not isinstance(other, AddList):
+ return False
+ else:
+ return set(self.terms) == set(other.terms)
+
+
+class Num(Express):
+ """Expression of number
+
+ Args:
+ value (float): the value of the number.
+
+ Example:
+ >>> from pyqubo import Qbit, Num
+ >>> a = Qbit('a')
+ >>> a + 1
+ (Qbit(a)+Num(1))
+ >>> a + Num(1)
+ (Qbit(a)+Num(1))
+ """
+ def __init__(self, value):
+ super(Num, self).__init__()
+ self.value = value
+
+ def __hash__(self):
+ return hash(self.value)
+
+ def __repr__(self):
+ return "Num({})".format(str(self.value))
+
+ def __eq__(self, other):
+ if not isinstance(other, Num):
+ return False
+ else:
+ return self.value == other.value
+
diff --git a/pyqubo/core/model.py b/pyqubo/core/model.py
new file mode 100644
index 00000000..a753b68f
--- /dev/null
+++ b/pyqubo/core/model.py
@@ -0,0 +1,380 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+from operator import or_
+import numpy as np
+import dimod
+from six.moves import reduce
+
+
+class Model:
+ """Model represents binary quadratic optimization problem.
+
+ By compiling :class:`Express` object, you get a :class:`Model` object.
+ It contains the information about QUBO (or equivalent Ising Model),
+ and it also has the function to decode the solution
+ into the original variable structure.
+
+ Note:
+ We do not need to create this object directly. Instead,
+ we get this by compiling `Express` objects.
+
+ Args:
+ compiled_qubo (:class:`.CompiledQubo`):
+ Half-compiled QUBO. If we want to get the final QUBO, we need to evaluate this QUBO
+ by passing :obj:`params`. See :func:`CompiledQubo.eval()`.
+
+ structure (`dict[label, Tuple(key1, key2, key3, ...)]`):
+ It defines the mapping of the variable used in :func:`decode_solution`.
+ A solution of `label` is mapped to
+ :obj:`decoded_solution[key1][key2][key3][...]`.
+ For more details, see :func:`decode_solution()`.
+
+ constraints (dict[label, polynomial_term]):
+ It contains constraints of the problem. `label` is each constraint name and
+ `polynomial_term` is corresponding polynomial which should be zero when the
+ constraint is satisfied.
+
+ Attributes:
+ variable_order (list):
+ The list of labels. The order is corresponds to the index of QUBO or Ising model.
+
+ index2label (dict[int, label]):
+ The dictionary which maps an index to a label.
+
+ label2index (dict[label, index]):
+ The dictionary which maps a label to an index.
+ """
+
+ def __init__(self, compiled_qubo, structure, constraints):
+ self.compiled_qubo = compiled_qubo
+ self.structure = structure
+ self.constraints = constraints
+ self.variable_order = sorted(self.compiled_qubo.variables)
+ self.index2label = dict(enumerate(self.variable_order))
+ self.label2index = {v: k for k, v in self.index2label.items()}
+
+ def __repr__(self):
+ from pprint import pformat
+ return "Model({}, structure={})".format(repr(self.compiled_qubo), str(pformat(self.structure)))
+
+ def _parse_solution(self, solution, var_type):
+ """Parse solutions.
+
+ Args:
+ solution (list[bit]/dict[label, bit]/dict[index, bit]):
+ The solution returned from solvers.
+
+ var_type (str):
+ Specify the variable type. "binary" or "spin".
+
+ Returns:
+ dict[label, bit]: dictionary of label and binary bit.
+ """
+ assert var_type in ("binary", "spin"), "var_type should be either 'binary' or 'spin'."
+
+ if isinstance(solution, list) or isinstance(solution, np.ndarray):
+ if len(self.variable_order) != len(solution):
+ raise ValueError("Illegal solution. Length of the solution is different from"
+ "that of self.variable_order.")
+ dict_solution = dict(zip(self.variable_order, solution))
+ elif isinstance(solution, dict):
+
+ if set(solution.keys()) == set(self.variable_order):
+ dict_solution = solution
+
+ elif set(solution.keys()) == set(range(len(self.variable_order))):
+ dict_solution = {self.index2label[index]: v for index, v in solution.items()}
+
+ else:
+ raise ValueError("Illegal solution. The keys of the solution"
+ " should be same as self.variable_order")
+ else:
+ raise TypeError("Unexpected type of solution.")
+
+ if var_type == "spin":
+ dict_solution = {k: (v + 1) / 2 for k, v in dict_solution.items()}
+
+ return dict_solution
+
+ def energy(self, solution, var_type, params=None):
+ """Returns energy of the solution.
+
+ Args:
+ solution (list[bit]/dict[label, bit]/dict[index, bit]):
+ The solution returned from solvers.
+
+ var_type (str):
+ Specify the variable type. "binary" or "spin".
+
+ params (dict[str, float]):
+ Specify the parameter values.
+
+ Returns:
+ float: energy of the solution.
+ """
+ dict_solution = self._parse_solution(solution, var_type)
+ qubo, offset = self.to_qubo(params=params)
+ s = 0.0
+ for (label1, label2), value in qubo.items():
+ s += dict_solution[label1] * dict_solution[label2] * value
+ return s + offset
+
+ def decode_solution(self, solution, var_type):
+ """Returns decoded solution.
+
+ Args:
+ solution (list[bit]/dict[label, bit]/dict[index, bit]):
+ The solution returned from solvers.
+
+ var_type (str):
+ Specify the input and output variable type. "binary" or "spin".
+
+ Returns:
+ tuple(dict, dict): Tuple of the decoded solution and broken constraints.
+ Structure of this dict is defined by :obj:`structure`.
+ """
+
+ def put_value_with_keys(dict_body, keys, value):
+ for key in keys[:-1]:
+ if key not in dict_body:
+ dict_body[key] = {}
+ dict_body = dict_body[key]
+ dict_body[keys[-1]] = value
+
+ def evaluate_constraint(constraint, dict_value):
+ e = 0.0
+ for term_key, value in constraint.items():
+ e += term_key.calc_product(dict_value) * value
+ return e
+
+ decoded_solution = {}
+
+ dict_bin_solution = self._parse_solution(solution, var_type)
+
+ for label, bit in dict_bin_solution.items():
+ if label in self.structure:
+ if var_type == "spin":
+ out_value = 2 * bit - 1
+ elif var_type == "binary":
+ out_value = bit
+ else: # pragma: no cover
+ raise ValueError("var_type should be either 'binary' or 'spin'.")
+ put_value_with_keys(decoded_solution, self.structure[label], out_value)
+
+ # Check satisfaction of constraints
+ broken_const = {}
+ for label, const in self.constraints.items():
+ energy = evaluate_constraint(const, dict_bin_solution)
+ if energy > 0.0:
+ result_value = {var: dict_bin_solution[var] for var in
+ reduce(or_, [k.keys for k in const.keys()])}
+ broken_const[label] = {"result": result_value, "penalty": energy}
+ elif energy < 0.0:
+ raise ValueError("The energy of the constraint \"{label}\" is {energy}."
+ "But an energy of constraints should not be negative."
+ .format(label=label, energy=energy))
+
+ return decoded_solution, broken_const
+
+ def to_qubo(self, index_label=False, params=None):
+ """Returns QUBO and energy offset.
+
+ Args:
+ index_label (bool):
+ If true, the keys of returned QUBO are indexed with a positive integer number.
+
+ params (dict[str, float]):
+ If the expression contains :class:`Param` objects,
+ you have to specify the value of them by :obj:`params`.
+
+ Returns:
+ tuple(QUBO, float): Tuple of QUBO and energy offset.
+ QUBO takes the form of ``dict[(label, label), value]``.
+
+ Examples:
+ This example creates the :obj:`model` from the expression, and
+ we get the resulting QUBO by calling :func:`model.to_qubo()`.
+
+ >>> from pyqubo import Qbit
+ >>> x, y, z = Qbit("x"), Qbit("y"), Qbit("z")
+ >>> model = (x*y + y*z + 3*z).compile()
+ >>> pprint(model.to_qubo())
+ ({('x', 'x'): 0.0,
+ ('x', 'y'): 1.0,
+ ('y', 'y'): 0.0,
+ ('y', 'z'): 1.0,
+ ('z', 'z'): 3.0},
+ 0.0)
+
+ If you want a QUBO which has index labels, specify the argument ``index_label=True``.
+ The mapping of the indices and the corresponding labels is
+ stored in :obj:`model.variable_order`.
+
+ >>> pprint(model.to_qubo(index_label=True))
+ ({(0, 0): 0.0, (0, 1): 1.0, (1, 1): 0.0, (1, 2): 1.0, (2, 2): 3.0}, 0.0)
+ >>> model.variable_order
+ ['x', 'y', 'z']
+
+ """
+
+ bqm = self.compiled_qubo.eval(params)
+ q, offset = bqm.to_qubo()
+
+ # Evaluate values of QUBO
+ qubo = {}
+ for (label1, label2), v in q.items():
+ if index_label:
+ i = self.label2index[label1]
+ j = self.label2index[label2]
+ else:
+ i = label1
+ j = label2
+ qubo[(i, j)] = v
+
+ return qubo, offset
+
+ def to_ising(self, index_label=False, params=None):
+ """Returns Ising Model and energy offset.
+
+ Args:
+ index_label (bool):
+ If true, the keys of returned Ising model are
+ indexed with a positive integer number.
+
+ params (dict[str, float]):
+ If the expression contains :class:`Param` objects,
+ you have to specify the value of them by :obj:`params`.
+
+ Returns:
+ tuple(linear, quadratic, float):
+ Tuple of Ising Model and energy offset. Where `linear` takes the form of
+ ``(dict[label, value])``, and `quadratic` takes the form of
+ ``dict[(label, label), value]``.
+
+ Examples:
+ This example creates the :obj:`model` from the expression, and
+ we get the resulting Ising model by calling :func:`model.to_ising()`.
+
+ >>> from pyqubo import Qbit
+ >>> x, y, z = Qbit("x"), Qbit("y"), Qbit("z")
+ >>> model = (x*y + y*z + 3*z).compile()
+ >>> pprint(model.to_ising())
+ ({'x': 0.25, 'y': 0.5, 'z': 1.75}, {('x', 'y'): 0.25, ('y', 'z'): 0.25}, 2.0)
+
+ If you want a Ising model which has index labels,
+ specify the argument ``index_label=True``.
+ The mapping of the indices and the corresponding labels is
+ stored in :obj:`model.variable_order`.
+
+ >>> pprint(model.to_ising(index_label=True))
+ ({0: 0.25, 1: 0.5, 2: 1.75}, {(0, 1): 0.25, (1, 2): 0.25}, 2.0)
+ >>> model.variable_order
+ ['x', 'y', 'z']
+
+ """
+
+ bqm = self.compiled_qubo.eval(params)
+ linear, quadratic, offset = bqm.to_ising()
+
+ # Construct linear
+ new_linear = {}
+ for label, v in linear.items():
+ if index_label:
+ i = self.label2index[label]
+ else:
+ i = label
+ new_linear[i] = v
+
+ # Construct quadratic
+ new_quadratic = {}
+ for (label1, label2), v in quadratic.items():
+ if index_label:
+ i = self.label2index[label1]
+ j = self.label2index[label2]
+ else:
+ i = label1
+ j = label2
+ new_quadratic[(i, j)] = v
+
+ return new_linear, new_quadratic, offset
+
+
+class CompiledQubo:
+ """Half-compiled QUBO.
+
+ Args:
+ qubo (dict[label, :class:`Coefficient`/float]): QUBO
+
+ offset (:class:`Coefficient`/float): Offset of QUBO
+
+ Attributes:
+ qubo (dict[label, :class:`Coefficient`/float]): QUBO
+
+ offset (:class:`Coefficient`/float): Offset of QUBO
+
+ This contains QUBO and the offset, but the value of the QUBO
+ has not been evaluated yet. To get the final QUBO, you need to
+ evaluate this QUBO by calling :func:`CompiledQubo.eval`.
+ """
+
+ def __init__(self, qubo, offset):
+ self.qubo = qubo
+ self.offset = offset
+
+ @property
+ def variables(self):
+ """Unique labels contained in keys of QUBO."""
+ return [i for (i, j) in self.qubo.keys() if i == j]
+
+ def eval(self, params):
+ """Returns evaluated QUBO.
+
+ Args:
+ params (dict[str, float]): Pass the value of the parameter.
+
+ Returns:
+ :class:`BinaryQuadraticModel`
+ """
+ evaluated_qubo = {}
+ for k, v in self.qubo.items():
+ evaluated_qubo[k] = CompiledQubo._eval_if_not_float(v, params)
+
+ evaluated_offset = CompiledQubo._eval_if_not_float(self.offset, params)
+
+ return dimod.BinaryQuadraticModel.from_qubo(
+ evaluated_qubo, evaluated_offset)
+
+ def __repr__(self):
+ from pprint import pformat
+ return "CompiledQubo({}, offset={})".format(pformat(self.qubo), self.offset)
+
+ @staticmethod
+ def _eval_if_not_float(v, params):
+ """ If v is not float (i.e. v is :class:`Express`), returns an evaluated value.
+
+ Args:
+ v (float/:class:`Coefficient`):
+ The value to be evaluated.
+
+ params (dict[str, float]):
+ Parameters for evaluation.
+
+ Returns:
+ float: Evaluated value of the input :obj:`v`:
+ """
+ if isinstance(v, float):
+ return v
+ else:
+ return v.eval(params)
diff --git a/pyqubo/core/paramprod.py b/pyqubo/core/paramprod.py
new file mode 100644
index 00000000..be984418
--- /dev/null
+++ b/pyqubo/core/paramprod.py
@@ -0,0 +1,102 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+from operator import mul, xor
+from six.moves import reduce
+import copy
+
+
+class ParamProd(object):
+ """A product of parameter variables.
+ This class is used as a key of dictionary when you represent a polynomial as a dictionary.
+
+ For example, a polynomial :math:`2a^2 b + 2` is represented as
+
+ .. code-block:: python
+
+ {ParamProd({'a': 2, 'b': 1}): 2.0, ParamProd({}): 2.0}
+
+ Note:
+ ParamProd initialized with empty key corresponds to constant.
+
+ Args:
+ keys (dict[label, int]):
+ dictionary with a key being label, a int value being order of power.
+ """
+
+ JOINT_SYMBOL = "*"
+ CONST_STRING = "const"
+
+ def __init__(self, keys):
+ assert isinstance(keys, dict)
+ self.keys = keys
+ if keys:
+ self.cached_hash = reduce(xor, [hash(k) ^ hash(v) for k, v in self.keys.items()])
+ self.is_const = False
+
+ # When :obj:`keys` is empty dict, this object represents constant.
+ else:
+ self.cached_hash = 0
+ self.is_const = True
+
+ def __hash__(self):
+ return self.cached_hash
+
+ def __eq__(self, other):
+ if not isinstance(other, ParamProd):
+ return False
+ else:
+ return self.keys == other.keys
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __repr__(self):
+ if self.is_constant():
+ return self.CONST_STRING
+ else:
+ return self.JOINT_SYMBOL.join(sorted(["{}^{}".format(k, v)
+ for k, v in self.keys.items()]))
+
+ def is_constant(self):
+ """Returns whether this is constant or not.
+
+ Returns:
+ bool
+ """
+ return self.is_const
+
+ def calc_product(self, dict_values):
+ """Returns the value of the product of binary variables.
+
+ Args:
+ dict_values (dict[label, float]): value of binary variable.
+
+ Returns:
+ float
+ """
+ if self.is_constant():
+ return 1.0
+ else:
+ return reduce(mul, [dict_values[k] ** v for k, v in self.keys.items()])
+
+ @staticmethod
+ def merge_term_key(term_key1, term_key2):
+ term_key1_copy = copy.copy(term_key1.keys)
+ for k, v in term_key2.keys.items():
+ if k in term_key1_copy:
+ term_key1_copy[k] += v
+ else:
+ term_key1_copy[k] = v
+ return ParamProd(term_key1_copy)
diff --git a/pyqubo/func.py b/pyqubo/func.py
new file mode 100644
index 00000000..964e3339
--- /dev/null
+++ b/pyqubo/func.py
@@ -0,0 +1,47 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+from .core import UserDefinedExpress
+
+
+class Sum(UserDefinedExpress):
+ """Define sum of the expressions over sequent indices.
+
+ Note:
+ Indices run from :obj:`start_index` to :obj:`end_index-1`.
+
+ Args:
+ start_index (int): index to start with.
+
+ end_index (int): index ends with end_index-1.
+
+ func (function): function which takes integer as an argument and returns :class:`Express`.
+
+ Example:
+ >>> from pyqubo import Sum, Vector
+ >>> x = Vector('x', n_dim=3)
+ >>> exp = (Sum(0, 3, lambda i: x[i]) - 1.0)**2
+ >>> pprint(exp.compile().to_qubo())
+ ({('x[0]', 'x[0]'): -1.0,
+ ('x[0]', 'x[1]'): 2.0,
+ ('x[0]', 'x[2]'): 2.0,
+ ('x[1]', 'x[1]'): -1.0,
+ ('x[1]', 'x[2]'): 2.0,
+ ('x[2]', 'x[2]'): -1.0},
+ 1.0)
+ """
+
+ def __init__(self, start_index, end_index, func):
+ express = sum(func(i) for i in range(start_index, end_index))
+ super(Sum, self).__init__(express)
diff --git a/pyqubo/logic.py b/pyqubo/logic.py
new file mode 100644
index 00000000..1542bbed
--- /dev/null
+++ b/pyqubo/logic.py
@@ -0,0 +1,91 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+from .core import UserDefinedExpress
+
+
+class Not(UserDefinedExpress):
+ """Logical NOT of input.
+
+ Args:
+ bit (:class:`Express`): expression to be binary
+
+ Examples:
+ >>> from pyqubo import Qbit, Not
+ >>> a = Qbit('a')
+ >>> exp = Not(a)
+ >>> model = exp.compile()
+ >>> for a in (0, 1):
+ ... print(a, int(model.energy({'a': a}, var_type='binary')))
+ 0 1
+ 1 0
+ """
+
+ def __init__(self, bit):
+ express = 1 - bit
+ super(Not, self).__init__(express)
+
+
+class And(UserDefinedExpress):
+ """Logical AND of inputs.
+
+ Args:
+ bit_a (:class:`Express`): expression to be binary
+
+ bit_b (:class:`Express`): expression to be binary
+
+ Examples:
+ >>> from pyqubo import Qbit, And
+ >>> import itertools
+ >>> a, b = Qbit('a'), Qbit('b')
+ >>> exp = And(a, b)
+ >>> model = exp.compile()
+ >>> for a, b in itertools.product(*[(0, 1)] * 2):
+ ... print(a, b, int(model.energy({'a': a, 'b': b}, var_type='binary')))
+ 0 0 0
+ 0 1 0
+ 1 0 0
+ 1 1 1
+ """
+
+ def __init__(self, bit_a, bit_b):
+ express = bit_a * bit_b
+ super(And, self).__init__(express)
+
+
+class Or(UserDefinedExpress):
+ """Logical OR of inputs.
+
+ Args:
+ bit_a (:class:`Express`): expression to be binary
+ bit_b (:class:`Express`): expression to be binary
+
+ Examples:
+
+ >>> from pyqubo import Qbit, Or
+ >>> import itertools
+ >>> a, b = Qbit('a'), Qbit('b')
+ >>> exp = Or(a, b)
+ >>> model = exp.compile()
+ >>> for a, b in itertools.product(*[(0, 1)] * 2):
+ ... print(a, b, int(model.energy({'a': a, 'b': b}, var_type='binary')))
+ 0 0 0
+ 0 1 1
+ 1 0 1
+ 1 1 1
+ """
+
+ def __init__(self, bit_a, bit_b):
+ express = Not(And(Not(bit_a), Not(bit_b)))
+ super(Or, self).__init__(express)
diff --git a/pyqubo/tensor.py b/pyqubo/tensor.py
new file mode 100644
index 00000000..115da207
--- /dev/null
+++ b/pyqubo/tensor.py
@@ -0,0 +1,114 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+from .core import Spin, Qbit
+
+
+class Matrix:
+ """Matrix of variables.
+
+ Args:
+ name (str): Name of the matrix. It is used as a part of the label of variables.
+ For example, if the matrix name is 'x',
+ the label of `(i, j)` th variable will be ``x[i][j]``.
+
+ n_row (int): Size of rows.
+
+ n_col (int): Size of columns.
+
+ spin (bool): If True, the element of the matrix is defined as :class:`Spin`.
+
+ Examples:
+ Create a binary matrix with a size of 2 * 3.
+
+ >>> from pyqubo import Matrix
+ >>> x = Matrix('x', 2, 3)
+ >>> x[0, 1] + x[1, 2]
+ (Qbit(x[0][1])+Qbit(x[1][2]))
+ """
+
+ def __init__(self, name, n_row, n_col, spin=False):
+ self.n_row = n_row
+ self.n_col = n_col
+ self.name = name
+ self.mat = {}
+ structure = self._create_structure()
+ for i in range(n_row):
+ for j in range(n_col):
+ if spin:
+ self.mat[(i, j)] = Spin(self._var_name(i, j), structure=structure)
+ else:
+ self.mat[(i, j)] = Qbit(self._var_name(i, j), structure=structure)
+
+ def __getitem__(self, key):
+ i, j = key
+ if i < 0 or j < 0 or i >= self.n_row or j >= self.n_col:
+ raise IndexError
+ return self.mat[(i, j)]
+
+ def _var_name(self, i, j):
+ return "{name}[{i}][{j}]".format(name=self.name, i=i, j=j)
+
+ def _create_structure(self):
+ structure = dict()
+ for i in range(self.n_row):
+ for j in range(self.n_col):
+ structure[self._var_name(i, j)] = (self.name, i, j)
+ return structure
+
+
+class Vector:
+ """Vector of variables.
+
+ Args:
+ name (str): Name of the vector. It is used as a part of the label of variables.
+ For example, if the vector name is 'x', the label of `i` th variable will be ``x[i]``.
+
+ n_dim (int): Size of the vector.
+
+ spin (bool): If True, the element of the vector is defined as :class:`Spin`.
+
+ Examples:
+ Create a binary vector with a size of 3.
+
+ >>> from pyqubo import Vector
+ >>> x = Vector('x', 3)
+ >>> x[0] + x[2]
+ (Qbit(x[0])+Qbit(x[2]))
+ """
+
+ def __init__(self, name, n_dim, spin=False):
+ self.n_dim = n_dim
+ self.vec = {}
+ self.name = name
+ structure = self._create_structure()
+ for i in range(n_dim):
+ if spin:
+ self.vec[i] = Spin(self._var_name(i), structure=structure)
+ else:
+ self.vec[i] = Qbit(self._var_name(i), structure=structure)
+
+ def __getitem__(self, i):
+ if i < 0 or i >= self.n_dim:
+ raise IndexError
+ return self.vec[i]
+
+ def _var_name(self, i):
+ return "{name}[{i}]".format(name=self.name, i=i)
+
+ def _create_structure(self):
+ structure = dict()
+ for i in range(self.n_dim):
+ structure[self._var_name(i)] = (self.name, i)
+ return structure
diff --git a/pyqubo/utils/__init__.py b/pyqubo/utils/__init__.py
new file mode 100644
index 00000000..d8c9f4be
--- /dev/null
+++ b/pyqubo/utils/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+from pyqubo.utils.solver import *
+from pyqubo.utils.asserts import *
diff --git a/pyqubo/utils/asserts.py b/pyqubo/utils/asserts.py
new file mode 100644
index 00000000..cefeb068
--- /dev/null
+++ b/pyqubo/utils/asserts.py
@@ -0,0 +1,42 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+
+def assert_qubo_equal(qubo1, qubo2):
+ """ Assert the given QUBOs are identical.
+
+ Args:
+ qubo1 (dict[(label, label), float]): QUBO to be compared.
+
+ qubo2 (dict[(label, label), float]): QUBO to be compared.
+ """
+
+ msg = "QUBO should be an dict instance."
+ assert isinstance(qubo1, dict), msg
+ assert isinstance(qubo2, dict), msg
+
+ assert len(qubo1) == len(qubo2), "Number of elements in QUBO doesn't match."
+
+ for (label1, label2), value in qubo1.items():
+ if (label1, label2) in qubo2:
+ if qubo2[label1, label2] != value:
+ assert qubo2[label1, label2] == value,\
+ "Value of {key} doesn't match".format(key=(label1, label2))
+ elif (label2, label1) in qubo2:
+ if qubo2[label2, label1] != value:
+ assert qubo2[label2, label1] == value, \
+ "Value of {key} doesn't match".format(key=(label2, label1))
+ else:
+ raise AssertionError("Key: {key} of qubo1 isn't contained in qubo2"
+ .format(key=(label1, label2)))
diff --git a/pyqubo/utils/solver.py b/pyqubo/utils/solver.py
new file mode 100644
index 00000000..08e2c313
--- /dev/null
+++ b/pyqubo/utils/solver.py
@@ -0,0 +1,69 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+import dimod
+import numpy as np
+
+
+def solve_qubo(qubo, num_reads=10, num_sweeps=1000, beta_range=(1.0, 50.0)):
+ """Solve QUBO with Simulated Annealing (SA) provided by dimod.
+
+ Args:
+ qubo (dict[(label, label), float]): The QUBO to be solved.
+
+ num_reads (int, default=10): Number of run repetitions of SA.
+
+ num_sweeps (int, default=1000): Number of iterations in each run of SA.
+
+ beta_range (tuple(float, float), default=(1.0, 50.0)): Tuple of start beta and end beta.
+
+ Returns:
+ dict[label, bit]: The solution of SA.
+ """
+ max_abs_value = float(max(abs(v) for v in qubo.values()))
+ scale_qubo = {k: float(v) / max_abs_value for k, v in qubo.items()}
+ sa = dimod.reference.SimulatedAnnealingSampler()
+ sa_computation = sa.sample_qubo(scale_qubo, num_reads=num_reads,
+ num_sweeps=num_sweeps, beta_range=beta_range)
+ best = np.argmin(sa_computation.record.energy)
+ best_solution = list(sa_computation.record.sample[best])
+ return dict(zip(sa_computation.variable_labels, best_solution))
+
+
+def solve_ising(linear, quad, num_reads=10, num_sweeps=1000, beta_range=(1.0, 50.0)):
+ """Solve Ising model with Simulated Annealing (SA) provided by dimod.
+
+ Args:
+ linear (dict[label, float]): The linear parameter of the Ising model.
+
+ quad (dict[(label, label), float]): The quadratic parameter of the Ising model.
+
+ num_reads (int, default=10): Number of run repetitions of SA.
+
+ num_sweeps (int, default=1000): Number of iterations in each run of SA.
+
+ beta_range (tuple(float, float), default=(1.0, 50.0)): Tuple of start beta and end beta.
+
+ Returns:
+ dict[label, bit]: The solution of SA.
+ """
+ max_abs_value = float(max(abs(v) for v in (list(quad.values()) + list(linear.values()))))
+ scale_linear = {k: float(v) / max_abs_value for k, v in linear.items()}
+ scale_quad = {k: float(v) / max_abs_value for k, v in quad.items()}
+ sa = dimod.reference.SimulatedAnnealingSampler()
+ sa_computation = sa.sample_ising(scale_linear, scale_quad, num_reads=num_reads,
+ num_sweeps=num_sweeps, beta_range=beta_range)
+ best = np.argmin(sa_computation.record.energy)
+ best_solution = list(sa_computation.record.sample[best])
+ return dict(zip(sa_computation.variable_labels, best_solution))
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..45ed2ddd
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,7 @@
+dimod==0.7.4
+numpy==1.15.1
+six==1.11.0
+coverage==4.5.1
+codecov==2.0.15
+nbsphinx==0.3.5
+ipykernel==4.9.0
diff --git a/setup.py b/setup.py
new file mode 100644
index 00000000..054f6ec0
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,24 @@
+from __future__ import absolute_import
+from setuptools import setup
+
+install_requires = ['dimod>=0.7.4',
+ 'numpy>=1.14.0,<2.0.0',
+ 'six>=1.10.0,<2.0.0']
+
+packages = ['pyqubo', 'pyqubo.core', 'pyqubo.utils']
+
+python_requires = '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*'
+
+setup(
+ name='PyQUBO',
+ version='0.0.1',
+ author='Recruit Communications Co., Ltd.',
+ description='PyQUBO allows you to create QUBOs or Ising models'
+ 'from mathematical expressions.',
+ download_url='https://github.com/recruit-communications/pyqubo',
+ license='Apache 2.0',
+ packages=packages,
+ install_requires=install_requires,
+ include_package_data=True,
+ python_requires=python_requires
+)
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 00000000..ba399266
--- /dev/null
+++ b/test/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
diff --git a/test/test_asserts.py b/test/test_asserts.py
new file mode 100644
index 00000000..b619cfd4
--- /dev/null
+++ b/test/test_asserts.py
@@ -0,0 +1,31 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+import unittest
+from pyqubo import assert_qubo_equal
+
+
+class TestAsserts(unittest.TestCase):
+
+ def test_qubo_equal(self):
+ qubo1 = {('a', 'b'): 1.0}
+ qubo2 = {('b', 'a'): 1.0}
+ qubo3 = {('a', 'b'): 2.0}
+ qubo4 = {('b', 'a'): 2.0}
+ qubo5 = {('c', 'a'): 1.0}
+
+ assert_qubo_equal(qubo1, qubo2)
+ self.assertRaises(AssertionError, lambda: assert_qubo_equal(qubo1, qubo3))
+ self.assertRaises(AssertionError, lambda: assert_qubo_equal(qubo1, qubo4))
+ self.assertRaises(AssertionError, lambda: assert_qubo_equal(qubo1, qubo5))
diff --git a/test/test_binaryprod.py b/test/test_binaryprod.py
new file mode 100644
index 00000000..0d4a53f3
--- /dev/null
+++ b/test/test_binaryprod.py
@@ -0,0 +1,59 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+import unittest
+
+from pyqubo import BinaryProd
+
+
+class TestBinaryProd(unittest.TestCase):
+
+ def test_equality(self):
+ term_key1 = BinaryProd({"a", "b"})
+ term_key2 = BinaryProd({"a", "b"})
+ term_key3 = BinaryProd({"a", "b", "c"})
+ self.assertTrue(term_key1 == term_key2)
+ self.assertTrue(term_key1 != term_key3)
+ self.assertTrue(term_key1 != 1)
+
+ def test_equality_const(self):
+ term_key1 = BinaryProd(set())
+ term_key2 = BinaryProd(set())
+ term_key3 = BinaryProd({"a"})
+ self.assertTrue(term_key1.is_constant())
+ self.assertEqual(term_key1, term_key2)
+ self.assertNotEqual(term_key1, term_key3)
+
+ def test_merge(self):
+ term_key1 = BinaryProd({"a", "b"})
+ term_key2 = BinaryProd({"a", "b"})
+ term_key3 = BinaryProd({"b", "c"})
+ term_key4 = BinaryProd({"a", "b", "c"})
+ self.assertEqual(term_key1, BinaryProd.merge_term_key(term_key1, term_key2))
+ self.assertEqual(term_key4, BinaryProd.merge_term_key(term_key2, term_key3))
+
+ def test_evaluate(self):
+ term_key1 = BinaryProd({"a", "b"})
+ empty_key = BinaryProd(set())
+ dict_value = {"a": 3.0, "b": 5.0}
+ prod = term_key1.calc_product(dict_value)
+ expected_prod = 15
+ self.assertEqual(expected_prod, prod)
+ self.assertEqual(1, empty_key.calc_product({}))
+
+ def test_repr(self):
+ term_key1 = BinaryProd({"a", "b"})
+ empty_key = BinaryProd(set())
+ self.assertIn(repr(term_key1), "a*b")
+ self.assertEqual(repr(empty_key), "const")
diff --git a/test/test_coefficient.py b/test/test_coefficient.py
new file mode 100644
index 00000000..59e77741
--- /dev/null
+++ b/test/test_coefficient.py
@@ -0,0 +1,24 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+import unittest
+from pyqubo import Coefficient, ParamProd
+
+
+class TestCoefficient(unittest.TestCase):
+
+ def test_coefficient_exception(self):
+ coeff = Coefficient({ParamProd({'a': 1, 'b': 1}): 2.0, ParamProd({}): 2.0})
+ self.assertRaises(ValueError, lambda: coeff.eval({}))
+ self.assertRaises(ValueError, lambda: coeff.eval({'a': 1.0}))
diff --git a/test/test_constraint.py b/test/test_constraint.py
new file mode 100644
index 00000000..c408b402
--- /dev/null
+++ b/test/test_constraint.py
@@ -0,0 +1,76 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+import unittest
+
+from pyqubo import Qbit, AndConst, OrConst, XorConst, NotConst
+
+
+class TestConstraint(unittest.TestCase):
+
+ def test_not(self):
+ a, b = Qbit("a"), Qbit("b")
+ exp = NotConst(a, b, label="not")
+ model = exp.compile()
+ self.assertTrue(model.energy({"a": 1, "b": 0}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 0, "b": 1}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 1, "b": 1}, var_type="binary") > 0)
+ self.assertTrue(model.energy({"a": 0, "b": 0}, var_type="binary") > 0)
+
+ def test_and(self):
+ a, b, c = Qbit("a"), Qbit("b"), Qbit("c")
+ exp = AndConst(a, b, c, label="and")
+ model = exp.compile()
+ self.assertTrue(model.energy({"a": 1, "b": 1, "c": 1}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 1, "b": 0, "c": 0}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 0, "b": 1, "c": 0}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 0, "b": 0, "c": 0}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 1, "b": 1, "c": 0}, var_type="binary") > 0)
+ self.assertTrue(model.energy({"a": 0, "b": 0, "c": 1}, var_type="binary") > 0)
+ self.assertTrue(model.energy({"a": 1, "b": 0, "c": 1}, var_type="binary") > 0)
+ self.assertTrue(model.energy({"a": 0, "b": 1, "c": 1}, var_type="binary") > 0)
+
+ def test_or(self):
+ a, b, c = Qbit("a"), Qbit("b"), Qbit("c")
+ exp = OrConst(a, b, c, label="or")
+ model = exp.compile()
+ self.assertTrue(model.energy({"a": 1, "b": 1, "c": 1}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 1, "b": 0, "c": 1}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 0, "b": 1, "c": 1}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 0, "b": 0, "c": 0}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 1, "b": 1, "c": 0}, var_type="binary") > 0)
+ self.assertTrue(model.energy({"a": 1, "b": 0, "c": 0}, var_type="binary") > 0)
+ self.assertTrue(model.energy({"a": 0, "b": 1, "c": 0}, var_type="binary") > 0)
+ self.assertTrue(model.energy({"a": 0, "b": 0, "c": 1}, var_type="binary") > 0)
+
+ def test_xor(self):
+ a, b, c = Qbit("a"), Qbit("b"), Qbit("c")
+ exp = XorConst(a, b, c, label="xor")
+ model = exp.compile()
+ self.assertTrue(model.energy({"a": 1, "b": 1, "c": 0, "aux_xor": 1}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 1, "b": 0, "c": 1, "aux_xor": 0}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 0, "b": 1, "c": 1, "aux_xor": 0}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 1, "b": 1, "c": 0, "aux_xor": 1}, var_type="binary") == 0)
+ self.assertTrue(model.energy({"a": 0, "b": 0, "c": 1, "aux_xor": 1}, var_type="binary") > 0)
+ self.assertTrue(model.energy({"a": 1, "b": 1, "c": 1, "aux_xor": 1}, var_type="binary") > 0)
+
+ def test_equality(self):
+ xor1 = XorConst(Qbit("a"), Qbit("b"), Qbit("c"), label="xor")
+ xor2 = XorConst(Qbit("a"), Qbit("b"), Qbit("c"), label="xor")
+ xor3 = XorConst(Qbit("b"), Qbit("c"), Qbit("a"), label="xor")
+ or1 = OrConst(Qbit("a"), Qbit("b"), Qbit("c"), label="xor")
+ self.assertTrue(xor1 + 1 == xor2 + 1)
+ self.assertTrue(xor1 == xor2)
+ self.assertFalse(xor1 == or1)
+ self.assertFalse(xor1 == xor3)
diff --git a/test/test_express_compile.py b/test/test_express_compile.py
new file mode 100644
index 00000000..2e67453f
--- /dev/null
+++ b/test/test_express_compile.py
@@ -0,0 +1,214 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+import unittest
+
+from pyqubo import Qbit, Spin, Param, Constraint, Vector, Matrix, Add, Sum
+from pyqubo import assert_qubo_equal
+
+
+class TestExpressCompile(unittest.TestCase):
+
+ def compile_check(self, exp, expected_qubo, expected_offset, expected_structure,
+ params=None):
+ model = exp.compile(strength=5)
+ qubo, offset = model.to_qubo(params=params)
+ assert_qubo_equal(qubo, expected_qubo)
+ self.assertEqual(qubo, expected_qubo)
+ self.assertEqual(offset, expected_offset)
+ self.assertEqual(model.structure, expected_structure)
+
+ def test_compile(self):
+ a, b = Qbit("a"), Qbit("b")
+ exp = 1 + a*b + a - 2
+ expected_qubo = {('a', 'a'): 1.0, ('a', 'b'): 1.0, ('b', 'b'): 0.0}
+ expected_offset = -1
+ expected_structure = {'a': ('a',), 'b': ('b',)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure)
+
+ def test_compile_expand(self):
+ a, b = Qbit("a"), Qbit("b")
+ exp = (a+b)*(a-b)
+ expected_qubo = {('a', 'a'): 1.0, ('a', 'b'): 0.0, ('b', 'b'): -1.0}
+ expected_offset = 0.0
+ expected_structure = {'a': ('a',), 'b': ('b',)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure)
+
+ def test_compile_spin(self):
+ a, b = Qbit("a"), Spin("b")
+ exp = a * b
+ expected_qubo = {('a', 'a'): -1.0, ('a', 'b'): 2.0, ('b', 'b'): 0.0}
+ expected_offset = 0.0
+ expected_structure = {'a': ('a',), 'b': ('b',)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure)
+
+ def test_compile_2nd_order(self):
+ a, b = Qbit("a"), Qbit("b")
+ exp = (Add(a, b)-3)**2
+ expected_qubo = {('a', 'a'): -5.0, ('a', 'b'): 2.0, ('b', 'b'): -5.0}
+ expected_offset = 9.0
+ expected_structure = {'a': ('a',), 'b': ('b',)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure)
+
+ def test_compile_3rd_order(self):
+ a, b = Qbit("a"), Qbit("b")
+ exp = (a+b-2)**3
+ expected_qubo = {('a', 'a'): 7.0, ('a', 'b'): -6.0, ('b', 'b'): 7.0}
+ expected_offset = -8.0
+ expected_structure = {'a': ('a',), 'b': ('b',)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure)
+
+ def test_compile_reduce_degree(self):
+ a, b, c, d = Qbit("a"), Qbit("b"), Qbit("c"), Qbit("d")
+ exp = a * b * c + b * c * d
+ expected_qubo = {
+ ('a', 'a'): 0.0,
+ ('a', 'b*c'): 1.0,
+ ('b', 'b'): 0.0,
+ ('b', 'b*c'): -10.0,
+ ('b', 'c'): 5.0,
+ ('c', 'c'): 0.0,
+ ('b*c', 'c'): -10.0,
+ ('b*c', 'b*c'): 15.0,
+ ('b*c', 'd'): 1.0,
+ ('d', 'd'): 0.0}
+ expected_offset = 0.0
+ expected_structure = {'a': ('a',), 'b': ('b',), 'c': ('c',), 'd': ('d',)}
+
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure)
+
+ def test_compile_div(self):
+ a, b = Qbit("a"), Qbit("b")
+ exp = (a+b-2)/2
+ expected_qubo = {('a', 'a'): 0.5, ('b', 'b'): 0.5}
+ expected_offset = -1
+ expected_structure = {'a': ('a',), 'b': ('b',)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure)
+
+ def test_compile_param(self):
+ a, b, w, v = Qbit("a"), Qbit("b"), Param("w"), Param("v")
+ exp = w*(a+b-2) + v
+ expected_qubo = {('a', 'a'): 3.0, ('b', 'b'): 3.0}
+ expected_offset = -1
+ expected_structure = {'a': ('a',), 'b': ('b',)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure,
+ params={"w": 3.0, "v": 5.0})
+
+ def test_compile_param2(self):
+ a, b, w, v = Qbit("a"), Qbit("b"), Param("w"), Param("v")
+ exp = v*w*(a+b-2) + v
+ expected_qubo = {('a', 'a'): 15.0, ('b', 'b'): 15.0}
+ expected_offset = -25
+ expected_structure = {'a': ('a',), 'b': ('b',)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure,
+ params={"w": 3.0, "v": 5.0})
+
+ def test_compile_param3(self):
+ a, v = Qbit("a"), Param("v")
+ exp = v*v*a + v
+ expected_qubo = {('a', 'a'): 25.0}
+ expected_offset = 5
+ expected_structure = {'a': ('a',)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure,
+ params={"v": 5.0})
+
+ def test_compile_const(self):
+ a, b, w = Qbit("a"), Qbit("b"), Param("w")
+ exp = Constraint(Constraint(w * (a + b - 1), label="const1") + Constraint((a + b - 1) ** 2, label="const2"),
+ label="const_all")
+ expected_qubo = {('a', 'a'): 2.0, ('a', 'b'): 2.0, ('b', 'b'): 2.0}
+ expected_offset = -2.0
+ expected_structure = {'a': ('a',), 'b': ('b',)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure,
+ params={"w": 3.0})
+
+ def test_compile_vector(self):
+ x = Vector("x", n_dim=5)
+ a = Qbit("a")
+ exp = x[1] * (x[0] + 2*a + 1)
+ expected_qubo = {
+ ('a', 'a'): 0.0,
+ ('a', 'x[1]'): 2.0,
+ ('x[0]', 'x[0]'): 0.0,
+ ('x[0]', 'x[1]'): 1.0,
+ ('x[1]', 'x[1]'): 1.0}
+ expected_offset = 0.0
+ expected_structure = {
+ 'a': ('a',),
+ 'x[0]': ('x', 0),
+ 'x[1]': ('x', 1),
+ 'x[2]': ('x', 2),
+ 'x[3]': ('x', 3),
+ 'x[4]': ('x', 4)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure)
+
+ def test_compile_vector_spin(self):
+ x = Vector("x", n_dim=2, spin=True)
+ exp = x[1] * x[0] + x[0]
+ expected_qubo = {('x[1]', 'x[1]'): -2.0, ('x[0]', 'x[0]'): 0.0, ('x[0]', 'x[1]'): 4.0}
+ expected_offset = 0.0
+ expected_structure = {
+ 'x[0]': ('x', 0),
+ 'x[1]': ('x', 1)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure)
+
+ def test_compile_matrix(self):
+ x = Matrix("x", n_row=2, n_col=2)
+ a = Qbit("a")
+ exp = x[0, 1] * (x[1, 1] + 2*a + 1)
+ expected_qubo = {
+ ('a', 'a'): 0.0,
+ ('a', 'x[0][1]'): 2.0,
+ ('x[0][1]', 'x[0][1]'): 1.0,
+ ('x[0][1]', 'x[1][1]'): 1.0,
+ ('x[1][1]', 'x[1][1]'): 0.0}
+ expected_offset = 0.0
+ expected_structure = {
+ 'a': ('a',),
+ 'x[0][0]': ('x', 0, 0),
+ 'x[0][1]': ('x', 0, 1),
+ 'x[1][0]': ('x', 1, 0),
+ 'x[1][1]': ('x', 1, 1)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure)
+
+ def test_compile_matrix_spin(self):
+ x = Matrix("x", 2, 2, spin=True)
+ exp = x[1, 1] * x[0, 0] + x[0, 0]
+ expected_qubo = {('x[1][1]', 'x[1][1]'): -2.0, ('x[0][0]', 'x[0][0]'): 0.0,
+ ('x[0][0]', 'x[1][1]'): 4.0}
+ expected_offset = 0.0
+ expected_structure = {
+ 'x[0][0]': ('x', 0, 0),
+ 'x[0][1]': ('x', 0, 1),
+ 'x[1][0]': ('x', 1, 0),
+ 'x[1][1]': ('x', 1, 1)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure)
+
+ def test_index_error_vector(self):
+ x = Vector("x", n_dim=2)
+ self.assertRaises(IndexError, lambda: x[2])
+
+ x = Matrix("x", 2, 1)
+ self.assertRaises(IndexError, lambda: x[2, 2])
+
+ def test_sum(self):
+ x = Vector('x', 2)
+ exp = (Sum(0, 2, lambda i: x[i]) - 1) ** 2
+
+ expected_qubo = {('x[0]', 'x[0]'): -1.0, ('x[1]', 'x[1]'): -1.0, ('x[0]', 'x[1]'): 2.0}
+ expected_offset = 1.0
+ expected_structure = {
+ 'x[0]': ('x', 0),
+ 'x[1]': ('x', 1)}
+ self.compile_check(exp, expected_qubo, expected_offset, expected_structure)
diff --git a/test/test_express_equality.py b/test/test_express_equality.py
new file mode 100644
index 00000000..786ba38e
--- /dev/null
+++ b/test/test_express_equality.py
@@ -0,0 +1,140 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+import unittest
+
+from pyqubo import Qbit, Spin, AddList, Mul, Add, Num, Param, Constraint
+
+
+class TestExpressEquality(unittest.TestCase):
+
+ def test_equality_of_add_list(self):
+ exp1 = AddList([Qbit("a"), Qbit("b")])
+ exp2 = AddList([Qbit("b"), Qbit("a")])
+ exp3 = AddList([Qbit("a"), Qbit("a")])
+ self.assertTrue(exp1 == exp2)
+ self.assertTrue(hash(exp1) == hash(exp2))
+ self.assertFalse(exp1 == exp3)
+ self.assertFalse(exp1 == Qbit("a"))
+
+ def test_equality_of_add(self):
+ exp1 = Add(Qbit("a"), Qbit("b"))
+ exp2 = Add(Qbit("b"), Qbit("a"))
+ exp3 = Add(Qbit("a"), Qbit("a"))
+ exp4 = Add(Qbit("a"), Qbit("b"))
+ exp5 = Add(Qbit("a"), 1)
+ self.assertTrue(exp1 == exp2)
+ self.assertTrue(exp1 == exp4)
+ self.assertTrue(hash(exp1) == hash(exp2))
+ self.assertTrue(exp1 != exp3)
+ self.assertFalse(exp1 == exp5)
+ self.assertFalse(exp1 == Qbit("a"))
+ self.assertFalse(exp1 == exp3)
+ self.assertEqual(repr(exp1), "(Qbit(a)+Qbit(b))")
+
+ def test_equality_of_mul(self):
+ exp1 = Mul(Qbit("a"), Qbit("b"))
+ exp2 = Mul(Qbit("b"), Qbit("a"))
+ exp3 = Mul(Qbit("a"), Qbit("a"))
+ self.assertTrue(exp1 == exp2)
+ self.assertTrue(hash(exp1) == hash(exp2))
+ self.assertFalse(exp1 == exp3)
+ self.assertTrue(exp1 != Qbit("a"))
+
+ def test_equality_of_num(self):
+ self.assertTrue(Num(1) == Num(1))
+ self.assertFalse(Num(1) == Num(2))
+ self.assertFalse(Num(1) == Qbit("a"))
+
+ def test_equality_of_param(self):
+ p1 = Param("p1")
+ p2 = Param("p2")
+ p3 = Param("p1")
+ self.assertTrue(p1 == p3)
+ self.assertTrue(hash(p1) == hash(p3))
+ self.assertTrue(p1 != p2)
+ self.assertTrue(p1 != Qbit("a"))
+
+ def test_equality_of_const(self):
+ c1 = Constraint(Qbit("a"), label="c1")
+ c2 = Constraint(Qbit("b"), label="c1")
+ c3 = Constraint(Qbit("a"), label="c3")
+ c4 = Constraint(Qbit("a"), label="c1")
+ self.assertTrue(c1 == c4)
+ self.assertFalse(c1 != c4)
+ self.assertTrue(hash(c1) == hash(c4))
+ self.assertTrue(c1 != c2)
+ self.assertTrue(c1 != c3)
+ self.assertTrue(c1 != Qbit("a"))
+
+ def test_equality_of_spin(self):
+ a, b, c = Spin("a"), Spin("b"), Spin("a")
+ self.assertTrue(a == c)
+ self.assertFalse(a != c)
+ self.assertTrue(hash(a) == hash(c))
+ self.assertTrue(a != b)
+ self.assertTrue(a != Qbit("a"))
+ self.assertEqual(repr(a), "Spin(a)")
+
+ def test_equality_of_qbit(self):
+ a, b, c = Qbit("a"), Qbit("b"), Qbit("a")
+ self.assertTrue(a == c)
+ self.assertFalse(a != c)
+ self.assertTrue(hash(a) == hash(c))
+ self.assertTrue(a != b)
+ self.assertTrue(a != Spin("a"))
+
+ def test_equality_of_express(self):
+ a, b = Qbit("a"), Qbit("b")
+ exp = a * b + 2*a - 1
+ expected_exp = AddList([Mul(a, b), Num(-1.0), Mul(a, 2)])
+ self.assertTrue(exp == expected_exp)
+
+ def test_equality_sub(self):
+ a, b = Qbit("a"), Qbit("b")
+ exp = 1-a-b
+ expected_exp = AddList([Mul(a, -1), Num(1.0), Mul(b, -1)])
+ self.assertTrue(exp == expected_exp)
+ self.assertTrue(exp - 0.0 == expected_exp)
+
+ def test_equality_sub2(self):
+ a, b = Qbit("a"), Qbit("b")
+ exp = a-b-1
+ expected_exp = AddList([a, Num(-1.0), Mul(b, -1)])
+ self.assertTrue(exp == expected_exp)
+
+ def test_equality_of_express_with_param(self):
+ a, b, p = Qbit("a"), Qbit("b"), Param("p")
+ exp = a + b - 1 + a * p
+ expected_exp = AddList([a, Num(-1.0), b, Mul(p, a)])
+ self.assertTrue(exp == expected_exp)
+
+ def test_equality_of_express_with_const(self):
+ a, b = Qbit("a"), Spin("b")
+ exp = a + b - 1 + Constraint(a * b, label="const")
+ expected_exp = AddList([a, Num(-1.0), b, Constraint(Mul(a, b), label="const")])
+ self.assertTrue(exp == expected_exp)
+
+ def test_repr(self):
+ a, b, p = Qbit("a"), Qbit("b"), Param("p")
+ exp = a + p - 1 + Constraint(a * b, label="const")
+ expected = "(Qbit(a)+Param(p)+Num(-1)+Const(const, (Qbit(a)*Qbit(b))))"
+ self.assertTrue(repr(exp) == expected)
+
+ def test_express_error(self):
+ self.assertRaises(ValueError, lambda: 2 / Qbit("a"))
+ self.assertRaises(ValueError, lambda: Qbit("a") / Qbit("a"))
+ self.assertRaises(ValueError, lambda: Qbit("a") ** 1.5)
+ self.assertRaises(ValueError, lambda: Mul(1, Qbit("b")))
+ self.assertRaises(ValueError, lambda: Add(1, Qbit("b")))
diff --git a/test/test_logic.py b/test/test_logic.py
new file mode 100644
index 00000000..061d84d2
--- /dev/null
+++ b/test/test_logic.py
@@ -0,0 +1,46 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+import unittest
+import itertools
+from pyqubo import Qbit, And, Or, Not
+
+
+class TestLogic(unittest.TestCase):
+
+ def test_and(self):
+ a, b = Qbit('a'), Qbit('b')
+ exp = And(a, b)
+ model = exp.compile()
+ for a, b in itertools.product(*[(0, 1)] * 2):
+ e = int(model.energy({'a': a, 'b': b}, var_type='binary'))
+ self.assertEqual(a*b, e)
+
+ def test_or(self):
+ a, b = Qbit('a'), Qbit('b')
+ exp = Or(a, b)
+ model = exp.compile()
+ for a, b in itertools.product(*[(0, 1)] * 2):
+ e = int(model.energy({'a': a, 'b': b}, var_type='binary'))
+ self.assertEqual(int(a+b > 0), e)
+
+ def test_not(self):
+ a = Qbit('a')
+ exp = Not(a)
+ model = exp.compile()
+ for a in [0, 1]:
+ e = int(model.energy({'a': a}, var_type='binary'))
+ self.assertEqual(1-a, e)
+
+ self.assertEqual(repr(exp), "Not(((Qbit(a)*Num(-1))+Num(1)))")
diff --git a/test/test_model.py b/test/test_model.py
new file mode 100644
index 00000000..07b25541
--- /dev/null
+++ b/test/test_model.py
@@ -0,0 +1,139 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+import unittest
+
+from pyqubo import Qbit, Matrix, Constraint, Param
+from pyqubo import assert_qubo_equal
+
+
+class TestModel(unittest.TestCase):
+
+ def test_to_qubo(self):
+ a, b = Qbit("a"), Qbit("b")
+ exp = 1 + a * b + a - 2
+ model = exp.compile()
+ qubo, offset = model.to_qubo()
+ assert_qubo_equal(qubo, {("a", "a"): 1.0, ("a", "b"): 1.0, ("b", "b"): 0.0})
+ self.assertTrue(offset == -1)
+
+ def test_to_qubo_with_index(self):
+ a, b = Qbit("a"), Qbit("b")
+ exp = 1 + a * b + a - 2
+ model = exp.compile()
+ qubo, offset = model.to_qubo(index_label=True)
+ assert_qubo_equal(qubo, {(0, 0): 1.0, (0, 1): 1.0, (1, 1): 0.0})
+ self.assertTrue(offset == -1)
+
+ def test_to_ising(self):
+ a, b = Qbit("a"), Qbit("b")
+ exp = 1 + a * b + a - 2
+ model = exp.compile()
+ linear, quad, offset = model.to_ising()
+ self.assertTrue(linear == {'a': 0.75, 'b': 0.25})
+ assert_qubo_equal(quad, {('a', 'b'): 0.25})
+ self.assertTrue(offset == -0.25)
+
+ def test_to_ising_with_index(self):
+ a, b = Qbit("a"), Qbit("b")
+ exp = 1 + a * b + a - 2
+ model = exp.compile()
+ linear, quad, offset = model.to_ising(index_label=True)
+ self.assertTrue(linear == {0: 0.75, 1: 0.25})
+ assert_qubo_equal(quad, {(0, 1): 0.25})
+ self.assertTrue(offset == -0.25)
+
+ def test_decode(self):
+ x = Matrix("x", n_row=2, n_col=2)
+ exp = Constraint((x[1, 1] - x[0, 1]) ** 2, label="const")
+ model = exp.compile()
+
+ # check __repr__ of CompiledQubo
+ expected_repr = "CompiledQubo({('x[0][1]', 'x[0][1]'): 1.0,\n" \
+ " ('x[0][1]', 'x[1][1]'): -2.0,\n" \
+ " ('x[1][1]', 'x[1][1]'): 1.0}, offset=0.0)"
+ self.assertEqual(repr(model.compiled_qubo), expected_repr)
+
+ # check __repr__ of Model
+ expected_repr = "Model(CompiledQubo({('x[0][1]', 'x[0][1]'): 1.0,\n"\
+ " ('x[0][1]', 'x[1][1]'): -2.0,\n"\
+ " ('x[1][1]', 'x[1][1]'): 1.0}, offset=0.0), "\
+ "structure={'x[0][0]': ('x', 0, 0),\n"\
+ " 'x[0][1]': ('x', 0, 1),\n"\
+ " 'x[1][0]': ('x', 1, 0),\n"\
+ " 'x[1][1]': ('x', 1, 1)})"
+ self.assertEqual(repr(model), expected_repr)
+
+ # when the constraint is not broken
+
+ # type of solution is dict[label, bit]
+ decoded_sol, broken = model.decode_solution({'x[0][1]': 1.0, 'x[1][1]': 1.0},
+ var_type="binary")
+ self.assertTrue(decoded_sol == {'x': {0: {1: 1}, 1: {1: 1}}})
+ self.assertTrue(len(broken) == 0)
+
+ # type of solution is list[bit]
+ decoded_sol, broken = model.decode_solution([1, 1], var_type="binary")
+ self.assertTrue(decoded_sol == {'x': {0: {1: 1}, 1: {1: 1}}})
+ self.assertTrue(len(broken) == 0)
+
+ # type of solution is dict[index_label(int), bit]
+ decoded_sol, broken = model.decode_solution({0: 1.0, 1: 1.0},
+ var_type="binary")
+ self.assertTrue(decoded_sol == {'x': {0: {1: 1}, 1: {1: 1}}})
+ self.assertTrue(len(broken) == 0)
+
+ # when the constraint is broken
+
+ # type of solution is dict[label, bit]
+ decoded_sol, broken = model.decode_solution({'x[0][1]': 0.0, 'x[1][1]': 1.0},
+ var_type="binary")
+ self.assertTrue(decoded_sol == {'x': {0: {1: 0}, 1: {1: 1}}})
+ self.assertTrue(len(broken) == 1)
+
+ # type of solution is dict[label, spin]
+ decoded_sol, broken = model.decode_solution({'x[0][1]': 1.0, 'x[1][1]': -1.0},
+ var_type="spin")
+ self.assertTrue(decoded_sol == {'x': {0: {1: 1}, 1: {1: -1}}})
+ self.assertTrue(len(broken) == 1)
+
+ # invalid solution
+ self.assertRaises(ValueError, lambda: model.decode_solution([1, 1, 1], var_type="binary"))
+ self.assertRaises(ValueError, lambda: model.decode_solution(
+ {'x[0][2]': 1.0, 'x[1][1]': 0.0}, var_type="binary"))
+ self.assertRaises(TypeError, lambda: model.decode_solution((1, 1), var_type="binary"))
+
+ # invalid var_type
+ self.assertRaises(AssertionError, lambda: model.decode_solution([1, 1], var_type="sp"))
+
+ def test_decode2(self):
+ x = Matrix("x", n_row=2, n_col=2)
+ exp = Constraint(-(x[1, 1] - x[0, 1]) ** 2, label="const")
+ model = exp.compile()
+ self.assertRaises(ValueError, lambda: model.decode_solution([1, 0], var_type="binary"))
+
+ def test_params(self):
+ a, b, p = Qbit("a"), Qbit("b"), Param("p")
+ params = {'p': 2.0}
+ exp = p * (1 + a * b + a)
+ model = exp.compile()
+ qubo, offset = model.to_qubo(index_label=False, params=params)
+ dict_solution = {'a': 1, 'b': 0}
+ dict_energy = model.energy(dict_solution, var_type="binary", params=params)
+ list_solution = [1, 0]
+ list_energy = model.energy(list_solution, var_type="binary", params=params)
+ assert_qubo_equal(qubo, {('a', 'b'): 2.0, ('a', 'a'): 2.0, ('b', 'b'): 0.0})
+ self.assertEqual(offset, 2.0)
+ self.assertTrue(dict_energy, 2.0)
+ self.assertTrue(list_energy, 2.0)
diff --git a/test/test_paramprod.py b/test/test_paramprod.py
new file mode 100644
index 00000000..216bb2c8
--- /dev/null
+++ b/test/test_paramprod.py
@@ -0,0 +1,59 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+import unittest
+
+from pyqubo import ParamProd
+
+
+class TestParamProd(unittest.TestCase):
+
+ def test_equality(self):
+ term_key1 = ParamProd({"a": 1, "b": 2})
+ term_key2 = ParamProd({"a": 1, "b": 2})
+ term_key3 = ParamProd({"a": 2, "b": 1})
+ self.assertTrue(term_key1 == term_key2)
+ self.assertTrue(term_key1 != term_key3)
+ self.assertTrue(term_key1 != 1)
+
+ def test_equality_const(self):
+ term_key1 = ParamProd({})
+ term_key2 = ParamProd({})
+ term_key3 = ParamProd({'a': 1})
+ self.assertTrue(term_key1.is_constant())
+ self.assertEqual(term_key1, term_key2)
+ self.assertNotEqual(term_key1, term_key3)
+
+ def test_merge(self):
+ term_key1 = ParamProd({"a": 1, "b": 2})
+ term_key2 = ParamProd({"a": 1, "b": 1})
+ term_key3 = ParamProd({"a": 2, "b": 3})
+ term_key4 = ParamProd({})
+ self.assertEqual(term_key3, ParamProd.merge_term_key(term_key1, term_key2))
+ self.assertEqual(term_key1, ParamProd.merge_term_key(term_key1, term_key4))
+
+ def test_evaluate(self):
+ term_key1 = ParamProd({"a": 1, "b": 2})
+ empty_key = ParamProd({})
+ dict_value = {"a": 3.0, "b": 5.0}
+ prod = term_key1.calc_product(dict_value)
+ expected_prod = 75
+ self.assertEqual(expected_prod, prod)
+ self.assertEqual(1, empty_key.calc_product({}))
+
+ def test_repr(self):
+ term_key1 = ParamProd({"a": 2, "b": 3})
+ empty_key = ParamProd({})
+ self.assertIn(repr(term_key1), "a^2*b^3")
+ self.assertEqual(repr(empty_key), "const")
diff --git a/test/test_solver.py b/test/test_solver.py
new file mode 100644
index 00000000..ae29a7c6
--- /dev/null
+++ b/test/test_solver.py
@@ -0,0 +1,38 @@
+# Copyright 2018 Recruit Communications Co., Ltd.
+#
+# 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.
+
+import unittest
+
+from pyqubo import solve_qubo, solve_ising, Spin
+
+
+class TestSolver(unittest.TestCase):
+
+ @staticmethod
+ def create_number_partition_model():
+ s1, s2, s3 = Spin("s1"), Spin("s2"), Spin("s3")
+ H = (2 * s1 + 4 * s2 + 6 * s3) ** 2
+ return H.compile()
+
+ def test_solve_qubo(self):
+ model = TestSolver.create_number_partition_model()
+ qubo, offset = model.to_qubo()
+ solution = solve_qubo(qubo, num_reads=1, num_sweeps=10)
+ self.assertTrue(solution == {'s1': 0, 's2': 0, 's3': 1} or {'s1': 1, 's2': 1, 's3': 0})
+
+ def test_solve_ising(self):
+ model = TestSolver.create_number_partition_model()
+ linear, quad, offset = model.to_ising()
+ solution = solve_ising(linear, quad, num_reads=1, num_sweeps=10)
+ self.assertTrue(solution == {'s1': -1, 's2': -1, 's3': 1} or {'s1': 1, 's2': 1, 's3': -1})