From e60181ded43e224d0170be54e711dd1241a90600 Mon Sep 17 00:00:00 2001 From: "Joseph T. Iosue" Date: Sat, 4 Jul 2020 14:22:53 -0700 Subject: [PATCH] v1.2.2 (#30) * Recreated dev branch and incremented version * sped up value functions * updated demo notebook * added to_enumerated, anneal warning, sum, lam=0 * sum function to docs --- docs/utils/methods.rst | 2 + notebook_examples/qubovert_tutorial.ipynb | 350 +++++++++++++++------- qubovert/_pcbo.py | 12 + qubovert/_pcso.py | 12 + qubovert/_puso.py | 48 ++- qubovert/_version.py | 2 +- qubovert/sim/_anneal.py | 14 + qubovert/utils/_binary_helpers.py | 40 ++- qubovert/utils/_bo_parentclass.py | 34 +++ qubovert/utils/_solve_bruteforce.py | 26 +- qubovert/utils/_values.py | 57 ++-- tests/sim/test_anneal.py | 88 +++--- tests/test_pcbo.py | 159 +++++++++- tests/test_pcso.py | 163 +++++++++- tests/test_pubo.py | 10 +- tests/test_puso.py | 10 +- tests/test_qubo.py | 10 +- tests/test_quso.py | 10 +- tests/utils/test_binary_helpers.py | 15 + tests/utils/test_values.py | 75 +++++ 20 files changed, 957 insertions(+), 180 deletions(-) create mode 100644 tests/utils/test_values.py diff --git a/docs/utils/methods.rst b/docs/utils/methods.rst index 969540c..6a04bd7 100644 --- a/docs/utils/methods.rst +++ b/docs/utils/methods.rst @@ -72,6 +72,8 @@ Approximate Extrema Useful functions ---------------- +.. autofunction:: qubovert.utils.sum + .. autofunction:: qubovert.utils.subgraph .. autofunction:: qubovert.utils.subvalue diff --git a/notebook_examples/qubovert_tutorial.ipynb b/notebook_examples/qubovert_tutorial.ipynb index a25bcf4..f1a8214 100644 --- a/notebook_examples/qubovert_tutorial.ipynb +++ b/notebook_examples/qubovert_tutorial.ipynb @@ -19,7 +19,7 @@ "metadata": {}, "outputs": [], "source": [ - "# !pip install qubovert==1.2.1\n", + "# !pip install qubovert==1.2.2\n", "import qubovert as qv" ] }, @@ -27,7 +27,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "*This notebook was updated in June 2020 and uses qubovert v1.2.1. There may be newer features available with later versions of qubovert even if this notebook does not show them.*\n", + "*This notebook was updated in July 2020 and uses qubovert v1.2.2. There may be newer features available with later versions of qubovert even if this notebook does not show them.*\n", "\n", "Given how big this notebook is, I suggest installing the Jupyter Notebook Table of Contents extension to help you to navigate this tutorial. You can do this by running the following shell commands\n", "\n", @@ -115,6 +115,7 @@ "('QUBOVertWarning',\n", " 'is_solution_spin',\n", " 'num_bits',\n", + " 'sum',\n", " 'approximate_pubo_extrema',\n", " 'approximate_puso_extrema',\n", " 'approximate_qubo_extrema',\n", @@ -926,7 +927,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{('x0', 'y0'): 7, ('y1', 'x0', 'y0'): 20, ('y2', 'x0', 'y0'): 40, ('x0', 'y0', 'x1'): 20, ('y1', 'x0', 'y0', 'x1'): 16, ('y2', 'x0', 'y0', 'x1'): 32, ('x2', 'x0', 'y0'): 40, ('x2', 'y1', 'x0', 'y0'): 32, ('x2', 'y2', 'x0', 'y0'): 64, ('y1', 'x0'): 24, ('y1', 'y2', 'x0'): 80, ('y1', 'x0', 'x1'): 48, ('y1', 'y2', 'x0', 'x1'): 64, ('x2', 'y1', 'x0'): 96, ('x2', 'y1', 'y2', 'x0'): 128, ('y2', 'x0'): 88, ('y2', 'x0', 'x1'): 128, ('x2', 'y2', 'x0'): 256, ('x0',): -16, ('x0', 'x1'): 16, ('x2', 'x0'): 32, ('y0', 'x1'): 24, ('y1', 'y0', 'x1'): 48, ('y2', 'y0', 'x1'): 96, ('x2', 'y0', 'x1'): 80, ('x2', 'y1', 'y0', 'x1'): 64, ('x2', 'y2', 'y0', 'x1'): 128, ('y1', 'x1'): 72, ('y1', 'y2', 'x1'): 192, ('x2', 'y1', 'x1'): 192, ('x2', 'y1', 'y2', 'x1'): 256, ('y2', 'x1'): 240, ('x2', 'y2', 'x1'): 512, ('x1',): -24, ('x2', 'x1'): 64, ('x2', 'y0'): 88, ('x2', 'y1', 'y0'): 128, ('x2', 'y2', 'y0'): 256, ('x2', 'y1'): 240, ('x2', 'y1', 'y2'): 512, ('x2', 'y2'): 736, ('x2',): -16, ('y0',): -16, ('y1', 'y0'): 16, ('y2', 'y0'): 32, ('y1',): -24, ('y1', 'y2'): 64, ('y2',): -16, (): 25}\n" + "{('y0', 'x0'): 7, ('y1', 'y0', 'x0'): 20, ('y0', 'y2', 'x0'): 40, ('x1', 'y0', 'x0'): 20, ('x1', 'y1', 'y0', 'x0'): 16, ('x1', 'y0', 'y2', 'x0'): 32, ('x2', 'y0', 'x0'): 40, ('x2', 'y1', 'y0', 'x0'): 32, ('x2', 'y0', 'y2', 'x0'): 64, ('y1', 'x0'): 24, ('y1', 'y2', 'x0'): 80, ('x1', 'y1', 'x0'): 48, ('x1', 'y1', 'y2', 'x0'): 64, ('x2', 'y1', 'x0'): 96, ('x2', 'y1', 'y2', 'x0'): 128, ('y2', 'x0'): 88, ('x1', 'y2', 'x0'): 128, ('x2', 'y2', 'x0'): 256, ('x0',): -16, ('x1', 'x0'): 16, ('x2', 'x0'): 32, ('x1', 'y0'): 24, ('x1', 'y1', 'y0'): 48, ('x1', 'y0', 'y2'): 96, ('x2', 'x1', 'y0'): 80, ('x2', 'x1', 'y1', 'y0'): 64, ('x2', 'x1', 'y0', 'y2'): 128, ('x1', 'y1'): 72, ('x1', 'y1', 'y2'): 192, ('x2', 'x1', 'y1'): 192, ('x2', 'x1', 'y1', 'y2'): 256, ('x1', 'y2'): 240, ('x2', 'x1', 'y2'): 512, ('x1',): -24, ('x2', 'x1'): 64, ('x2', 'y0'): 88, ('x2', 'y1', 'y0'): 128, ('x2', 'y0', 'y2'): 256, ('x2', 'y1'): 240, ('x2', 'y1', 'y2'): 512, ('x2', 'y2'): 736, ('x2',): -16, ('y0',): -16, ('y1', 'y0'): 16, ('y0', 'y2'): 32, ('y1',): -24, ('y1', 'y2'): 64, ('y2',): -16, (): 25}\n" ] } ], @@ -985,7 +986,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{('y1', 'x0', 'y0'): 20, ('y2', 'x0', 'y0'): 40, ('x0', 'y0', 'x1'): 20, ('y1', 'x0', 'y0', 'x1'): 16, ('y2', 'x0', 'y0', 'x1'): 32, ('x2', 'x0', 'y0'): 40, ('x2', 'y1', 'x0', 'y0'): 32, ('x2', 'y2', 'x0', 'y0'): 64, ('y1', 'x0'): 24, ('y1', 'y2', 'x0'): 80, ('y1', 'x0', 'x1'): 48, ('y1', 'y2', 'x0', 'x1'): 64, ('x2', 'y1', 'x0'): 96, ('x2', 'y1', 'y2', 'x0'): 128, ('y2', 'x0'): 88, ('y2', 'x0', 'x1'): 128, ('x2', 'y2', 'x0'): 256, ('x0', 'y0'): 7, ('x0',): -16, ('x0', 'x1'): 16, ('x2', 'x0'): 32, ('y0', 'x1'): 24, ('y1', 'y0', 'x1'): 48, ('y2', 'y0', 'x1'): 96, ('x2', 'y0', 'x1'): 80, ('x2', 'y1', 'y0', 'x1'): 64, ('x2', 'y2', 'y0', 'x1'): 128, ('y1', 'x1'): 72, ('y1', 'y2', 'x1'): 192, ('x2', 'y1', 'x1'): 192, ('x2', 'y1', 'y2', 'x1'): 256, ('y2', 'x1'): 240, ('x2', 'y2', 'x1'): 512, ('x1',): -24, ('x2', 'x1'): 64, ('x2', 'y0'): 88, ('x2', 'y1', 'y0'): 128, ('x2', 'y2', 'y0'): 256, ('x2', 'y1'): 240, ('x2', 'y1', 'y2'): 512, ('x2', 'y2'): 736, ('x2',): -16, ('y0',): -16, ('y1', 'y0'): 16, ('y2', 'y0'): 32, ('y1',): -24, ('y1', 'y2'): 64, ('y2',): -16, (): 25}\n" + "{('y1', 'y0', 'x0'): 20, ('y0', 'y2', 'x0'): 40, ('x1', 'y0', 'x0'): 20, ('x1', 'y1', 'y0', 'x0'): 16, ('x1', 'y0', 'y2', 'x0'): 32, ('x2', 'y0', 'x0'): 40, ('x2', 'y1', 'y0', 'x0'): 32, ('x2', 'y0', 'y2', 'x0'): 64, ('y1', 'x0'): 24, ('y1', 'y2', 'x0'): 80, ('x1', 'y1', 'x0'): 48, ('x1', 'y1', 'y2', 'x0'): 64, ('x2', 'y1', 'x0'): 96, ('x2', 'y1', 'y2', 'x0'): 128, ('y2', 'x0'): 88, ('x1', 'y2', 'x0'): 128, ('x2', 'y2', 'x0'): 256, ('y0', 'x0'): 7, ('x0',): -16, ('x1', 'x0'): 16, ('x2', 'x0'): 32, ('x1', 'y0'): 24, ('x1', 'y1', 'y0'): 48, ('x1', 'y0', 'y2'): 96, ('x2', 'x1', 'y0'): 80, ('x2', 'x1', 'y1', 'y0'): 64, ('x2', 'x1', 'y0', 'y2'): 128, ('x1', 'y1'): 72, ('x1', 'y1', 'y2'): 192, ('x2', 'x1', 'y1'): 192, ('x2', 'x1', 'y1', 'y2'): 256, ('x1', 'y2'): 240, ('x2', 'x1', 'y2'): 512, ('x1',): -24, ('x2', 'x1'): 64, ('x2', 'y0'): 88, ('x2', 'y1', 'y0'): 128, ('x2', 'y0', 'y2'): 256, ('x2', 'y1'): 240, ('x2', 'y1', 'y2'): 512, ('x2', 'y2'): 736, ('x2',): -16, ('y0',): -16, ('y1', 'y0'): 16, ('y0', 'y2'): 32, ('y1',): -24, ('y1', 'y2'): 64, ('y2',): -16, (): 25}\n" ] } ], @@ -1072,7 +1073,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{('y1', 'x0', 'y0'): 20, ('y2', 'x0', 'y0'): 40, ('x0', 'y0', 'x1'): 20, ('y1', 'x0', 'y0', 'x1'): 16, ('y2', 'x0', 'y0', 'x1'): 32, ('x2', 'x0', 'y0'): 40, ('x2', 'y1', 'x0', 'y0'): 32, ('x2', 'y2', 'x0', 'y0'): 64, ('y1', 'x0'): 24, ('y1', 'y2', 'x0'): 80, ('y1', 'x0', 'x1'): 48, ('y1', 'y2', 'x0', 'x1'): 64, ('x2', 'y1', 'x0'): 96, ('x2', 'y1', 'y2', 'x0'): 128, ('y2', 'x0'): 88, ('y2', 'x0', 'x1'): 128, ('x2', 'y2', 'x0'): 256, ('x0', 'y0'): 7, ('x0',): -16, ('x0', 'x1'): 16, ('x2', 'x0'): 32, ('y0', 'x1'): 24, ('y1', 'y0', 'x1'): 48, ('y2', 'y0', 'x1'): 96, ('x2', 'y0', 'x1'): 80, ('x2', 'y1', 'y0', 'x1'): 64, ('x2', 'y2', 'y0', 'x1'): 128, ('y1', 'x1'): 72, ('y1', 'y2', 'x1'): 192, ('x2', 'y1', 'x1'): 192, ('x2', 'y1', 'y2', 'x1'): 256, ('y2', 'x1'): 240, ('x2', 'y2', 'x1'): 512, ('x1',): -24, ('x2', 'x1'): 64, ('x2', 'y0'): 88, ('x2', 'y1', 'y0'): 128, ('x2', 'y2', 'y0'): 256, ('x2', 'y1'): 240, ('x2', 'y1', 'y2'): 512, ('x2', 'y2'): 736, ('x2',): -16, ('y0',): -16, ('y1', 'y0'): 16, ('y2', 'y0'): 32, ('y1',): -24, ('y1', 'y2'): 64, ('y2',): -16, (): 25}\n" + "{('y1', 'y0', 'x0'): 20, ('y0', 'y2', 'x0'): 40, ('x1', 'y0', 'x0'): 20, ('x1', 'y1', 'y0', 'x0'): 16, ('x1', 'y0', 'y2', 'x0'): 32, ('x2', 'y0', 'x0'): 40, ('x2', 'y1', 'y0', 'x0'): 32, ('x2', 'y0', 'y2', 'x0'): 64, ('y1', 'x0'): 24, ('y1', 'y2', 'x0'): 80, ('x1', 'y1', 'x0'): 48, ('x1', 'y1', 'y2', 'x0'): 64, ('x2', 'y1', 'x0'): 96, ('x2', 'y1', 'y2', 'x0'): 128, ('y2', 'x0'): 88, ('x1', 'y2', 'x0'): 128, ('x2', 'y2', 'x0'): 256, ('y0', 'x0'): 7, ('x0',): -16, ('x1', 'x0'): 16, ('x2', 'x0'): 32, ('x1', 'y0'): 24, ('x1', 'y1', 'y0'): 48, ('x1', 'y0', 'y2'): 96, ('x2', 'x1', 'y0'): 80, ('x2', 'x1', 'y1', 'y0'): 64, ('x2', 'x1', 'y0', 'y2'): 128, ('x1', 'y1'): 72, ('x1', 'y1', 'y2'): 192, ('x2', 'x1', 'y1'): 192, ('x2', 'x1', 'y1', 'y2'): 256, ('x1', 'y2'): 240, ('x2', 'x1', 'y2'): 512, ('x1',): -24, ('x2', 'x1'): 64, ('x2', 'y0'): 88, ('x2', 'y1', 'y0'): 128, ('x2', 'y0', 'y2'): 256, ('x2', 'y1'): 240, ('x2', 'y1', 'y2'): 512, ('x2', 'y2'): 736, ('x2',): -16, ('y0',): -16, ('y1', 'y0'): 16, ('y0', 'y2'): 32, ('y1',): -24, ('y1', 'y2'): 64, ('y2',): -16, (): 25}\n" ] } ], @@ -1134,9 +1135,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "{(0, 1, 2): 20, (0, 1, 3): 40, (0, 1, 4): 20, (0, 1, 2, 4): 16, (0, 1, 3, 4): 32, (0, 1, 5): 40, (0, 1, 2, 5): 32, (0, 1, 3, 5): 64, (0, 2): 24, (0, 2, 3): 80, (0, 2, 4): 48, (0, 2, 3, 4): 64, (0, 2, 5): 96, (0, 2, 3, 5): 128, (0, 3): 88, (0, 3, 4): 128, (0, 3, 5): 256, (0, 1): 7, (0,): -16, (0, 4): 16, (0, 5): 32, (1, 4): 24, (1, 2, 4): 48, (1, 3, 4): 96, (1, 4, 5): 80, (1, 2, 4, 5): 64, (1, 3, 4, 5): 128, (2, 4): 72, (2, 3, 4): 192, (2, 4, 5): 192, (2, 3, 4, 5): 256, (3, 4): 240, (3, 4, 5): 512, (4,): -24, (4, 5): 64, (1, 5): 88, (1, 2, 5): 128, (1, 3, 5): 256, (2, 5): 240, (2, 3, 5): 512, (3, 5): 736, (5,): -16, (1,): -16, (1, 2): 16, (1, 3): 32, (2,): -24, (2, 3): 64, (3,): -16, (): 25}\n", + "{(0, 1, 2): 20, (0, 1, 3): 40, (0, 1, 4): 20, (0, 1, 2, 4): 16, (0, 1, 3, 4): 32, (0, 1, 5): 40, (0, 1, 2, 5): 32, (0, 1, 3, 5): 64, (1, 2): 24, (1, 2, 3): 80, (1, 2, 4): 48, (1, 2, 3, 4): 64, (1, 2, 5): 96, (1, 2, 3, 5): 128, (1, 3): 88, (1, 3, 4): 128, (1, 3, 5): 256, (0, 1): 7, (1,): -16, (1, 4): 16, (1, 5): 32, (0, 4): 24, (0, 2, 4): 48, (0, 3, 4): 96, (0, 4, 5): 80, (0, 2, 4, 5): 64, (0, 3, 4, 5): 128, (2, 4): 72, (2, 3, 4): 192, (2, 4, 5): 192, (2, 3, 4, 5): 256, (3, 4): 240, (3, 4, 5): 512, (4,): -24, (4, 5): 64, (0, 5): 88, (0, 2, 5): 128, (0, 3, 5): 256, (2, 5): 240, (2, 3, 5): 512, (3, 5): 736, (5,): -16, (0,): -16, (0, 2): 16, (0, 3): 32, (2,): -24, (2, 3): 64, (3,): -16, (): 25}\n", "\n", - "20 x(0) x(1) x(2) + 40 x(0) x(1) x(3) + 20 x(0) x(1) x(4) + 16 x(0) x(1) x(2) x(4) + 32 x(0) x(1) x(3) x(4) + 40 x(0) x(1) x(5) + 32 x(0) x(1) x(2) x(5) + 64 x(0) x(1) x(3) x(5) + 24 x(0) x(2) + 80 x(0) x(2) x(3) + 48 x(0) x(2) x(4) + 64 x(0) x(2) x(3) x(4) + 96 x(0) x(2) x(5) + 128 x(0) x(2) x(3) x(5) + 88 x(0) x(3) + 128 x(0) x(3) x(4) + 256 x(0) x(3) x(5) + 7 x(0) x(1) - 16 x(0) + 16 x(0) x(4) + 32 x(0) x(5) + 24 x(1) x(4) + 48 x(1) x(2) x(4) + 96 x(1) x(3) x(4) + 80 x(1) x(4) x(5) + 64 x(1) x(2) x(4) x(5) + 128 x(1) x(3) x(4) x(5) + 72 x(2) x(4) + 192 x(2) x(3) x(4) + 192 x(2) x(4) x(5) + 256 x(2) x(3) x(4) x(5) + 240 x(3) x(4) + 512 x(3) x(4) x(5) - 24 x(4) + 64 x(4) x(5) + 88 x(1) x(5) + 128 x(1) x(2) x(5) + 256 x(1) x(3) x(5) + 240 x(2) x(5) + 512 x(2) x(3) x(5) + 736 x(3) x(5) - 16 x(5) - 16 x(1) + 16 x(1) x(2) + 32 x(1) x(3) - 24 x(2) + 64 x(2) x(3) - 16 x(3) + 25\n" + "20 x(0) x(1) x(2) + 40 x(0) x(1) x(3) + 20 x(0) x(1) x(4) + 16 x(0) x(1) x(2) x(4) + 32 x(0) x(1) x(3) x(4) + 40 x(0) x(1) x(5) + 32 x(0) x(1) x(2) x(5) + 64 x(0) x(1) x(3) x(5) + 24 x(1) x(2) + 80 x(1) x(2) x(3) + 48 x(1) x(2) x(4) + 64 x(1) x(2) x(3) x(4) + 96 x(1) x(2) x(5) + 128 x(1) x(2) x(3) x(5) + 88 x(1) x(3) + 128 x(1) x(3) x(4) + 256 x(1) x(3) x(5) + 7 x(0) x(1) - 16 x(1) + 16 x(1) x(4) + 32 x(1) x(5) + 24 x(0) x(4) + 48 x(0) x(2) x(4) + 96 x(0) x(3) x(4) + 80 x(0) x(4) x(5) + 64 x(0) x(2) x(4) x(5) + 128 x(0) x(3) x(4) x(5) + 72 x(2) x(4) + 192 x(2) x(3) x(4) + 192 x(2) x(4) x(5) + 256 x(2) x(3) x(4) x(5) + 240 x(3) x(4) + 512 x(3) x(4) x(5) - 24 x(4) + 64 x(4) x(5) + 88 x(0) x(5) + 128 x(0) x(2) x(5) + 256 x(0) x(3) x(5) + 240 x(2) x(5) + 512 x(2) x(3) x(5) + 736 x(3) x(5) - 16 x(5) - 16 x(0) + 16 x(0) x(2) + 32 x(0) x(3) - 24 x(2) + 64 x(2) x(3) - 16 x(3) + 25\n" ] } ], @@ -1343,7 +1344,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "F vars: {'x1', 'y2', 'y0', 'x2', 'x0', 'y1'}\n", + "F vars: {'x0', 'x2', 'y0', 'y1', 'x1', 'y2'}\n", "int_F vars: {0, 1, 2, 3, 4, 5}\n" ] } @@ -1370,8 +1371,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "F -> int_F: {'x0': 0, 'y0': 1, 'y1': 2, 'y2': 3, 'x1': 4, 'x2': 5}\n", - "int_F -> F: {0: 'x0', 1: 'y0', 2: 'y1', 3: 'y2', 4: 'x1', 5: 'x2'}\n" + "F -> int_F: {'y0': 0, 'x0': 1, 'y1': 2, 'y2': 3, 'x1': 4, 'x2': 5}\n", + "int_F -> F: {0: 'y0', 1: 'x0', 2: 'y1', 3: 'y2', 4: 'x1', 5: 'x2'}\n" ] } ], @@ -1421,7 +1422,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'x0': 1, 'y0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n" + "{'y0': 1, 'x0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n" ] } ], @@ -1474,7 +1475,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'x0': 1, 'y0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", + "{'y0': 1, 'x0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", "x = 3\n", "y = 3\n", "F(x, y) = 0\n" @@ -1621,7 +1622,7 @@ "F:\n", " number of variables : 6\n", " degree : 4\n", - " variables : {'x1', 'y2', 'y0', 'x2', 'x0', 'y1'}\n", + " variables : {'x0', 'x2', 'y0', 'y1', 'x1', 'y2'}\n", "deg_3_F:\n", " number of variables : 10\n", " degree : 3\n", @@ -1714,7 +1715,7 @@ "F:\n", " number of variables : 6\n", " degree : 4\n", - " variables : {'x1', 'y2', 'y0', 'x2', 'x0', 'y1'}\n", + " variables : {'x0', 'x2', 'y0', 'y1', 'x1', 'y2'}\n", "qubo_F:\n", " number of variables : 13\n", " degree : 2\n", @@ -1838,8 +1839,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "qubovert's QUBO : {('x0',): 1, ('x0', 'x1'): 2, (): -1}\n", - "D-Wave's QUBO : {('x0', 'x0'): 1, ('x0', 'x1'): 2}\n", + "qubovert's QUBO : {('x0',): 1, ('x1', 'x0'): 2, (): -1}\n", + "D-Wave's QUBO : {('x0', 'x0'): 1, ('x1', 'x0'): 2}\n", "offset : -1\n" ] } @@ -1867,7 +1868,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{(6, 6): 816, (0, 1): 279, (0, 6): -544, (1, 6): -544, (2, 6): 20, (3, 6): 40, (4, 6): 20, (7, 7): 2127, (2, 4): 781, (2, 7): -1418, (4, 7): -1418, (6, 7): 16, (8, 8): 2511, (3, 4): 1077, (3, 8): -1674, (4, 8): -1674, (6, 8): 32, (5, 6): 40, (9, 9): 2220, (2, 5): 980, (2, 9): -1480, (5, 9): -1480, (6, 9): 32, (10, 10): 3282, (3, 5): 1830, (3, 10): -2188, (5, 10): -2188, (6, 10): 64, (0, 2): 445, (11, 11): 1263, (0, 11): -842, (2, 11): -842, (3, 11): 80, (4, 11): 48, (8, 11): 64, (5, 11): 96, (10, 11): 128, (0, 3): 88, (0, 8): 128, (0, 10): 256, (0, 0): -16, (0, 4): 16, (0, 5): 32, (1, 4): 299, (1, 7): 48, (1, 8): 96, (12, 12): 825, (1, 12): -550, (4, 12): -550, (5, 12): 80, (9, 12): 64, (10, 12): 128, (3, 7): 192, (5, 7): 192, (7, 10): 256, (5, 8): 512, (4, 4): -24, (4, 5): 64, (1, 5): 88, (1, 9): 128, (1, 10): 256, (3, 9): 512, (5, 5): -16, (1, 1): -16, (1, 2): 16, (1, 3): 32, (2, 2): -24, (2, 3): 64, (3, 3): -16}\n" + "{(6, 6): 816, (0, 1): 279, (0, 6): -544, (1, 6): -544, (2, 6): 20, (3, 6): 40, (4, 6): 20, (7, 7): 2127, (2, 4): 781, (2, 7): -1418, (4, 7): -1418, (6, 7): 16, (8, 8): 2511, (3, 4): 1077, (3, 8): -1674, (4, 8): -1674, (6, 8): 32, (5, 6): 40, (9, 9): 2220, (2, 5): 980, (2, 9): -1480, (5, 9): -1480, (6, 9): 32, (10, 10): 3282, (3, 5): 1830, (3, 10): -2188, (5, 10): -2188, (6, 10): 64, (1, 2): 445, (11, 11): 1263, (1, 11): -842, (2, 11): -842, (3, 11): 80, (4, 11): 48, (8, 11): 64, (5, 11): 96, (10, 11): 128, (1, 3): 88, (1, 8): 128, (1, 10): 256, (1, 1): -16, (1, 4): 16, (1, 5): 32, (0, 4): 299, (0, 7): 48, (0, 8): 96, (12, 12): 825, (0, 12): -550, (4, 12): -550, (5, 12): 80, (9, 12): 64, (10, 12): 128, (3, 7): 192, (5, 7): 192, (7, 10): 256, (5, 8): 512, (4, 4): -24, (4, 5): 64, (0, 5): 88, (0, 9): 128, (0, 10): 256, (3, 9): 512, (5, 5): -16, (0, 0): -16, (0, 2): 16, (0, 3): 32, (2, 2): -24, (2, 3): 64, (3, 3): -16}\n" ] } ], @@ -1925,7 +1926,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "F_solution = {'x0': 1, 'y0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", + "F_solution = {'y0': 1, 'x0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", "x = 3\n", "y = 3\n", "F(x, y) = 0\n" @@ -1984,9 +1985,9 @@ "text": [ "offset : 4547.75\n", "\n", - "linear terms : {6: -202.0, 0: 43.5, 1: -29.0, 2: 370.5, 3: -5.25, 4: 346.25, 7: -530.5, 8: -626.5, 5: -53.5, 9: -554.0, 10: -819.0, 11: -314.5, 12: -205.5}\n", + "linear terms : {6: -202.0, 0: -29.0, 1: 43.5, 2: 370.5, 3: -5.25, 4: 346.25, 7: -530.5, 8: -626.5, 5: -53.5, 9: -554.0, 10: -819.0, 11: -314.5, 12: -205.5}\n", "\n", - "couplng terms : {(0, 1): 69.75, (0, 6): -136.0, (1, 6): -136.0, (2, 6): 5.0, (3, 6): 10.0, (4, 6): 5.0, (2, 4): 195.25, (2, 7): -354.5, (4, 7): -354.5, (6, 7): 4.0, (3, 4): 269.25, (3, 8): -418.5, (4, 8): -418.5, (6, 8): 8.0, (5, 6): 10.0, (2, 5): 245.0, (2, 9): -370.0, (5, 9): -370.0, (6, 9): 8.0, (3, 5): 457.5, (3, 10): -547.0, (5, 10): -547.0, (6, 10): 16.0, (0, 2): 111.25, (0, 11): -210.5, (2, 11): -210.5, (3, 11): 20.0, (4, 11): 12.0, (8, 11): 16.0, (5, 11): 24.0, (10, 11): 32.0, (0, 3): 22.0, (0, 8): 32.0, (0, 10): 64.0, (0, 4): 4.0, (0, 5): 8.0, (1, 4): 74.75, (1, 7): 12.0, (1, 8): 24.0, (1, 12): -137.5, (4, 12): -137.5, (5, 12): 20.0, (9, 12): 16.0, (10, 12): 32.0, (3, 7): 48.0, (5, 7): 48.0, (7, 10): 64.0, (5, 8): 128.0, (4, 5): 16.0, (1, 5): 22.0, (1, 9): 32.0, (1, 10): 64.0, (3, 9): 128.0, (1, 2): 4.0, (1, 3): 8.0, (2, 3): 16.0}\n" + "couplng terms : {(0, 1): 69.75, (0, 6): -136.0, (1, 6): -136.0, (2, 6): 5.0, (3, 6): 10.0, (4, 6): 5.0, (2, 4): 195.25, (2, 7): -354.5, (4, 7): -354.5, (6, 7): 4.0, (3, 4): 269.25, (3, 8): -418.5, (4, 8): -418.5, (6, 8): 8.0, (5, 6): 10.0, (2, 5): 245.0, (2, 9): -370.0, (5, 9): -370.0, (6, 9): 8.0, (3, 5): 457.5, (3, 10): -547.0, (5, 10): -547.0, (6, 10): 16.0, (1, 2): 111.25, (1, 11): -210.5, (2, 11): -210.5, (3, 11): 20.0, (4, 11): 12.0, (8, 11): 16.0, (5, 11): 24.0, (10, 11): 32.0, (1, 3): 22.0, (1, 8): 32.0, (1, 10): 64.0, (1, 4): 4.0, (1, 5): 8.0, (0, 4): 74.75, (0, 7): 12.0, (0, 8): 24.0, (0, 12): -137.5, (4, 12): -137.5, (5, 12): 20.0, (9, 12): 16.0, (10, 12): 32.0, (3, 7): 48.0, (5, 7): 48.0, (7, 10): 64.0, (5, 8): 128.0, (4, 5): 16.0, (0, 5): 22.0, (0, 9): 32.0, (0, 10): 64.0, (3, 9): 128.0, (0, 2): 4.0, (0, 3): 8.0, (2, 3): 16.0}\n" ] } ], @@ -2045,7 +2046,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "F_solution = {'x0': 1, 'y0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", + "F_solution = {'y0': 1, 'x0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", "x = 3\n", "y = 3\n", "F(x, y) = 0\n" @@ -2095,11 +2096,11 @@ "name": "stdout", "output_type": "stream", "text": [ - " state: {'y1': 0, 'x0': 1, 'y0': 1, 'y2': 0, 'x1': 0, 'x2': 0}\n", + " state: {'y1': 0, 'y0': 1, 'x0': 1, 'y2': 0, 'x1': 0, 'x2': 0}\n", " value: 0\n", " spin: False\n", "\n", - "F_solution = {'y1': 0, 'x0': 1, 'y0': 1, 'y2': 0, 'x1': 0, 'x2': 0}\n", + "F_solution = {'y1': 0, 'y0': 1, 'x0': 1, 'y2': 0, 'x1': 0, 'x2': 0}\n", "x = 3\n", "y = 3\n", "F(x, y) = 0\n" @@ -2160,7 +2161,7 @@ { "data": { "text/plain": [ - "286" + "272" ] }, "execution_count": 70, @@ -2206,7 +2207,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYUAAAEaCAYAAAD+E0veAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAWdUlEQVR4nO3dfbRldX3f8fdHHrQVEJAJwoCOUWLDUwYyUtrEh6hVIMbBxBBteFBp0SW2uIzLoGmrS+MK0QCNtTGiGAejQdQoo2KUoNZlKuKgU2QGKaMOhXFgBhTEULXAt3+cfX85Dufee+7MPefcO/f9Wuusu/dv7/07373vzPnsp7NvqgpJkgAeMekCJEkLh6EgSWoMBUlSYyhIkhpDQZLUGAqSpMZQkCQ1hoIkqTEUNGdJNid5zqTrmJLkKUnWJ7kvyX+c574X1LpOJ8mGJM8cYr6RbSvtHgyFJSjJ3yV5y4D21UnuSLLnJOraBa8HvlhV+1bVO3ecmOSAJJXkx32vLUkyYN55C4FRBcqgfqvqqKr60hCLz7itFpq+391Xd2j/yyQXT6qu3ZmhsDStAU4f8KF4BvChqnpgAjXtiicAG2aYvhL4QVXt0/daXkvzGS+zbauFZiVwB3Bkksf1tR8HrJ9MSbs3Q2Fp+iTwWOBpUw1JDgCeD1zWjZ+f5DvdaYaNSV44qKNuL+7JfeMfSPLHfeOHJvl4ku1Jvtd/yiLJH3Z77PcluTnJs6d5j19O8qUk93SnSV7QN+0LwG8A7+qOAH5pQBcrgY2zbZQkHwQeD3yq6+v1U8snuSHJvUk+kuRRfcsM3E4z9LXjew7cBnPtt//oYYY+d9xWf5jk4zvU884kfz5NrW9P8sm+8XckuSbJfkkeTHJI37Sjk2xNsu9s230WK4F1wNXA6q7vPYBjgG/uYt8apKp8LcEX8F7gfX3jrwDW943/LnAovR2H3wP+ETikm7YZeE43XMCT+5b7APDH3fAjgOuB/wLsDfwi8F3gecBTgNuAQ7t5VwBPGlDnXsAm4I1dH88C7gOe0jfPl4B/N8O6Xga8Z8jt0tatb/y6blscCNwEvHKu22ma95p2G8y136m22bZr/7YCDun63b8b3xPYBvzqNPU+FriX3l76K4FvAY/ppm0AfrNv3k8D/2GH5T8N3DPN69Mz/O7eCpwOfLZrOwr4CbDXpP8f7Y4vjxSWrjXAi/r2es/s2gCoqo9W1fer6qGq+ghwC3DCHN/jqcCyqnpLVf2sqr5LL4xeDDwIPJLeaYG9qmpzVX1nQB8nAvsAF3R9fIHeh8tL5lDHSuCM7kjjniTfmuN6vLPbFj8APtX1B+zydpp2G+xCv8NuV6pqK/BlegEEcBJwV1VdP838dwMX0/t38gbglKq6t5v8deB4gCRPB44E3rPD8s+vqv2neT1/mvVZSe800WeAp3VHHiuBDVX1/2bfHJorQ2GJqqqvAHcBpyZ5Er0PnA9PTU9yZneXyj1J7gGOBg6a49s8ATi078P4Hnp7/AdX1SbgNcCbgW1JLk9y6IA+DgVuq6qH+tpuBZYPU0CSRwK/DDy97wPomDmuxx19w/fTC6mp/nd6O820DXa23zls1ylr6O2F0/384Cxv8U16p27eUFW39bW3UADeDvznqvrZbPXOpO93t76qfkjviO1kvJ4wUobC0nYZvSOE04HPVdWdAEmeQG+P/tXAY6tqf+BG4GF369D7kPznfeP9FwNvA763wx7hvlV1CkBVfbiqfp1eeBTwpwP6/z5weJL+f6uPB7YMuY5HAw8BNww5/9AXn4fYTrP2NWgb7Gq/Q27XKZ8Ejk1yNL1rSh+absYkxwDvphckL99h8teB45P8DvAo+nYw+pb/bH7+DrD+12cHvOXR9P59fbev1lPphYLXE0bEUFjaLqN3Hvrf03fqCHg0vQ+T7QBJXkbvP+gg64F/m2SPJCcBz+ibdh1wX3dB85918xyd5Knp3S//rG5v8CfA/6X34b2jr9H7YHh9kr3Suxf/t4DLh1zH44Ab57DXeie9ax/DmG07zdjXDNtgp/udw3YFoKp+AnyM3of4dVX1f6bpdzm9U2evBF4FHJOf/17E/6K3Q3AhvaOIhwVXVZ1cP38HWP/r5AFvexxwQ19fa4FT8EhhpAyFJayqNgP/k96H0Nq+9o30/nN/ld4H0DHAP0zTzXn0PqTvAX6f3t7cVD8P0tv7XAl8j97pqvcBj6F33vuCru0O4Bfonafescafdf2f3M37F8CZVfXtIVdz6u6VYf0J8J+60zavm2nGIbbTbH0N3Aa72O9Q23UHa7r3GHjqKMl+wFXARVW1tqruB94BvG1qnqr6Kb0Lz5uratBe/86Yup4w9R6b6V1Q359eCGkEMiDQJS0hSR4PfBt4XFX9aCf72JveXWKnVdW181mfxssjBWkJ667VvBa4fGcDofMm4B8MhMVvsT3OQNI8SfJoeqenbqV3O+rO9HE88EV6F/IHfsFRi4unjyRJjaePJEnNoj59dNBBB9WKFSsmXYYkLSrXX3/9XVW1bNC0RR0KK1asYN26udxtKElKcut00zx9JElqDAVJUmMoSJIaQ0GS1BgKkqTGUJAkNYaCJKkxFCRJjaEgSWoW9TeapYVsxfmfmcj7br7gNyfyvto9eKQgSWoMBUlSYyhIkhpDQZLUGAqSpMZQkCQ1hoIkqTEUJEmNoSBJagwFSVJjKEiSGkNBktSMLBSSHJ7ki0k2JtmQ5Lyu/c1JtiRZ371O6VvmDUk2Jbk5yfNGVZskabBRPiX1AeAPquobSfYFrk9ydTft4qr6s/6ZkxwJvBg4CjgU+Pskv1RVD46wRklSn5EdKVTV1qr6Rjd8H3ATsHyGRVYDl1fVT6vqe8Am4IRR1SdJerixXFNIsgI4Dvha1/TqJDckeX+SA7q25cBtfYvdzoAQSXJOknVJ1m3fvn2EVUvS0jPyUEiyD/Bx4DVV9SPg3cCTgJXAVuDCufRXVZdU1aqqWrVs2bJ5r1eSlrKRhkKSvegFwoeq6m8BqurOqnqwqh4C3ss/nSLaAhzet/hhXZskaUxGefdRgEuBm6rqor72Q/pmeyFwYze8FnhxkkcmeSJwBHDdqOqTJD3cKO8++jXgDOBbSdZ3bW8EXpJkJVDAZuAVAFW1IckVwEZ6dy6d651HkjReIwuFqvoKkAGTrpphmbcBbxtVTZKkmfmNZklSYyhIkhpDQZLUGAqSpMZQkCQ1hoIkqTEUJEmNoSBJagwFSVJjKEiSGkNBktQYCpKkxlCQJDWGgiSpMRQkSY2hIElqDAVJUmMoSJIaQ0GS1BgKkqTGUJAkNYaCJKkxFCRJjaEgSWoMBUlSYyhIkhpDQZLUGAqSpMZQkCQ1IwuFJIcn+WKSjUk2JDmvaz8wydVJbul+HtC1J8k7k2xKckOS40dVmyRpsFEeKTwA/EFVHQmcCJyb5EjgfOCaqjoCuKYbBzgZOKJ7nQO8e4S1SZIGGFkoVNXWqvpGN3wfcBOwHFgNrOlmWwOc2g2vBi6rnmuB/ZMcMqr6JEkPN5ZrCklWAMcBXwMOrqqt3aQ7gIO74eXAbX2L3d61SZLGZOShkGQf4OPAa6rqR/3TqqqAmmN/5yRZl2Td9u3b57FSSdJIQyHJXvQC4UNV9bdd851Tp4W6n9u69i3A4X2LH9a1/ZyquqSqVlXVqmXLlo2ueElagkZ591GAS4GbquqivklrgbO64bOAK/vaz+zuQjoRuLfvNJMkaQz2HGHfvwacAXwryfqu7Y3ABcAVSc4GbgVO66ZdBZwCbALuB142wtokSQOMLBSq6itAppn87AHzF3DuqOqRJM3ObzRLkhpDQZLUGAqSpMZQkCQ1hoIkqTEUJEmNoSBJagwFSVJjKEiSGkNBktQYCpKkxlCQJDWGgiSpMRQkSY2hIElqDAVJUmMoSJIaQ0GS1BgKkqTGUJAkNYaCJKkxFCRJjaEgSWoMBUlSYyhIkpqhQiHJeUn2S8+lSb6R5LmjLk6SNF7DHim8vKp+BDwXOAA4A7hgZFVJkiZi2FBI9/MU4INVtaGvTZK0mxg2FK5P8nl6ofC5JPsCD42uLEnSJAwbCmcD5wNPrar7gb2Bl820QJL3J9mW5Ma+tjcn2ZJkffc6pW/aG5JsSnJzkuftxLpIknbRsKFwdVV9o6ruAaiqu4GLZ1nmA8BJA9ovrqqV3esqgCRHAi8GjuqW+YskewxZmyRpnswYCkkeleRA4KAkByQ5sHutAJbPtGxVfRn4wZB1rAYur6qfVtX3gE3ACUMuK0maJ7MdKbwCuB74F93PqdeVwLt28j1fneSG7vTSAV3bcuC2vnluZ5bQkSTNvxlDoar+vKqeCLyuqn6xqp7YvX6lqnYmFN4NPAlYCWwFLpxrB0nOSbIuybrt27fvRAmSpOnsOcxMVfXfkvxrYEX/MlV12VzerKrunBpO8l7g093oFuDwvlkP69oG9XEJcAnAqlWrai7vL0ma2VChkOSD9Pbw1wMPds0FzCkUkhxSVVu70RcCU3cmrQU+nOQi4FDgCOC6ufQtSdp1Q4UCsAo4sqqG3jNP8jfAM+ldpL4deBPwzCQr6QXKZnrXLKiqDUmuADYCDwDnVtWDg/qVJI3OsKFwI/A4etcBhlJVLxnQfOkM878NeNuw/UuS5t+woXAQsDHJdcBPpxqr6gUjqUqSNBHDhsKbR1mEJGlhGPbuo/8x6kIkSZM37N1H99G7OAy95x7tBfxjVe03qsIkSeM37JHCvlPDSULvsRQnjqooSdJkzPnPcVbPJwGfZCpJu5lhTx/9dt/oI+h9b+EnI6lIkjQxw9599Ft9ww/Q++LZ6nmvRpI0UcNeU5jxD+pIknYPQ11TSHJYkk90f0ltW5KPJzls1MVJksZr2AvNf0XvoXWHdq9PdW2SpN3IsKGwrKr+qqoe6F4fAJaNsC5J0gQMGwp3Jzk9yR7d63Tg7lEWJkkav2FD4eXAacAd9J6U+iLgpSOqSZI0IcPekvoW4Kyq+iFAkgOBP6MXFpKk3cSwRwrHTgUCQFX9ADhuNCVJkiZl2FB4RJIDpka6I4VhjzIkSYvEsB/sFwJfTfLRbvx38a+kSdJuZ9hvNF+WZB3wrK7pt6tq4+jKkiRNwtCngLoQMAgkaTc250dnS5J2X4aCJKkxFCRJjaEgSWoMBUlSYyhIkhpDQZLUGAqSpMZQkCQ1hoIkqRlZKCR5f5JtSW7sazswydVJbul+HtC1J8k7k2xKckOS40dVlyRpeqM8UvgAcNIObecD11TVEcA13TjAycAR3esc4N0jrEuSNI2RhUJVfRn4wQ7Nq4E13fAa4NS+9suq51pg/ySHjKo2SdJg476mcHBVbe2G7wAO7oaXA7f1zXd71/YwSc5Jsi7Juu3bt4+uUklagiZ2obmqCqidWO6SqlpVVauWLVs2gsokaekadyjcOXVaqPu5rWvfAhzeN99hXZskaYzGHQprgbO64bOAK/vaz+zuQjoRuLfvNJMkaUyG/strc5Xkb4BnAgcluR14E3ABcEWSs4FbgdO62a8CTgE2AfcDLxtVXZKk6Y0sFKrqJdNMevaAeQs4d1S1SJKG4zeaJUmNoSBJagwFSVJjKEiSGkNBktQYCpKkxlCQJDWGgiSpMRQkSY2hIElqDAVJUmMoSJIaQ0GS1BgKkqTGUJAkNYaCJKkxFCRJjaEgSWoMBUlSYyhIkhpDQZLUGAqSpMZQkCQ1hoIkqTEUJEmNoSBJagwFSVJjKEiSGkNBktTsOYk3TbIZuA94EHigqlYlORD4CLAC2AycVlU/nER9krRUTfJI4TeqamVVrerGzweuqaojgGu6cUnSGC2k00ergTXd8Brg1AnWIklL0qRCoYDPJ7k+yTld28FVtbUbvgM4eNCCSc5Jsi7Juu3bt4+jVklaMiZyTQH49arakuQXgKuTfLt/YlVVkhq0YFVdAlwCsGrVqoHzSJJ2zkSOFKpqS/dzG/AJ4ATgziSHAHQ/t02iNklaysYeCkkenWTfqWHgucCNwFrgrG62s4Arx12bJC11kzh9dDDwiSRT7//hqvq7JF8HrkhyNnArcNoEapOkJW3soVBV3wV+ZUD73cCzx12PJOmfLKRbUiVJE2YoSJIaQ0GS1BgKkqTGUJAkNYaCJKkxFCRJjaEgSWoMBUlSYyhIkhpDQZLUGAqSpMZQkCQ1hoIkqTEUJEmNoSBJagwFSVJjKEiSGkNBktQYCpKkxlCQJDWGgiSpMRQkSY2hIElqDAVJUmMoSJIaQ0GS1BgKkqTGUJAkNQsuFJKclOTmJJuSnD/peiRpKVlQoZBkD+C/AycDRwIvSXLkZKuSpKVjQYUCcAKwqaq+W1U/Ay4HVk+4JklaMvacdAE7WA7c1jd+O/Av+2dIcg5wTjf64yQ3j6m2+XQQcNekixgz13lM8qfjfsfG3/Hi8YTpJiy0UJhVVV0CXDLpOnZFknVVtWrSdYyT67z7W2rrC7vnOi+000dbgMP7xg/r2iRJY7DQQuHrwBFJnphkb+DFwNoJ1yRJS8aCOn1UVQ8keTXwOWAP4P1VtWHCZY3Coj79tZNc593fUltf2A3XOVU16RokSQvEQjt9JEmaIENBktQYCmOQ5MAkVye5pft5wAzz7pfk9iTvGmeN822YdU6yMslXk2xIckOS35tErbtitseyJHlkko9007+WZMX4q5xfQ6zza5Ns7H6n1ySZ9p74xWLYx+8k+Z0klWTR3qZqKIzH+cA1VXUEcE03Pp23Al8eS1WjNcw63w+cWVVHAScB/zXJ/mOscZcM+ViWs4EfVtWTgYuByX21bB4Muc7fBFZV1bHAx4C3j7fK+TXs43eS7AucB3xtvBXOL0NhPFYDa7rhNcCpg2ZK8qvAwcDnx1TXKM26zlX1v6vqlm74+8A2YNnYKtx1wzyWpX87fAx4dpKMscb5Nus6V9UXq+r+bvRaet83WsyGffzOW+mF/k/GWdx8MxTG4+Cq2toN30Hvg//nJHkEcCHwunEWNkKzrnO/JCcAewPfGXVh82jQY1mWTzdPVT0A3As8dizVjcYw69zvbOCzI61o9GZd5yTHA4dX1WfGWdgoLKjvKSxmSf4eeNyASX/UP1JVlWTQfcCvAq6qqtsXy47kPKzzVD+HAB8Ezqqqh+a3Sk1KktOBVcAzJl3LKHU7dBcBL51wKfPCUJgnVfWc6aYluTPJIVW1tfsA3DZgtn8FPC3Jq4B9gL2T/LiqFuzflJiHdSbJfsBngD+qqmtHVOqoDPNYlql5bk+yJ/AY4O7xlDcSQz2KJslz6O0cPKOqfjqm2kZltnXeFzga+FK3Q/c4YG2SF1TVurFVOU88fTQea4GzuuGzgCt3nKGqfr+qHl9VK+idQrpsIQfCEGZd5+5RJp+gt64fG2Nt82WYx7L0b4cXAV+oxf2N0VnXOclxwHuAF1TVwJ2BRWbGda6qe6vqoKpa0f3/vZbeui+6QABDYVwuAP5NkluA53TjJFmV5H0TrWx0hlnn04CnAy9Nsr57rZxMuXPXXSOYeizLTcAVVbUhyVuSvKCb7VLgsUk2Aa9l5jvPFrwh1/kd9I52P9r9Thf188uGXOfdho+5kCQ1HilIkhpDQZLUGAqSpMZQkCQ1hoIkqTEUJEmNoSBJagwFaZ4leUWSrX1fyPvrSdckDcsvr0nzrPsDSd+sqksnXYs0Vx4pSPPvWGD9pIuQdoZHCtI8S3I3vadoPgTcNdPTZKWFxkdnS/MoyeHAHd2fopQWHU8fSfPrGGDDpIuQdpahIM2vYzEUtIgZCtL8OgbYOOkipJ3lhWZJUuORgiSpMRQkSY2hIElqDAVJUmMoSJIaQ0GS1BgKkqTm/wOPjIngW7f9FwAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] @@ -2243,7 +2244,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYUAAAEaCAYAAAD+E0veAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAWxklEQVR4nO3dfbRddX3n8fdHAj6gNQGuKSTBMEq1LB+AddVYO05LrApawziWwakSKTPxD1Scdo2iM2umutq1cK1pFXWGtRipBosK4gNUqUpBZ6YzFQ0PIg86RoQmkZArEESppch3/ji/bE7CSXISsu+55L5fa5119v7t39nne/dKzufs3344qSokSQJ4wqQLkCTNHYaCJKljKEiSOoaCJKljKEiSOoaCJKljKEiSOoaCJKljKOhxIcntSV4x6Tq2SfKcJDckuT/JOyZdz2PxWLZtkk8k+ZN9XZMmx1DQrEjylSTvH9G+KsnmJAsmUddj8C7g61X1tKr68I4LkyxKUkl+NvTYlCQTqFUam6Gg2bIWeNOID8U3AxdV1UMTqOmxeCZw8y6WHwvcU1VPHXosKe8roznOUNBs+SJwKPDPtzUkWQS8FriwzZ+d5IdtSOaWJP9y1IraN/BnD81vN4SR5Igkn0syk+RHw8M7Sd7dvrHfn+T7SVbu5D1+Pck3kmxNcnOS1w0tuxr4beCjbQ/g10as4ljglnE2zM5q2tX2aEM+/yHJjUl+nuSCJIuT/HXr/zdt+w73f09bz71JPp7kSTupZ1fb77gk17X3uBgYuQ49fhkKmhVV9Q/AJcBpQ82nAN+rqu+0+R8yCI2nA+8D/jLJ4XvyPkmeAPwV8B1gCbASeGeSVyV5DvA24EVV9TTgVcDtI9ZxYFvH14BnAG8HLmqvp6pOAP438La2B/D/RpRyHGOEwm5q2t32+FfA7wC/Bvwu8NfAe4EpBv+3dzzW8ftt/c9qr/lPI+rZ1fY7iEG4fxI4BPhsq0H7EUNBs2kt8Iahb6intTYAquqzVfXjqnq4qi4GfgC8eA/f40XAVFW9v6oerKrbgP8BnAr8EngicEySA6vq9qr64Yh1rACeCpzT1nE18CXgjXtQx7HAm9uextYk391Jv53WNMb2+EhV3VVVmxiE1DVVdX1V/QL4AoNgGvbRqtpQVfcAf7qTv2dX228FcCDwoar6p6q6FPj2HmwTPQ4YCpo1VfW3wE+Ak5M8i8EH3Ke2LU9yWjujZ2uSrcDzgMP28G2eCRwx9GG8lcG358VVtR54J/DHwJYkn0lyxIh1HAFsqKqHh9ruYPDNebeSPBH4deDlVbWwPZ4/qu+uahpje9w1NP0PI+afusPbbdjh7xn1t+90+7X+m3Y4LnLHqL9Lj1+GgmbbhQz2EN4EfLWq7gJI8kwG30jfBhxaVQuBm4BRZ+s8ADxlaP5Xh6Y3AD8a+jBe2M4QOgmgqj5VVb/J4MOvgA+MWP+PgWVtKGWbI4FNY/6NzwMeBm4cp/OomvZwe4xr2dD0kQz+zh3tavvdCSzZ4WSBIx9DPZqDDAXNtguBVwD/jqGhI+BgBh+IMwBJTmfw4TrKDcC/SXJAklcD/2Jo2beA+9vB2ye3Ps9L8qIMri04oX2T/wWDb9MPj1j/NQyC511JDkzyWwzG7D8z5t94HHBTVT24u467qGlPtse4zkyyNMkhwH8ELh7RZ6fbD/g74CHgHW27vJ49H97THGcoaFZV1e3A/2XwoXf5UPstwJ8x+OC5C3g+8H92spqzGHxIb2Vw8PSLQ+v5JYMzmo4FfsRguOpjDA7WPhE4p7VtZnAQ+T0janywrf/E1ve/A6dV1ffG/DOPBdaN2XdkTXu4Pcb1KQYHz29jcBD7URed7Wr7te3yeuAtwD3AvwY+/xhr0hwTT5uW9n9Jbgf+bVX9zaRr0dzmnoIkqWMoSJI6Dh9JkjruKUiSOr3dmbJdvj98yts/A/4zg1MSLwaWM7ic/5Squred+3wucBKD0wHfUlXX7eo9DjvssFq+fPk+r12S9mfXXnvtT6pqatSyWRk+SnIAgwt/XgKcyeDukeckORtYVFXvTnISg3vMnNT6nVtVL9nVeqenp2vdunHP/JMkASS5tqqmRy2breGjlcAPq+oOYBWPXLS0Fji5Ta8CLqyBbwIL9/RmaJKkx2a2QuFU4NNtenFV3dmmNzO4pwoM7iszfG+WjYy410ySNUnWJVk3MzPTV72SNC/1HgrtdruvY3Cb3e20G2vt0fhVVZ1fVdNVNT01NXJITJK0l2ZjT+FE4LptNz4D7to2LNSet7T2TWx/w66ljH8DMknSPjAbofBGHhk6gsH9bla36dXAZUPtp2VgBXDf0DCTJGkW9Ppj6UkOZvDLUG8daj4HuCTJGQzuxX5Ka7+CwZlH6xmcknp6n7VJkh6t11Coqp8z+F3e4ba7GZyNtGPfYnC6qiRpQryiWZLUMRQkSZ1eh4802vKzvzyR9739nNdM5H0lPX64pyBJ6hgKkqSOoSBJ6hgKkqSOoSBJ6hgKkqSOoSBJ6hgKkqSOoSBJ6hgKkqSOoSBJ6hgKkqSOoSBJ6hgKkqSOoSBJ6hgKkqSOoSBJ6hgKkqROr6GQZGGSS5N8L8mtSV6a5JAkVyb5QXte1PomyYeTrE9yY5Lj+6xNkvRofe8pnAt8paqeC7wQuBU4G7iqqo4GrmrzACcCR7fHGuC8nmuTJO2gt1BI8nTg5cAFAFX1YFVtBVYBa1u3tcDJbXoVcGENfBNYmOTwvuqTJD1an3sKRwEzwMeTXJ/kY0kOBhZX1Z2tz2ZgcZteAmwYev3G1radJGuSrEuybmZmpsfyJWn+6TMUFgDHA+dV1XHAz3lkqAiAqiqg9mSlVXV+VU1X1fTU1NQ+K1aS1G8obAQ2VtU1bf5SBiFx17Zhofa8pS3fBCwbev3S1iZJmiW9hUJVbQY2JHlOa1oJ3AJcDqxubauBy9r05cBp7SykFcB9Q8NMkqRZsKDn9b8duCjJQcBtwOkMguiSJGcAdwCntL5XACcB64EHWl9J0izqNRSq6gZgesSilSP6FnBmn/VIknbNK5olSR1DQZLUMRQkSR1DQZLUMRQkSR1DQZLUMRQkSR1DQZLUMRQkSR1DQZLUMRQkSR1DQZLUMRQkSR1DQZLUMRQkSR1DQZLUMRQkSR1DQZLUMRQkSR1DQZLUMRQkSR1DQZLU6TUUktye5LtJbkiyrrUdkuTKJD9oz4tae5J8OMn6JDcmOb7P2iRJjzYbewq/XVXHVtV0mz8buKqqjgauavMAJwJHt8ca4LxZqE2SNGQSw0ergLVtei1w8lD7hTXwTWBhksMnUJ8kzVt9h0IBX0tybZI1rW1xVd3ZpjcDi9v0EmDD0Gs3trbtJFmTZF2SdTMzM33VLUnz0oKe1/+bVbUpyTOAK5N8b3hhVVWS2pMVVtX5wPkA09PTe/RaSdKu9bqnUFWb2vMW4AvAi4G7tg0LtectrfsmYNnQy5e2NknSLOktFJIcnORp26aBVwI3AZcDq1u31cBlbfpy4LR2FtIK4L6hYSZJ0izoc/hoMfCFJNve51NV9ZUk3wYuSXIGcAdwSut/BXASsB54ADi9x9okSSP0FgpVdRvwwhHtdwMrR7QXcGZf9UiSds8rmiVJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktTpPRSSHJDk+iRfavNHJbkmyfokFyc5qLU/sc2vb8uX912bJGl7s7GncBZw69D8B4APVtWzgXuBM1r7GcC9rf2DrZ8kaRb1GgpJlgKvAT7W5gOcAFzauqwFTm7Tq9o8bfnK1l+SNEv63lP4EPAu4OE2fyiwtaoeavMbgSVtegmwAaAtv6/1306SNUnWJVk3MzPTZ+2SNO+MFQpJzkryKxm4IMl1SV65m9e8FthSVdfuk0qbqjq/qqaranpqampfrlqS5r1x9xT+oKp+CrwSWAS8GThnN695GfC6JLcDn2EwbHQusDDJgtZnKbCpTW8ClgG05U8H7h6zPknSPjBuKGwb2z8J+GRV3TzUNlJVvaeqllbVcuBU4Oqq+n3g68AbWrfVwGVt+vI2T1t+dVXVmPVJkvaBcUPh2iRfYxAKX03yNB45TrCn3g38YZL1DI4ZXNDaLwAObe1/CJy9l+uXJO2lBbvvAgxOFz0WuK2qHkhyKHD6uG9SVd8AvtGmbwNePKLPL4DfG3edkqR9b9w9hSur6rqq2gpQVXczuJZAkrQf2eWeQpInAU8BDkuyiEeOI/wKj5xKKknaT+xu+OitwDuBI4BreSQUfgp8tMe6JEkTsMtQqKpzgXOTvL2qPjJLNUmSJmSsA81V9ZEkvwEsH35NVV3YU12SpAkYKxSSfBJ4FnAD8MvWXIChIEn7kXFPSZ0GjvFiMknav417SupNwK/2WYgkafLG3VM4DLglybeAf9zWWFWv66UqSdJEjBsKf9xnEZKkuWHcs4/+Z9+FSJImb9yzj+5ncLYRwEHAgcDPq+pX+ipMkjT7xt1TeNq26fYTmauAFX0VJUmajD3+Oc4a+CLwqh7qkSRN0LjDR68fmn0Cg+sWftFLRZKkiRn37KPfHZp+CLidwRCSJGk/Mu4xhbF/UEeS9Pg11jGFJEuTfCHJlvb4XJKlfRcnSZpd4x5o/jhwOYPfVTgC+KvWJknaj4wbClNV9fGqeqg9PgFM9ViXJGkCxg2Fu5O8KckB7fEm4O4+C5Mkzb5xQ+EPgFOAzcCdwBuAt+zqBUmelORbSb6T5OYk72vtRyW5Jsn6JBcnOai1P7HNr2/Ll+/l3yRJ2kvjhsL7gdVVNVVVz2AQEu/bzWv+ETihql4IHAu8OskK4APAB6vq2cC9wBmt/xnAva39g62fJGkWjRsKL6iqe7fNVNU9wHG7ekG78vlnbfbA9ijgBODS1r4WOLlNr2rztOUr2y01JEmzZNxQeEKSRdtmkhzCGNc4tOMPNwBbgCuBHwJbq+qh1mUjsKRNLwE2ALTl9wGHjlmfJGkfGPeK5j8D/i7JZ9v87wF/ursXVdUvgWOTLAS+ADx3r6ockmQNsAbgyCOPfKyrkyQNGWtPoaouBF4P3NUer6+qT477JlW1Ffg68FJgYZJtYbQU2NSmNwHLANrypzPiDKeqOr+qpqtqemrKs2IlaV8a+y6pVXVLVX20PW7ZXf8kU20PgSRPBn4HuJVBOLyhdVsNXNamL2/ztOVXV1UhSZo14w4f7Y3DgbVJDmAQPpdU1ZeS3AJ8JsmfANcDF7T+FwCfTLIeuAc4tcfaJEkj9BYKVXUjI85QqqrbgBePaP8Fg2MVkqQJ2eMf2ZEk7b8MBUlSx1CQJHX6PNA8py0/+8uTLkGS5hz3FCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktQxFCRJHUNBktTpLRSSLEvy9SS3JLk5yVmt/ZAkVyb5QXte1NqT5MNJ1ie5McnxfdUmSRqtzz2Fh4A/qqpjgBXAmUmOAc4Grqqqo4Gr2jzAicDR7bEGOK/H2iRJI/QWClV1Z1Vd16bvB24FlgCrgLWt21rg5Da9CriwBr4JLExyeF/1SZIebVaOKSRZDhwHXAMsrqo726LNwOI2vQTYMPSyja1tx3WtSbIuybqZmZneapak+aj3UEjyVOBzwDur6qfDy6qqgNqT9VXV+VU1XVXTU1NT+7BSSVKvoZDkQAaBcFFVfb4137VtWKg9b2ntm4BlQy9f2tokSbOkz7OPAlwA3FpVfz606HJgdZteDVw21H5aOwtpBXDf0DCTJGkWLOhx3S8D3gx8N8kNre29wDnAJUnOAO4ATmnLrgBOAtYDDwCn91ibJGmE3kKhqv4WyE4WrxzRv4Az+6pHkrR7XtEsSeoYCpKkjqEgSeoYCpKkjqEgSeoYCpKkjqEgSeoYCpKkjqEgSeoYCpKkjqEgSeoYCpKkjqEgSeoYCpKkjqEgSeoYCpKkjqEgSeoYCpKkjqEgSeoYCpKkjqEgSeoYCpKkTm+hkOQvkmxJctNQ2yFJrkzyg/a8qLUnyYeTrE9yY5Lj+6pLkrRzfe4pfAJ49Q5tZwNXVdXRwFVtHuBE4Oj2WAOc12NdkqSd6C0Uqup/Affs0LwKWNum1wInD7VfWAPfBBYmObyv2iRJo832MYXFVXVnm94MLG7TS4ANQ/02trZHSbImybok62ZmZvqrVJLmoYkdaK6qAmovXnd+VU1X1fTU1FQPlUnS/DXboXDXtmGh9ryltW8Clg31W9raJEmzaLZD4XJgdZteDVw21H5aOwtpBXDf0DCTJGmWLOhrxUk+DfwWcFiSjcB/Ac4BLklyBnAHcErrfgVwErAeeAA4va+6JEk711soVNUbd7Jo5Yi+BZzZVy2SpPF4RbMkqWMoSJI6hoIkqWMoSJI6hoIkqWMoSJI6hoIkqWMoSJI6vV28Jkn7u+Vnf3li7337Oa/pZb3uKUiSOoaCJKljKEiSOoaCJKljKEiSOoaCJKljKEiSOoaCJKljKEiSOoaCJKljKEiSOoaCJKljKEiSOnMqFJK8Osn3k6xPcvak65Gk+WbOhEKSA4D/BpwIHAO8Mckxk61KkuaXORMKwIuB9VV1W1U9CHwGWDXhmiRpXplLP7KzBNgwNL8ReMmOnZKsAda02Z8l+f5evt9hwE/28rWPS/nALhfPu+2xG26PR7gttjcntsdu/j/vzjN3tmAuhcJYqup84PzHup4k66pqeh+UtF9we2zP7fEIt8X29vftMZeGjzYBy4bml7Y2SdIsmUuh8G3g6CRHJTkIOBW4fMI1SdK8MmeGj6rqoSRvA74KHAD8RVXd3ONbPuYhqP2M22N7bo9HuC22t19vj1TVpGuQJM0Rc2n4SJI0YYaCJKkzL0PB22kMJFmW5OtJbklyc5KzJl3TXJDkgCTXJ/nSpGuZtCQLk1ya5HtJbk3y0knXNClJ/n37f3JTkk8nedKka+rDvAsFb6exnYeAP6qqY4AVwJnzeFsMOwu4ddJFzBHnAl+pqucCL2SebpckS4B3ANNV9TwGJ8OcOtmq+jHvQgFvp9Gpqjur6ro2fT+D//BLJlvVZCVZCrwG+Nika5m0JE8HXg5cAFBVD1bV1slWNVELgCcnWQA8BfjxhOvpxXwMhVG305jXH4QASZYDxwHXTLaSifsQ8C7g4UkXMgccBcwAH2/DaR9LcvCki5qEqtoE/Ffg74E7gfuq6muTraof8zEUtIMkTwU+B7yzqn466XomJclrgS1Vde2ka5kjFgDHA+dV1XHAz4F5eQwuySIGIwpHAUcAByd502Sr6sd8DAVvpzEkyYEMAuGiqvr8pOuZsJcBr0tyO4NhxROS/OVkS5qojcDGqtq293gpg5CYj14B/KiqZqrqn4DPA78x4Zp6MR9DwdtpNEnCYLz41qr680nXM2lV9Z6qWlpVyxn8u7i6qvbLb4PjqKrNwIYkz2lNK4FbJljSJP09sCLJU9r/m5Xspwfd58xtLmbLBG6nMZe9DHgz8N0kN7S291bVFROsSXPL24GL2heo24DTJ1zPRFTVNUkuBa5jcNbe9eynt7vwNheSpM58HD6SJO2EoSBJ6hgKkqSOoSBJ6hgKkqSOoSBJ6hgKkqSOoSDtY0nemuTOJDe0x3y+VYYeZ7x4TdrHknwUuL6qLph0LdKeck9B2vdeANyw217SHOSegrSPJbmbwZ13HwZ+UlWvmHBJ0tjm3Q3xpD4lWQZsrqoXTLoWaW84fCTtW88H5utdd7UfMBSkfesFGAp6HDMUpH3r+czfH6LRfsADzZKkjnsKkqSOoSBJ6hgKkqSOoSBJ6hgKkqSOoSBJ6hgKkqTO/wduz9vso7PVaQAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] @@ -2306,7 +2307,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "F_solution = {'x0': 1, 'y0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", + "F_solution = {'y0': 1, 'x0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", "x = 3\n", "y = 3\n", "F(x, y) = 0\n" @@ -2338,7 +2339,7 @@ "name": "stdout", "output_type": "stream", "text": [ - " state: {'x0': 1, 'y0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", + " state: {'y0': 1, 'x0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", " value: 0\n", " spin: False\n" ] @@ -2366,7 +2367,7 @@ "output_type": "stream", "text": [ "1000\n", - "75\n" + "72\n" ] } ], @@ -2396,10 +2397,10 @@ "name": "stdout", "output_type": "stream", "text": [ - " state: {'x0': 1, 'y0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", + " state: {'y0': 1, 'x0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", " value: 0\n", " spin: False\n", - "282\n" + "283\n" ] } ], @@ -2429,10 +2430,10 @@ "name": "stdout", "output_type": "stream", "text": [ - " state: {'y1': 0, 'x0': 1, 'y0': 1, 'y2': 0, 'x1': 0, 'x2': 0}\n", + " state: {'y1': 0, 'y0': 1, 'x0': 1, 'y2': 0, 'x1': 0, 'x2': 0}\n", " value: 0\n", " spin: False\n", - "290\n" + "284\n" ] } ], @@ -2461,10 +2462,10 @@ "name": "stdout", "output_type": "stream", "text": [ - " state: {'x0': 1, 'y0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", + " state: {'y0': 1, 'x0': 1, 'y1': 0, 'y2': 0, 'x1': 0, 'x2': 0}\n", " value: 0\n", " spin: False\n", - "61\n" + "74\n" ] } ], @@ -2549,7 +2550,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{('x', 'w', 'y', 'z'): 1}\n", + "{('x', 'z', 'y', 'w'): 1}\n", "\n", "{'w': 0, 'x': 0, 'y': 0, 'z': 0} --> 0\n", "{'w': 0, 'x': 0, 'y': 0, 'z': 1} --> 0\n", @@ -2601,7 +2602,7 @@ "output_type": "stream", "text": [ "Forumla\n", - "C(x, y, z) = {(): 1, ('x', 'y', 'z'): 1, ('y', 'z'): -1}\n", + "C(x, y, z) = {(): 1, ('x', 'z', 'y'): 1, ('z', 'y'): -1}\n", "\n", "Values\n", "{'x': 0, 'y': 0, 'z': 0} --> C = 1\n", @@ -2696,7 +2697,7 @@ "{(): 1}\n", "{('x',): 1}\n", "{('x', 'y'): 1}\n", - "{('x', 'y', 'z'): 1}\n" + "{('x', 'z', 'y'): 1}\n" ] } ], @@ -2780,8 +2781,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "{('w', 'z'): 1}\n", - "{('x',): 1, ('x', 'y'): -1, ('y',): 1, ('x', 'w', 'z'): -1, ('x', 'w', 'y', 'z'): 1, ('w', 'y', 'z'): -1, ('w', 'z'): 1}\n" + "{('z', 'w'): 1}\n", + "{('x',): 1, ('x', 'y'): -1, ('y',): 1, ('x', 'z', 'w'): -1, ('x', 'z', 'y', 'w'): 1, ('z', 'y', 'w'): -1, ('z', 'w'): 1}\n" ] } ], @@ -2859,7 +2860,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{('x', 'w', 'y'): 1}\n", + "{('x', 'y', 'w'): 1}\n", "{('x', 'y'): 1}\n" ] } @@ -2886,7 +2887,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{('x', 'w', 'y'): 1}\n", + "{('x', 'y', 'w'): 1}\n", "{}\n" ] } @@ -3126,7 +3127,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{(0, 1): 19, (1, 2): 19, (2, 3): 19, (3, 4): 19, (4, 5): 19, (0, 1, 2): 10, (1, 2, 3): 10, (2, 3, 4): 10, (3, 4, 5): 10, (0,): -50, (0, 2): 20, (0, 3): 20, (0, 4): 20, (0, 5): 20, (0, '__a0'): 20, ('__a1', 0): 40, (1,): -50, (1, 3): 20, (1, 4): 20, (1, 5): 20, (1, '__a0'): 20, ('__a1', 1): 40, (2,): -50, (2, 4): 20, (2, 5): 20, (2, '__a0'): 20, ('__a1', 2): 40, (3,): -50, (3, 5): 20, (3, '__a0'): 20, ('__a1', 3): 40, (4,): -50, (4, '__a0'): 20, ('__a1', 4): 40, (5,): -50, (5, '__a0'): 20, ('__a1', 5): 40, (): 90, ('__a0',): -50, ('__a1',): -80, ('__a1', '__a0'): 40}\n" + "{(0, 1): 19, (1, 2): 19, (2, 3): 19, (3, 4): 19, (4, 5): 19, (0, 1, 2): 10, (1, 2, 3): 10, (2, 3, 4): 10, (3, 4, 5): 10, (0,): -50, (0, 2): 20, (0, 3): 20, (0, 4): 20, (0, 5): 20, ('__a0', 0): 20, (0, '__a1'): 40, (1,): -50, (1, 3): 20, (1, 4): 20, (1, 5): 20, ('__a0', 1): 20, (1, '__a1'): 40, (2,): -50, (2, 4): 20, (2, 5): 20, ('__a0', 2): 20, (2, '__a1'): 40, (3,): -50, (3, 5): 20, ('__a0', 3): 20, (3, '__a1'): 40, (4,): -50, ('__a0', 4): 20, (4, '__a1'): 40, (5,): -50, ('__a0', 5): 20, (5, '__a1'): 40, (): 90, ('__a0',): -50, ('__a1',): -80, ('__a0', '__a1'): 40}\n" ] } ], @@ -3293,24 +3294,24 @@ "\n", "AnnealResults\n", "\n", - " state: {0: 1, 1: 0, 2: 0, 3: 1, 4: 1, 5: 0}\n", - " value: -1\n", + " state: {0: 0, 1: 0, 2: 1, 3: 0, 4: 0, 5: 1}\n", + " value: 0\n", " spin: False\n", "\n", - " state: {0: 0, 1: 0, 2: 1, 3: 0, 4: 1, 5: 1}\n", + " state: {0: 1, 1: 1, 2: 0, 3: 0, 4: 0, 5: 0}\n", " value: -1\n", " spin: False\n", "\n", - " state: {0: 0, 1: 0, 2: 0, 3: 0, 4: 1, 5: 1}\n", - " value: -1\n", + " state: {0: 0, 1: 1, 2: 0, 3: 0, 4: 0, 5: 1}\n", + " value: 0\n", " spin: False\n", "\n", - " state: {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 1}\n", + " state: {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0}\n", " value: 0\n", " spin: False\n", "\n", - " state: {0: 0, 1: 1, 2: 0, 3: 1, 4: 0, 5: 0}\n", - " value: 0\n", + " state: {0: 0, 1: 0, 2: 0, 3: 0, 4: 1, 5: 1}\n", + " value: -1\n", " spin: False\n" ] } @@ -3343,24 +3344,24 @@ "\n", "AnnealResults\n", "\n", - " state: {0: 1, 1: 0, 2: 1, 3: 0, 4: 0, 5: 1}\n", + " state: {0: 0, 1: 0, 2: 1, 3: 0, 4: 0, 5: 1}\n", " value: 0\n", " spin: True\n", "\n", - " state: {0: 1, 1: 1, 2: 0, 3: 0, 4: 0, 5: 0}\n", + " state: {0: 0, 1: 0, 2: 1, 3: 1, 4: 0, 5: 1}\n", " value: -1\n", " spin: True\n", "\n", - " state: {0: 0, 1: 0, 2: 1, 3: 0, 4: 1, 5: 1}\n", + " state: {0: 1, 1: 1, 2: 0, 3: 0, 4: 0, 5: 1}\n", " value: -1\n", " spin: True\n", "\n", - " state: {0: 1, 1: 0, 2: 1, 3: 0, 4: 1, 5: 0}\n", - " value: 0\n", + " state: {0: 1, 1: 0, 2: 1, 3: 1, 4: 0, 5: 0}\n", + " value: -1\n", " spin: True\n", "\n", - " state: {0: 0, 1: 1, 2: 0, 3: 0, 4: 0, 5: 0}\n", - " value: 0\n", + " state: {0: 1, 1: 1, 2: 0, 3: 1, 4: 0, 5: 0}\n", + " value: -1\n", " spin: True\n" ] } @@ -3479,7 +3480,7 @@ "Number of variables : 8\n", "Number of ancilla variables : 3\n", "Degree : 4\n", - "Variables : {'__a2', 'x2', 'x4', '__a1', 'x3', '__a0', 'x1', 'x0'}\n", + "Variables : {'x0', 'x2', '__a0', '__a1', '__a2', 'x4', 'x3', 'x1'}\n", "Number of terms : 30\n" ] } @@ -3546,7 +3547,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'eq': [{('x0',): 1, ('x0', 'x1'): -2, ('x1',): 1, ('x3',): -1}, {('x2', 'x1', 'x4'): 1, ('x2', 'x4'): -1, ('x2', 'x1', 'x3', 'x4'): -1, ('x1', 'x4'): -1, ('x4',): 1, ('x1', 'x3', 'x4'): 1, ('x1',): 1, ('x1', 'x3'): -1}], 'gt': [{('x0',): 3, ('x1',): 2, ('x3',): 4, (): -3}]}\n" + "{'eq': [{('x0',): 1, ('x1', 'x0'): -2, ('x1',): 1, ('x3',): -1}, {('x2', 'x4', 'x1'): 1, ('x2', 'x4'): -1, ('x3', 'x2', 'x4', 'x1'): -1, ('x4', 'x1'): -1, ('x4',): 1, ('x3', 'x4', 'x1'): 1, ('x1',): 1, ('x3', 'x1'): -1}], 'gt': [{('x0',): 3, ('x1',): 2, ('x3',): 4, (): -3}]}\n" ] } ], @@ -3929,7 +3930,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "({2, 3, 5}, {0, 1, 4})\n", + "({1, 3, 5}, {0, 2, 4})\n", "is solution valid : True\n" ] } @@ -3956,9 +3957,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "qubo solution : {0: 0, 1: 0, 2: 1, 3: 1, 4: 0, 5: 1}\n", - "qubo value : 4.5603838088336985\n", - "Solution : ({2, 3, 5}, {0, 1, 4})\n", + "qubo solution : {0: 0, 1: 1, 2: 0, 3: 1, 4: 0, 5: 1}\n", + "qubo value : 4.699956009288265\n", + "Solution : ({1, 3, 5}, {0, 2, 4})\n", "is solution valid : True\n" ] } @@ -3989,9 +3990,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "qubo solution : {0: 0, 1: 0, 2: 1, 3: 1, 4: 0, 5: 1}\n", - "qubo value : 4.5603838088336985\n", - "Solution : ({2, 3, 5}, {0, 1, 4})\n", + "qubo solution : {0: 0, 1: 1, 2: 0, 3: 1, 4: 0, 5: 1}\n", + "qubo value : 4.699956009288265\n", + "Solution : ({1, 3, 5}, {0, 2, 4})\n", "is solution valid : True\n" ] } @@ -4022,7 +4023,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "4.560383808833699\n" + "4.6999560092882655\n" ] } ], @@ -4273,7 +4274,8 @@ "6. Whenever working with D-Wave, you'll have to deal with the fact that D-Wave represents their QUBOs and QUSOs differently than qubovert. If you have a `QUBO` or `QUBOMatrix` object in qubovert, then to get it into a form that D-Wave uses you can use the `.Q` property. If you have a `QUSO` or `QUSOMatrix` object in qubovert, then to get it into a form that D-Wave uses (note that D-Wave calls QUSOs \"ising\") you can get the linear terms with the `.h` property and the coupling terms with the `.J` property. Note that D-Wave ignores constant terms, so the output of the `.Q`, `.h`, and `.J` properties will not contain constant terms. You can get the constant term of a function with the `.offset` property, or by accessing the empty tuple key `()`. \n", "7. PCBOs and PCSOs add ancillas and penalties automatically. Since they add penalties automatically, you can sometimes treat them like PUBOs and PUSOs respectively. For example, if a solver accepts arbitrary PUBOs that can be labeled with anything, then you can send the PCBO directly into this solver. However, if a solver requires integer labels, then you should use the `.to_` methods. After using the `.convert_solution` method, you can use the `.is_solution_valid` method to see if all of the constraints are satisfied. To get rid of the ancilla information from a solution to the model, you can use `qv.PCBO.remove_ancilla_from_solution` staticmethod. To see all of the constraints, see the `.constraints` property.\n", "8. The utility functions work with any `dict` type. So if you don't want to use qubovert to help with formulating problems but instead already have you problem formulated in a pure `dict` (ie not a `qv.QUBO` or other object), then you can still use most of the functions. For example, the `qv.utils.pubo_value`, `qv.utils.quso_value`, etc. function will work. Similarly, the `qv.utils.subgraph`, `qv.utils.subvalue`, `qv.utils.normalize`, and `qv.utils.solve_..._bruteforce` functions will work. But if you have qubovert objects, then you can still use those if you want, or you can just use their respective methods. So you can use the `.value` method, or the `.subgraph`, `.subvalue`, `.normalize`, and `.solve_bruteforce` methods.\n", - "9. There is *a lot* of documentation for everything. Use `help` to figure out what any function, method, or object does.\n", + "9. Most QUBO/QUSO functions/methods work *faster* than their PUBO/PUSO counterparts. So for example, `qv.utils.qubo_value` is faster than `qv.utils.pubo_value`; similarly, `qv.utils.solve_qubo_bruteforce` is faster than `qv.utils.solve_pubo_bruteforce`. This is because the QUBO/QUSO functions take advantage of the model being of degree $\\leq 2$. This speed advantage is also true for the `qv.QUBO.value` function compared to the `qv.PUBO.value` function, and etc.\n", + "10. There is *a lot* of documentation for everything. Use `help` to figure out what any function, method, or object does.\n", "\n", "You may encounter some seemingly unexpected behavior when using qubovert, so please the next section. I will also touch on some advanced details later, so please check out the [Tips, tricks, and advanced details](#Tips,-tricks,-and-advanced-details) section as well." ] @@ -4653,7 +4655,7 @@ "output_type": "stream", "text": [ "degree : 2\n", - "variables : {'y', 'x'}\n", + "variables : {'x', 'y'}\n", "num of variables : 2\n" ] } @@ -4736,7 +4738,7 @@ "output_type": "stream", "text": [ "degree : 2\n", - "variables : {'y', 'x', 'z'}\n", + "variables : {'x', 'y', 'z'}\n", "num of variables : 3\n" ] } @@ -4843,14 +4845,14 @@ "text": [ "Before adding y:\n", " {('x',): 1, ('y',): -1, ('z',): 1, ('x', 'z'): 1}\n", - " variables : {'y', 'x', 'z'}\n", + " variables : {'x', 'y', 'z'}\n", " mapping : {'x': 0, 'y': 1, 'z': 2}\n", " num variables : 3\n", " degree : 2\n", "\n", "After adding y:\n", " {('x',): 1, ('z',): 1, ('x', 'z'): 1}\n", - " variables : {'y', 'x', 'z'}\n", + " variables : {'x', 'y', 'z'}\n", " mapping : {'x': 0, 'y': 1, 'z': 2}\n", " num variables : 3\n", " degree : 2\n", @@ -5084,8 +5086,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'x': 0, 'y': 1, 'z': 2}\n", - "{'y': 0, 'z': 1, 'x': 2}\n" + "{'x': 0, 'z': 1, 'y': 2}\n", + "{'z': 0, 'y': 1, 'x': 2}\n" ] } ], @@ -5110,11 +5112,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "Enumerated solution to P1 : {0: -1, 1: -1, 2: 1}\n", - "Enumerated solution to P2 : {0: -1, 1: 1, 2: -1}\n", + "Enumerated solution to P1 : {0: -1, 1: 1, 2: -1}\n", + "Enumerated solution to P2 : {0: 1, 1: -1, 2: -1}\n", "\n", - "Converted solution to P1 : {'x': -1, 'y': -1, 'z': 1}\n", - "Converted solution to P2 : {'y': -1, 'z': 1, 'x': -1}\n" + "Converted solution to P1 : {'x': -1, 'z': 1, 'y': -1}\n", + "Converted solution to P2 : {'z': 1, 'y': -1, 'x': -1}\n" ] } ], @@ -5149,11 +5151,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "Enumerated solution to P1 : {0: -1, 1: -1, 2: 1}\n", - "Enumerated solution to P2 : {0: -1, 1: -1, 2: 1}\n", + "Enumerated solution to P1 : {0: -1, 1: 1, 2: -1}\n", + "Enumerated solution to P2 : {0: -1, 1: 1, 2: -1}\n", "\n", - "Converted solution to P1 : {'x': -1, 'y': -1, 'z': 1}\n", - "Converted solution to P2 : {'x': -1, 'y': -1, 'z': 1}\n" + "Converted solution to P1 : {'x': -1, 'z': 1, 'y': -1}\n", + "Converted solution to P2 : {'x': -1, 'z': 1, 'y': -1}\n" ] } ], @@ -5403,8 +5405,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "{('w', 'z'): 1}\n", - "{('x',): 1, ('x', 'y'): -1, ('y',): 1, ('x', 'w', 'z'): -1, ('x', 'w', 'y', 'z'): 1, ('w', 'y', 'z'): -1, ('w', 'z'): 1}\n" + "{('z', 'w'): 1}\n", + "{('x',): 1, ('x', 'y'): -1, ('y',): 1, ('x', 'z', 'w'): -1, ('x', 'z', 'y', 'w'): 1, ('z', 'y', 'w'): -1, ('z', 'w'): 1}\n" ] } ], @@ -5482,7 +5484,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{('x', 'w', 'y'): 1}\n", + "{('x', 'y', 'w'): 1}\n", "{('x', 'y'): 1}\n" ] } @@ -5509,7 +5511,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{('x', 'w', 'y'): 1}\n", + "{('x', 'y', 'w'): 1}\n", "{}\n" ] } @@ -5579,9 +5581,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "Method 0: 4.472064733505249\n", - "Method 1: 0.007885932922363281\n", - "Method 2: 0.007630825042724609\n" + "Method 0: 3.29614520072937\n", + "Method 1: 0.007680654525756836\n", + "Method 2: 0.007586002349853516\n", + "Method 3: 0.007012128829956055\n" ] } ], @@ -5601,15 +5604,19 @@ "print(\"Method 1:\", time.time() - t0)\n", "\n", "t0 = time.time()\n", - "pubo2 = qv.PCBO({(i,): 1 for i in range(1000)})\n", - "print(\"Method 2:\", time.time() - t0)" + "pubo2 = qv.utils.sum(xs)\n", + "print(\"Method 2:\", time.time() - t0)\n", + "\n", + "t0 = time.time()\n", + "pubo3 = qv.PCBO({(i,): 1 for i in range(1000)})\n", + "print(\"Method 3:\", time.time() - t0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The second two methods are orders of magnitude faster. This is because whenver you do not do in-place arithmetic, many copying operations occur. For example, consider the following example." + "The last three methods are orders of magnitude faster than the first (Note that Method 2 is exactly the same as Method 1; Method 1 is exactly what the `qv.utils.sum` function does). This is because whenver you do not do in-place arithmetic, many copying operations occur. For example, consider the following example." ] }, { @@ -5648,7 +5655,7 @@ "In this case, *only one copy is performed*. The first two lines are equivalent to `pubo = x.copy()`. Then for the remaining lines the `pubo` objects is updated in-place, and the `y` and `z` objects do not need to be copied.\n", "\n", "**Conclusion**\n", - "Using one-line arithmetic is often very convenient and makes code very nice and pretty. For example, being able to add a constraint like `model = qv.PCBO().add_constraint_gt_zero(sum(i * x[i] for i in range(10)))` is clean and makes a lot of sense. For small functions this will work completely find, and I do it all of the time. But if you are working with large functions and you care about the time it takes to create the function, use in-place arithmetic. Or, for a simple example like the one I just showed, you could instead do `model = qv.PCBO().add_constraint_gt_zero({(i,): i for i in range(10)})`." + "Using one-line arithmetic is often very convenient and makes code very nice and pretty. For example, being able to add a constraint like `model = qv.PCBO().add_constraint_gt_zero(sum(i * x[i] for i in range(10)))` is clean and makes a lot of sense. For small functions this will work completely find, and I do it all of the time. But if you are working with large functions and you care about the time it takes to create the function, use in-place arithmetic either by explicitly writing it out or using the `qv.utils.sum` function. Or, for a simple example like the one I just showed, you could instead do `model = qv.PCBO().add_constraint_gt_zero({(i,): i for i in range(10)})`." ] }, { @@ -5788,7 +5795,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -8187,7 +8194,142 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Tip #14: creating variables\n", + "## Tip #14: use QUBO/QUSO functions/methods whenever possible\n", + "\n", + "Most QUBO/QUSO functions/methods work *faster* than their PUBO/PUSO counterparts. So for example, `qv.utils.qubo_value` is faster than `qv.utils.pubo_value`; similarly, `qv.utils.solve_qubo_bruteforce` is faster than `qv.utils.solve_pubo_bruteforce`. This is because the QUBO/QUSO functions take advantage of the model being of degree $\\leq 2$. This speed advantage is also true for the `qv.QUBO.value` function compared to the `qv.PUBO.value` function, and etc.\n", + "\n", + "Let's see one example of this. Let's time how long it takes to bruteforce solve a QUBO model with the PUBO solver versus the QUBO solver. We'll just create a random QUBO." + ] + }, + { + "cell_type": "code", + "execution_count": 263, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "solve_pubo_bruteforce took 4.006385564804077 seconds\n", + "solve_qubo_bruteforce took 2.274083375930786 seconds\n" + ] + } + ], + "source": [ + "import random\n", + "import time\n", + "\n", + "qubo = {(i, j): random.random() for i in range(16) for j in range(i, 16)}\n", + "\n", + "for f in (qv.utils.solve_pubo_bruteforce, qv.utils.solve_qubo_bruteforce):\n", + " t0 = time.time()\n", + " f(qubo)\n", + " print(f.__name__, \"took\", time.time() - t0, \"seconds\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is similarly true when we use the methods." + ] + }, + { + "cell_type": "code", + "execution_count": 264, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PUBO took 4.308476209640503 seconds\n", + "QUBO took 2.024702548980713 seconds\n" + ] + } + ], + "source": [ + "pubo = qv.PUBO(qubo)\n", + "t0 = time.time()\n", + "pubo.solve_bruteforce()\n", + "print(\"PUBO took\", time.time() - t0, \"seconds\")\n", + "\n", + "qubo = qv.QUBO(qubo)\n", + "t0 = time.time()\n", + "qubo.solve_bruteforce()\n", + "print(\"QUBO took\", time.time() - t0, \"seconds\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Moral of the story is to use the QUBO/QUSO function/methods/objects whenever you can!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tip #15: num_binary_variables versus max_index\n", + "\n", + "Consider two `PUSOMatrix` objects representing the functions $P_1 = \\sum_{i=0}^9 z_i$ and $P_2= \\sum_{i=0}^4 z_i + \\sum_{i=6}^9 z_i$." + ] + }, + { + "cell_type": "code", + "execution_count": 265, + "metadata": {}, + "outputs": [], + "source": [ + "P1 = qv.utils.PUSOMatrix({(i,): 1 for i in range(10)})\n", + "P2 = qv.utils.PUSOMatrix({(i,): 1 for i in range(5)}) + {(i,): 1 for i in range(6, 10)}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From our standpoint, we can see that $P_2$ is not properly enumerated! The variable $z_5$ doesn't exist in $P_2$, so a proper enumeration of the variables would not include it. How can we tell that it's not properly enumerated? We can see that `max_index + 1 != num_binary_variables`. `max_index` refers to the maximum index labeling the variables -- in this case that would be 9 because of the $z_9$ variable." + ] + }, + { + "cell_type": "code", + "execution_count": 266, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "P1: num_binary_variables = 10; max_index = 9\n", + "P2: num_binary_variables = 9; max_index = 9\n" + ] + } + ], + "source": [ + "print(\n", + " \"P1: num_binary_variables = %d; max_index = %d\" % \n", + " (P1.num_binary_variables, P1.max_index)\n", + ")\n", + "print(\n", + " \"P2: num_binary_variables = %d; max_index = %d\" % \n", + " (P2.num_binary_variables, P2.max_index)\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So if you are ever writing a function *that assumes that the input has been enumerated*, then you may want to assume that it has been enumerated from `0` to `max_index` as opposed to from `0` to `num_binary_variables-1`. Either that or your function should redo the enumeration!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tip #16: creating variables\n", "\n", "You've seen many times in this notebook expressions of the form\n", "\n", @@ -8199,7 +8341,7 @@ }, { "cell_type": "code", - "execution_count": 263, + "execution_count": 267, "metadata": {}, "outputs": [ { @@ -8223,7 +8365,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Tip #15: integer variables\n", + "## Tip #17: integer variables\n", "\n", "I already sort of showed the usage of integer variables, but let me quickly show it one more time. Let's come full circle and end this notebook in approximately the same way that we started it, by discussing the factoring example.\n", "\n", @@ -8240,7 +8382,7 @@ }, { "cell_type": "code", - "execution_count": 264, + "execution_count": 268, "metadata": {}, "outputs": [ { @@ -8272,7 +8414,7 @@ }, { "cell_type": "code", - "execution_count": 265, + "execution_count": 269, "metadata": {}, "outputs": [ { @@ -8301,7 +8443,7 @@ }, { "cell_type": "code", - "execution_count": 266, + "execution_count": 270, "metadata": {}, "outputs": [ { @@ -8331,7 +8473,7 @@ }, { "cell_type": "code", - "execution_count": 267, + "execution_count": 271, "metadata": {}, "outputs": [ { @@ -8358,13 +8500,15 @@ "---\n", "# Conclusion\n", "\n", - "I hope this is helpful. Feel free to suggest edits or request additions." + "What I think is cool about qubovert is that there is nothing crazy going on in any function or method. Each individual piece of qubovert is straightforward and not particularly special. But when you put it all together in an organized way, it becomes useful! Take as an example the ending of the [Motivation #7: constraints and ancillas](#Motivation-#7:-constraints-and-ancillas) section. We had a bunch of constraints going on, a bunch of ancillas being added for those constraints, and a bunch of ancillas being added when reducing the degree and making the problem into a QUBO. The constraints themselves were not complicated to implement, but it would have been tedious to do the arithmetic and keep track of the ancillas. Similarly reducing the degree of the model to quadratic is tedious and requires keeping track of a lot of information. But we can pretty much forget about ancillas altogether because qubovert does all of the work for us internally. This saves a bunch of time because we don't have to do these otherwise simple but time-consuming and bug-ridden things." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "I hope this is helpful. Feel free to suggest edits or request additions.\n", + "\n", "Back to top" ] } diff --git a/qubovert/_pcbo.py b/qubovert/_pcbo.py index 9a5422a..eac1705 100644 --- a/qubovert/_pcbo.py +++ b/qubovert/_pcbo.py @@ -730,6 +730,8 @@ def add_constraint_eq_zero(self, """ P = PUBO(P) self._append_constraint("eq", P) + if not lam: + return self if _special_constraints_eq_zero(self, P, lam): return self @@ -849,6 +851,8 @@ def add_constraint_ne_zero(self, """ P = PUBO(P) self._append_constraint("ne", P) + if not lam: + return self min_val, max_val = _get_bounds(P, bounds) @@ -984,6 +988,8 @@ def add_constraint_lt_zero(self, """ P = PUBO(P) self._append_constraint("lt", P) + if not lam: + return self min_val, max_val = _get_bounds(P, bounds) @@ -1100,6 +1106,8 @@ def add_constraint_le_zero(self, """ P = PUBO(P) self._append_constraint("le", P) + if not lam: + return self bounds = min_val, max_val = _get_bounds(P, bounds) if _special_constraints_le_zero(self, P, lam, log_trick, bounds): @@ -1216,6 +1224,8 @@ def add_constraint_gt_zero(self, """ P = PUBO(P) self._append_constraint("gt", P) + if not lam: + return self min_val, max_val = _get_bounds(P, bounds) bounds = -max_val, -min_val self.add_constraint_lt_zero( @@ -1320,6 +1330,8 @@ def add_constraint_ge_zero(self, """ P = PUBO(P) self._append_constraint("ge", P) + if not lam: + return self min_val, max_val = _get_bounds(P, bounds) bounds = -max_val, -min_val self.add_constraint_le_zero( diff --git a/qubovert/_pcso.py b/qubovert/_pcso.py index 5b1a044..c08b1c8 100644 --- a/qubovert/_pcso.py +++ b/qubovert/_pcso.py @@ -399,6 +399,8 @@ def add_constraint_eq_zero(self, """ H = PUSO(H) self._append_constraint("eq", H) + if not lam: + return self h = _empty_pcbo(self).add_constraint_eq_zero( puso_to_pubo(H), lam=lam, @@ -463,6 +465,8 @@ def add_constraint_ne_zero(self, """ H = PUSO(H) self._append_constraint("ne", H) + if not lam: + return self h = _empty_pcbo(self).add_constraint_ne_zero( puso_to_pubo(H), lam=lam, log_trick=log_trick, bounds=bounds, suppress_warnings=suppress_warnings @@ -540,6 +544,8 @@ def add_constraint_lt_zero(self, """ H = PUSO(H) self._append_constraint("lt", H) + if not lam: + return self h = _empty_pcbo(self).add_constraint_lt_zero( puso_to_pubo(H), lam=lam, log_trick=log_trick, bounds=bounds, suppress_warnings=suppress_warnings @@ -620,6 +626,8 @@ def add_constraint_le_zero(self, """ H = PUSO(H) self._append_constraint("le", H) + if not lam: + return self h = _empty_pcbo(self).add_constraint_le_zero( puso_to_pubo(H), lam=lam, log_trick=log_trick, bounds=bounds, suppress_warnings=suppress_warnings @@ -696,6 +704,8 @@ def add_constraint_gt_zero(self, """ H = PUSO(H) self._append_constraint("gt", H) + if not lam: + return self h = _empty_pcbo(self).add_constraint_gt_zero( puso_to_pubo(H), lam=lam, log_trick=log_trick, bounds=bounds, suppress_warnings=suppress_warnings @@ -775,6 +785,8 @@ def add_constraint_ge_zero(self, """ H = PUSO(H) self._append_constraint("ge", H) + if not lam: + return self h = _empty_pcbo(self).add_constraint_ge_zero( puso_to_pubo(H), lam=lam, log_trick=log_trick, bounds=bounds, suppress_warnings=suppress_warnings diff --git a/qubovert/_puso.py b/qubovert/_puso.py index 05e282e..907b8f0 100644 --- a/qubovert/_puso.py +++ b/qubovert/_puso.py @@ -18,7 +18,7 @@ """ -from .utils import BO, PUSOMatrix, puso_to_pubo +from .utils import BO, PUSOMatrix, puso_to_pubo, QUSOMatrix from . import QUSO @@ -351,6 +351,52 @@ def to_qubo(self, lam=None, pairs=None): """ return self._create_pubo().to_qubo(lam, pairs) + def to_quso(self, lam=None, pairs=None): + """to_quso. + + Create and return upper triangular QUSO representing the problem. + The labels will be integers from 0 to n-1. We introduce ancilla + variables in order to reduce the degree of the PUSO to a QUSO. The + solution to the PUSO can be read from the solution to the QUSO by + using the ``convert_solution`` method. + + Parameters + ---------- + lam : function (optional, defaults to None). + If ``lam`` is None, the function ``PUBO.default_lam`` will be used. + ``lam`` is the penalty factor to introduce in order to enforce the + ancilla constraints. When we reduce the degree of the PUSO to a + QUSO, we add penalties to the QUSO in order to enforce ancilla + variable constraints. These constraints will be multiplied by + ``lam(v)``, where ``v`` is the value associated with the term that + it is reducing. For example, a term ``(0, 1, 2): 3`` in the PUSO + may be reduced to a term ``(0, 3): 3`` for the QUSO, and then the + fact that ``3`` should be the product of ``1`` and ``2`` will be + enforced with a penalty weight ``lam(3)``. + pairs : set (optional, defaults to None). + A set of tuples of variable pairs to prioritize pairing together in + to degree reduction. If a pair in ``pairs`` is found together in + the PUSO, it will be chosen as a pair to reduce to a single + ancilla. You should supply this parameter if you have a good idea + of an efficient way to reduce the degree of the PUSO. If ``pairs`` + is None, then it will be the empty set ``set()``. In other words, + no variable pairs will be prioritized, and instead variable pairs + will be chosen to reduce to an ancilla bases solely on frequency + of occurrance. + + Return + ------ + L : qubovert.utils.QUSOMatrix object. + The upper triangular QUSO matrix, an QUSOMatrix object. + For most practical purposes, you can use QUSOMatrix in the + same way as an ordinary dictionary. For more information, + see ``help(qubovert.utils.QUSOMatrix)``. + + """ + if self.degree <= 2: + return QUSOMatrix(self._to_puso()) + return super().to_quso(lam, pairs) + def convert_solution(self, solution, spin=True): """convert_solution. diff --git a/qubovert/_version.py b/qubovert/_version.py index 5ca74e1..6485dfd 100644 --- a/qubovert/_version.py +++ b/qubovert/_version.py @@ -20,7 +20,7 @@ ) -__version__ = "1.2.1" +__version__ = "1.2.2" __author__ = "Joseph T. Iosue" __authoremail__ = "jtiosue@gmail.com" __license__ = "Apache Software License 2.0" diff --git a/qubovert/sim/_anneal.py b/qubovert/sim/_anneal.py index be36a67..cc86bc1 100644 --- a/qubovert/sim/_anneal.py +++ b/qubovert/sim/_anneal.py @@ -233,6 +233,9 @@ def anneal_puso(H, num_anneals=1, anneal_duration=1000, initial_state=None, qubovert.utils.QUBOVertWarning If both the ``temperature_range`` and explicit ``schedule`` arguments are provided. + qubovert.utils.QUBOVertWarning + If the degree of the model is 2 or less then a warning is issued that + says you should use the ``anneal_qubo`` or ``anneal_quso`` functions. Example ------- @@ -279,6 +282,14 @@ def anneal_puso(H, num_anneals=1, anneal_duration=1000, initial_state=None, model = H.to_puso() reverse_mapping = H.reverse_mapping + if model.degree <= 2: + QUBOVertWarning.warn( + "The input problem has degree <= 2; consider using the " + "``qubovert.sim.anneal_qubo`` or ``qubovert.sim.anneal_quso`` " + "functions, which are significantly faster than this function " + "because they take advantage of the low degree." + ) + # solve `model`, convert solutions back to `H` if not N: @@ -561,6 +572,9 @@ def anneal_pubo(P, num_anneals=1, anneal_duration=1000, initial_state=None, qubovert.utils.QUBOVertWarning If both the ``temperature_range`` and explicit ``schedule`` arguments are provided. + qubovert.utils.QUBOVertWarning + If the degree of the model is 2 or less then a warning is issued that + says you should use the ``anneal_qubo`` or ``anneal_quso`` functions. Example ------- diff --git a/qubovert/utils/_binary_helpers.py b/qubovert/utils/_binary_helpers.py index 02a388e..b7ff67c 100644 --- a/qubovert/utils/_binary_helpers.py +++ b/qubovert/utils/_binary_helpers.py @@ -20,7 +20,7 @@ from math import ceil -__all__ = 'is_solution_spin', 'num_bits' +__all__ = 'is_solution_spin', 'num_bits', 'sum' def is_solution_spin(solution, default=False): @@ -113,3 +113,41 @@ def num_bits(val, log_trick=True): raise ValueError("``val`` must be >= 0") val = int(ceil(val)) return int.bit_length(val) if log_trick else val + + +def sum(iterable, start=0): + """sum. + + A utility for summing qubovert types. This will perform way faster than + Python's built-in ``sum`` function when you use it with qubovert types. + + Parameters + ---------- + iterable : any iterable. + start : numeric or qubovert type (optional, defaults to 0). + + Returns + ------- + res : same type as ``sum(iterable, start)`` with Python's builtin ``sum``. + + Examples + -------- + >>> import time + >>> import qubovert as qv + >>> + >>> xs = [qv.boolean_var(i) for i in range(1000)] + + >>> t0 = time.time() + >>> sum(xs) + >>> print(time.time() - t0) + 3.345559597015381 + + >>> t0 = time.time() + >>> qv.utils.sum(xs) + >>> print(time.time() - t0) + 0.011152505874633789 + + """ + for i in iterable: + start += i + return start diff --git a/qubovert/utils/_bo_parentclass.py b/qubovert/utils/_bo_parentclass.py index c6530a0..24d835b 100644 --- a/qubovert/utils/_bo_parentclass.py +++ b/qubovert/utils/_bo_parentclass.py @@ -223,3 +223,37 @@ def __setitem__(self, key, value): self._mapping[i] = self._next_label self._reverse_mapping[self._next_label] = i self._next_label += 1 + + def to_enumerated(self): + """to_enumerated. + + Return the default enumerated Matrix object. + + If ``self`` is a QUBO, + ``self.to_enumerated()`` is equivalent to ``self.to_qubo()``. + + If ``self`` is a QUSO, + ``self.to_enumerated()`` is equivalent to ``self.to_quso()``. + + If ``self`` is a PUBO or PCBO, + ``self.to_enumerated()`` is equivalent to ``self.to_pubo()``. + + If ``self`` is a PUSO or PCSO, + ``self.to_enumerated()`` is equivalent to ``self.to_puso()``. + + Returns + ------- + res : QUBOMatrix, QUSOMatrix, PUBOMatrix, or PUSOMatrix object. + If ``self`` is a QUBO type, then this method returns the + corresponding QUBOMatrix type. If ``self`` is a QUSO type, + then this method returns the corresponding QUSOMatrix type. + If ``self`` is a PUBO or PCBO type, then this method returns the + corresponding PUBOMatrix type. If ``self`` is a PUSO or PCSO type, + then this method returns the corresponding PUSOMatrix type. + + """ + # we replace c with u so that pcbo and pcso go to pubo and puso. + return getattr( + self, + "to_" + self.__class__.__name__.lower().replace('c', 'u') + )() diff --git a/qubovert/utils/_solve_bruteforce.py b/qubovert/utils/_solve_bruteforce.py index 5610fc0..325317e 100644 --- a/qubovert/utils/_solve_bruteforce.py +++ b/qubovert/utils/_solve_bruteforce.py @@ -19,7 +19,7 @@ """ import itertools -from . import pubo_value, puso_value +from . import pubo_value, puso_value, qubo_value, quso_value __all__ = ( 'solve_pubo_bruteforce', 'solve_qubo_bruteforce', @@ -27,10 +27,11 @@ ) -def _solve_bruteforce(D, all_solutions, valid, spin): +def _solve_bruteforce(D, all_solutions, valid, spin, value): """_solve_bruteforce. - Helper function for solve_pubo_bruteforce and solve_puso_bruteforce. + Helper function for solve_pubo_bruteforce, solve_puso_bruteforce, + solve_qubo_bruteforce, and solve_quso_bruteforce. Iterate through all the possible solutions to a BO formulated problem and find the best one (the one that gives the minimum objective value). Do @@ -50,6 +51,9 @@ def _solve_bruteforce(D, all_solutions, valid, spin): indicating whether that bitstring or spinstring is a valid solutions. spin : bool. Whether we're bruteforce solving a spin model or boolean model. + value : function. + One of ``qubo_value``, ``quso_value``, ``pubo_value``, or + ``puso_value``. Returns ------- @@ -102,7 +106,7 @@ def _solve_bruteforce(D, all_solutions, valid, spin): x = {mapping[i]: v for i, v in enumerate(test_sol)} if not valid(x): continue - v = puso_value(x, D) if spin else pubo_value(x, D) + v = value(x, D) if all_solutions and (best[0] is None or v <= best[0]): best = v, x all_sols.setdefault(v, []).append(x) @@ -177,7 +181,7 @@ def solve_pubo_bruteforce(P, all_solutions=False, valid=lambda x: True): is 0, :math:`x_2` is 1. """ - return _solve_bruteforce(P, all_solutions, valid, False) + return _solve_bruteforce(P, all_solutions, valid, False, pubo_value) def solve_qubo_bruteforce(Q, all_solutions=False, valid=lambda x: True): @@ -244,7 +248,10 @@ def solve_qubo_bruteforce(Q, all_solutions=False, valid=lambda x: True): is 0, :math:`x_2` is 1. """ - return solve_pubo_bruteforce(Q, all_solutions, valid) + # we could just do the same as we did in solve_pubo_bruteforce, but the + # qubo_value function is much faster than the pubo_value function, so this + # will be much faster! + return _solve_bruteforce(Q, all_solutions, valid, False, qubo_value) def solve_puso_bruteforce(H, all_solutions=False, valid=lambda x: True): @@ -309,7 +316,7 @@ def solve_puso_bruteforce(H, all_solutions=False, valid=lambda x: True): is -1, :math:`z_2` is 1. """ - return _solve_bruteforce(H, all_solutions, valid, True) + return _solve_bruteforce(H, all_solutions, valid, True, puso_value) def solve_quso_bruteforce(L, all_solutions=False, valid=lambda x: True): @@ -374,4 +381,7 @@ def solve_quso_bruteforce(L, all_solutions=False, valid=lambda x: True): is -1, :math:`z_2` is 1. """ - return solve_puso_bruteforce(L, all_solutions, valid) + # we could just do the same as we did in solve_puso_bruteforce, but the + # quso_value function is much faster than the puso_value function, so this + # will be much faster! + return _solve_bruteforce(L, all_solutions, valid, True, quso_value) diff --git a/qubovert/utils/_values.py b/qubovert/utils/_values.py index c959249..9a9f18b 100644 --- a/qubovert/utils/_values.py +++ b/qubovert/utils/_values.py @@ -26,15 +26,15 @@ def pubo_value(x, P): r"""pubo_value. - Find the value of - :math:`\sum_{i,...,j} P_{i...j} x_{i} ... x_{j}` + Find the value of the PUBO for a given assignment of the boolean variables + ``x``. Parameters ---------- x : dict or iterable. Maps boolean variable indices to their boolean values, 0 or 1. Ie ``x[i]`` must be the boolean value of variable i. - P : dict, qubovert.utils.PUBOMatrix, or qubovert.PUBO object. + P : dict, or any object in ``qv.BOOLEAN_MODELS``. Maps tuples of boolean variables indices to the P value. Returns @@ -44,20 +44,25 @@ def pubo_value(x, P): Example ------- - >>> P = {(0, 0): 1, (0, 1): -1} + >>> P = {(0,): 1, (0, 1): -1} >>> x = {0: 1, 1: 0} >>> pubo_value(x, P) 1 """ - return sum(v for k, v in P.items() if all(x[i] for i in k)) + # map is faster than all(x[i] for i in k) + return sum(v for k, v in P.items() if all(map(lambda i: x[i], k))) def qubo_value(x, Q): r"""qubo_value. - Find the value of - :math:`\sum_{i,j} Q_{ij} x_{i} x_{j}` + Find the value of the QUBO for a given assignment of the boolean variables + ``x``. + + Please note that THIS FUNCTION WILL NOT RAISE AN EXCEPTION IF ``Q`` IS + NOT A QUBO!! If you input a ``Q`` that is, for example, degree 3 instead + of degree 2, then this function will return an incorrect value! Parameters ---------- @@ -72,34 +77,36 @@ def qubo_value(x, Q): value : float. The value of the QUBO with the given assignment `x`. Ie - >>> sum( - Q[(i, j)] * x[i] * x[j] - for i in range(n) for j in range(n) - ) - Example ------- - >>> Q = {(0, 0): 1, (0, 1): -1} + >>> Q = {(0,): 1, (0, 1): -1} >>> x = {0: 1, 1: 0} >>> qubo_value(x, Q) 1 """ - return pubo_value(x, Q) + # we could just return pubo_value(x, Q), but instead let's take + # advantage of a maximum degree of 2 to not have to loop through keys + return sum( + v for k, v in Q.items() if ( + not k or (len(k) == 1 and x[k[0]]) or + (len(k) == 2 and x[k[0]] and x[k[1]]) + ) + ) def puso_value(z, H): r"""puso_value. - Find the value of - :math:`\sum_{i,...,j} H_{i...j} z_{i} ... z_{j}`. + Find the value of the PUSO for a given assignment of the spin variables + ``z``. Parameters ---------- z: dict or iterable. Maps variable labels to their values, 1 or -1. Ie z[i] must be the value of variable i. - H : dict, qubovert.utils.PUSOMatrix, or qubovert.PUSO object. + H : dict, or any object in ``qv.SPIN_MODELS``. Maps spin labels to values. Returns @@ -124,11 +131,12 @@ def puso_value(z, H): def quso_value(z, L): r"""quso_value. - Find the value of - :math:`\sum_{i,j} J_{ij} z_{i} z_{j} + \sum_{i} h_{i} z_{i}`. + Find the value of the QUSO for a given assignment of the spin variables + ``z``. - The J's are encoded by keys with pairs of labels in L, and the h's are - encoded by keys with a single label in L. + Please note that THIS FUNCTION WILL NOT RAISE AN EXCEPTION IF ``L`` IS + NOT A QUSO!! If you input a ``L`` that is, for example, degree 3 instead + of degree 2, then this function will return an incorrect value! Parameters ---------- @@ -151,4 +159,9 @@ def quso_value(z, L): 0 """ - return puso_value(z, L) + # we could just return puso_value(z, L), but instead let's take + # advantage of a maximum degree of 2 to not have to loop through keys + return sum( + v * (z[k[0]] if k else 1) * (z[k[1]] if len(k) > 1 else 1) + for k, v in L.items() + ) diff --git a/tests/sim/test_anneal.py b/tests/sim/test_anneal.py index a0efaab..f22d19d 100644 --- a/tests/sim/test_anneal.py +++ b/tests/sim/test_anneal.py @@ -50,6 +50,10 @@ def _anneal_puso(type_): with assert_warns(QUBOVertWarning): anneal_puso(H, temperature_range=(1, 2), schedule=[3, 2]) + with assert_warns(QUBOVertWarning): + # a quadratic model warns that you shouldn't use anneal_puso + anneal_puso({(0, 1): 1}) + with assert_raises(ValueError): anneal_puso(H, temperature_range=(1, 2)) @@ -59,7 +63,9 @@ def _anneal_puso(type_): empty_result = AnnealResults(True) for _ in range(4): empty_result.add_state({}, 2) - assert anneal_puso({(): 2}, num_anneals=4) == empty_result + # less than quadratic model so will warn + with assert_warns(QUBOVertWarning): + assert anneal_puso({(): 2}, num_anneals=4) == empty_result assert anneal_puso(H, num_anneals=0) == AnnealResults(True) assert anneal_puso(H, num_anneals=-1) == AnnealResults(True) @@ -72,7 +78,9 @@ def _anneal_puso(type_): # check to see if we find the groundstate of a simple but largeish model. H = type_({(i, i+1): -1 for i in range(30)}) - res = anneal_puso(H, num_anneals=4, seed=0) + # quadratic model so will warn + with assert_warns(QUBOVertWarning): + res = anneal_puso(H, num_anneals=4, seed=0) assert res.best.state in ( dict(enumerate([1]*31)), dict(enumerate([-1]*31)) ) @@ -80,7 +88,9 @@ def _anneal_puso(type_): assert len([x for x in res]) == 4 # check to see if we find the groundstate of same but out of order - res = anneal_puso(H, num_anneals=4, in_order=False, seed=0) + # quadratic so will warn + with assert_warns(QUBOVertWarning): + res = anneal_puso(H, num_anneals=4, in_order=False, seed=0) assert res.best.state in ( dict(enumerate([1]*31)), dict(enumerate([-1]*31)) ) @@ -89,7 +99,9 @@ def _anneal_puso(type_): # make sure we run branch where an explicit schedule is given and no # temperature range is supplied - anneal_puso(H, schedule=[3, 2]) + # quadratic so will warn + with assert_warns(QUBOVertWarning): + anneal_puso(H, schedule=[3, 2]) # make sure it works with fields res = anneal_puso( @@ -106,11 +118,11 @@ def _anneal_puso(type_): type_( {(i, j, j + 1): 1 for i in range(100) for j in range(i+1, 100)} ), - num_anneals=50 + num_anneals=20 ) - assert len(res) == 50 + assert len(res) == 20 res.sort() - for i in range(49): + for i in range(19): assert res[i].value <= res[i + 1].value @@ -188,11 +200,11 @@ def _anneal_quso(type_): # big ordering res = anneal_quso( type_({(i, j): 1 for i in range(100) for j in range(i+1, 100)}), - num_anneals=50 + num_anneals=20 ) - assert len(res) == 50 + assert len(res) == 20 res.sort() - for i in range(49): + for i in range(19): assert res[i].value <= res[i + 1].value @@ -217,6 +229,10 @@ def _anneal_pubo(type_): with assert_warns(QUBOVertWarning): anneal_pubo(P, temperature_range=(1, 2), schedule=[3, 2]) + with assert_warns(QUBOVertWarning): + # a quadratic model warns that you shouldn't use anneal_pubo + anneal_pubo({(0, 1): 1}) + with assert_raises(ValueError): anneal_pubo(P, temperature_range=(1, 2)) @@ -226,7 +242,9 @@ def _anneal_pubo(type_): empty_result = AnnealResults(False) for _ in range(4): empty_result.add_state({}, 2) - assert anneal_pubo({(): 2}, num_anneals=4) == empty_result + # less than quadratic so will warn + with assert_warns(QUBOVertWarning): + assert anneal_pubo({(): 2}, num_anneals=4) == empty_result assert anneal_pubo(P, num_anneals=0) == AnnealResults(False) assert anneal_pubo(P, num_anneals=-1) == AnnealResults(False) @@ -239,7 +257,9 @@ def _anneal_pubo(type_): # check to see if we find the groundstate of a simple but largeish model. P = type_(puso_to_pubo({(i, i+1): -1 for i in range(30)})) - res = anneal_pubo(P, num_anneals=4, seed=0) + # quadratic so will warn + with assert_warns(QUBOVertWarning): + res = anneal_pubo(P, num_anneals=4, seed=0) assert res.best.state in ( dict(enumerate([0]*31)), dict(enumerate([1]*31)) ) @@ -247,7 +267,9 @@ def _anneal_pubo(type_): assert len([x for x in res]) == 4 # check to see if we find the groundstate of same but out of order - res = anneal_pubo(P, num_anneals=4, in_order=False, seed=0) + # quadratic so will warn + with assert_warns(QUBOVertWarning): + res = anneal_pubo(P, num_anneals=4, in_order=False, seed=0) assert res.best.state in ( dict(enumerate([0]*31)), dict(enumerate([1]*31)) ) @@ -256,7 +278,9 @@ def _anneal_pubo(type_): # make sure we run branch where an explicit schedule is given and no # temperature range is supplied - anneal_pubo(P, schedule=[3] * 10 + [2] * 15) + # quadratic so will warn + with assert_warns(QUBOVertWarning): + anneal_pubo(P, schedule=[3] * 10 + [2] * 15) # make sure it works with fields res = anneal_pubo(type_({(0, 1, 2): 1, (1,): -1, (): 2}), num_anneals=10) @@ -270,11 +294,11 @@ def _anneal_pubo(type_): type_( {(i, j, j + 1): 1 for i in range(100) for j in range(i+1, 100)} ), - num_anneals=50 + num_anneals=20 ) - assert len(res) == 50 + assert len(res) == 20 res.sort() - for i in range(49): + for i in range(19): assert res[i].value <= res[i + 1].value @@ -352,11 +376,11 @@ def _anneal_qubo(type_): # big ordering res = anneal_qubo( type_({(i, j): 1 for i in range(100) for j in range(i+1, 100)}), - num_anneals=50 + num_anneals=20 ) - assert len(res) == 50 + assert len(res) == 20 res.sort() - for i in range(49): + for i in range(19): assert res[i].value <= res[i + 1].value @@ -373,16 +397,14 @@ def test_anneal_quso_vs_anneal_puso(): kwargs['in_order'] = in_order for anneal_duration in (10, 100, 1000): kwargs['anneal_duration'] = anneal_duration - for num_anneals in range(7): + for num_anneals in range(1, 7): kwargs['num_anneals'] = num_anneals for T0 in (.1, 1, 10): kwargs['temperature_range'] = T0, T0 / 2 - res = ( - anneal_quso(L, **kwargs) - == - anneal_puso(L, **kwargs) - ) - assert res + # quadratic so anneal_puso will warn + with assert_warns(QUBOVertWarning): + respuso = anneal_puso(L, **kwargs) + assert respuso == anneal_quso(L, **kwargs) def test_anneal_qubo_vs_anneal_pubo(): @@ -398,13 +420,11 @@ def test_anneal_qubo_vs_anneal_pubo(): kwargs['in_order'] = in_order for anneal_duration in (10, 100, 1000): kwargs['anneal_duration'] = anneal_duration - for num_anneals in range(7): + for num_anneals in range(1, 7): kwargs['num_anneals'] = num_anneals for T0 in (.1, 1, 10): kwargs['temperature_range'] = T0, T0 / 2 - res = ( - anneal_qubo(Q, **kwargs) - == - anneal_pubo(Q, **kwargs) - ) - assert res + # quadratic so anneal_pubo will warn + with assert_warns(QUBOVertWarning): + respubo = anneal_pubo(Q, **kwargs) + assert respubo == anneal_qubo(Q, **kwargs) diff --git a/tests/test_pcbo.py b/tests/test_pcbo.py index 73d559a..45226a4 100644 --- a/tests/test_pcbo.py +++ b/tests/test_pcbo.py @@ -20,7 +20,7 @@ from qubovert.utils import ( solve_qubo_bruteforce, solve_quso_bruteforce, solve_pubo_bruteforce, solve_puso_bruteforce, - pubo_value, decimal_to_boolean, QUBOVertWarning + pubo_value, decimal_to_boolean, QUBOVertWarning, PUBOMatrix ) from sympy import Symbol from numpy import allclose @@ -460,6 +460,14 @@ def test_integer_var(): assert var.name == 'b' +def test_to_enumerated(): + + d = PCBO({('a', 'b'): 1, ('a',): 2}) + dt = d.to_enumerated() + assert type(dt) == PUBOMatrix + assert dt == d.to_pubo() + + """ TESTS FOR THE CONSTRAINT METHODS """ @@ -507,6 +515,29 @@ def test_pcbo_eq_constraint(): allclose(e, obj) )) + # lam = 0 + P = PCBO({ + ('a',): -1, ('b',): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2 + }) + P.add_constraint_eq_zero( + {('a',): 1, ('b',): 1, ('b', 'c'): -1}, + lam=0 + ) + + sol = P.solve_bruteforce() + assert all(( + P.is_solution_valid(sol), + sol == solution + )) + + e, sol = solve_pubo_bruteforce(P.to_pubo()) + sol = problem.convert_solution(sol) + assert all(( + not P.is_solution_valid(sol), + sol != solution, + not allclose(e, obj) + )) + def test_pcbo_ne_constraint_logtrick(): @@ -516,15 +547,39 @@ def test_pcbo_ne_constraint_logtrick(): for sol in H.solve_bruteforce(True): assert P.value(sol) + for i in range(1 << 4): + P = integer_var('a', 4) - i + H = PCBO(P).add_constraint_ne_zero(P, lam=0) + for sol in H.solve_bruteforce(True): + assert P.value(sol) + + for i in range(1 << 2): + P = integer_var('a', 2) - i + H = PCBO().add_constraint_ne_zero(P) + for sol in solve_pubo_bruteforce(H, True)[1]: + assert P.value(sol) + def test_pcbo_ne_constraint(): - for i in range(1 << 3): - P = integer_var('a', 3) - i + for i in range(1 << 2): + P = integer_var('a', 2) - i H = PCBO().add_constraint_ne_zero(P, log_trick=False) for sol in H.solve_bruteforce(True): assert P.value(sol) + for i in range(1 << 2): + P = integer_var('a', 2) - i + H = PCBO(P).add_constraint_ne_zero(P, lam=0, log_trick=False) + for sol in H.solve_bruteforce(True): + assert P.value(sol) + + for i in range(1 << 2): + P = integer_var('a', 2) - i + H = PCBO().add_constraint_ne_zero(P, log_trick=False) + for sol in solve_pubo_bruteforce(H, True)[1]: + assert P.value(sol) + with assert_warns(QUBOVertWarning): # never satisfied PCBO().add_constraint_ne_zero({}) @@ -587,6 +642,30 @@ def test_pcbo_lt_constraint_logtrick(): allclose(e, obj) )) + # lam = 0 + P = PCBO({ + ('a',): -1, ('b',): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2 + }) + P.add_constraint_lt_zero( + {('a',): 1, ('b',): 1, ('b', 'c'): 1, (): -3}, + lam=0 + ) + + sol = P.remove_ancilla_from_solution(P.solve_bruteforce()) + assert all(( + P.is_solution_valid(sol), + sol == solution + )) + + e, sol = solve_pubo_bruteforce(P.to_pubo()) + sol = P.convert_solution(sol) + sol = P.remove_ancilla_from_solution(sol) + assert all(( + not P.is_solution_valid(sol), + sol != solution, + not allclose(e, obj) + )) + def test_pcbo_lt_constraint(): @@ -684,6 +763,31 @@ def test_pcbo_le_constraint_logtrick(): allclose(e, obj) )) + # lam = 0 + P = PCBO({ + ('a',): -1, ('b',): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2, + ('d',): -1 + }) + P.add_constraint_le_zero( + {('a',): 1, ('b',): 1, ('b', 'c'): 1, ('d',): 1, (): -3}, + lam=0 + ) + + sol = P.remove_ancilla_from_solution(P.solve_bruteforce()) + assert all(( + P.is_solution_valid(sol), + sol == solution + )) + + e, sol = solve_pubo_bruteforce(P.to_pubo()) + sol = P.convert_solution(sol) + sol = P.remove_ancilla_from_solution(sol) + assert all(( + not P.is_solution_valid(sol), + sol != solution, + not allclose(e, obj) + )) + def test_pcbo_le_constraint(): @@ -826,6 +930,30 @@ def test_pcbo_gt_constraint_logtrick(): allclose(e, obj) )) + # lam = 0 + P = PCBO({ + ('a',): -1, ('b',): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2 + }) + P.add_constraint_gt_zero( + {('a',): -1, ('b',): -1, ('b', 'c'): -1, (): 3}, + lam=0 + ) + + sol = P.remove_ancilla_from_solution(P.solve_bruteforce()) + assert all(( + P.is_solution_valid(sol), + sol == solution + )) + + e, sol = solve_pubo_bruteforce(P.to_pubo()) + sol = P.convert_solution(sol) + sol = P.remove_ancilla_from_solution(sol) + assert all(( + not P.is_solution_valid(sol), + sol != solution, + not allclose(e, obj) + )) + def test_pcbo_gt_constraint(): @@ -923,6 +1051,31 @@ def test_pcbo_ge_constraint_logtrick(): allclose(e, obj) )) + # lam = 0 + P = PCBO({ + ('a',): -1, ('b',): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2, + ('d',): -1 + }) + P.add_constraint_ge_zero( + {('a',): -1, ('b',): -1, ('b', 'c'): -1, ('d',): -1, (): 3}, + lam=0 + ) + + sol = P.remove_ancilla_from_solution(P.solve_bruteforce()) + assert all(( + P.is_solution_valid(sol), + sol == solution + )) + + e, sol = solve_pubo_bruteforce(P.to_pubo()) + sol = P.convert_solution(sol) + sol = P.remove_ancilla_from_solution(sol) + assert all(( + not P.is_solution_valid(sol), + sol != solution, + not allclose(e, obj) + )) + def test_pcbo_ge_constraint(): diff --git a/tests/test_pcso.py b/tests/test_pcso.py index d61efe6..39552c3 100644 --- a/tests/test_pcso.py +++ b/tests/test_pcso.py @@ -21,7 +21,7 @@ solve_qubo_bruteforce, solve_quso_bruteforce, solve_pubo_bruteforce, solve_puso_bruteforce, puso_value, pubo_to_puso, boolean_to_spin, - QUBOVertWarning + QUBOVertWarning, PUSOMatrix ) from sympy import Symbol from numpy import allclose @@ -440,6 +440,14 @@ def test_spin_var(): assert all(z[i].name == i for i in range(5)) +def test_to_enumerated(): + + d = PCSO({('a', 'b'): 1, ('a',): 2}) + dt = d.to_enumerated() + assert type(dt) == PUSOMatrix + assert dt == d.to_puso() + + """ TESTS FOR THE CONSTRAINT METHODS """ @@ -487,6 +495,29 @@ def test_pcso_eq_constraint(): allclose(e, obj) )) + # lam = 0 + H = PCSO(pubo_to_puso({ + ('a',): -1, ('b',): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2 + })) + H.add_constraint_eq_zero( + pubo_to_puso({('a',): 1, ('b',): 1, ('b', 'c'): -1}), + lam=0 + ) + + sol = H.solve_bruteforce() + assert all(( + H.is_solution_valid(sol), + sol == solution + )) + + e, sol = solve_pubo_bruteforce(H.to_pubo()) + sol = H.convert_solution(sol, False) + assert all(( + not H.is_solution_valid(sol), + sol != solution, + not allclose(e, obj) + )) + def test_pcso_ne_constraint_logtrick(): @@ -496,15 +527,39 @@ def test_pcso_ne_constraint_logtrick(): for sol in H.solve_bruteforce(True): assert P.value(sol) + for i in range(1 << 4): + P = pubo_to_puso(integer_var('a', 4)) - i + H = PCSO(P).add_constraint_ne_zero(P, lam=0) + for sol in H.solve_bruteforce(True): + assert P.value(sol) + + for i in range(1 << 2): + P = pubo_to_puso(integer_var('a', 2)) - i + H = PCSO().add_constraint_ne_zero(P) + for sol in solve_puso_bruteforce(H, True)[1]: + assert P.value(sol) + def test_pcso_ne_constraint(): - for i in range(1 << 3): - P = pubo_to_puso(integer_var('a', 3)) - i + for i in range(1 << 2): + P = pubo_to_puso(integer_var('a', 2)) - i H = PCSO().add_constraint_ne_zero(P, log_trick=False) for sol in H.solve_bruteforce(True): assert P.value(sol) + for i in range(1 << 2): + P = pubo_to_puso(integer_var('a', 2)) - i + H = PCSO(P).add_constraint_ne_zero(P, lam=0, log_trick=False) + for sol in H.solve_bruteforce(True): + assert P.value(sol) + + for i in range(1 << 2): + P = pubo_to_puso(integer_var('a', 2)) - i + H = PCSO().add_constraint_ne_zero(P, log_trick=False) + for sol in solve_puso_bruteforce(H, True)[1]: + assert P.value(sol) + def test_pcso_lt_constraint_logtrick(): @@ -553,6 +608,30 @@ def test_pcso_lt_constraint_logtrick(): allclose(e, obj) )) + # lam = 0 + H = PCSO(pubo_to_puso({ + ('a',): -1, ('b',): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2 + })) + H.add_constraint_lt_zero( + pubo_to_puso({('a',): 1, ('b',): 1, ('b', 'c'): 1, (): -3}), + lam=0 + ) + + sol = H.remove_ancilla_from_solution(H.solve_bruteforce()) + assert all(( + H.is_solution_valid(sol), + sol == solution + )) + + e, sol = solve_pubo_bruteforce(H.to_pubo()) + sol = H.convert_solution(sol, spin=False) + sol = H.remove_ancilla_from_solution(sol) + assert all(( + not H.is_solution_valid(sol), + sol != solution, + not allclose(e, obj) + )) + def test_pcso_lt_constraint(): @@ -652,6 +731,33 @@ def test_pcso_le_constraint_logtrick(): allclose(e, obj) )) + # lam = 0 + H = PCSO(pubo_to_puso({ + ('a',): -1, ('b',): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2, + ('d',): -1 + })) + H.add_constraint_le_zero( + pubo_to_puso( + {('a',): 1, ('b',): 1, ('b', 'c'): 1, ('d',): 1, (): -3} + ), + lam=0 + ) + + sol = H.remove_ancilla_from_solution(H.solve_bruteforce()) + assert all(( + H.is_solution_valid(sol), + sol == solution + )) + + e, sol = solve_pubo_bruteforce(H.to_pubo()) + sol = H.convert_solution(sol, spin=False) + sol = H.remove_ancilla_from_solution(sol) + assert all(( + not H.is_solution_valid(sol), + sol != solution, + not allclose(e, obj) + )) + def test_pcso_le_constraint(): @@ -751,6 +857,30 @@ def test_pcso_gt_constraint_logtrick(): allclose(e, obj) )) + # lam = 0 + H = PCSO(pubo_to_puso({ + ('a',): -1, ('b',): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2 + })) + H.add_constraint_gt_zero( + pubo_to_puso({('a',): -1, ('b',): -1, ('b', 'c'): -1, (): 3}), + lam=0 + ) + + sol = H.remove_ancilla_from_solution(H.solve_bruteforce()) + assert all(( + H.is_solution_valid(sol), + sol == solution + )) + + e, sol = solve_pubo_bruteforce(H.to_pubo()) + sol = H.convert_solution(sol, spin=False) + sol = H.remove_ancilla_from_solution(sol) + assert all(( + not H.is_solution_valid(sol), + sol != solution, + not allclose(e, obj) + )) + def test_pcso_gt_constraint(): @@ -850,6 +980,33 @@ def test_pcso_ge_constraint_logtrick(): allclose(e, obj) )) + # lam = 0 + H = PCSO(pubo_to_puso({ + ('a',): -1, ('b',): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2, + ('d',): -1 + })) + H.add_constraint_ge_zero( + pubo_to_puso( + {('a',): -1, ('b',): -1, ('b', 'c'): -1, ('d',): -1, (): 3} + ), + lam=0 + ) + + sol = H.remove_ancilla_from_solution(H.solve_bruteforce()) + assert all(( + H.is_solution_valid(sol), + sol == solution + )) + + e, sol = solve_pubo_bruteforce(H.to_pubo()) + sol = H.convert_solution(sol, spin=False) + sol = H.remove_ancilla_from_solution(sol) + assert all(( + not H.is_solution_valid(sol), + sol != solution, + not allclose(e, obj) + )) + def test_pcso_ge_constraint(): diff --git a/tests/test_pubo.py b/tests/test_pubo.py index f21c0db..add47d4 100644 --- a/tests/test_pubo.py +++ b/tests/test_pubo.py @@ -20,7 +20,7 @@ from qubovert.utils import ( solve_qubo_bruteforce, solve_quso_bruteforce, solve_pubo_bruteforce, solve_puso_bruteforce, - pubo_value + pubo_value, PUBOMatrix ) from sympy import Symbol from numpy import allclose @@ -414,6 +414,14 @@ def test_set_mapping(): assert d.to_pubo() == {(0, 2): 1, (0,): 2} +def test_to_enumerated(): + + d = PUBO({('a', 'b'): 1, ('a',): 2}) + dt = d.to_enumerated() + assert type(dt) == PUBOMatrix + assert dt == d.to_pubo() + + def test_pubo_degree_reduction_pairs(): pubo = PUBO({ diff --git a/tests/test_puso.py b/tests/test_puso.py index 57e0dcb..420838e 100644 --- a/tests/test_puso.py +++ b/tests/test_puso.py @@ -18,7 +18,7 @@ from qubovert.utils import ( solve_qubo_bruteforce, solve_quso_bruteforce, solve_pubo_bruteforce, solve_puso_bruteforce, - puso_value, pubo_to_puso + puso_value, pubo_to_puso, PUSOMatrix ) from sympy import Symbol from numpy import allclose @@ -415,6 +415,14 @@ def test_set_mapping(): assert d.to_puso() == {(0, 2): 1, (0,): 2} +def test_to_enumerated(): + + d = PUSO({('a', 'b'): 1, ('a',): 2}) + dt = d.to_enumerated() + assert type(dt) == PUSOMatrix + assert dt == d.to_puso() + + def test_puso_degree_reduction_pairs(): puso = pubo_to_puso({ diff --git a/tests/test_qubo.py b/tests/test_qubo.py index b1d4391..5897ab6 100644 --- a/tests/test_qubo.py +++ b/tests/test_qubo.py @@ -20,7 +20,7 @@ from qubovert.utils import ( solve_qubo_bruteforce, solve_quso_bruteforce, solve_pubo_bruteforce, solve_puso_bruteforce, - qubo_value + qubo_value, QUBOMatrix ) from sympy import Symbol from numpy import allclose @@ -394,3 +394,11 @@ def test_set_mapping(): d = QUBO({('a', 'b'): 1, ('a',): 2}) d.set_reverse_mapping({0: 'a', 2: 'b'}) assert d.to_qubo() == {(0, 2): 1, (0,): 2} + + +def test_to_enumerated(): + + d = QUBO({('a', 'b'): 1, ('a',): 2}) + dt = d.to_enumerated() + assert type(dt) == QUBOMatrix + assert dt == d.to_qubo() diff --git a/tests/test_quso.py b/tests/test_quso.py index 385442b..a448d89 100644 --- a/tests/test_quso.py +++ b/tests/test_quso.py @@ -20,7 +20,7 @@ from qubovert.utils import ( solve_qubo_bruteforce, solve_quso_bruteforce, solve_pubo_bruteforce, solve_puso_bruteforce, - quso_value + quso_value, QUSOMatrix ) from sympy import Symbol from numpy import allclose @@ -403,3 +403,11 @@ def test_set_mapping(): d = QUSO({('a', 'b'): 1, ('a',): 2}) d.set_reverse_mapping({0: 'a', 2: 'b'}) assert d.to_quso() == {(0, 2): 1, (0,): 2} + + +def test_to_enumerated(): + + d = QUSO({('a', 'b'): 1, ('a',): 2}) + dt = d.to_enumerated() + assert type(dt) == QUSOMatrix + assert dt == d.to_quso() diff --git a/tests/utils/test_binary_helpers.py b/tests/utils/test_binary_helpers.py index 2a37fd3..bb5cd61 100644 --- a/tests/utils/test_binary_helpers.py +++ b/tests/utils/test_binary_helpers.py @@ -17,6 +17,8 @@ """ from qubovert.utils import is_solution_spin, num_bits +from qubovert.utils import sum as qvsum +from qubovert import boolean_var, spin_var, PCBO, PCSO from numpy.testing import assert_raises @@ -47,3 +49,16 @@ def test_num_bits(): with assert_raises(ValueError): num_bits(-1, False) + + +def test_sum(): + + xs = [boolean_var(i) for i in range(100)] + assert sum(xs) == qvsum(xs) == qvsum(xs[i] for i in range(100)) + assert sum(xs, 2) == qvsum(xs, 2) + assert isinstance(qvsum(xs), PCBO) + + zs = [spin_var(i) for i in range(100)] + assert sum(zs) == qvsum(zs) == qvsum(zs[i] for i in range(100)) + assert sum(zs, 2) == qvsum(zs, 2) + assert isinstance(qvsum(zs), PCSO) diff --git a/tests/utils/test_values.py b/tests/utils/test_values.py new file mode 100644 index 0000000..b6ea1e2 --- /dev/null +++ b/tests/utils/test_values.py @@ -0,0 +1,75 @@ +# Copyright 2020 Joseph T. Iosue +# +# 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. + +""" +Contains tests for functions in the _values.py file. +""" + +from qubovert.utils import ( + pubo_value, puso_value, qubo_value, quso_value, + pubo_to_puso, qubo_to_quso, boolean_to_spin +) +from numpy.testing import assert_allclose +import random +import itertools + + +def test_pubo_qubo_equal(): + + random.seed(123) + qubo = {(i, j): random.random() for i in range(7) for j in range(7)} + qubo.update({(i,): random.random() for i in range(7)}) + qubo[()] = random.random() + for sol in itertools.product((0, 1), repeat=7): + assert_allclose(pubo_value(sol, qubo), qubo_value(sol, qubo)) + + +def test_puso_quso_equal(): + + random.seed(321) + quso = {(i, j): random.random() for i in range(7) for j in range(7)} + quso.update({(i,): random.random() for i in range(7)}) + quso[()] = random.random() + for sol in itertools.product((-1, 1), repeat=7): + assert_allclose(puso_value(sol, quso), quso_value(sol, quso)) + + +def test_pubo_puso_equal(): + + random.seed(518) + pubo = { + (i, j, k): random.random() + for i in range(7) for j in range(7) for k in range(7) + } + pubo.update({(i, j): random.random() for i in range(7) for j in range(7)}) + pubo.update({(i,): random.random() for i in range(7)}) + pubo[()] = random.random() + for sol in itertools.product((0, 1), repeat=7): + assert_allclose( + pubo_value(sol, pubo), + puso_value(boolean_to_spin(sol), pubo_to_puso(pubo)) + ) + + +def test_qubo_quso_equal(): + + random.seed(815) + qubo = {(i, j): random.random() for i in range(7) for j in range(7)} + qubo.update({(i,): random.random() for i in range(7)}) + qubo[()] = random.random() + for sol in itertools.product((0, 1), repeat=7): + assert_allclose( + qubo_value(sol, qubo), + quso_value(boolean_to_spin(sol), qubo_to_quso(qubo)) + )