From 7f2fae6f444fef4dbfd2efb996d8a454366bda79 Mon Sep 17 00:00:00 2001 From: "Joseph T. Iosue" Date: Wed, 17 Jun 2020 11:07:33 -0700 Subject: [PATCH] v1.2.0 (#28) * Recreated dev branch and incremented version * Better C library for annealing QUSOs and PUSOs, remove Simulation. (#27) * anneal c library, remove simulation objects * update links in README * update links in README * pretty_str method * added test for pretty str --- README.rst | 31 +- docs/copyright.rst | 2 +- docs/index.rst | 2 +- docs/sim/anneal.rst | 18 +- docs/sim/anneal_results.rst | 13 + docs/sim/simulation.rst | 38 - notebook_examples/3SAT_example.ipynb | 80 +- notebook_examples/Annealing.ipynb | 759 ++++++++++++++ .../Annealing_and_simulation.ipynb | 983 ------------------ notebook_examples/FaultTree_example.ipynb | 74 +- notebook_examples/JobSequencing_example.ipynb | 20 +- notebook_examples/PCBO_example.ipynb | 66 +- notebook_examples/VertexCover_example.ipynb | 80 +- qubovert/__init__.py | 2 +- qubovert/_qubo.py | 7 +- qubovert/_quso.py | 5 +- qubovert/_version.py | 4 +- .../_alternating_sectors_chain.py | 5 +- qubovert/problems/np/bilp/_bilp.py | 5 +- .../problems/np/coloring/_job_sequencing.py | 5 +- qubovert/problems/np/covering/_set_cover.py | 5 +- .../problems/np/covering/_vertex_cover.py | 5 +- qubovert/sim/__init__.py | 20 +- qubovert/sim/_anneal.py | 560 +++++----- qubovert/sim/_anneal_temperature_range.py | 112 ++ qubovert/sim/_canneal.c | 354 +++++++ qubovert/sim/_pubo_simulation.py | 133 --- qubovert/sim/_puso_simulation.py | 320 ------ qubovert/sim/_qubo_simulation.py | 132 --- qubovert/sim/_quso_simulation.py | 261 ----- qubovert/sim/_simulate_quso.c | 186 ---- qubovert/sim/src/anneal_puso.c | 340 ++++++ qubovert/sim/src/anneal_puso.h | 10 + qubovert/sim/src/anneal_quso.c | 352 +++++++ qubovert/sim/src/anneal_quso.h | 10 + qubovert/sim/src/random.c | 41 + qubovert/sim/src/random.h | 18 + qubovert/sim/src/simulate_quso.c | 248 ----- qubovert/sim/src/simulate_quso.h | 25 - qubovert/utils/_binary_helpers.py | 44 +- qubovert/utils/_pubomatrix.py | 42 + qubovert/utils/_pusomatrix.py | 18 + setup.py | 10 +- tests/sim/test_anneal.py | 278 +++-- tests/sim/test_anneal_temperature_range.py | 86 ++ tests/sim/test_pubo_simulation.py | 106 -- tests/sim/test_puso_simulation.py | 119 --- tests/sim/test_qubo_simulation.py | 151 --- tests/sim/test_quso_simulation.py | 166 --- tests/test_pcbo.py | 23 + tests/test_pcso.py | 23 + tests/test_pubo.py | 23 + tests/test_puso.py | 23 + tests/test_qubo.py | 23 + tests/test_quso.py | 23 + tests/utils/test_binary_helpers.py | 24 +- tests/utils/test_pubomatrix.py | 26 + tests/utils/test_pusomatrix.py | 23 + tests/utils/test_qubomatrix.py | 23 + tests/utils/test_qusomatrix.py | 23 + 60 files changed, 3091 insertions(+), 3517 deletions(-) create mode 100644 docs/sim/anneal_results.rst delete mode 100644 docs/sim/simulation.rst create mode 100644 notebook_examples/Annealing.ipynb delete mode 100644 notebook_examples/Annealing_and_simulation.ipynb create mode 100644 qubovert/sim/_anneal_temperature_range.py create mode 100644 qubovert/sim/_canneal.c delete mode 100644 qubovert/sim/_pubo_simulation.py delete mode 100644 qubovert/sim/_puso_simulation.py delete mode 100644 qubovert/sim/_qubo_simulation.py delete mode 100644 qubovert/sim/_quso_simulation.py delete mode 100644 qubovert/sim/_simulate_quso.c create mode 100644 qubovert/sim/src/anneal_puso.c create mode 100644 qubovert/sim/src/anneal_puso.h create mode 100644 qubovert/sim/src/anneal_quso.c create mode 100644 qubovert/sim/src/anneal_quso.h create mode 100644 qubovert/sim/src/random.c create mode 100644 qubovert/sim/src/random.h delete mode 100644 qubovert/sim/src/simulate_quso.c delete mode 100644 qubovert/sim/src/simulate_quso.h create mode 100644 tests/sim/test_anneal_temperature_range.py delete mode 100644 tests/sim/test_pubo_simulation.py delete mode 100644 tests/sim/test_puso_simulation.py delete mode 100644 tests/sim/test_qubo_simulation.py delete mode 100644 tests/sim/test_quso_simulation.py diff --git a/README.rst b/README.rst index aaece65..ed0da68 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ The one-stop package for formulating, simulating, and solving problems in boolea :target: https://qubovert.readthedocs.io/en/latest/ :alt: Documentation Status .. image:: https://codecov.io/gh/jtiosue/qubovert/branch/master/graph/badge.svg - :target: https://codecov.io/gh/jtiosue/qubovert + :target: https://codecov.io/gh/jtiosue/qubovert/branch/master :alt: Code Coverage .. image:: https://img.shields.io/lgtm/grade/python/g/jtiosue/qubovert.svg?logo=lgtm&logoWidth=18 :target: https://lgtm.com/projects/g/jtiosue/qubovert/context:python @@ -28,7 +28,7 @@ The one-stop package for formulating, simulating, and solving problems in boolea :target: https://qubovert.readthedocs.io/en/dev/ :alt: Documentation Status .. image:: https://codecov.io/gh/jtiosue/qubovert/branch/dev/graph/badge.svg - :target: https://codecov.io/gh/jtiosue/qubovert + :target: https://codecov.io/gh/jtiosue/qubovert/branch/dev :alt: Code Coverage *pypi distribution* @@ -327,33 +327,6 @@ Now we have to convert the solution in terms of the QUBO/QUSO variables back to print(model.is_solution_valid(converted_quso_solution)) -Simulating spin and boolean systems ------------------------------------ - -We use a Metropolis algorithm to simulate spin and boolean system. Below we show an example for simulating a spin system (specifically, a 1D ferromagnetic chain). Similar functionality exists for QUBO, PUBO, and PUSO simulation with ``qubovert.sim.QUBOSimulation``, ``qubovert.sim.PUBOSimulation``, ``qubovert.sim.PUSOSimulation``. Please note that `QUSOSimulation`` and ``QUBOSimulation`` are much faster than ``PUSOSimulation`` and ``PUBOSimulation`` because the former are implemented in C. If you have a degree 2 or less model, use the QUBO and QUSO simulations! - -.. code:: python - - import qubovert as qv - - length = 50 - spin_system = sum( - -qv.spin_var(i) * qv.spin_var(i+1) for i in range(length) - ) - - # initial state is all spin down - initial_state = {i: -1 for i in range(length)} - sim = qv.sim.QUSOSimulation(spin_system, initial_state) - - # define a schedule. here we simulate at temperature 4 for 25 time - # steps, then temperature 2 for 25 time steps, then temperature 1 for - # 10 time steps. - schedule = (4, 25), (2, 25), (1, 10) - sim.schedule_update(schedule) - - print("final state", sim.state) - - Convert common problems to quadratic form (the *problems* library) ------------------------------------------------------------------ diff --git a/docs/copyright.rst b/docs/copyright.rst index 30baff8..1f3e4a0 100644 --- a/docs/copyright.rst +++ b/docs/copyright.rst @@ -1,6 +1,6 @@ Copyright ========= -Joseph T. Iosue, joe.iosue@yahoo.com. +Joseph T. Iosue, jtiosue@gmail.com. .. include:: ../LICENSE diff --git a/docs/index.rst b/docs/index.rst index afde092..5fc46b7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,7 +32,7 @@ Indices and tables :caption: Boolean/Spin Simulation sim/anneal - sim/simulation + sim/anneal_results .. toctree:: diff --git a/docs/sim/anneal.rst b/docs/sim/anneal.rst index 023ce72..d27e44b 100644 --- a/docs/sim/anneal.rst +++ b/docs/sim/anneal.rst @@ -1,9 +1,9 @@ Annealing ========= -Note that the ``sim`` module will not be imported with ``from qubovert import *``. You must import ``qubovert.sim`` explicitly. Here we show some functions to use the boolean and spin simulation to run simulated annealing on the models. +These function interface with C source code to provide fast execution of the simulated annealing algorithm. Note that the ``sim`` module will not be imported with ``from qubovert import *``. You must import ``qubovert.sim`` explicitly. Here we show some functions to use the boolean and spin simulation to run simulated annealing on the models. -**Please note** that the ``qv.sim.anneal_qubo`` and ``qv.sim.anneal_quso`` functions perform much faster than the ``qv.sim.anneal_pubo`` and ``qv.sim.anneal_puso`` functions since the former are written in C and wrapped in Python. If your system has degree 2 or less, then you should use the QUBO or QUSO anneal functions! +**Please note** that the ``qv.sim.anneal_qubo`` and ``qv.sim.anneal_quso`` functions perform faster than the ``qv.sim.anneal_pubo`` and ``qv.sim.anneal_puso`` functions. If your system has degree 2 or less, then you should use the QUBO or QUSO anneal functions! @@ -31,20 +31,6 @@ Anneal QUSO .. autofunction:: qubovert.sim.anneal_quso -Anneal Results --------------- - -These objects are defined to deal with the output of the annealing functions. - - -.. autoclass:: qubovert.sim.AnnealResults - :members: - - -.. autoclass:: qubovert.sim.AnnealResult - :members: - - Anneal temperature range ------------------------ diff --git a/docs/sim/anneal_results.rst b/docs/sim/anneal_results.rst new file mode 100644 index 0000000..57ce94c --- /dev/null +++ b/docs/sim/anneal_results.rst @@ -0,0 +1,13 @@ +Anneal Results +============== + +These objects are defined to deal with the output of the annealing functions. + + +.. autoclass:: qubovert.sim.AnnealResults + :members: + :inherited-members: + + +.. autoclass:: qubovert.sim.AnnealResult + :members: diff --git a/docs/sim/simulation.rst b/docs/sim/simulation.rst deleted file mode 100644 index 2b6bb3b..0000000 --- a/docs/sim/simulation.rst +++ /dev/null @@ -1,38 +0,0 @@ -Simulation -========== - -Note that the ``sim`` module will not be imported with ``from qubovert import *``. You must import ``qubovert.sim`` explicitly. - -**Please note** that the ``qv.sim.QUBOSimulation`` and ``qv.sim.QUSOSimulation`` objects perform simulation much faster than the ``qv.sim.PUBOSimulation`` and ``qv.sim.PUSOSimulation`` objects since the former are written in C and wrapped in Python. If your system has degree 2 or less, then you should use the QUBO or QUSO simulations! - - -PUBO Simulation ---------------- - -.. autoclass:: qubovert.sim.PUBOSimulation - :members: - :inherited-members: - - -PUSO Simulation ---------------- - -.. autoclass:: qubovert.sim.PUSOSimulation - :members: - :inherited-members: - - -QUBO Simulation ---------------- - -.. autoclass:: qubovert.sim.QUBOSimulation - :members: - :inherited-members: - - -QUSO Simulation ---------------- - -.. autoclass:: qubovert.sim.QUSOSimulation - :members: - :inherited-members: diff --git a/notebook_examples/3SAT_example.ipynb b/notebook_examples/3SAT_example.ipynb index aa0d9c5..96ee3c6 100644 --- a/notebook_examples/3SAT_example.ipynb +++ b/notebook_examples/3SAT_example.ipynb @@ -40,7 +40,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -73,7 +73,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -104,7 +104,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -117,7 +117,7 @@ "\n", "{('x_3',): -1, (): 1, ('-x_3',): -1}\n", "\n", - "{('x_0',): -1, ('x_1', 'x_0'): 1, ('x_1',): -1, ('x_3', 'x_0'): 1, ('x_3', 'x_1', 'x_0'): -1, ('x_3', 'x_1'): 1, ('x_3',): -1, (): 1}\n", + "{('x_0',): -1, ('x_1', 'x_0'): 1, ('x_1',): -1, ('x_3', 'x_0'): 1, ('x_1', 'x_3', 'x_0'): -1, ('x_1', 'x_3'): 1, ('x_3',): -1, (): 1}\n", "\n", "{('x_1',): -1, ('x_1', '-x_2'): 1, ('-x_2',): -1, ('x_1', '-x_3'): 1, ('x_1', '-x_3', '-x_2'): -1, ('-x_3', '-x_2'): 1, ('-x_3',): -1, (): 1}\n", "\n", @@ -141,27 +141,27 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{('-x_0', 'x_0'): 2,\n", + "{('x_0', '-x_0'): 2,\n", " ('x_0',): -2,\n", " (): 6,\n", " ('-x_0',): -2,\n", - " ('x_2', '-x_2'): 2,\n", + " ('-x_2', 'x_2'): 2,\n", " ('x_2',): -2,\n", " ('-x_2',): -2,\n", - " ('x_3', '-x_3'): 2,\n", + " ('-x_3', 'x_3'): 2,\n", " ('x_3',): -2,\n", " ('-x_3',): -3,\n", " ('x_1', 'x_0'): 1,\n", " ('x_1',): -2,\n", " ('x_3', 'x_0'): 1,\n", - " ('x_3', 'x_1', 'x_0'): -1,\n", - " ('x_3', 'x_1'): 1,\n", + " ('x_1', 'x_3', 'x_0'): -1,\n", + " ('x_1', 'x_3'): 1,\n", " ('x_1', '-x_2'): 1,\n", " ('x_1', '-x_3'): 1,\n", " ('x_1', '-x_3', '-x_2'): -1,\n", @@ -172,7 +172,7 @@ " ('-x_3', 'x_2'): 1}" ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -190,7 +190,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -224,7 +224,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -233,7 +233,7 @@ "3" ] }, - "execution_count": 7, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -251,14 +251,14 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{(0, 1): 2, (1,): -2, (0,): -2, (2, 3): 2, (2,): -2, (3,): -2, (4, 5): 2, (4,): -2, (5,): -3, (1, 6): 1, (6,): -2, (1, 4): 3, (7,): 6, (1, 7): -4, (4, 7): -4, (6, 7): -1, (4, 6): 1, (3, 6): 1, (5, 6): 1, (8,): 6, (3, 5): 3, (3, 8): -4, (5, 8): -4, (6, 8): -1, (0, 2): 3, (0, 5): 1, (9,): 6, (0, 9): -4, (2, 9): -4, (5, 9): -1, (2, 5): 1, (): 6}\n" + "{(0, 1): 2, (0,): -2, (1,): -2, (2, 3): 2, (3,): -2, (2,): -2, (4, 5): 2, (5,): -2, (4,): -3, (0, 6): 1, (6,): -2, (0, 5): 3, (7,): 6, (0, 7): -4, (5, 7): -4, (6, 7): -1, (5, 6): 1, (2, 6): 1, (4, 6): 1, (8,): 6, (2, 4): 3, (2, 8): -4, (4, 8): -4, (6, 8): -1, (1, 3): 3, (1, 4): 1, (9,): 6, (1, 9): -4, (3, 9): -4, (4, 9): -1, (3, 4): 1, (): 6}\n" ] } ], @@ -276,7 +276,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -305,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -324,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -333,9 +333,9 @@ "text": [ "objective function: 0.0 \n", "\n", - "qubo solution: {0: 1, 1: 0, 2: 1, 3: 0, 4: 1, 5: 0, 6: 1, 7: 0, 8: 0, 9: 1} \n", + "qubo solution: {0: 1, 1: 0, 2: 0, 3: 1, 4: 1, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0} \n", "\n", - "pcbo solution: {'-x_0': 1, 'x_0': 0, 'x_2': 1, '-x_2': 0, 'x_3': 1, '-x_3': 0, 'x_1': 1}\n", + "pcbo solution: {'x_0': 1, '-x_0': 0, '-x_2': 0, 'x_2': 1, '-x_3': 1, 'x_3': 0, 'x_1': 0}\n", "objective function: 0 \n", "\n", "The solution is valid\n" @@ -367,14 +367,14 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{(0, 1): 0.5, (0,): 0.5, (1,): 0.5, (2, 3): 0.5, (2,): 0.5, (3,): 0.5, (4, 5): 0.5, (4,): 0.5, (1, 6): 0.25, (6,): 0.5, (1, 4): 0.75, (7,): -0.75, (1, 7): -1.0, (4, 7): -1.0, (6, 7): -0.25, (4, 6): 0.25, (3, 6): 0.25, (5, 6): 0.25, (8,): -0.75, (3, 5): 0.75, (3, 8): -1.0, (5, 8): -1.0, (5,): 0.75, (6, 8): -0.25, (0, 2): 0.75, (0, 5): 0.25, (9,): -0.75, (0, 9): -1.0, (2, 9): -1.0, (5, 9): -0.25, (2, 5): 0.25, (): 6}\n" + "{(0, 1): 0.5, (0,): 0.5, (1,): 0.5, (2, 3): 0.5, (2,): 0.5, (3,): 0.5, (4, 5): 0.5, (5,): 0.5, (0, 6): 0.25, (6,): 0.5, (0, 5): 0.75, (7,): -0.75, (0, 7): -1.0, (5, 7): -1.0, (6, 7): -0.25, (5, 6): 0.25, (2, 6): 0.25, (4, 6): 0.25, (8,): -0.75, (2, 4): 0.75, (2, 8): -1.0, (4, 8): -1.0, (4,): 0.75, (6, 8): -0.25, (1, 3): 0.75, (1, 4): 0.25, (9,): -0.75, (1, 9): -1.0, (3, 9): -1.0, (4, 9): -0.25, (3, 4): 0.25, (): 6}\n" ] } ], @@ -392,7 +392,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -401,9 +401,9 @@ "text": [ "objective function: 0.0 \n", "\n", - "quso solution: {0: -1, 1: 1, 2: 1, 3: -1, 4: 1, 5: -1, 6: -1, 7: 1, 8: -1, 9: 1} \n", + "quso solution: {0: 1, 1: -1, 2: 1, 3: -1, 4: -1, 5: 1, 6: -1, 7: 1, 8: 1, 9: -1} \n", "\n", - "pcbo solution: {'-x_0': 1, 'x_0': 0, 'x_2': 0, '-x_2': 1, 'x_3': 0, '-x_3': 1, 'x_1': 1}\n", + "pcbo solution: {'x_0': 0, '-x_0': 1, '-x_2': 0, 'x_2': 1, '-x_3': 1, 'x_3': 0, 'x_1': 1}\n", "objective function: 0 \n", "\n", "The solution is valid\n" @@ -452,14 +452,14 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{('x_0',): 1, ('x_3', 'x_0'): -2, ('x_1', 'x_0'): -1, ('x_1',): 1, ('x_3', 'x_1'): -1, ('x_3', 'x_1', 'x_0'): 1, ('x_3',): 1, ('x_3', 'x_0', 'x_2'): 1, ('x_3', 'x_1', 'x_2'): 1, ('x_3', 'x_2'): -1}\n" + "{('x_0',): 1, ('x_3', 'x_0'): -2, ('x_1', 'x_0'): -1, ('x_1',): 1, ('x_1', 'x_3'): -1, ('x_1', 'x_3', 'x_0'): 1, ('x_3',): 1, ('x_3', 'x_0', 'x_2'): 1, ('x_1', 'x_3', 'x_2'): 1, ('x_3', 'x_2'): -1}\n" ] } ], @@ -483,7 +483,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -510,7 +510,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -537,7 +537,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -546,10 +546,10 @@ "text": [ "Number of QUBO variables: 6 \n", "\n", - "qubo solution: {0: 1, 1: 0, 2: 1, 3: 0, 4: 1, 5: 0, 6: 1, 7: 0, 8: 0, 9: 1}\n", + "qubo solution: {0: 1, 1: 0, 2: 0, 3: 1, 4: 1, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0}\n", "qubo objective function: -1.0 \n", "\n", - "pubo solution: {'x_0': 1, 'x_3': 0, 'x_1': 1, 'x_2': 0}\n", + "pubo solution: {'x_0': 1, 'x_3': 0, 'x_1': 0, 'x_2': 1}\n", "pubo objective function: -1 \n", "\n", "C objective function (1 if satisfying else 0): 1 \n", @@ -586,7 +586,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -595,13 +595,13 @@ "text": [ "Number of QUSO variables: 6 \n", "\n", - "quso solution: {0: -1, 1: 1, 2: 1, 3: -1, 4: 1, 5: -1, 6: -1, 7: 1, 8: -1, 9: 1}\n", + "quso solution: {0: 1, 1: -1, 2: 1, 3: -1, 4: -1, 5: 1, 6: -1, 7: 1, 8: 1, 9: -1}\n", "quso objective function: -1.0 \n", "\n", - "pubo solution: {'x_0': 1, 'x_3': 0, 'x_1': 0, 'x_2': 1}\n", - "pubo objective function: -1 \n", + "pubo solution: {'x_0': 0, 'x_3': 1, 'x_1': 0, 'x_2': 1}\n", + "pubo objective function: 0 \n", "\n", - "C objective function (1 if satisfying else 0): 1 \n", + "C objective function (1 if satisfying else 0): 0 \n", "\n" ] } @@ -654,7 +654,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -710,7 +710,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.6.9" } }, "nbformat": 4, diff --git a/notebook_examples/Annealing.ipynb b/notebook_examples/Annealing.ipynb new file mode 100644 index 0000000..3100917 --- /dev/null +++ b/notebook_examples/Annealing.ipynb @@ -0,0 +1,759 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Annealing with `qubovert`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*qubovert* must be pip installed." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Import `qubovert`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import qubovert as qv" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this notebook, we will review some basics of the annealing and simulation functionality provided by `qubovert`. Let's look at everything in the simulation (`sim`) library." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "('anneal_temperature_range', 'AnnealResult', 'AnnealResults', 'anneal_qubo', 'anneal_quso', 'anneal_pubo', 'anneal_puso', 'SCHEDULES')\n" + ] + } + ], + "source": [ + "print(qv.sim.__all__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this notebook we will discuss `anneal_puso`, `anneal_temperature_range`, `AnnealResults`, and `AnnealResult`. We will not discuss `anneal_qubo`, `anneal_quso`, and `anneal_pubo` are all used very similarly to `anneal_puso`. **Please note that the `anneal_quso`, `anneal_qubo` functions are faster than their counterparts**. If you have a QUSO or QUBO, do not use the PUSO or PUBO methods!\n", + "\n", + "`AnnealResults`, `AnnealResult`, and `anneal_puso` will be discussed in the Annealing section; a more detailed usage of `anneal_pubo` and `anneal_temperature_range` will be discussed in the Advanced Annealing section.\n", + "\n", + "### Table of Contents\n", + "\n", + "1. Working Example\n", + "3. Annealing\n", + "4. Advanced Annealing\n", + " 1. Adjusting the temperature range\n", + " 2. Adjusting the initial state\n", + " 3. Adjusting the schedule" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Working Example\n", + "\n", + "For all of the following examples, we will use the following example; an Ising model on a line of length $L$ with nearest- and next-to-nearest-neighbor interactions with periodic boundary conditions. Our system is represented by the Hamiltonian\n", + "\n", + "$$H = -\\sum_{i=0}^{L-1} z_i z_{i+1} z_{i+2}$$\n", + "\n", + "where each $z_i \\in \\{1, -1 \\}$ and they are defined modulo $L$ such that $z_{i+L} = z_{i}$. First, let's define the length of our system and create the Hamiltonian." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "L = 100\n", + "\n", + "# define the function z that returns the ith variable modulo L\n", + "z_variables = [qv.spin_var(i) for i in range(L)]\n", + "z = lambda i: z_variables[i % L]\n", + "\n", + "# create the Hamiltonian\n", + "H = 0\n", + "for i in range(L):\n", + " H -= z(i) * z(i+1) * z(i+2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that the groundstate of this Hamiltonian is the state where each $z_i = 1$ (if we did not assume periodic boundary conditions, then the ground state is degenerate, with some being more complicated then the all 1 state). " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Back to Table of Contents" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Annealing" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the sake of reproducability throughout this notebook we will fix a seed for the random number generator." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "seed = 34" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we show basic usage of the `anneal_puso` function. We will use it to try to find the groundstate of the Hamiltonian discuessed and created in the Working Example section. Let's anneal the Hamiltonian to see if we can find the groundstate. Recall that PUSO is short for Polynomial Unconstrained Spin Optimization. We can use the `anneal_puso` function to anneal the Hamiltonian.\n", + "\n", + "For the sake of reproducability, we will seed the random number generator that `anneal_puso` uses." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "res = qv.sim.anneal_puso(H, num_anneals=10, seed=seed)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's look at the result that if found with the lowest energy." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " state: {0: 1, 1: -1, 2: -1, 3: 1, 4: -1, 5: -1, 6: 1, 7: -1, 8: -1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: -1, 28: -1, 29: 1, 30: -1, 31: -1, 32: 1, 33: -1, 34: -1, 35: 1, 36: -1, 37: -1, 38: 1, 39: -1, 40: -1, 41: 1, 42: -1, 43: -1, 44: 1, 45: -1, 46: -1, 47: 1, 48: -1, 49: -1, 50: 1, 51: -1, 52: 1, 53: -1, 54: -1, 55: 1, 56: -1, 57: -1, 58: 1, 59: -1, 60: -1, 61: 1, 62: -1, 63: -1, 64: 1, 65: -1, 66: -1, 67: 1, 68: -1, 69: -1, 70: 1, 71: -1, 72: -1, 73: 1, 74: -1, 75: -1, 76: 1, 77: -1, 78: -1, 79: 1, 80: -1, 81: -1, 82: 1, 83: -1, 84: -1, 85: 1, 86: -1, 87: -1, 88: 1, 89: -1, 90: -1, 91: 1, 92: -1, 93: -1, 94: 1, 95: -1, 96: -1, 97: 1, 98: -1, 99: -1}\n", + " value: -94\n", + " spin: True\n" + ] + } + ], + "source": [ + "print(res.best)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice that none of the 10 results found the ground state! But if we increase the duration of each anneal, then we can. By default, the anneal duration (the number of time steps to run each simulation for) is 1000. Let's increase it to 6000 and see if it finds the ground state." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " state: {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1, 71: 1, 72: 1, 73: 1, 74: 1, 75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 1, 82: 1, 83: 1, 84: 1, 85: 1, 86: 1, 87: 1, 88: 1, 89: 1, 90: 1, 91: 1, 92: 1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: 1, 99: 1}\n", + " value: -100\n", + " spin: True\n" + ] + } + ], + "source": [ + "res = qv.sim.anneal_puso(H, num_anneals=10, anneal_duration=6000, seed=seed)\n", + "print(res.best)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Indeed we find the ground state! Adjusting the anneal duration is one thing you can do to try to get better performance out of the annealer. In the next section, we will discuss some more.\n", + "\n", + "Now let's look at the result. `res` is a `qubovert.sim.AnnealResults` object. It will contain 10 results, since we set `num_anneals` to 10." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "10" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(res)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's look at the result that it found with the lowest energy. `res.best` is a `qubovert.sim.AnnealResult` object. We can get the state with `.state` and the energy with `.value`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Energy -100.0\n", + "State {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1, 71: 1, 72: 1, 73: 1, 74: 1, 75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 1, 82: 1, 83: 1, 84: 1, 85: 1, 86: 1, 87: 1, 88: 1, 89: 1, 90: 1, 91: 1, 92: 1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: 1, 99: 1}\n" + ] + } + ], + "source": [ + "print(\"Energy\", res.best.value)\n", + "print(\"State\", res.best.state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's sort the results and then look at all 10 of them." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AnnealResults\n", + " state: {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1, 71: 1, 72: 1, 73: 1, 74: 1, 75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 1, 82: 1, 83: 1, 84: 1, 85: 1, 86: 1, 87: 1, 88: 1, 89: 1, 90: 1, 91: 1, 92: 1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: 1, 99: 1}\n", + " value: -100\n", + " spin: True\n", + "\n", + " state: {0: -1, 1: 1, 2: -1, 3: -1, 4: 1, 5: -1, 6: -1, 7: 1, 8: -1, 9: -1, 10: 1, 11: -1, 12: -1, 13: 1, 14: -1, 15: -1, 16: -1, 17: 1, 18: -1, 19: -1, 20: 1, 21: -1, 22: -1, 23: 1, 24: -1, 25: -1, 26: 1, 27: -1, 28: -1, 29: 1, 30: -1, 31: -1, 32: 1, 33: -1, 34: -1, 35: 1, 36: -1, 37: -1, 38: 1, 39: -1, 40: -1, 41: 1, 42: -1, 43: -1, 44: 1, 45: -1, 46: -1, 47: 1, 48: -1, 49: -1, 50: 1, 51: -1, 52: -1, 53: 1, 54: -1, 55: -1, 56: 1, 57: -1, 58: -1, 59: 1, 60: -1, 61: -1, 62: 1, 63: -1, 64: -1, 65: 1, 66: -1, 67: -1, 68: 1, 69: -1, 70: -1, 71: 1, 72: -1, 73: -1, 74: 1, 75: -1, 76: -1, 77: 1, 78: -1, 79: -1, 80: 1, 81: -1, 82: -1, 83: 1, 84: -1, 85: -1, 86: 1, 87: -1, 88: -1, 89: 1, 90: -1, 91: -1, 92: 1, 93: -1, 94: -1, 95: 1, 96: -1, 97: -1, 98: 1, 99: -1}\n", + " value: -98\n", + " spin: True\n", + "\n", + " state: {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: -1, 63: -1, 64: 1, 65: -1, 66: -1, 67: 1, 68: -1, 69: -1, 70: 1, 71: -1, 72: -1, 73: 1, 74: 1, 75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 1, 82: 1, 83: 1, 84: 1, 85: 1, 86: 1, 87: 1, 88: 1, 89: 1, 90: 1, 91: 1, 92: 1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: 1, 99: 1}\n", + " value: -96\n", + " spin: True\n", + "\n", + " state: {0: -1, 1: -1, 2: 1, 3: -1, 4: -1, 5: 1, 6: -1, 7: -1, 8: 1, 9: -1, 10: -1, 11: 1, 12: -1, 13: -1, 14: 1, 15: -1, 16: -1, 17: 1, 18: -1, 19: -1, 20: 1, 21: -1, 22: -1, 23: 1, 24: -1, 25: -1, 26: 1, 27: -1, 28: -1, 29: 1, 30: -1, 31: -1, 32: 1, 33: -1, 34: -1, 35: 1, 36: -1, 37: -1, 38: 1, 39: -1, 40: -1, 41: 1, 42: -1, 43: -1, 44: 1, 45: -1, 46: -1, 47: 1, 48: -1, 49: 1, 50: -1, 51: -1, 52: 1, 53: -1, 54: -1, 55: 1, 56: -1, 57: -1, 58: 1, 59: -1, 60: -1, 61: 1, 62: -1, 63: -1, 64: 1, 65: -1, 66: -1, 67: 1, 68: -1, 69: -1, 70: 1, 71: -1, 72: -1, 73: 1, 74: -1, 75: -1, 76: 1, 77: -1, 78: -1, 79: 1, 80: -1, 81: -1, 82: 1, 83: -1, 84: -1, 85: 1, 86: -1, 87: -1, 88: 1, 89: -1, 90: 1, 91: -1, 92: -1, 93: 1, 94: -1, 95: -1, 96: 1, 97: -1, 98: -1, 99: 1}\n", + " value: -96\n", + " spin: True\n", + "\n", + " state: {0: -1, 1: 1, 2: -1, 3: -1, 4: 1, 5: -1, 6: -1, 7: 1, 8: -1, 9: -1, 10: 1, 11: -1, 12: -1, 13: 1, 14: -1, 15: -1, 16: 1, 17: -1, 18: -1, 19: 1, 20: -1, 21: -1, 22: 1, 23: -1, 24: -1, 25: 1, 26: -1, 27: -1, 28: 1, 29: -1, 30: -1, 31: 1, 32: -1, 33: -1, 34: 1, 35: -1, 36: -1, 37: 1, 38: -1, 39: -1, 40: 1, 41: -1, 42: -1, 43: 1, 44: -1, 45: -1, 46: 1, 47: -1, 48: -1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1, 71: 1, 72: -1, 73: -1, 74: 1, 75: -1, 76: -1, 77: 1, 78: -1, 79: -1, 80: 1, 81: -1, 82: -1, 83: 1, 84: -1, 85: -1, 86: 1, 87: -1, 88: -1, 89: 1, 90: -1, 91: -1, 92: 1, 93: -1, 94: -1, 95: 1, 96: -1, 97: -1, 98: 1, 99: -1}\n", + " value: -96\n", + " spin: True\n", + "\n", + " state: {0: -1, 1: 1, 2: -1, 3: -1, 4: 1, 5: -1, 6: -1, 7: 1, 8: -1, 9: -1, 10: 1, 11: -1, 12: -1, 13: 1, 14: -1, 15: -1, 16: 1, 17: -1, 18: 1, 19: -1, 20: -1, 21: 1, 22: -1, 23: -1, 24: 1, 25: -1, 26: -1, 27: 1, 28: -1, 29: -1, 30: 1, 31: -1, 32: -1, 33: 1, 34: -1, 35: -1, 36: 1, 37: -1, 38: -1, 39: 1, 40: -1, 41: -1, 42: 1, 43: -1, 44: -1, 45: 1, 46: -1, 47: -1, 48: 1, 49: -1, 50: -1, 51: 1, 52: -1, 53: 1, 54: -1, 55: -1, 56: 1, 57: -1, 58: -1, 59: 1, 60: -1, 61: -1, 62: 1, 63: -1, 64: -1, 65: 1, 66: -1, 67: -1, 68: 1, 69: -1, 70: -1, 71: 1, 72: -1, 73: -1, 74: 1, 75: -1, 76: -1, 77: 1, 78: -1, 79: -1, 80: 1, 81: -1, 82: -1, 83: 1, 84: -1, 85: -1, 86: 1, 87: -1, 88: -1, 89: 1, 90: -1, 91: -1, 92: 1, 93: -1, 94: -1, 95: 1, 96: -1, 97: -1, 98: 1, 99: -1}\n", + " value: -96\n", + " spin: True\n", + "\n", + " state: {0: -1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: -1, 37: -1, 38: 1, 39: -1, 40: -1, 41: 1, 42: -1, 43: -1, 44: 1, 45: -1, 46: -1, 47: 1, 48: -1, 49: 1, 50: -1, 51: -1, 52: 1, 53: -1, 54: -1, 55: 1, 56: -1, 57: -1, 58: 1, 59: -1, 60: -1, 61: 1, 62: -1, 63: -1, 64: 1, 65: -1, 66: -1, 67: 1, 68: -1, 69: -1, 70: -1, 71: 1, 72: -1, 73: -1, 74: 1, 75: -1, 76: -1, 77: 1, 78: -1, 79: -1, 80: 1, 81: -1, 82: -1, 83: 1, 84: -1, 85: -1, 86: 1, 87: -1, 88: -1, 89: 1, 90: -1, 91: -1, 92: 1, 93: -1, 94: -1, 95: 1, 96: -1, 97: -1, 98: 1, 99: -1}\n", + " value: -92\n", + " spin: True\n", + "\n", + " state: {0: 1, 1: -1, 2: -1, 3: 1, 4: -1, 5: -1, 6: 1, 7: -1, 8: -1, 9: 1, 10: -1, 11: -1, 12: 1, 13: -1, 14: -1, 15: 1, 16: -1, 17: -1, 18: 1, 19: -1, 20: -1, 21: 1, 22: -1, 23: -1, 24: 1, 25: -1, 26: -1, 27: 1, 28: -1, 29: -1, 30: 1, 31: -1, 32: -1, 33: -1, 34: -1, 35: 1, 36: -1, 37: -1, 38: 1, 39: -1, 40: -1, 41: 1, 42: -1, 43: -1, 44: 1, 45: -1, 46: -1, 47: 1, 48: -1, 49: -1, 50: 1, 51: -1, 52: -1, 53: -1, 54: 1, 55: -1, 56: -1, 57: 1, 58: -1, 59: -1, 60: 1, 61: -1, 62: -1, 63: 1, 64: -1, 65: -1, 66: 1, 67: -1, 68: -1, 69: 1, 70: -1, 71: -1, 72: 1, 73: -1, 74: -1, 75: 1, 76: -1, 77: -1, 78: 1, 79: -1, 80: -1, 81: 1, 82: -1, 83: -1, 84: 1, 85: -1, 86: -1, 87: 1, 88: -1, 89: -1, 90: 1, 91: 1, 92: 1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: 1, 99: 1}\n", + " value: -90\n", + " spin: True\n", + "\n", + " state: {0: -1, 1: 1, 2: -1, 3: 1, 4: -1, 5: -1, 6: 1, 7: -1, 8: -1, 9: 1, 10: -1, 11: -1, 12: 1, 13: -1, 14: -1, 15: 1, 16: -1, 17: -1, 18: 1, 19: -1, 20: -1, 21: 1, 22: -1, 23: -1, 24: 1, 25: -1, 26: -1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: -1, 34: -1, 35: 1, 36: -1, 37: -1, 38: 1, 39: -1, 40: -1, 41: 1, 42: -1, 43: -1, 44: 1, 45: -1, 46: -1, 47: 1, 48: -1, 49: -1, 50: 1, 51: -1, 52: -1, 53: 1, 54: -1, 55: -1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1, 71: 1, 72: 1, 73: -1, 74: -1, 75: 1, 76: -1, 77: 1, 78: -1, 79: -1, 80: 1, 81: -1, 82: -1, 83: 1, 84: -1, 85: -1, 86: 1, 87: -1, 88: -1, 89: 1, 90: -1, 91: -1, 92: 1, 93: -1, 94: -1, 95: 1, 96: -1, 97: -1, 98: 1, 99: -1}\n", + " value: -88\n", + " spin: True\n", + "\n", + " state: {0: 1, 1: 1, 2: 1, 3: 1, 4: -1, 5: -1, 6: 1, 7: -1, 8: -1, 9: 1, 10: -1, 11: -1, 12: 1, 13: -1, 14: -1, 15: 1, 16: -1, 17: -1, 18: -1, 19: 1, 20: -1, 21: -1, 22: 1, 23: -1, 24: -1, 25: 1, 26: -1, 27: -1, 28: 1, 29: -1, 30: -1, 31: 1, 32: -1, 33: -1, 34: 1, 35: -1, 36: -1, 37: 1, 38: -1, 39: -1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: -1, 58: -1, 59: 1, 60: -1, 61: -1, 62: 1, 63: -1, 64: -1, 65: 1, 66: -1, 67: -1, 68: 1, 69: -1, 70: -1, 71: 1, 72: -1, 73: -1, 74: 1, 75: -1, 76: -1, 77: -1, 78: 1, 79: -1, 80: -1, 81: 1, 82: -1, 83: -1, 84: 1, 85: -1, 86: -1, 87: -1, 88: 1, 89: -1, 90: -1, 91: 1, 92: -1, 93: -1, 94: 1, 95: -1, 96: -1, 97: 1, 98: 1, 99: 1}\n", + " value: -86\n", + " spin: True\n" + ] + } + ], + "source": [ + "res.sort()\n", + "print(res)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can access each individual result by iterating through `res`. Each element in `res` is also a `qubovert.sim.AnnealResult` object. For each result, let's count the number of 1s and -1s." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Energy: -100.0\n", + " Number of 1s: 100\n", + " Number of -1s: 0\n", + "Energy: -98.0\n", + " Number of 1s: 33\n", + " Number of -1s: 67\n", + "Energy: -96.0\n", + " Number of 1s: 92\n", + " Number of -1s: 8\n", + "Energy: -96.0\n", + " Number of 1s: 34\n", + " Number of -1s: 66\n", + "Energy: -96.0\n", + " Number of 1s: 48\n", + " Number of -1s: 52\n", + "Energy: -96.0\n", + " Number of 1s: 34\n", + " Number of -1s: 66\n", + "Energy: -92.0\n", + " Number of 1s: 56\n", + " Number of -1s: 44\n", + "Energy: -90.0\n", + " Number of 1s: 39\n", + " Number of -1s: 61\n", + "Energy: -88.0\n", + " Number of 1s: 48\n", + " Number of -1s: 52\n", + "Energy: -86.0\n", + " Number of 1s: 47\n", + " Number of -1s: 53\n" + ] + } + ], + "source": [ + "for s in res:\n", + " print(\"Energy:\", s.value)\n", + " print(\" Number of 1s:\", sum(v for v in s.state.values() if v == 1))\n", + " print(\" Number of -1s:\", sum(-v for v in s.state.values() if v == -1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the next section we will review some of the more advanced annealing features that can be used." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Back to Table of Contents" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Advanced Annealing" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To see what advanced options we can play with when annealing, let's look at the docstring of the `anneal_puso` function." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on function anneal_puso in module qubovert.sim._anneal:\n", + "\n", + "anneal_puso(H, num_anneals=1, anneal_duration=1000, initial_state=None, temperature_range=None, schedule='geometric', in_order=True, seed=None)\n", + " anneal_puso.\n", + " \n", + " Run a simulated annealing algorithm to try to find the minimum of the PUSO\n", + " given by ``H``. Please see all of the parameters for details.\n", + " \n", + " **Please note** that the ``qv.sim.anneal_quso`` function performs\n", + " faster than the ``qv.sim.anneal_puso`` function. If your system has\n", + " degree 2 or less, then you should use the ``qv.sim.anneal_quso``\n", + " function.\n", + " \n", + " Parameters\n", + " ----------\n", + " H : dict, or any type in ``qubovert.SPIN_MODELS``.\n", + " Maps spin labels to their values in the Hamiltonian.\n", + " Please see the docstrings of any of the objects in\n", + " ``qubovert.SPIN_MODELS`` to see how ``H`` should be formatted.\n", + " num_anneals : int >= 1 (optional, defaults to 1).\n", + " The number of times to run the simulated annealing algorithm.\n", + " anneal_duration : int >= 1 (optional, defaults to 1000).\n", + " The total number of updates to the simulation during the anneal.\n", + " This is related to the amount of time we spend in the cooling schedule.\n", + " If an explicit schedule is provided, then ``anneal_duration`` will be\n", + " ignored.\n", + " initial_state : dict (optional, defaults to None).\n", + " The initial state to start the anneal in. ``initial_state`` must map\n", + " the spin label names to their values in {1, -1}. If ``initial_state``\n", + " is None, then a random state will be chosen to start each anneal.\n", + " Otherwise, ``initial_state`` will be the starting state for all of the\n", + " anneals.\n", + " temperature_range : tuple (optional, defaults to None).\n", + " The temperature to start and end the anneal at.\n", + " ``temperature = (T0, Tf)``. ``T0`` must be >= ``Tf``. To see more\n", + " details on picking a temperature range, please see the function\n", + " ``qubovert.sim.anneal_temperature_range``. If ``temperature_range`` is\n", + " None, then it will by default be set to\n", + " ``T0, Tf = qubovert.sim.anneal_temperature_range(H, spin=True)``.\n", + " Note that a temperature can only be zero if ``schedule`` is explicitly\n", + " given or if ``schedule`` is linear.\n", + " schedule : str, or list of floats (optional, defaults to ``'geometric'``).\n", + " What type of cooling schedule to use. If ``schedule == 'linear'``,\n", + " then the cooling schedule will be a linear interpolation between the\n", + " values in ``temperature_range``. If ``schedule == 'geometric'``, then\n", + " the cooling schedule will be a geometric interpolation between the\n", + " values in ``temperature_range``. Otherwise, ``schedule`` must be an\n", + " iterable of floats being the explicit temperature schedule for the\n", + " anneal to follow.\n", + " in_order : bool (optional, defaults to True).\n", + " Whether to iterate through the variables in order or randomly\n", + " during an update step. When ``in_order`` is False, the simulation\n", + " is more physically realistic, but when using the simulation for\n", + " annealing, often it is better to have ``in_order = True``.\n", + " seed : number (optional, defaults to None).\n", + " The number to seed Python's builtin ``random`` module with. If\n", + " ``seed is None``, then ``random.seed`` will not be called.\n", + " \n", + " Returns\n", + " -------\n", + " res : qubovert.sim.AnnealResults object.\n", + " ``res`` contains information on the final states of the simulations.\n", + " See Examples below for an example of how to read from ``res``.\n", + " See ``help(qubovert.sim.AnnealResults)`` for more info.\n", + " \n", + " Raises\n", + " ------\n", + " ValueError\n", + " If the ``schedule`` argument provided is formatted incorrectly. See the\n", + " Parameters section.\n", + " ValueError\n", + " If the initial temperature is less than the final temperature.\n", + " \n", + " Warns\n", + " -----\n", + " qubovert.utils.QUBOVertWarning\n", + " If both the ``temperature_range`` and explicit ``schedule`` arguments\n", + " are provided.\n", + " \n", + " Example\n", + " -------\n", + " Consider the example of finding the ground state of the 1D\n", + " antiferromagnetic Ising chain of length 5.\n", + " \n", + " >>> import qubovert as qv\n", + " >>>\n", + " >>> H = sum(qv.spin_var(i) * qv.spin_var(i+1) for i in range(4))\n", + " >>> anneal_res = qv.sim.anneal_puso(H, num_anneals=3)\n", + " >>>\n", + " >>> print(anneal_res.best.value)\n", + " -4\n", + " >>> print(anneal_res.best.state)\n", + " {0: 1, 1: -1, 2: 1, 3: -1, 4: 1}\n", + " >>> # now sort the results\n", + " >>> anneal_res.sort()\n", + " >>>\n", + " >>> # now iterate through all of the results in the sorted order\n", + " >>> for res in anneal_res:\n", + " >>> print(res.value, res.state)\n", + " -4, {0: 1, 1: -1, 2: 1, 3: -1, 4: 1}\n", + " -4, {0: -1, 1: 1, 2: -1, 3: 1, 4: -1}\n", + " -4, {0: 1, 1: -1, 2: 1, 3: -1, 4: 1}\n", + "\n" + ] + } + ], + "source": [ + "help(qv.sim.anneal_puso)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice the `anneal_duration` argument that we played with in the previous section. Next we will show examples using `initial_state`, `temperature_range`, and `schedule``." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Adjusting the temperature range" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can adjust the `temperature_range` argument in `anneal_puso`. This argument is a tuple of values `(T0, Tf)` that indicate the temperature to start and end the anneal. To get an idea of what temperatures to pick. We will look at the `anneal_temperature_range` function.\n", + "\n", + "The `anneal_temperature_range` function accepts a model and a start and end flip probability. These indicate how probable we want it to be that a bit is flipped even if it results in a worse energy. By default these values are set at 50% at the beginning of the anneal, and 1% at the end of the anneal. In other words, at the beginning of the anneal, the temperature is set such that every spin has at least a 50% chance of being flipped even if it results in a worse energy. Similarly, at the end of the anneal, the temperature is set such that every spin has at most a 1% chance of being flipped if when flipping it results in a worse energy. Let's look at what the temperature range would be for our Hamiltonian." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "8.656170245333781 0.43429448190325187\n" + ] + } + ], + "source": [ + "# spin indicates that this is a spin model\n", + "T0, Tf = qv.sim.anneal_temperature_range(H, spin=True)\n", + "print(T0, Tf)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can play around with the start and end flip probabilities to see how they affect the temperature." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "20.856356980693242 0.2895296546021679\n" + ] + } + ], + "source": [ + "# spin indicates that this is a spin model\n", + "T0, Tf = qv.sim.anneal_temperature_range(H, start_flip_prob=.75, end_flip_prob=.001, spin=True)\n", + "print(T0, Tf)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can adjust the temperature range in `anneal_puso` via `anneal_puso(H, temperature_range=(T0, Tf), ...)`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Adjusting the initial state" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we do not provide an initial state to the anneal function, then it will randomly initialize a new state at the start of each anneal. However, if we provide an initial state, then it will begin in this state at the beginning of each anneal. This could be useful if we already have a decent idea of what our solution might look like. \n", + "\n", + "Let's take the example of the Hamiltonian $H$ from above. What if we start out in a state where the first 75 spins are 1 and the last 25 spins are -1? If we use the intuition that we are sort of close to the true ground state, then we should probably reduce the starting flip probability. So we will make the starting flip probability equal to 25% and keep the ending flip probability at 1%. This is how we will set the temperature. *Note that since we know that 75% of the spins are oriented correctly, 25% spin flip probability is a reasonable guess. Of course for a problem where the ground state is a mystery, we would not know this.*" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " state: {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1, 71: 1, 72: 1, 73: 1, 74: 1, 75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 1, 82: 1, 83: 1, 84: 1, 85: 1, 86: 1, 87: 1, 88: 1, 89: 1, 90: 1, 91: 1, 92: 1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: 1, 99: 1}\n", + " value: -100\n", + " spin: True\n" + ] + } + ], + "source": [ + "initial_state = {i: 1 if i < 75 else -1 for i in range(L)}\n", + "\n", + "temperature_range = qv.sim.anneal_temperature_range(\n", + " H, start_flip_prob=.25, end_flip_prob=0.01, spin=True,\n", + ")\n", + "\n", + "res = qv.sim.anneal_puso(\n", + " H, \n", + " initial_state=initial_state, \n", + " temperature_range=temperature_range,\n", + " num_anneals=10,\n", + " anneal_duration=4000,\n", + " seed=seed\n", + ")\n", + "\n", + "print(res.best)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice that we get the right answer with `anneal_duration=4000`, whereas in the previous section we used around 6000." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Adjusting the schedule" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are three options for the `schedule` argument.\n", + "\n", + "1. Geometric (`schedule='geometric'`). This is the default option in `anneal_puso` (learned from D-Wave's simulated annealing software). In this case, the temperature will be decreased from from the initial to final temperature in evenly spaced increments along a log scale.\n", + "2. Linear (`schedule='linear'`). In this case, the temperature will be decreased from from the initial to final temperature in evenly spaced increments along a linear scale.\n", + "3. Custom. \n", + "\n", + "We will show a custom schedule example. Consider exactly the above example in the Adjusting the initial state section, where we start off in an initial state that we think is pretty close to the optimal state. We will start off in a state where the first 75 spins are 1 and the last 25 are -1. For the same reasons as above, we will make the temperature range such that we start the anneal with a 25% chance of flipping a spin even if it is energetically unfavorable, and we will end with 1% chance. But this time we will customize the schedulee so that it first *reverse anneals*, and then anneals. See D-Wave reverse annealing for some info on reverse quantum annealing.\n", + "\n", + "We will use the same $T_0, T_f$ as before, but this time we will create a geometric schedule where we *start* with temperature $T_f$ and heat the system to $T_0$ quickly (50 time steps). Then we will cool the system with a geometric schedule from $T_0$ to $T_f$ in 3250 time steps." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " state: {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1, 71: 1, 72: 1, 73: 1, 74: 1, 75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 1, 82: 1, 83: 1, 84: 1, 85: 1, 86: 1, 87: 1, 88: 1, 89: 1, 90: 1, 91: 1, 92: 1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: 1, 99: 1}\n", + " value: -100\n", + " spin: True\n" + ] + } + ], + "source": [ + "from numpy import geomspace\n", + "\n", + "T0, Tf = temperature_range # same as the previous cell\n", + "\n", + "schedule = list(geomspace(Tf, T0, 50)) + list(geomspace(T0, Tf, 3250)) \n", + "\n", + "res = qv.sim.anneal_puso(\n", + " H, \n", + " initial_state=initial_state,\n", + " num_anneals=10,\n", + " schedule=schedule,\n", + " seed=seed\n", + ")\n", + "\n", + "print(res.best)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice this time we get the right answer with a total anneal duration of 3300, whereas before we used around 4000." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Back to Table of Contents" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebook_examples/Annealing_and_simulation.ipynb b/notebook_examples/Annealing_and_simulation.ipynb deleted file mode 100644 index e487cd9..0000000 --- a/notebook_examples/Annealing_and_simulation.ipynb +++ /dev/null @@ -1,983 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Annealing and Simulation with `qubovert`" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "*qubovert* must be pip installed." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Import `qubovert`." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import qubovert as qv" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this notebook, we will review some basics of the annealing and simulation functionality provided by `qubovert`. Let's look at everything in the simulation (`sim`) library." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "('PUSOSimulation', 'PUBOSimulation', 'QUSOSimulation', 'QUBOSimulation', AnnealResult', 'AnnealResults', 'anneal_qubo', 'anneal_quso', 'anneal_pubo', 'anneal_puso', 'anneal_temperature_range')\n" - ] - } - ], - "source": [ - "print(qv.sim.__all__)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this notebook we will discuss `PUSOSimulation`, `anneal_puso`, `anneal_temperature_range`, `AnnealResults`, and `AnnealResult`. We will not discuss `PUBOSimulation`, `QUBOSimulation`, or `QUSOSimulation`, though they are used in exactly the same way as `PUSOSimulation` but with boolean function. Similarly, `anneal_qubo`, `anneal_quso`, and `anneal_puso` are all used very similarly to `anneal_puso`. **Please note tht the `anneal_quso`, `anneal_qubo` functions and `QUSOSimulation` , `QUBOSimulation` objects are much faster than their counterparts** because they are implemented in C. If you have aa QUSO or QUBO, do not use the PUSO or PUBO methods!\n", - "\n", - "`PUSOSimulation` will be discussed in the Simulation section; `AnnealResults`, `AnnealResult`, and `anneal_puso` will be discussed in the Annealing section; a more detailed usage of `anneal_pubo` and `anneal_temperature_range` will be discussed in the Advanced Annealing section.\n", - "\n", - "### Table of Contents\n", - "\n", - "1. Working Example\n", - "2. Simulation\n", - "3. Annealing\n", - "4. Advanced Annealing\n", - " 1. Adjusting the temperature range\n", - " 2. Adjusting the initial state\n", - " 3. Adjusting the schedule\n", - "4. Conclusion\n", - " 1. A note on the purpose of the `qubovert.sim` library" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Working Example\n", - "\n", - "For all of the following examples, we will use the following example; an Ising model on a line of length $L$ with nearest- and next-to-nearest-neighbor interactions with periodic boundary conditions. Our system is represented by the Hamiltonian\n", - "\n", - "$$H = -\\sum_{i=0}^{L-1} z_i z_{i+1} z_{i+2}$$\n", - "\n", - "where each $z_i \\in \\{1, -1 \\}$ and they are defined modulo $L$ such that $z_{i+L} = z_{i}$. First, let's define the length of our system and create the Hamiltonian." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "L = 100\n", - "\n", - "# define the function z that returns the ith variable modulo L\n", - "z_variables = [qv.spin_var(i) for i in range(L)]\n", - "z = lambda i: z_variables[i % L]\n", - "\n", - "# create the Hamiltonian\n", - "H = 0\n", - "for i in range(L):\n", - " H -= z(i) * z(i+1) * z(i+2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that the groundstate of this Hamiltonian is the state where each $z_i = 1$ (if we did not assume periodic boundary conditions, then the ground state is degenerate, with some being more complicated then the all 1 state). " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Simulation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We'll first create a `PUSOSimulation` object for the Hamiltonian. We will add some memory to the object. The `memory` keyword indicates how many of the most recent states to remember. We will use this later." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "sim = qv.sim.PUSOSimulation(H, memory=1000)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's put the simulation in a random state. Ie each of the $L$ spins is randomly oriented in the 1 or -1 direction." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{0: -1, 1: 1, 2: 1, 3: -1, 4: -1, 5: -1, 6: 1, 7: 1, 8: -1, 9: -1, 10: 1, 11: -1, 12: -1, 13: -1, 14: -1, 15: 1, 16: -1, 17: -1, 18: -1, 19: 1, 20: 1, 21: -1, 22: -1, 23: -1, 24: 1, 25: 1, 26: 1, 27: 1, 28: -1, 29: 1, 30: 1, 31: -1, 32: -1, 33: -1, 34: 1, 35: -1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: -1, 42: -1, 43: -1, 44: -1, 45: 1, 46: 1, 47: 1, 48: 1, 49: -1, 50: 1, 51: 1, 52: -1, 53: 1, 54: -1, 55: 1, 56: 1, 57: -1, 58: 1, 59: -1, 60: 1, 61: 1, 62: -1, 63: 1, 64: -1, 65: 1, 66: 1, 67: 1, 68: -1, 69: -1, 70: 1, 71: 1, 72: 1, 73: 1, 74: 1, 75: -1, 76: 1, 77: -1, 78: 1, 79: 1, 80: -1, 81: 1, 82: -1, 83: -1, 84: 1, 85: 1, 86: -1, 87: -1, 88: -1, 89: 1, 90: 1, 91: -1, 92: -1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: -1, 99: -1}\n" - ] - } - ], - "source": [ - "import random\n", - "\n", - "initial_state = {i: random.choice((-1, 1)) for i in range(L)}\n", - "sim.set_state(initial_state)\n", - "\n", - "print(sim.state)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we'll define a schedule to simulate the system. A schedule is a collection of `(T, n)` pairs, where `T` is the temperature to update the simulation at, and `n` is the number of time steps to simlulate the system at that temperature. Let's cool the system from temperature 5 to 0.5." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "schedule = (5, 20), (4, 25), (3, 30), (2, 35), (1, 40), (.5, 100)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we simulate the system with this schedule." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "sim.schedule_update(schedule)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can look at exactly what state the simulation was in at each time step by looking into the memory." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "all_states = sim.get_past_states()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice that the 0th state should be the same as the initial state, and the last state should be the same as the current state. *Note that if you did not provide the `PUSOSimulation` object with enough memory then it will have forgotten about the initial_state!*" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "True\n", - "True\n" - ] - } - ], - "source": [ - "print(all_states[0] == initial_state)\n", - "print(all_states[-1] == sim.state)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let's do the same thing, but 100 times! We'll keep track of the state that the simulation ends in at each iteration." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "ending_states = []\n", - "for _ in range(100):\n", - " sim.reset()\n", - " sim.set_state([random.choice((-1, 1)) for _ in range(L)])\n", - " sim.schedule_update(schedule)\n", - " ending_states.append(sim.state)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, let's calculate the average correlation that each spin has to the 0th spin over the ending states." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "# avg_correaltion[i] is the average correlation of spin i to spin 0\n", - "avg_correlation = [\n", - " sum(s[0] * s[i] for s in ending_states) / len(ending_states)\n", - " for i in range(L)\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally we plot it! *You must have `matplotlib` pip installed.*" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "%matplotlib inline\n", - "import matplotlib.pyplot as plt\n", - "\n", - "plt.plot(avg_correlation)\n", - "plt.xlabel('spin')\n", - "plt.ylabel('avg correlation')\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As a sanity check, we see that the correlation between spin 0 and itself is always 1! Similarly, we see the correlation begin to go up towards the end of the chain; this is because of the periodic boundary conditions." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Back to Table of Contents" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Annealing" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here we show basic usage of the `anneal_puso` function. We will use it to try to find the groundstate of the Hamiltonian discuessed and created in the Working Example section. Let's anneal the Hamiltonian to see if we can find the groundstate. Recall that PUSO is short for Polynomial Unconstrained Spin Optimization. We can use the `anneal_puso` function to anneal the Hamiltonian.\n", - "\n", - "For the sake of reproducability, we will seed the random number generator that `anneal_puso` uses." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "res = qv.sim.anneal_puso(H, num_anneals=10, seed=34)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's look at the result that if found with the lowest energy." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " state: {0: -1, 1: 1, 2: -1, 3: -1, 4: 1, 5: -1, 6: -1, 7: 1, 8: -1, 9: -1, 10: 1, 11: -1, 12: -1, 13: -1, 14: 1, 15: -1, 16: -1, 17: 1, 18: -1, 19: -1, 20: 1, 21: -1, 22: -1, 23: 1, 24: -1, 25: -1, 26: 1, 27: -1, 28: -1, 29: 1, 30: -1, 31: -1, 32: 1, 33: -1, 34: -1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: -1, 58: -1, 59: 1, 60: -1, 61: -1, 62: 1, 63: -1, 64: -1, 65: 1, 66: -1, 67: -1, 68: 1, 69: -1, 70: -1, 71: 1, 72: -1, 73: -1, 74: 1, 75: -1, 76: -1, 77: 1, 78: -1, 79: -1, 80: 1, 81: -1, 82: -1, 83: 1, 84: -1, 85: -1, 86: 1, 87: -1, 88: -1, 89: 1, 90: -1, 91: -1, 92: 1, 93: -1, 94: -1, 95: 1, 96: -1, 97: -1, 98: 1, 99: -1}\n", - " value: -94\n", - " spin: True\n" - ] - } - ], - "source": [ - "print(res.best)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice that none of the 10 results found the ground state! But if we increase the duration of each anneal, then we can. By default, the anneal duration (the number of time steps to run each simulation for) is 1000. Let's increase it to 7500 and see if it finds the ground state." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " state: {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1, 71: 1, 72: 1, 73: 1, 74: 1, 75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 1, 82: 1, 83: 1, 84: 1, 85: 1, 86: 1, 87: 1, 88: 1, 89: 1, 90: 1, 91: 1, 92: 1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: 1, 99: 1}\n", - " value: -100\n", - " spin: True\n" - ] - } - ], - "source": [ - "res = qv.sim.anneal_puso(H, num_anneals=10, anneal_duration=7500, seed=34)\n", - "print(res.best)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Indeed we find the ground state! Adjusting the anneal duration is one thing you can do to try to get better performance out of the annealer. In the next section, we will discuss some more.\n", - "\n", - "Now let's look at the result. `res` is a `qubovert.sim.AnnealResults` object. It will contain 10 results, since we set `num_anneals` to 10." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "10" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(res)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's look at the result that it found with the lowest energy. `res.best` is a `qubovert.sim.AnnealResult` object. We can get the state with `.state` and the energy with `.value`." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Energy -100\n", - "State {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1, 71: 1, 72: 1, 73: 1, 74: 1, 75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 1, 82: 1, 83: 1, 84: 1, 85: 1, 86: 1, 87: 1, 88: 1, 89: 1, 90: 1, 91: 1, 92: 1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: 1, 99: 1}\n" - ] - } - ], - "source": [ - "print(\"Energy\", res.best.value)\n", - "print(\"State\", res.best.state)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's sort the results and then look at all 10 of them." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "AnnealResults\n", - " state: {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1, 71: 1, 72: 1, 73: 1, 74: 1, 75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 1, 82: 1, 83: 1, 84: 1, 85: 1, 86: 1, 87: 1, 88: 1, 89: 1, 90: 1, 91: 1, 92: 1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: 1, 99: 1}\n", - " value: -100\n", - " spin: True\n", - "\n", - " state: {0: -1, 1: -1, 2: 1, 3: -1, 4: -1, 5: 1, 6: -1, 7: -1, 8: 1, 9: -1, 10: -1, 11: 1, 12: -1, 13: -1, 14: 1, 15: -1, 16: -1, 17: 1, 18: -1, 19: -1, 20: 1, 21: -1, 22: -1, 23: 1, 24: -1, 25: -1, 26: 1, 27: -1, 28: -1, 29: 1, 30: -1, 31: -1, 32: 1, 33: -1, 34: -1, 35: 1, 36: -1, 37: -1, 38: 1, 39: -1, 40: -1, 41: 1, 42: -1, 43: -1, 44: 1, 45: -1, 46: -1, 47: 1, 48: -1, 49: -1, 50: 1, 51: -1, 52: -1, 53: 1, 54: -1, 55: -1, 56: 1, 57: -1, 58: -1, 59: 1, 60: -1, 61: -1, 62: 1, 63: -1, 64: -1, 65: 1, 66: -1, 67: -1, 68: 1, 69: -1, 70: -1, 71: 1, 72: -1, 73: -1, 74: 1, 75: -1, 76: -1, 77: 1, 78: -1, 79: -1, 80: 1, 81: -1, 82: -1, 83: 1, 84: -1, 85: -1, 86: 1, 87: -1, 88: -1, 89: -1, 90: 1, 91: -1, 92: -1, 93: 1, 94: -1, 95: -1, 96: 1, 97: -1, 98: -1, 99: 1}\n", - " value: -98\n", - " spin: True\n", - "\n", - " state: {0: 1, 1: -1, 2: -1, 3: 1, 4: -1, 5: -1, 6: 1, 7: -1, 8: -1, 9: 1, 10: -1, 11: -1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: -1, 30: -1, 31: 1, 32: -1, 33: -1, 34: 1, 35: -1, 36: -1, 37: 1, 38: -1, 39: -1, 40: 1, 41: -1, 42: -1, 43: 1, 44: -1, 45: -1, 46: 1, 47: -1, 48: -1, 49: 1, 50: -1, 51: -1, 52: 1, 53: -1, 54: -1, 55: 1, 56: -1, 57: -1, 58: 1, 59: -1, 60: -1, 61: 1, 62: -1, 63: -1, 64: 1, 65: -1, 66: -1, 67: 1, 68: -1, 69: -1, 70: 1, 71: -1, 72: -1, 73: 1, 74: -1, 75: -1, 76: 1, 77: -1, 78: -1, 79: 1, 80: -1, 81: -1, 82: 1, 83: -1, 84: -1, 85: 1, 86: -1, 87: -1, 88: 1, 89: -1, 90: -1, 91: 1, 92: -1, 93: -1, 94: 1, 95: -1, 96: -1, 97: 1, 98: -1, 99: -1}\n", - " value: -96\n", - " spin: True\n", - "\n", - " state: {0: -1, 1: 1, 2: -1, 3: -1, 4: 1, 5: -1, 6: -1, 7: 1, 8: -1, 9: -1, 10: 1, 11: -1, 12: 1, 13: -1, 14: -1, 15: 1, 16: -1, 17: -1, 18: 1, 19: -1, 20: -1, 21: 1, 22: -1, 23: -1, 24: 1, 25: -1, 26: -1, 27: 1, 28: -1, 29: -1, 30: 1, 31: -1, 32: -1, 33: 1, 34: -1, 35: -1, 36: 1, 37: -1, 38: -1, 39: -1, 40: 1, 41: -1, 42: -1, 43: 1, 44: -1, 45: -1, 46: 1, 47: -1, 48: -1, 49: 1, 50: -1, 51: -1, 52: 1, 53: -1, 54: -1, 55: 1, 56: -1, 57: -1, 58: 1, 59: -1, 60: -1, 61: 1, 62: -1, 63: -1, 64: 1, 65: -1, 66: -1, 67: 1, 68: -1, 69: -1, 70: 1, 71: -1, 72: -1, 73: 1, 74: -1, 75: -1, 76: 1, 77: -1, 78: -1, 79: 1, 80: -1, 81: -1, 82: 1, 83: -1, 84: -1, 85: 1, 86: -1, 87: -1, 88: 1, 89: -1, 90: -1, 91: 1, 92: -1, 93: -1, 94: -1, 95: 1, 96: -1, 97: -1, 98: 1, 99: -1}\n", - " value: -94\n", - " spin: True\n", - "\n", - " state: {0: 1, 1: -1, 2: -1, 3: 1, 4: -1, 5: -1, 6: 1, 7: -1, 8: -1, 9: 1, 10: -1, 11: 1, 12: -1, 13: -1, 14: 1, 15: -1, 16: -1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: -1, 39: -1, 40: 1, 41: -1, 42: -1, 43: 1, 44: -1, 45: -1, 46: 1, 47: -1, 48: -1, 49: 1, 50: -1, 51: -1, 52: 1, 53: -1, 54: -1, 55: 1, 56: -1, 57: -1, 58: 1, 59: -1, 60: -1, 61: 1, 62: -1, 63: -1, 64: 1, 65: -1, 66: -1, 67: 1, 68: -1, 69: -1, 70: 1, 71: -1, 72: -1, 73: 1, 74: -1, 75: -1, 76: 1, 77: -1, 78: -1, 79: 1, 80: -1, 81: -1, 82: 1, 83: -1, 84: -1, 85: 1, 86: -1, 87: -1, 88: 1, 89: -1, 90: -1, 91: 1, 92: -1, 93: -1, 94: 1, 95: -1, 96: -1, 97: 1, 98: -1, 99: -1}\n", - " value: -94\n", - " spin: True\n", - "\n", - " state: {0: 1, 1: -1, 2: -1, 3: 1, 4: -1, 5: -1, 6: 1, 7: -1, 8: -1, 9: 1, 10: -1, 11: -1, 12: 1, 13: -1, 14: -1, 15: 1, 16: -1, 17: -1, 18: 1, 19: -1, 20: -1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: -1, 52: -1, 53: 1, 54: -1, 55: -1, 56: 1, 57: -1, 58: -1, 59: 1, 60: -1, 61: -1, 62: 1, 63: -1, 64: -1, 65: 1, 66: -1, 67: -1, 68: 1, 69: -1, 70: -1, 71: 1, 72: -1, 73: -1, 74: 1, 75: -1, 76: -1, 77: 1, 78: -1, 79: -1, 80: 1, 81: -1, 82: -1, 83: 1, 84: -1, 85: -1, 86: 1, 87: -1, 88: -1, 89: 1, 90: -1, 91: 1, 92: -1, 93: -1, 94: 1, 95: -1, 96: -1, 97: 1, 98: -1, 99: -1}\n", - " value: -94\n", - " spin: True\n", - "\n", - " state: {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: -1, 10: -1, 11: 1, 12: -1, 13: -1, 14: 1, 15: -1, 16: 1, 17: -1, 18: -1, 19: 1, 20: -1, 21: -1, 22: 1, 23: -1, 24: 1, 25: -1, 26: -1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1, 71: 1, 72: 1, 73: 1, 74: 1, 75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 1, 82: 1, 83: 1, 84: 1, 85: 1, 86: 1, 87: 1, 88: 1, 89: 1, 90: 1, 91: 1, 92: 1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: 1, 99: 1}\n", - " value: -92\n", - " spin: True\n", - "\n", - " state: {0: -1, 1: -1, 2: 1, 3: -1, 4: -1, 5: 1, 6: -1, 7: -1, 8: -1, 9: 1, 10: -1, 11: -1, 12: 1, 13: -1, 14: -1, 15: 1, 16: -1, 17: -1, 18: 1, 19: -1, 20: -1, 21: 1, 22: -1, 23: -1, 24: 1, 25: -1, 26: -1, 27: 1, 28: -1, 29: -1, 30: 1, 31: -1, 32: -1, 33: 1, 34: -1, 35: -1, 36: 1, 37: -1, 38: -1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: -1, 52: -1, 53: 1, 54: -1, 55: -1, 56: 1, 57: -1, 58: -1, 59: 1, 60: -1, 61: -1, 62: 1, 63: -1, 64: -1, 65: 1, 66: -1, 67: 1, 68: -1, 69: 1, 70: -1, 71: -1, 72: 1, 73: -1, 74: -1, 75: 1, 76: -1, 77: -1, 78: 1, 79: -1, 80: -1, 81: 1, 82: -1, 83: -1, 84: 1, 85: -1, 86: -1, 87: 1, 88: -1, 89: -1, 90: 1, 91: -1, 92: -1, 93: 1, 94: -1, 95: -1, 96: 1, 97: -1, 98: -1, 99: 1}\n", - " value: -90\n", - " spin: True\n", - "\n", - " state: {0: 1, 1: -1, 2: -1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: -1, 27: -1, 28: 1, 29: -1, 30: -1, 31: 1, 32: -1, 33: -1, 34: 1, 35: -1, 36: -1, 37: 1, 38: -1, 39: -1, 40: 1, 41: -1, 42: -1, 43: 1, 44: -1, 45: -1, 46: 1, 47: -1, 48: -1, 49: 1, 50: -1, 51: -1, 52: 1, 53: -1, 54: -1, 55: -1, 56: -1, 57: 1, 58: -1, 59: -1, 60: 1, 61: -1, 62: -1, 63: 1, 64: -1, 65: -1, 66: 1, 67: -1, 68: -1, 69: 1, 70: -1, 71: -1, 72: -1, 73: 1, 74: -1, 75: -1, 76: 1, 77: -1, 78: -1, 79: 1, 80: -1, 81: -1, 82: 1, 83: -1, 84: -1, 85: 1, 86: -1, 87: -1, 88: 1, 89: -1, 90: -1, 91: 1, 92: -1, 93: -1, 94: 1, 95: -1, 96: -1, 97: 1, 98: -1, 99: -1}\n", - " value: -90\n", - " spin: True\n", - "\n", - " state: {0: 1, 1: -1, 2: -1, 3: 1, 4: -1, 5: -1, 6: 1, 7: -1, 8: 1, 9: -1, 10: -1, 11: 1, 12: -1, 13: -1, 14: 1, 15: -1, 16: -1, 17: 1, 18: -1, 19: -1, 20: -1, 21: 1, 22: -1, 23: -1, 24: 1, 25: -1, 26: -1, 27: 1, 28: -1, 29: -1, 30: 1, 31: -1, 32: -1, 33: 1, 34: -1, 35: -1, 36: 1, 37: -1, 38: -1, 39: 1, 40: -1, 41: -1, 42: 1, 43: -1, 44: -1, 45: 1, 46: -1, 47: -1, 48: 1, 49: -1, 50: -1, 51: 1, 52: -1, 53: 1, 54: -1, 55: 1, 56: -1, 57: -1, 58: 1, 59: -1, 60: -1, 61: 1, 62: -1, 63: -1, 64: 1, 65: -1, 66: -1, 67: 1, 68: -1, 69: -1, 70: 1, 71: -1, 72: -1, 73: 1, 74: -1, 75: -1, 76: 1, 77: -1, 78: -1, 79: 1, 80: -1, 81: 1, 82: -1, 83: -1, 84: 1, 85: -1, 86: -1, 87: 1, 88: -1, 89: -1, 90: 1, 91: -1, 92: -1, 93: -1, 94: 1, 95: -1, 96: -1, 97: 1, 98: -1, 99: -1}\n", - " value: -88\n", - " spin: True\n" - ] - } - ], - "source": [ - "res.sort()\n", - "print(res)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can access each individual result by iterating through `res`. Each element in `res` is also a `qubovert.sim.AnnealResult` object. For each result, let's count the number of 1s and -1s." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Energy: -100\n", - " Number of 1s: 100\n", - " Number of -1s: 0\n", - "Energy: -98\n", - " Number of 1s: 33\n", - " Number of -1s: 67\n", - "Energy: -96\n", - " Number of 1s: 44\n", - " Number of -1s: 56\n", - "Energy: -94\n", - " Number of 1s: 33\n", - " Number of -1s: 67\n", - "Energy: -94\n", - " Number of 1s: 47\n", - " Number of -1s: 53\n", - "Energy: -94\n", - " Number of 1s: 53\n", - " Number of -1s: 47\n", - "Energy: -92\n", - " Number of 1s: 88\n", - " Number of -1s: 12\n", - "Energy: -90\n", - " Number of 1s: 41\n", - " Number of -1s: 59\n", - "Energy: -90\n", - " Number of 1s: 47\n", - " Number of -1s: 53\n", - "Energy: -88\n", - " Number of 1s: 34\n", - " Number of -1s: 66\n" - ] - } - ], - "source": [ - "for s in res:\n", - " print(\"Energy:\", s.value)\n", - " print(\" Number of 1s:\", sum(v for v in s.state.values() if v == 1))\n", - " print(\" Number of -1s:\", sum(-v for v in s.state.values() if v == -1))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the next section we will review some of the more advanced annealing features that can be used." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Back to Table of Contents" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Advanced Annealing" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To see what advanced options we can play with when annealing, let's look at the docstring of the `anneal_puso` function." - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Help on function anneal_puso in module qubovert.sim._anneal:\n", - "\n", - "anneal_puso(H, num_anneals=1, anneal_duration=1000, initial_state=None, temperature_range=None, schedule='geometric', seed=None)\n", - " anneal_puso.\n", - " \n", - " Run a simulated annealing algorithm to try to find the minimum of the PUSO\n", - " given by ``H``. ``anneal_puso`` uses a cooling schedule with the\n", - " ``qubovert.sim.PUSOSimulation`` object. Please see all of the parameters\n", - " for details.\n", - " \n", - " Parameters\n", - " ----------\n", - " H : dict, or any type in ``qubovert.SPIN_MODELS``.\n", - " Maps spin labels to their values in the Hamiltonian.\n", - " Please see the docstrings of any of the objects in\n", - " ``qubovert.SPIN_MODELS`` to see how ``H`` should be formatted.\n", - " num_anneals : int >= 1 (optional, defaults to 1).\n", - " The number of times to run the simulated annealing algorithm.\n", - " anneal_duration : int >= 1 (optional, defaults to 1000).\n", - " The total number of updates to the simulation during the anneal.\n", - " This is related to the amount of time we spend in the cooling schedule.\n", - " If an explicit schedule is provided, then ``anneal_duration`` will be\n", - " ignored.\n", - " initial_state : dict (optional, defaults to None).\n", - " The initial state to start the anneal in. ``initial_state`` must map\n", - " the spin label names to their values in {1, -1}. If ``initial_state``\n", - " is None, then a random state will be chosen to start each anneal.\n", - " Otherwise, ``initial_state`` will be the starting state for all of the\n", - " anneals.\n", - " temperature_range : tuple (optional, defaults to None).\n", - " The temperature to start and end the anneal at.\n", - " ``temperature = (T0, Tf)``. ``T0`` must be >= ``Tf``. To see more\n", - " details on picking a temperature range, please see the function\n", - " ``qubovert.sim.anneal_temperature_range``. If ``temperature_range`` is\n", - " None, then it will by default be set to\n", - " ``T0, Tf = qubovert.sim.anneal_temperature_range(H, spin=True)``.\n", - " Note that a temperature can only be zero if ``schedule`` is explicitly\n", - " given or if ``schedule`` is linear.\n", - " schedule : str or iterable of tuple (optional, defaults to ``'geometric'``)\n", - " What type of cooling schedule to use. If ``schedule == 'linear'``, then\n", - " the cooling schedule will be a linear interpolation between the values\n", - " in ``temperature_range``. If ``schedule == 'geometric'``, then the\n", - " cooling schedule will be a geometric interpolation between the values\n", - " in ``temperature_range``. Otherwise, you can supply an explicit\n", - " schedule. In this case, ``schedule`` should be an iterable of tuples,\n", - " where each tuple is a ``(T, n)`` pair, where ``T`` denotes the\n", - " temperature to update the simulation, and ``n`` denote the number of\n", - " times to update the simulation at that temperature. This schedule\n", - " will be sent directly into the\n", - " ``qubovert.sim.PUSOSimulation.schedule_update`` method.\n", - " seed : number (optional, defaults to None).\n", - " The number to seed Python's builtin ``random`` module with. If\n", - " ``seed is None``, then ``random.seed`` will not be called.\n", - " \n", - " Returns\n", - " -------\n", - " res : qubovert.sim.AnnealResults object.\n", - " ``res`` contains information on the final states of the simulations.\n", - " See Examples below for an example of how to read from ``res``.\n", - " See ``help(qubovert.sim.AnnealResults)`` for more info.\n", - " \n", - " Raises\n", - " ------\n", - " ValueError\n", - " If the ``schedule`` argument provided is formatted incorrectly. See the\n", - " Parameters section.\n", - " ValueError\n", - " If the initial temperature is less than the final temperature.\n", - " \n", - " Warns\n", - " -----\n", - " qubovert.utils.QUBOVertWarning\n", - " If both the ``temperature_range`` and explicit ``schedule`` arguments\n", - " are provided.\n", - " \n", - " Example\n", - " -------\n", - " Consider the example of finding the ground state of the 1D\n", - " antiferromagnetic Ising chain of length 5.\n", - " \n", - " >>> import qubovert as qv\n", - " >>>\n", - " >>> H = sum(qv.spin_var(i) * qv.spin_var(i+1) for i in range(4))\n", - " >>> anneal_res = qv.sim.anneal_puso(H, num_anneals=3)\n", - " >>>\n", - " >>> print(anneal_res.best.value)\n", - " -4\n", - " >>> print(anneal_res.best.state)\n", - " {0: 1, 1: -1, 2: 1, 3: -1, 4: 1}\n", - " >>> # now sort the results\n", - " >>> anneal_res.sort()\n", - " >>>\n", - " >>> # now iterate through all of the results in the sorted order\n", - " >>> for res in anneal_res:\n", - " >>> print(res.value, res.state)\n", - " -4, {0: 1, 1: -1, 2: 1, 3: -1, 4: 1}\n", - " -4, {0: -1, 1: 1, 2: -1, 3: 1, 4: -1}\n", - " -4, {0: 1, 1: -1, 2: 1, 3: -1, 4: 1}\n", - "\n" - ] - } - ], - "source": [ - "help(qv.sim.anneal_puso)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice the `anneal_duration` argument that we played with in the previous section. Next we will show examples using `initial_state`, `temperature_range`, and `schedule``." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adjusting the temperature range" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can adjust the `temperature_range` argument in `anneal_puso`. This argument is a tuple of values `(T0, Tf)` that indicate the temperature to start and end the anneal. To get an idea of what temperatures to pick. We will look at the `anneal_temperature_range` function.\n", - "\n", - "The `anneal_temperature_range` function accepts a model and a start and end flip probability. These indicate how probable we want it to be that a bit is flipped even if it results in a worse energy. By default these values are set at 50% at the beginning of the anneal, and 1% at the end of the anneal. In other words, at the beginning of the anneal, the temperature is set such that every spin has at least a 50% chance of being flipped even if it results in a worse energy. Similarly, at the end of the anneal, the temperature is set such that every spin has at most a 1% chance of being flipped if when flipping it results in a worse energy. Let's look at what the temperature range would be for our Hamiltonian." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "8.656170245333781 0.43429448190325187\n" - ] - } - ], - "source": [ - "# spin indicates that this is a spin model\n", - "T0, Tf = qv.sim.anneal_temperature_range(H, spin=True)\n", - "print(T0, Tf)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can play around with the start and end flip probabilities to see how they affect the temperature." - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "20.856356980693242 0.2895296546021679\n" - ] - } - ], - "source": [ - "# spin indicates that this is a spin model\n", - "T0, Tf = qv.sim.anneal_temperature_range(H, start_flip_prob=.75, end_flip_prob=.001, spin=True)\n", - "print(T0, Tf)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can adjust the temperature range in `anneal_puso` via `anneal_puso(H, temperature_range=(T0, Tf), ...)`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adjusting the initial state" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If we do not provide an initial state to the anneal function, then it will randomly initialize a new state at the start of each anneal. However, if we provide an initial state, then it will begin in this state at the beginning of each anneal. This could be useful if we already have a decent idea of what our solution might look like. \n", - "\n", - "Let's take the example of the Hamiltonian $H$ from above. What if we start out in a state where the first 75 spins are 1 and the last 25 spins are -1? If we use the intuition that we are sort of close to the true ground state, then we should probably reduce the starting flip probability. So we will make the starting flip probability equal to 25% and keep the ending flip probability at 1%. This is how we will set the temperature. *Note that since we know that 75% of the spins are oriented correctly, 25% spin flip probability is a reasonable guess. Of course for a problem where the ground state is a mystery, we would not know this.*" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " state: {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1, 71: 1, 72: 1, 73: 1, 74: 1, 75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 1, 82: 1, 83: 1, 84: 1, 85: 1, 86: 1, 87: 1, 88: 1, 89: 1, 90: 1, 91: 1, 92: 1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: 1, 99: 1}\n", - " value: -100\n", - " spin: True\n" - ] - } - ], - "source": [ - "initial_state = {i: 1 if i < 75 else -1 for i in range(L)}\n", - "\n", - "temperature_range = qv.sim.anneal_temperature_range(\n", - " H, start_flip_prob=.25, end_flip_prob=0.01, spin=True,\n", - ")\n", - "\n", - "res = qv.sim.anneal_puso(\n", - " H, \n", - " initial_state=initial_state, \n", - " temperature_range=temperature_range,\n", - " num_anneals=10,\n", - " anneal_duration=2500,\n", - " seed=34\n", - ")\n", - "\n", - "print(res.best)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice that we get the right answer with `anneal_duration=2500`, whereas in the previous section we needed around 7500!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adjusting the schedule" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The last topic we will discuss is adjusting the schedule. There are three options for the `schedule` argument.\n", - "\n", - "1. Geometric (`schedule='geometric'`). This is the default option in `anneal_puso` (learned from D-Wave's simulated annealing software). In this case, the temperature will be decreased from from the initial to final temperature in evenly spaced increments along a log scale.\n", - "2. Linear (`schedule='linear'`). In this case, the temperature will be decreased from from the initial to final temperature in evenly spaced increments along a linear scale.\n", - "3. Custom. \n", - "\n", - "For the custom schedule, we can provide a schedule exactly as we did in the Simulation example in the first section. A schedule is a collection of `(T, n)` pairs, where `T` is the temperature to update the simulation at, and `n` is the number of time steps to simlulate the system at that temperature.\n", - "\n", - "We will show a custom schedule example. Consider exactly the above example in the Adjusting the initial state section, where we start off in an initial state that we think is pretty close to the optimal state. We will start off in a state where the first 75 spins are 1 and the last 25 are -1. For the same reasons as above, we will make the temperature range such that we start the anneal with a 25% chance of flipping a spin even if it is energetically unfavorable, and we will end with 1% chance. But this time we will customize the schedulee so that it first *reverse anneals*, and then anneals. See D-Wave reverse annealing for some info on reverse quantum annealing.\n", - "\n", - "We will use the same $T_0, T_f$ as before, but this time we will create a geometric schedule where we *start* with temperature $T_f$ and heat the system to $T_0$ quickly (50 time steps). Then we will cool the system with a geometric schedule from $T_0$ to $T_f$ in 1950 time steps." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " state: {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1, 71: 1, 72: 1, 73: 1, 74: 1, 75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 1, 82: 1, 83: 1, 84: 1, 85: 1, 86: 1, 87: 1, 88: 1, 89: 1, 90: 1, 91: 1, 92: 1, 93: 1, 94: 1, 95: 1, 96: 1, 97: 1, 98: 1, 99: 1}\n", - " value: -100\n", - " spin: True\n" - ] - } - ], - "source": [ - "from numpy import geomspace\n", - "\n", - "T0, Tf = temperature_range # same as the previous cell\n", - "\n", - "schedule = [\n", - " (T, 1) for T in geomspace(Tf, T0, 50)\n", - "] + [\n", - " (T, 1) for T in geomspace(T0, Tf, 1950)\n", - "]\n", - " \n", - "\n", - "res = qv.sim.anneal_puso(\n", - " H, \n", - " initial_state=initial_state,\n", - " num_anneals=10,\n", - " schedule=schedule,\n", - " seed=34\n", - ")\n", - "\n", - "print(res.best)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice this time we get the right answer with a total anneal duration of 2000, whereas before we needed around 2500!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Back to Table of Contents" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Conclusion" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### A note on the purpose of the `qubovert.sim` library\n", - "\n", - "I'd like to remark that D-Wave's QUBO and QUSO simulated annealing implementations in their Python package `neal` are faster than `qubovert`'s implementations. If your goal is to use simulated annealing to find the ground state of a QUBO or QUSO, then you should probably use `neal`. In fact, the main purpose of `qubovert` is to aid in the formulation of binary optimization models so that they can easily be sent to D-Wave (either their simulated or quantum annealer). See all the other notebooks where I use `neal` to solve the models that I create.\n", - "\n", - "However, `neal` does not provide the functionality for simulated annealing of PUBOs and PUSOs, nor does it allow the ability to provide an arbitrary temperature schedule. If your goal is to research different schedules (like we did with the reverse annealing example) or to solve higher order models that are expensive to reduce to QUBOs and QUSOs, then the `qubovert.sim` library may be useful. Similarly, if you are interested in how physical systems behave with time and temperature, then `qubovert`'s simulation functionaliy may be useful; for example, see a graphical simulation implementation that I wrote for the 1D ferromagnetic Ising chain." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Back to Table of Contents" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.2" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebook_examples/FaultTree_example.ipynb b/notebook_examples/FaultTree_example.ipynb index 288436e..44acbe7 100644 --- a/notebook_examples/FaultTree_example.ipynb +++ b/notebook_examples/FaultTree_example.ipynb @@ -50,14 +50,14 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{('e0',): 1, ('e1', 'e3', 'e0'): -1, ('e1', 'e3'): 1, ('e1', 'e3', 'e0', 'e2'): 1, ('e1', 'e3', 'e2'): -1, ('e1', 'e0', 'e2'): -1, ('e1', 'e2'): 1}\n" + "{('e0',): 1, ('e0', 'e1', 'e3'): -1, ('e1', 'e3'): 1, ('e0', 'e1', 'e3', 'e2'): 1, ('e1', 'e3', 'e2'): -1, ('e0', 'e1', 'e2'): -1, ('e1', 'e2'): 1}\n" ] } ], @@ -85,7 +85,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -111,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -119,10 +119,10 @@ "output_type": "stream", "text": [ "PCBO:\n", - " {('e0',): -lam + 1, ('e1',): 1, ('e2',): 1, ('e3',): 1, ('e1', 'e3', 'e0'): lam, ('e1', 'e3'): -lam, ('e1', 'e3', 'e0', 'e2'): -lam, ('e1', 'e3', 'e2'): lam, ('e1', 'e0', 'e2'): lam, ('e1', 'e2'): -lam, (): lam} \n", + " {('e0',): 1 - lam, ('e1',): 1, ('e2',): 1, ('e3',): 1, ('e0', 'e1', 'e3'): lam, ('e1', 'e3'): -lam, ('e0', 'e1', 'e3', 'e2'): -lam, ('e1', 'e3', 'e2'): lam, ('e0', 'e1', 'e2'): lam, ('e1', 'e2'): -lam, (): lam} \n", "\n", "Constraints:\n", - " {'eq': [{('e0',): -1, ('e1', 'e3', 'e0'): 1, ('e1', 'e3'): -1, ('e1', 'e3', 'e0', 'e2'): -1, ('e1', 'e3', 'e2'): 1, ('e1', 'e0', 'e2'): 1, ('e1', 'e2'): -1, (): 1}]}\n" + " {'eq': [{('e0',): -1, ('e0', 'e1', 'e3'): 1, ('e1', 'e3'): -1, ('e0', 'e1', 'e3', 'e2'): -1, ('e1', 'e3', 'e2'): 1, ('e0', 'e1', 'e2'): 1, ('e1', 'e2'): -1, (): 1}]}\n" ] } ], @@ -156,7 +156,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -181,7 +181,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -208,7 +208,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -217,7 +217,7 @@ "4" ] }, - "execution_count": 7, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -235,7 +235,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -245,7 +245,7 @@ "num PUBO variables 4\n", "num QUBO variables 6\n", "\n", - "{(0,): -lam + 1, (1,): 1, (2,): 1, (3,): 1, (4,): 9*lam + 9, (1, 3): 2*lam + 3, (1, 4): -6*lam - 6, (3, 4): -6*lam - 6, (0, 4): lam, (5,): 6*lam + 6, (0, 2): 2*lam + 2, (0, 5): -4*lam - 4, (2, 5): -4*lam - 4, (4, 5): -lam, (2, 4): lam, (1, 5): lam, (1, 2): -lam, (): lam}\n" + "{(0,): 1 - lam, (1,): 1, (2,): 1, (3,): 1, (4,): 9*lam + 9, (1, 3): 2*lam + 3, (1, 4): -6*lam - 6, (3, 4): -6*lam - 6, (0, 4): lam, (5,): 6*lam + 6, (0, 2): 2*lam + 2, (0, 5): -4*lam - 4, (2, 5): -4*lam - 4, (4, 5): -lam, (2, 4): lam, (1, 5): lam, (1, 2): -lam, (): lam}\n" ] } ], @@ -266,7 +266,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -306,7 +306,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -322,7 +322,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -341,7 +341,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -384,7 +384,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -419,7 +419,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -471,7 +471,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -501,7 +501,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -525,14 +525,14 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{('e0',): 2*lam + 1, ('e1',): lam + 1, ('e2',): lam + 1, ('e3',): lam + 1, ('z0',): 2*lam, ('e3', 'e0'): lam, ('z0', 'e0'): -2*lam, ('z0', 'e3'): -2*lam, ('z1',): lam, ('z0', 'e2'): lam, ('e2', 'z1'): -2*lam, ('z0', 'z1'): -2*lam, ('z2',): lam, ('e1', 'e0'): lam, ('e0', 'z2'): -2*lam, ('e1', 'z2'): -2*lam}\n" + "{('e0',): 2*lam + 1, ('e1',): lam + 1, ('e2',): lam + 1, ('e3',): lam + 1, ('z0',): 2*lam, ('e0', 'e3'): lam, ('e0', 'z0'): -2*lam, ('z0', 'e3'): -2*lam, ('z1',): lam, ('z0', 'e2'): lam, ('e2', 'z1'): -2*lam, ('z0', 'z1'): -2*lam, ('z2',): lam, ('e0', 'e1'): lam, ('e0', 'z2'): -2*lam, ('e1', 'z2'): -2*lam}\n" ] } ], @@ -557,7 +557,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -565,10 +565,10 @@ "output_type": "stream", "text": [ "PCBO:\n", - " {('e0',): 2*lam + 1, ('e1',): lam + 1, ('e2',): lam + 1, ('e3',): lam + 1, ('z0',): 2*lam, ('e3', 'e0'): lam, ('z0', 'e0'): -2*lam, ('z0', 'e3'): -2*lam, ('z1',): lam, ('z0', 'e2'): lam, ('e2', 'z1'): -2*lam, ('z0', 'z1'): -2*lam, ('z2',): lam, ('e1', 'e0'): lam, ('e0', 'z2'): -2*lam, ('e1', 'z2'): -2*lam, ('z2', 'z1'): -lam, (): lam} \n", + " {('e0',): 2*lam + 1, ('e1',): lam + 1, ('e2',): lam + 1, ('e3',): lam + 1, ('z0',): 2*lam, ('e0', 'e3'): lam, ('e0', 'z0'): -2*lam, ('z0', 'e3'): -2*lam, ('z1',): lam, ('z0', 'e2'): lam, ('e2', 'z1'): -2*lam, ('z0', 'z1'): -2*lam, ('z2',): lam, ('e0', 'e1'): lam, ('e0', 'z2'): -2*lam, ('e1', 'z2'): -2*lam, ('z2', 'z1'): -lam, (): lam} \n", "\n", "Constraints:\n", - " {'eq': [{('z0',): 1, ('e0',): 1, ('e3',): 1, ('e3', 'e0'): 1, ('z0', 'e0'): -2, ('z0', 'e3'): -2}, {('z1',): 1, ('e2',): 1, ('z0',): 1, ('z0', 'e2'): 1, ('e2', 'z1'): -2, ('z0', 'z1'): -2}, {('z2',): 1, ('e0',): 1, ('e1',): 1, ('e1', 'e0'): 1, ('e0', 'z2'): -2, ('e1', 'z2'): -2}, {('z2', 'z1'): -1, (): 1}]}\n" + " {'eq': [{('z0',): 1, ('e0',): 1, ('e3',): 1, ('e0', 'e3'): 1, ('e0', 'z0'): -2, ('z0', 'e3'): -2}, {('z1',): 1, ('e2',): 1, ('z0',): 1, ('z0', 'e2'): 1, ('e2', 'z1'): -2, ('z0', 'z1'): -2}, {('z2',): 1, ('e0',): 1, ('e1',): 1, ('e0', 'e1'): 1, ('e0', 'z2'): -2, ('e1', 'z2'): -2}, {('z2', 'z1'): -1, (): 1}]}\n" ] } ], @@ -594,7 +594,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -619,7 +619,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -646,7 +646,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -655,7 +655,7 @@ "2" ] }, - "execution_count": 21, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -666,7 +666,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -691,7 +691,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -731,7 +731,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ @@ -747,7 +747,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -766,7 +766,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "metadata": {}, "outputs": [ { @@ -809,7 +809,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 26, "metadata": {}, "outputs": [ { @@ -842,7 +842,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 27, "metadata": {}, "outputs": [ { @@ -898,7 +898,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.6.9" } }, "nbformat": 4, diff --git a/notebook_examples/JobSequencing_example.ipynb b/notebook_examples/JobSequencing_example.ipynb index f5326b2..75f2e89 100644 --- a/notebook_examples/JobSequencing_example.ipynb +++ b/notebook_examples/JobSequencing_example.ipynb @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -45,7 +45,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -62,7 +62,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -78,7 +78,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -105,7 +105,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -124,7 +124,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -173,7 +173,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -184,9 +184,9 @@ "\n", "objective function: 3.0 \n", "\n", - "quso solution: {0: 1, 1: -1, 2: -1, 3: 1, 4: 1, 5: -1, 6: 1, 7: 1, 8: 1, 9: 1} \n", + "quso solution: {0: -1, 1: 1, 2: 1, 3: -1, 4: -1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1} \n", "\n", - "problem solution: ({'job2'}, {'job3', 'job1'}) \n", + "problem solution: ({'job3', 'job1'}, {'job2'}) \n", "\n", "The solution is valid\n" ] @@ -235,7 +235,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.6.9" } }, "nbformat": 4, diff --git a/notebook_examples/PCBO_example.ipynb b/notebook_examples/PCBO_example.ipynb index 7c440a9..58d743e 100644 --- a/notebook_examples/PCBO_example.ipynb +++ b/notebook_examples/PCBO_example.ipynb @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -41,14 +41,14 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{('x0', 'x1'): 1, ('x1',): -2, ('x3', 'x2', 'x1'): 1}\n" + "{('x1', 'x0'): 1, ('x1',): -2, ('x1', 'x3', 'x2'): 1}\n" ] } ], @@ -74,14 +74,14 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{('x0', 'x1'): 11*lam + 1, ('x1',): -19*lam - 2, ('x3', 'x2', 'x1'): 1, ('x0',): -26*lam, ('x3', 'x0'): 22*lam, ('x3', 'x0', 'x1'): 2*lam, ('x3', 'x1'): 14*lam, ('x3',): -30*lam, ('__a0', 'x0'): 6*lam, ('x0', '__a1'): 12*lam, ('x0', '__a2'): 24*lam, ('__a0', 'x1'): 4*lam, ('__a1', 'x1'): 8*lam, ('__a2', 'x1'): 16*lam, ('__a0', 'x3'): 8*lam, ('x3', '__a1'): 16*lam, ('x3', '__a2'): 32*lam, (): 36*lam, ('__a0',): -11*lam, ('__a1',): -20*lam, ('__a2',): -32*lam, ('__a0', '__a1'): 4*lam, ('__a0', '__a2'): 8*lam, ('__a1', '__a2'): 16*lam, ('x0', 'x2', 'x1'): 2*lam, ('x3', 'x2'): -2*lam, ('x2',): lam}\n" + "{('x1', 'x0'): 11*lam + 1, ('x1',): -19*lam - 2, ('x1', 'x3', 'x2'): 1, ('x0',): -26*lam, ('x3', 'x0'): 22*lam, ('x1', 'x3', 'x0'): 2*lam, ('x1', 'x3'): 14*lam, ('x3',): -30*lam, ('__a0', 'x0'): 6*lam, ('__a1', 'x0'): 12*lam, ('__a2', 'x0'): 24*lam, ('x1', '__a0'): 4*lam, ('x1', '__a1'): 8*lam, ('x1', '__a2'): 16*lam, ('x3', '__a0'): 8*lam, ('x3', '__a1'): 16*lam, ('x3', '__a2'): 32*lam, (): 36*lam, ('__a0',): -11*lam, ('__a1',): -20*lam, ('__a2',): -32*lam, ('__a0', '__a1'): 4*lam, ('__a2', '__a0'): 8*lam, ('__a2', '__a1'): 16*lam, ('x1', 'x2', 'x0'): 2*lam, ('x3', 'x2'): -2*lam, ('x2',): lam}\n" ] } ], @@ -114,14 +114,14 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{'eq': [{('x0',): 1, ('x0', 'x1'): -2, ('x1',): 1, ('x3',): -1}, {('x0', 'x1'): 1, ('x3',): -1, ('x2',): 1}], 'lt': [{('x0',): 3, ('x1',): 2, ('x3',): 4, (): -7}]}\n" + "{'eq': [{('x0',): 1, ('x1', 'x0'): -2, ('x1',): 1, ('x3',): -1}, {('x1', 'x0'): 1, ('x3',): -1, ('x2',): 1}], 'lt': [{('x0',): 3, ('x1',): 2, ('x3',): 4, (): -7}]}\n" ] } ], @@ -145,14 +145,14 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[{'x0': 0, 'x1': 1, 'x3': 1, 'x2': 1, '__a0': 0, '__a1': 0, '__a2': 0}]\n" + "[{'x1': 1, 'x0': 0, 'x3': 1, 'x2': 1, '__a0': 0, '__a1': 0, '__a2': 0}]\n" ] } ], @@ -170,16 +170,16 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'x0': 0, 'x1': 1, 'x3': 1, 'x2': 1}" + "{'x1': 1, 'x0': 0, 'x3': 1, 'x2': 1}" ] }, - "execution_count": 7, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -198,7 +198,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -207,7 +207,7 @@ "3" ] }, - "execution_count": 8, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -225,7 +225,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -235,7 +235,7 @@ "num PUBO variables 7\n", "num QUBO variables 9\n", "\n", - "{(0, 1): 13*lam + 2, (1,): -19*lam - 2, (7,): 6*lam + 9, (1, 2): 16*lam + 3, (1, 7): -4*lam - 6, (2, 7): -4*lam - 6, (3, 7): 1, (0,): -26*lam, (0, 2): 22*lam, (0, 7): 2*lam, (2,): -30*lam, (0, 4): 6*lam, (0, 5): 12*lam, (0, 6): 24*lam, (1, 4): 4*lam, (1, 5): 8*lam, (1, 6): 16*lam, (2, 4): 8*lam, (2, 5): 16*lam, (2, 6): 32*lam, (4,): -11*lam, (5,): -20*lam, (6,): -32*lam, (4, 5): 4*lam, (4, 6): 8*lam, (5, 6): 16*lam, (8,): 6*lam + 3, (0, 8): -4*lam - 2, (1, 8): -4*lam - 2, (3, 8): 2*lam, (2, 3): -2*lam, (3,): lam, (): 36*lam}\n" + "{(0, 1): 13*lam + 2, (0,): -19*lam - 2, (7,): 6*lam + 9, (0, 2): 16*lam + 3, (0, 7): -4*lam - 6, (2, 7): -4*lam - 6, (3, 7): 1, (1,): -26*lam, (1, 2): 22*lam, (1, 7): 2*lam, (2,): -30*lam, (1, 4): 6*lam, (1, 5): 12*lam, (1, 6): 24*lam, (0, 4): 4*lam, (0, 5): 8*lam, (0, 6): 16*lam, (2, 4): 8*lam, (2, 5): 16*lam, (2, 6): 32*lam, (4,): -11*lam, (5,): -20*lam, (6,): -32*lam, (4, 5): 4*lam, (4, 6): 8*lam, (5, 6): 16*lam, (8,): 6*lam + 3, (0, 8): -4*lam - 2, (1, 8): -4*lam - 2, (3, 8): 2*lam, (2, 3): -2*lam, (3,): lam, (): 36*lam}\n" ] } ], @@ -256,7 +256,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -264,15 +264,15 @@ "output_type": "stream", "text": [ "lam 1\n", - "\t {'x0': 0, 'x1': 1, 'x3': 0, 'x2': 0, '__a0': 0, '__a1': 0, '__a2': 1} is invalid\n", - "\t {'x0': 0, 'x1': 1, 'x3': 1, 'x2': 0, '__a0': 0, '__a1': 0, '__a2': 0} is invalid\n", - "\t {'x0': 0, 'x1': 1, 'x3': 1, 'x2': 1, '__a0': 0, '__a1': 0, '__a2': 0} is valid\n", + "\t {'x1': 1, 'x0': 0, 'x3': 0, 'x2': 0, '__a0': 0, '__a1': 0, '__a2': 1} is invalid\n", + "\t {'x1': 1, 'x0': 0, 'x3': 1, 'x2': 0, '__a0': 0, '__a1': 0, '__a2': 0} is invalid\n", + "\t {'x1': 1, 'x0': 0, 'x3': 1, 'x2': 1, '__a0': 0, '__a1': 0, '__a2': 0} is valid\n", "\n", "lam 2\n", - "\t {'x0': 0, 'x1': 1, 'x3': 1, 'x2': 1, '__a0': 0, '__a1': 0, '__a2': 0} is valid\n", + "\t {'x1': 1, 'x0': 0, 'x3': 1, 'x2': 1, '__a0': 0, '__a1': 0, '__a2': 0} is valid\n", "\n", "lam 3\n", - "\t {'x0': 0, 'x1': 1, 'x3': 1, 'x2': 1, '__a0': 0, '__a1': 0, '__a2': 0} is valid\n", + "\t {'x1': 1, 'x0': 0, 'x3': 1, 'x2': 1, '__a0': 0, '__a1': 0, '__a2': 0} is valid\n", "\n" ] } @@ -297,7 +297,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -313,7 +313,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -332,7 +332,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -341,9 +341,9 @@ "text": [ "objective function: -1.0 \n", "\n", - "qubo solution: {0: 0, 1: 1, 2: 1, 3: 1, 4: 0, 5: 0, 6: 0, 7: 1, 8: 0} \n", + "qubo solution: {0: 1, 1: 0, 2: 1, 3: 1, 4: 0, 5: 0, 6: 0, 7: 1, 8: 0} \n", "\n", - "pcbo solution: {'x0': 0, 'x1': 1, 'x3': 1, 'x2': 1, '__a0': 0, '__a1': 0, '__a2': 0}\n", + "pcbo solution: {'x1': 1, 'x0': 0, 'x3': 1, 'x2': 1, '__a0': 0, '__a1': 0, '__a2': 0}\n", "objective function: -1 \n", "\n", "The solution is valid\n" @@ -373,7 +373,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -383,7 +383,7 @@ "num PUBO variables 7\n", "num QUSO variables 9\n", "\n", - "{(0, 1): 7.0, (0,): -11.5, (1,): -3.75, (): 45.0, (7,): -4.75, (1, 2): 8.75, (2,): -13.25, (1, 7): -3.5, (2, 7): -3.5, (3, 7): 0.25, (3,): -1.25, (0, 2): 11.0, (0, 7): 1.0, (0, 4): 3.0, (0, 5): 6.0, (0, 6): 12.0, (6,): -16.0, (1, 4): 2.0, (1, 5): 4.0, (1, 6): 8.0, (2, 4): 4.0, (2, 5): 8.0, (2, 6): 16.0, (4, 5): 2.0, (4, 6): 4.0, (4,): -4.0, (5, 6): 8.0, (5,): -8.0, (8,): -3.5, (0, 8): -2.5, (1, 8): -2.5, (3, 8): 1.0, (2, 3): -1.0}\n" + "{(0, 1): 7.0, (0,): -3.75, (1,): -11.5, (): 45.0, (7,): -4.75, (0, 2): 8.75, (2,): -13.25, (0, 7): -3.5, (2, 7): -3.5, (3, 7): 0.25, (3,): -1.25, (1, 2): 11.0, (1, 7): 1.0, (1, 4): 3.0, (1, 5): 6.0, (1, 6): 12.0, (6,): -16.0, (0, 4): 2.0, (0, 5): 4.0, (0, 6): 8.0, (2, 4): 4.0, (2, 5): 8.0, (2, 6): 16.0, (4, 5): 2.0, (4, 6): 4.0, (4,): -4.0, (5, 6): 8.0, (5,): -8.0, (8,): -3.5, (0, 8): -2.5, (1, 8): -2.5, (3, 8): 1.0, (2, 3): -1.0}\n" ] } ], @@ -408,7 +408,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -417,9 +417,9 @@ "text": [ "objective function: -1.0 \n", "\n", - "quso solution: {0: 1, 1: -1, 2: -1, 3: -1, 4: 1, 5: 1, 6: 1, 7: -1, 8: 1} \n", + "quso solution: {0: -1, 1: 1, 2: -1, 3: -1, 4: 1, 5: 1, 6: 1, 7: -1, 8: 1} \n", "\n", - "pcbo solution: {'x0': 0, 'x1': 1, 'x3': 1, 'x2': 1, '__a0': 0, '__a1': 0, '__a2': 0}\n", + "pcbo solution: {'x1': 1, 'x0': 0, 'x3': 1, 'x2': 1, '__a0': 0, '__a1': 0, '__a2': 0}\n", "objective function: -1 \n", "\n", "The solution is valid\n" @@ -471,7 +471,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.6.9" } }, "nbformat": 4, diff --git a/notebook_examples/VertexCover_example.ipynb b/notebook_examples/VertexCover_example.ipynb index 54d4718..09296e5 100644 --- a/notebook_examples/VertexCover_example.ipynb +++ b/notebook_examples/VertexCover_example.ipynb @@ -35,14 +35,14 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{(4, 7), (6, 9), (5, 6), (8, 9), (0, 3), (5, 8), (1, 5), (3, 6), (0, 4), (2, 6), (4, 5), (0, 8), (7, 9), (3, 5), (6, 8), (0, 6), (1, 8), (0, 9), (3, 4)}\n" + "{(4, 7), (2, 6), (6, 9), (6, 8), (4, 5), (5, 6), (7, 9), (8, 9), (0, 6), (1, 5), (1, 8), (3, 6), (0, 4), (0, 9), (0, 3), (0, 8), (3, 4), (5, 8), (3, 5)}\n" ] } ], @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -95,7 +95,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -118,7 +118,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -133,54 +133,54 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{'eq': [{(4,): -1, (4, 7): 1, (7,): -1, (): 1}, {(6,): -1, (6, 9): 1, (9,): -1, (): 1}, {(5,): -1, (5, 6): 1, (6,): -1, (): 1}, {(8,): -1, (8, 9): 1, (9,): -1, (): 1}, {(0,): -1, (0, 3): 1, (3,): -1, (): 1}, {(5,): -1, (5, 8): 1, (8,): -1, (): 1}, {(1,): -1, (1, 5): 1, (5,): -1, (): 1}, {(3,): -1, (3, 6): 1, (6,): -1, (): 1}, {(0,): -1, (0, 4): 1, (4,): -1, (): 1}, {(2,): -1, (2, 6): 1, (6,): -1, (): 1}, {(4,): -1, (4, 5): 1, (5,): -1, (): 1}, {(0,): -1, (0, 8): 1, (8,): -1, (): 1}, {(7,): -1, (7, 9): 1, (9,): -1, (): 1}, {(3,): -1, (3, 5): 1, (5,): -1, (): 1}, {(6,): -1, (6, 8): 1, (8,): -1, (): 1}, {(0,): -1, (0, 6): 1, (6,): -1, (): 1}, {(1,): -1, (1, 8): 1, (8,): -1, (): 1}, {(0,): -1, (0, 9): 1, (9,): -1, (): 1}, {(3,): -1, (3, 4): 1, (4,): -1, (): 1}]} \n", + "{'eq': [{(4,): -1, (4, 7): 1, (7,): -1, (): 1}, {(2,): -1, (2, 6): 1, (6,): -1, (): 1}, {(6,): -1, (6, 9): 1, (9,): -1, (): 1}, {(6,): -1, (6, 8): 1, (8,): -1, (): 1}, {(4,): -1, (4, 5): 1, (5,): -1, (): 1}, {(5,): -1, (5, 6): 1, (6,): -1, (): 1}, {(7,): -1, (7, 9): 1, (9,): -1, (): 1}, {(8,): -1, (8, 9): 1, (9,): -1, (): 1}, {(0,): -1, (0, 6): 1, (6,): -1, (): 1}, {(1,): -1, (1, 5): 1, (5,): -1, (): 1}, {(1,): -1, (1, 8): 1, (8,): -1, (): 1}, {(3,): -1, (3, 6): 1, (6,): -1, (): 1}, {(0,): -1, (0, 4): 1, (4,): -1, (): 1}, {(0,): -1, (0, 9): 1, (9,): -1, (): 1}, {(0,): -1, (0, 3): 1, (3,): -1, (): 1}, {(0,): -1, (0, 8): 1, (8,): -1, (): 1}, {(3,): -1, (3, 4): 1, (4,): -1, (): 1}, {(5,): -1, (5, 8): 1, (8,): -1, (): 1}, {(3,): -1, (3, 5): 1, (5,): -1, (): 1}]} \n", "\n", "Equality constraints:\n", "\n", "{(4,): -1, (4, 7): 1, (7,): -1, (): 1} \n", "\n", + "{(2,): -1, (2, 6): 1, (6,): -1, (): 1} \n", + "\n", "{(6,): -1, (6, 9): 1, (9,): -1, (): 1} \n", "\n", + "{(6,): -1, (6, 8): 1, (8,): -1, (): 1} \n", + "\n", + "{(4,): -1, (4, 5): 1, (5,): -1, (): 1} \n", + "\n", "{(5,): -1, (5, 6): 1, (6,): -1, (): 1} \n", "\n", - "{(8,): -1, (8, 9): 1, (9,): -1, (): 1} \n", + "{(7,): -1, (7, 9): 1, (9,): -1, (): 1} \n", "\n", - "{(0,): -1, (0, 3): 1, (3,): -1, (): 1} \n", + "{(8,): -1, (8, 9): 1, (9,): -1, (): 1} \n", "\n", - "{(5,): -1, (5, 8): 1, (8,): -1, (): 1} \n", + "{(0,): -1, (0, 6): 1, (6,): -1, (): 1} \n", "\n", "{(1,): -1, (1, 5): 1, (5,): -1, (): 1} \n", "\n", + "{(1,): -1, (1, 8): 1, (8,): -1, (): 1} \n", + "\n", "{(3,): -1, (3, 6): 1, (6,): -1, (): 1} \n", "\n", "{(0,): -1, (0, 4): 1, (4,): -1, (): 1} \n", "\n", - "{(2,): -1, (2, 6): 1, (6,): -1, (): 1} \n", + "{(0,): -1, (0, 9): 1, (9,): -1, (): 1} \n", "\n", - "{(4,): -1, (4, 5): 1, (5,): -1, (): 1} \n", + "{(0,): -1, (0, 3): 1, (3,): -1, (): 1} \n", "\n", "{(0,): -1, (0, 8): 1, (8,): -1, (): 1} \n", "\n", - "{(7,): -1, (7, 9): 1, (9,): -1, (): 1} \n", - "\n", - "{(3,): -1, (3, 5): 1, (5,): -1, (): 1} \n", - "\n", - "{(6,): -1, (6, 8): 1, (8,): -1, (): 1} \n", - "\n", - "{(0,): -1, (0, 6): 1, (6,): -1, (): 1} \n", - "\n", - "{(1,): -1, (1, 8): 1, (8,): -1, (): 1} \n", + "{(3,): -1, (3, 4): 1, (4,): -1, (): 1} \n", "\n", - "{(0,): -1, (0, 9): 1, (9,): -1, (): 1} \n", + "{(5,): -1, (5, 8): 1, (8,): -1, (): 1} \n", "\n", - "{(3,): -1, (3, 4): 1, (4,): -1, (): 1} \n", + "{(3,): -1, (3, 5): 1, (5,): -1, (): 1} \n", "\n" ] } @@ -203,14 +203,14 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{(0,): -5*lam + 1, (1,): -2*lam + 1, (2,): -lam + 1, (3,): -4*lam + 1, (4,): -4*lam + 1, (5,): -5*lam + 1, (6,): -6*lam + 1, (7,): -2*lam + 1, (8,): -5*lam + 1, (9,): -4*lam + 1, (4, 7): lam, (): 19*lam, (6, 9): lam, (5, 6): lam, (8, 9): lam, (0, 3): lam, (5, 8): lam, (1, 5): lam, (3, 6): lam, (0, 4): lam, (2, 6): lam, (4, 5): lam, (0, 8): lam, (7, 9): lam, (3, 5): lam, (6, 8): lam, (0, 6): lam, (1, 8): lam, (0, 9): lam, (3, 4): lam} \n", + "{(0,): 1 - 5*lam, (1,): 1 - 2*lam, (2,): 1 - lam, (3,): 1 - 4*lam, (4,): 1 - 4*lam, (5,): 1 - 5*lam, (6,): 1 - 6*lam, (7,): 1 - 2*lam, (8,): 1 - 5*lam, (9,): 1 - 4*lam, (4, 7): lam, (): 19*lam, (2, 6): lam, (6, 9): lam, (6, 8): lam, (4, 5): lam, (5, 6): lam, (7, 9): lam, (8, 9): lam, (0, 6): lam, (1, 5): lam, (1, 8): lam, (3, 6): lam, (0, 4): lam, (0, 9): lam, (0, 3): lam, (0, 8): lam, (3, 4): lam, (5, 8): lam, (3, 5): lam} \n", "\n", "Number of variables: 10\n", "degree: 2\n" @@ -232,7 +232,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -270,14 +270,14 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{(0,): -5*lam + 1, (1,): -2*lam + 1, (2,): -lam + 1, (3,): -4*lam + 1, (4,): -4*lam + 1, (5,): -5*lam + 1, (6,): -6*lam + 1, (7,): -2*lam + 1, (8,): -5*lam + 1, (9,): -4*lam + 1, (4, 7): lam, (6, 9): lam, (5, 6): lam, (8, 9): lam, (0, 3): lam, (5, 8): lam, (1, 5): lam, (3, 6): lam, (0, 4): lam, (2, 6): lam, (4, 5): lam, (0, 8): lam, (7, 9): lam, (3, 5): lam, (6, 8): lam, (0, 6): lam, (1, 8): lam, (0, 9): lam, (3, 4): lam, (): 19*lam}\n" + "{(0,): 1 - 5*lam, (1,): 1 - 2*lam, (2,): 1 - lam, (3,): 1 - 4*lam, (4,): 1 - 4*lam, (5,): 1 - 5*lam, (6,): 1 - 6*lam, (7,): 1 - 2*lam, (8,): 1 - 5*lam, (9,): 1 - 4*lam, (4, 7): lam, (2, 6): lam, (6, 9): lam, (6, 8): lam, (4, 5): lam, (5, 6): lam, (7, 9): lam, (8, 9): lam, (0, 6): lam, (1, 5): lam, (1, 8): lam, (3, 6): lam, (0, 4): lam, (0, 9): lam, (0, 3): lam, (0, 8): lam, (3, 4): lam, (5, 8): lam, (3, 5): lam, (): 19*lam}\n" ] } ], @@ -295,7 +295,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": { "scrolled": false }, @@ -356,7 +356,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -372,7 +372,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -391,7 +391,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -434,14 +434,14 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{(0,): 2.0, (): 14.5, (1,): 0.5, (3,): 1.5, (4,): 1.5, (5,): 2.0, (6,): 2.5, (7,): 0.5, (8,): 2.0, (9,): 1.5, (4, 7): 0.5, (6, 9): 0.5, (5, 6): 0.5, (8, 9): 0.5, (0, 3): 0.5, (5, 8): 0.5, (1, 5): 0.5, (3, 6): 0.5, (0, 4): 0.5, (2, 6): 0.5, (4, 5): 0.5, (0, 8): 0.5, (7, 9): 0.5, (3, 5): 0.5, (6, 8): 0.5, (0, 6): 0.5, (1, 8): 0.5, (0, 9): 0.5, (3, 4): 0.5}\n" + "{(0,): 2.0, (): 14.5, (1,): 0.5, (3,): 1.5, (4,): 1.5, (5,): 2.0, (6,): 2.5, (7,): 0.5, (8,): 2.0, (9,): 1.5, (4, 7): 0.5, (2, 6): 0.5, (6, 9): 0.5, (6, 8): 0.5, (4, 5): 0.5, (5, 6): 0.5, (7, 9): 0.5, (8, 9): 0.5, (0, 6): 0.5, (1, 5): 0.5, (1, 8): 0.5, (3, 6): 0.5, (0, 4): 0.5, (0, 9): 0.5, (0, 3): 0.5, (0, 8): 0.5, (3, 4): 0.5, (5, 8): 0.5, (3, 5): 0.5}\n" ] } ], @@ -463,7 +463,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -515,7 +515,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -533,7 +533,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -564,7 +564,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -610,7 +610,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -669,7 +669,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.6.9" } }, "nbformat": 4, diff --git a/qubovert/__init__.py b/qubovert/__init__.py index 74d269a..f3bd935 100644 --- a/qubovert/__init__.py +++ b/qubovert/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""``qubovert`` is a module for converting problems into QUBO/QUSO form. +"""A module for converting problems into QUBO/QUSO form. QUBO stands for Quadratic Unconstrained Boolean Optimization. QUBO problems have a one-to-one mapping to classical QUSO problems, and most optimization diff --git a/qubovert/_qubo.py b/qubovert/_qubo.py index 86f1595..88fa855 100644 --- a/qubovert/_qubo.py +++ b/qubovert/_qubo.py @@ -18,7 +18,9 @@ """ -from .utils import BO, QUBOMatrix, PUBOMatrix, solution_type, spin_to_boolean +from .utils import ( + BO, QUBOMatrix, PUBOMatrix, is_solution_spin, spin_to_boolean +) __all__ = 'QUBO', @@ -228,8 +230,7 @@ def convert_solution(self, solution, spin=False): {'a': 1, 'b': 1, 'c': 0} """ - sol_type = solution_type(solution, 'spin' if spin else 'bool') - if sol_type == 'spin': + if is_solution_spin(solution, spin): solution = spin_to_boolean(solution) return { self._reverse_mapping[i]: solution[i] diff --git a/qubovert/_quso.py b/qubovert/_quso.py index 4dc2c86..4920048 100644 --- a/qubovert/_quso.py +++ b/qubovert/_quso.py @@ -19,7 +19,7 @@ """ from .utils import ( - BO, QUSOMatrix, PUSOMatrix, solution_type, boolean_to_spin + BO, QUSOMatrix, PUSOMatrix, is_solution_spin, boolean_to_spin ) @@ -230,8 +230,7 @@ def convert_solution(self, solution, spin=True): {'a': 1, 'b': 1, 'c': -1} """ - sol_type = solution_type(solution, 'spin' if spin else 'bool') - if sol_type == 'bool': + if not is_solution_spin(solution, spin): solution = boolean_to_spin(solution) return { self._reverse_mapping[i]: solution[i] diff --git a/qubovert/_version.py b/qubovert/_version.py index 4adca56..257e522 100644 --- a/qubovert/_version.py +++ b/qubovert/_version.py @@ -20,9 +20,9 @@ ) -__version__ = "1.1.7" +__version__ = "1.2.0" __author__ = "Joseph T. Iosue" -__authoremail__ = "joe.iosue@yahoo.com" +__authoremail__ = "jtiosue@gmail.com" __license__ = "Apache Software License 2.0" __sourceurl__ = "https://github.com/jtiosue/qubovert" __docsurl__ = "https://qubovert.readthedocs.io" diff --git a/qubovert/problems/benchmarking/_alternating_sectors_chain.py b/qubovert/problems/benchmarking/_alternating_sectors_chain.py index c54e8e0..df900e4 100644 --- a/qubovert/problems/benchmarking/_alternating_sectors_chain.py +++ b/qubovert/problems/benchmarking/_alternating_sectors_chain.py @@ -18,7 +18,7 @@ ``help(qubovert.problems.AlternatingSectorsChain)``. """ -from qubovert.utils import QUSOMatrix, boolean_to_spin, solution_type +from qubovert.utils import QUSOMatrix, boolean_to_spin, is_solution_spin from qubovert.problems import Problem @@ -203,8 +203,7 @@ def convert_solution(self, solution, spin=False): """ if isinstance(solution, dict): solution = tuple(v for _, v in sorted(solution.items())) - sol_type = solution_type(solution, 'spin' if spin else 'bool') - if sol_type == 'bool': + if not is_solution_spin(solution, spin): return boolean_to_spin(solution) return solution diff --git a/qubovert/problems/np/bilp/_bilp.py b/qubovert/problems/np/bilp/_bilp.py index 6cc00c6..3a42068 100644 --- a/qubovert/problems/np/bilp/_bilp.py +++ b/qubovert/problems/np/bilp/_bilp.py @@ -18,7 +18,7 @@ """ -from qubovert.utils import QUBOMatrix, solution_type, spin_to_boolean +from qubovert.utils import QUBOMatrix, is_solution_spin, spin_to_boolean from qubovert.problems import Problem import numpy as np @@ -238,8 +238,7 @@ def convert_solution(self, solution, spin=False): An array representing the :math:`\mathbf{x}` vector. """ - sol_type = solution_type(solution, 'spin' if spin else 'bool') - if sol_type == 'spin': + if is_solution_spin(solution, spin): solution = spin_to_boolean(solution) return np.array([int(bool(solution[i])) for i in range(self._N)]) diff --git a/qubovert/problems/np/coloring/_job_sequencing.py b/qubovert/problems/np/coloring/_job_sequencing.py index cce5784..42e2043 100644 --- a/qubovert/problems/np/coloring/_job_sequencing.py +++ b/qubovert/problems/np/coloring/_job_sequencing.py @@ -21,7 +21,7 @@ from math import log2 from qubovert.utils import ( - QUBOMatrix, decimal_to_boolean, solution_type, spin_to_boolean + QUBOMatrix, decimal_to_boolean, is_solution_spin, spin_to_boolean ) from qubovert.problems import Problem @@ -367,8 +367,7 @@ def convert_solution(self, solution, spin=False): of the tuple is a set of jobs that are assigned to that worker. """ - sol_type = solution_type(solution, 'spin' if spin else 'bool') - if sol_type == 'spin': + if is_solution_spin(solution, spin): solution = spin_to_boolean(solution) res = tuple(set() for _ in range(self._m)) for worker in range(self._m): diff --git a/qubovert/problems/np/covering/_set_cover.py b/qubovert/problems/np/covering/_set_cover.py index 3955095..d09e69a 100644 --- a/qubovert/problems/np/covering/_set_cover.py +++ b/qubovert/problems/np/covering/_set_cover.py @@ -21,7 +21,7 @@ from numpy import allclose from math import log2 from qubovert.utils import ( - QUBOMatrix, solve_qubo_bruteforce, solution_type, spin_to_boolean + QUBOMatrix, solve_qubo_bruteforce, is_solution_spin, spin_to_boolean ) from qubovert.problems import Problem @@ -398,8 +398,7 @@ def convert_solution(self, solution, spin=False): ``V[0]``, ``V[2]``, and ``V[3]``. """ - sol_type = solution_type(solution, 'spin' if spin else 'bool') - if sol_type == 'spin': + if is_solution_spin(solution, spin): solution = spin_to_boolean(solution) return set(i for i in range(self._N) if solution[i]) diff --git a/qubovert/problems/np/covering/_vertex_cover.py b/qubovert/problems/np/covering/_vertex_cover.py index 7634d97..5374479 100644 --- a/qubovert/problems/np/covering/_vertex_cover.py +++ b/qubovert/problems/np/covering/_vertex_cover.py @@ -21,7 +21,7 @@ from qubovert import PCBO from qubovert.problems import Problem from qubovert.utils import ( - solution_type, spin_to_boolean, QUBOMatrix, hash_function + is_solution_spin, spin_to_boolean, QUBOMatrix, hash_function ) @@ -239,8 +239,7 @@ def convert_solution(self, solution, spin=False): """ if not isinstance(solution, dict): solution = dict(enumerate(solution)) - sol_type = solution_type(solution, 'spin' if spin else 'bool') - if sol_type == 'spin': + if is_solution_spin(solution, spin): solution = spin_to_boolean(solution) return set( self._index_to_vertex[i] for i, x in solution.items() if x diff --git a/qubovert/sim/__init__.py b/qubovert/sim/__init__.py index 1ab5dbc..84ba42a 100644 --- a/qubovert/sim/__init__.py +++ b/qubovert/sim/__init__.py @@ -18,29 +18,19 @@ """ -# import order here is important! -from ._puso_simulation import * -from ._pubo_simulation import * -from ._quso_simulation import * -from ._qubo_simulation import * +# import order here is important +from ._anneal_temperature_range import * from ._anneal_results import * from ._anneal import * -from ._puso_simulation import __all__ as __all_pusosim__ -from ._pubo_simulation import __all__ as __all_pubosim__ -from ._quso_simulation import __all__ as __all_qusosim__ -from ._qubo_simulation import __all__ as __all_qubosim__ +from ._anneal_temperature_range import __all__ as __all_tr__ from ._anneal_results import __all__ as __all_results__ from ._anneal import __all__ as __all_anneal__ -__all__ = ( - __all_pusosim__ + __all_pubosim__ + __all_qusosim__ + __all_qubosim__ + - __all_results__ + __all_anneal__ -) +__all__ = __all_tr__ + __all_results__ + __all_anneal__ -del __all_pusosim__, __all_pubosim__, __all_qusosim__, __all_qubosim__ -del __all_results__, __all_anneal__ +del __all_tr__, __all_results__, __all_anneal__ name = "sim" diff --git a/qubovert/sim/_anneal.py b/qubovert/sim/_anneal.py index 3c18a71..be36a67 100644 --- a/qubovert/sim/_anneal.py +++ b/qubovert/sim/_anneal.py @@ -20,182 +20,60 @@ """ from qubovert.utils import ( - puso_value, pubo_to_puso, qubo_to_quso, QUBOVertWarning, boolean_to_spin + pubo_to_puso, qubo_to_quso, QUBOVertWarning, boolean_to_spin, + QUSOMatrix, PUSOMatrix ) -from . import PUSOSimulation, QUSOSimulation, AnnealResults -import random +from qubovert import QUSO, PUSO, PCSO +from . import anneal_temperature_range, AnnealResults, AnnealResult import numpy as np -from math import log +from itertools import chain +from ._canneal import c_anneal_quso, c_anneal_puso + __all__ = ( 'anneal_qubo', 'anneal_quso', 'anneal_pubo', 'anneal_puso', - 'anneal_temperature_range' + 'SCHEDULES' ) +SCHEDULES = 'linear', 'geometric' -# anneal temperature range function - -def anneal_temperature_range(model, start_flip_prob=0.5, - end_flip_prob=0.01, spin=False): - """anneal_temperature_range. - - Calculate the temperature to start and end an anneal of ``model``, such - that at the start of the anneal there is a ``start_flip_prob`` probability - that a bit is flipped despite it being energetically unfavorable, and at - the end of the anneal there is a ``end_flip_prob`` probability that a bit - is flipped despite it being energetically unfavorable. - - Parameters - ---------- - model : dict, or any type in ``qubovert.SPIN_MODELS`` or ``BOOLEAN_MODELS`` - Dictionary mapping tuples of binary labels to their values. See any of - the docstrings of a type in ``qubovert.SPIN_MODELS`` or - ``BOOLEAN_MODELS`` for more info. - start_flip_prob : float in [0, 1) (optional, defaults to 0.5). - The desired probability that a bit flips despite it being energetically - unfavorable at the start of the anneal. ``start_flip_prob`` must be - greater than ``end_flip_prob``. - end_flip_prob : float in [0, 1) (optional, defaults to 0.01). - The desired probability that a bit flips despite it being energetically - unfavorable at the end of the anneal. ``end_flip_prob`` must be - less than ``start_flip_prob``. - spin : bool (optional, default to False). - ``spin`` should be True if ``model`` is a spin model (ie - ``isinstance(model, qubovert.SPIN_MODELS)``) and should be False if - ``model`` is a boolean model (ie - ``isinstance(model, qubovert.BOOLEAN_MODELS)``). - - Returns - ------- - temp_range : tuple (hot, cold). - The ``hot`` temperature is the temperature to start the anneal at, and - the ``cold`` temperature is the temperature to end the anneal at. - Note that ``hot >= cold``. - - """ - # slight modification of _default_ising_beta_range in - # https://github.com/dwavesystems/dwave-neal/blob/master/neal/sampler.py - - # raise exception if invalid probabilities - if any(( - start_flip_prob < 0, start_flip_prob >= 1, - end_flip_prob < 0, end_flip_prob >= 1, - )): - raise ValueError("Flip probabilities must be in [0, 1)") - elif end_flip_prob > start_flip_prob: - raise ValueError("The starting flip probability must be greater than " - "the ending flip probability.") - - if not spin: - model = pubo_to_puso(model) - - # if D is a Matrix object or QUBO, PUBO, etc, then variables are defined - try: - # don't waste time copying (model.variables), since we never mutate it. - variables = model._variables - except AttributeError: - variables = set(v for k in model for v in k) - - # if the model is empty or just an offset - if not variables: - return 0, 0 - - factor = 2 # should be this - # factor = 1 # D-Wave neal does this. - - # calculate the approximate minimum possible change in energy by flipping - # a single bit. - min_del_energy = factor * min(abs(c) for k, c in model.items() if k) - # calculate the approximate maximum possible change in energy by flipping - # a single bit. - max_del_energy = factor * max( - sum(abs(c) for k, c in model.items() if v in k) - for v in variables - ) - # now ensure that the bolzmann weight satisfy the desired probabilities. - # ie exp(-del_energy / T) = prob - T0 = -max_del_energy / log(start_flip_prob) if start_flip_prob else 0 - Tf = -min_del_energy / log(end_flip_prob) if end_flip_prob else 0 - return float(T0), float(Tf) +# helpers +def _create_spin_schedule(spin_model, anneal_duration, + temperature_range, schedule): + """_create_spin_schedule. -# main spin annealing function - -def _anneal_spin(model, spin_simulation, num_anneals=1, - anneal_duration=1000, initial_state=None, - temperature_range=None, schedule='geometric', - in_order=True, seed=None): - """_anneal_spin. - - Run a simulated annealing algorithm to try to find the minimum of the spin - model given by ``model``. ``_anneal_spin`` uses a cooling schedule with the - ``spin_simulation`` object. Please see all of the parameters for details. - - Both ``qv.sim.anneal_puso`` and ``qv.sim.anneal_quso`` run through this - function. Since ``qv.sim.QUSOSimulation`` is faster than - ``qv.sim.PUSOSimulation``, we send in different simulation objects for - ``anneal_quso`` and ``anneal_puso``. + Internal function to create the temperature schedule from the input + parameters. Parameters ---------- - model : dict, or any type in ``qubovert.SPIN_MODELS``. - Maps spin labels to their values in the Hamiltonian. - Please see the docstrings of any of the objects in - ``qubovert.SPIN_MODELS`` to see how ``H`` should be formatted. - spin_simulation : qv.sim.PUSOSimulation or qv.sim.QUSOSimulation object. - Should be a ``qv.sim.QUSOSimulation`` object if this function is called - from ``qv.sim.anneal_quso``, or a ``qv.sim.PUSOSimulation`` object if - this function is called from ``qv.sim.anneal_puso``. - num_anneals : int >= 1 (optional, defaults to 1). - The number of times to run the simulated annealing algorithm. + spin_model : dict or any type in ``qubovert.SPIN_MODELS``. + Maps spin labels to their values in the objective function. anneal_duration : int >= 1 (optional, defaults to 1000). The total number of updates to the simulation during the anneal. This is related to the amount of time we spend in the cooling schedule. - If an explicit schedule is provided, then ``anneal_duration`` will be - ignored. - initial_state : dict (optional, defaults to None). - The initial state to start the anneal in. ``initial_state`` must map - the spin label names to their values in {1, -1}. If ``initial_state`` - is None, then a random state will be chosen to start each anneal. - Otherwise, ``initial_state`` will be the starting state for all of the - anneals. temperature_range : tuple (optional, defaults to None). The temperature to start and end the anneal at. ``temperature = (T0, Tf)``. ``T0`` must be >= ``Tf``. To see more details on picking a temperature range, please see the function ``qubovert.sim.anneal_temperature_range``. If ``temperature_range`` is None, then it will by default be set to - ``T0, Tf = qubovert.sim.anneal_temperature_range(H, spin=True)``. - Note that a temperature can only be zero if ``schedule`` is explicitly - given or if ``schedule`` is linear. - schedule : str or iterable of tuple (optional, defaults to ``'geometric'``) - What type of cooling schedule to use. If ``schedule == 'linear'``, then - the cooling schedule will be a linear interpolation between the values - in ``temperature_range``. If ``schedule == 'geometric'``, then the - cooling schedule will be a geometric interpolation between the values - in ``temperature_range``. Otherwise, you can supply an explicit - schedule. In this case, ``schedule`` should be an iterable of tuples, - where each tuple is a ``(T, n)`` pair, where ``T`` denotes the - temperature to update the simulation, and ``n`` denote the number of - times to update the simulation at that temperature. This schedule - will be sent directly into the - ``qubovert.sim.PUSOSimulation.schedule_update`` method. - in_order : bool (optional, defaults to True). - Whether to iterate through the variables in order or randomly - during an update step. When ``in_order`` is False, the simulation - is more physically realistic, but when using the simulation for - annealing, often it is better to have ``in_order = True``. - seed : number (optional, defaults to None). - The number to seed Python's builtin ``random`` module with. If - ``seed is None``, then ``random.seed`` will not be called. + ``T0, Tf = qubovert.sim.anneal_temperature_range(spin_model)``. + schedule : str, or list of floats (optional, defaults to ``'geometric'``). + What type of cooling schedule to use. If ``schedule == 'linear'``, + then the cooling schedule will be a linear interpolation between the + values in ``temperature_range``. If ``schedule == 'geometric'``, then + the cooling schedule will be a geometric interpolation between the + values in ``temperature_range``. Otherwise, ``schedule`` must be an + iterable of floats being the explicit temperature schedule for the + anneal to follow. Returns ------- - res : qubovert.sim.AnnealResults object. - ``res`` contains information on the final states of the simulations. - See Examples below for an example of how to read from ``res``. - See ``help(qubovert.sim.AnnealResults)`` for more info. + Ts : list of floats. + The explicit schedule of temperatures to update at each time step. Raises ------ @@ -208,59 +86,74 @@ def _anneal_spin(model, spin_simulation, num_anneals=1, Warns ----- qubovert.utils.QUBOVertWarning - If both the ``temperature_range`` and explicit ``schedule`` arguments - are provided. + If an explicit ``schedule`` is provided (ie ``schedule`` is an + iterable of floats) and a ``temperature_range`` is provided. The + ``temperature_range`` will be ignored. """ - if seed is not None: - random.seed(seed) - - if schedule in ('linear', 'geometric'): - T0, Tf = temperature_range or anneal_temperature_range(model, - spin=True) - if T0 < Tf: - raise ValueError("The final temperature must be less than the " - "initial temperature") - - # in the case that H is empty or just an offset and the user didn't - # supply a temperature range, then T0 and Tf will be 0. - if temperature_range is None and T0 == Tf == 0: - T0 = Tf = 1 - Ts = ( - np.linspace(T0, Tf, anneal_duration) if schedule == 'linear' else - np.geomspace(T0, Tf, anneal_duration) - ) - schedule = tuple((T, 1) for T in Ts) - elif isinstance(schedule, str): + if not isinstance(schedule, str): + if temperature_range is not None: + QUBOVertWarning.warn( + "Both a temperature range and an explicit schedule was " + "provided. The temperature range will be ignored and the " + "schedule used instead." + ) + return list(schedule) + elif schedule not in SCHEDULES: raise ValueError( - "Invalid schedule. Must be either 'linear', 'geometric', or an " - "explicit temperature schedule. See the docstring for more info." - ) - elif temperature_range: - QUBOVertWarning.warn( - "Both a temperature range and an explicit schedule was provided. " - "The temperature range will be ignored and the schedule used " - "instead." + "Invalid schedule. Must be one of %s. " + "See the docstring for more info." % str(SCHEDULES) ) - sim = spin_simulation(model, initial_state) + T0, Tf = ( + temperature_range or anneal_temperature_range(spin_model, spin=True) + ) + if T0 < Tf: + raise ValueError("The final temperature must be less than the " + "initial temperature") + + # in the case that model is empty or just an offset and the user didn't + # supply a temperature range, then T0 and Tf will be 0. + if temperature_range is None and T0 == Tf == 0: + T0 = Tf = 1 + return list( + np.linspace(T0, Tf, anneal_duration) if schedule == 'linear' else + np.geomspace(T0, Tf, anneal_duration) + ) - result = AnnealResults(True) - for _ in range(num_anneals): - if initial_state is None: - sim.set_state({v: random.choice((-1, 1)) for v in sim._variables}) - sim.schedule_update( - schedule, in_order=in_order, - seed=random.randint(0, 1 << 16) if seed is not None else None - ) - state = sim.state - result.add_state(state, puso_value(state, model)) - sim.reset() - return result +def _package_spin_results(states, values, offset, reverse_mapping): + """_package_spin_results. + Package the results of the C functions into the desired result form + of the Python functions. -# annealing functions + Parameters + ---------- + states : list of lists. + ``states`` has dimension ``state[num_anneals][len_state]``. + values : list of floats. + The value of the objective function that each state gives, + minus the offset. + offset : float. + The part of the objective function that does not depend on any + variables. + reverse_mapping : dict. + Maps the integer spin labels to the original model variables. + + Returns + ------- + res : qubovert.sim.AnnealResults object. + + """ + res = AnnealResults(True) + for i in range(len(states)): + state = {reverse_mapping[k]: v for k, v in enumerate(states[i])} + res.add_state(state, values[i] + offset) + return res + + +# spin annealing functions def anneal_puso(H, num_anneals=1, anneal_duration=1000, initial_state=None, temperature_range=None, schedule='geometric', @@ -268,14 +161,12 @@ def anneal_puso(H, num_anneals=1, anneal_duration=1000, initial_state=None, """anneal_puso. Run a simulated annealing algorithm to try to find the minimum of the PUSO - given by ``H``. ``anneal_puso`` uses a cooling schedule with the - ``qubovert.sim.PUSOSimulation`` object. Please see all of the parameters - for details. + given by ``H``. Please see all of the parameters for details. - **Please note** that the ``qv.sim.anneal_quso`` function performs much - faster than the ``qv.sim.anneal_puso`` function since the former is written - in C and wrapped in Python. If your system has degree 2 or less, then you - should use the ``qv.sim.anneal_quso`` function! + **Please note** that the ``qv.sim.anneal_quso`` function performs + faster than the ``qv.sim.anneal_puso`` function. If your system has + degree 2 or less, then you should use the ``qv.sim.anneal_quso`` + function. Parameters ---------- @@ -305,18 +196,14 @@ def anneal_puso(H, num_anneals=1, anneal_duration=1000, initial_state=None, ``T0, Tf = qubovert.sim.anneal_temperature_range(H, spin=True)``. Note that a temperature can only be zero if ``schedule`` is explicitly given or if ``schedule`` is linear. - schedule : str or iterable of tuple (optional, defaults to ``'geometric'``) - What type of cooling schedule to use. If ``schedule == 'linear'``, then - the cooling schedule will be a linear interpolation between the values - in ``temperature_range``. If ``schedule == 'geometric'``, then the - cooling schedule will be a geometric interpolation between the values - in ``temperature_range``. Otherwise, you can supply an explicit - schedule. In this case, ``schedule`` should be an iterable of tuples, - where each tuple is a ``(T, n)`` pair, where ``T`` denotes the - temperature to update the simulation, and ``n`` denote the number of - times to update the simulation at that temperature. This schedule - will be sent directly into the - ``qubovert.sim.PUSOSimulation.schedule_update`` method. + schedule : str, or list of floats (optional, defaults to ``'geometric'``). + What type of cooling schedule to use. If ``schedule == 'linear'``, + then the cooling schedule will be a linear interpolation between the + values in ``temperature_range``. If ``schedule == 'geometric'``, then + the cooling schedule will be a geometric interpolation between the + values in ``temperature_range``. Otherwise, ``schedule`` must be an + iterable of floats being the explicit temperature schedule for the + anneal to follow. in_order : bool (optional, defaults to True). Whether to iterate through the variables in order or randomly during an update step. When ``in_order`` is False, the simulation @@ -372,33 +259,74 @@ def anneal_puso(H, num_anneals=1, anneal_duration=1000, initial_state=None, -4, {0: 1, 1: -1, 2: 1, 3: -1, 4: 1} """ - return _anneal_spin( - H, PUSOSimulation, num_anneals, anneal_duration, initial_state, - temperature_range, schedule, in_order, seed + if num_anneals <= 0: + return AnnealResults(True) + + Ts = _create_spin_schedule( + H, anneal_duration, temperature_range, schedule ) + # must use type since we don't want errors from inheritance + if type(H) in (QUSOMatrix, PUSOMatrix): + N = H.max_index + 1 + model = H + reverse_mapping = dict(enumerate(range(N))) + elif type(H) not in (QUSO, PUSO, PCSO): + H = PUSO(H) + + if type(H) in (QUSO, PUSO, PCSO): + N = H.num_binary_variables + model = H.to_puso() + reverse_mapping = H.reverse_mapping + + # solve `model`, convert solutions back to `H` + + if not N: + return AnnealResults.from_list( + [AnnealResult({}, model.offset, True)] * num_anneals, + True + ) -def anneal_pubo(P, num_anneals=1, anneal_duration=1000, initial_state=None, + if initial_state is not None: + init_state = [1] * N + for k, v in reverse_mapping.items(): + init_state[k] = initial_state[v] + else: + init_state = [] + + # create arguments for the C function + # create terms and couplings + terms, couplings, num_couplings = [], [], [] + for term, coupling in model.items(): + if term: + couplings.append(float(coupling)) + terms.extend(term) + num_couplings.append(len(term)) + + states, values = c_anneal_puso( + N, num_couplings, terms, couplings, # describe the problem + Ts, num_anneals, int(in_order), init_state, # describe the algorithm + seed if seed is not None else -1 + ) + return _package_spin_results( + states, values, model.offset, reverse_mapping + ) + + +def anneal_quso(L, num_anneals=1, anneal_duration=1000, initial_state=None, temperature_range=None, schedule='geometric', in_order=True, seed=None): - """anneal_pubo. - - Run a simulated annealing algorithm to try to find the minimum of the PUBO - given by ``P``. ``anneal_pubo`` converts ``P`` to a PUSO and then uses a - cooling schedule with the ``qubovert.sim.PUSOSimulation`` object. Please - see all of the parameters for details. + """anneal_quso. - **Please note** that the ``qv.sim.anneal_qubo`` function performs much - faster than the ``qv.sim.anneal_pubo`` function since the former is written - in C and wrapped in Python. If your system has degree 2 or less, then you - should use the ``qv.sim.anneal_qubo`` function! + Run a simulated annealing algorithm to try to find the minimum of the QUSO + given by ``L``. Please see all of the parameters for details. Parameters ---------- - P : dict, or any type in ``qubovert.BOOLEAN_MODELS``. - Maps boolean labels to their values in the objective function. - Please see the docstrings of any of the objects in - ``qubovert.BOOLEAN_MODELS`` to see how ``P`` should be formatted. + L : dict, ``qubovert.utils.QUSOMatrix`` or ``qubovert.QUSO``. + Maps spin labels to their values in the objective function. + Please see the docstring of ``qubovert.QUSO`` for more info on how to + format ``L``. num_anneals : int >= 1 (optional, defaults to 1). The number of times to run the simulated annealing algorithm. anneal_duration : int >= 1 (optional, defaults to 1000). @@ -408,7 +336,7 @@ def anneal_pubo(P, num_anneals=1, anneal_duration=1000, initial_state=None, ignored. initial_state : dict (optional, defaults to None). The initial state to start the anneal in. ``initial_state`` must map - the boolean label names to their values in {0, 1}. If ``initial_state`` + the spin label names to their values in {1, -1}. If ``initial_state`` is None, then a random state will be chosen to start each anneal. Otherwise, ``initial_state`` will be the starting state for all of the anneals. @@ -418,19 +346,15 @@ def anneal_pubo(P, num_anneals=1, anneal_duration=1000, initial_state=None, details on picking a temperature range, please see the function ``qubovert.sim.anneal_temperature_range``. If ``temperature_range`` is None, then it will by default be set to - ``T0, Tf = qubovert.sim.anneal_temperature_range(P, spin=False)``. - schedule : str or iterable of tuple (optional, defaults to ``'geometric'``) - What type of cooling schedule to use. If ``schedule == 'linear'``, then - the cooling schedule will be a linear interpolation between the values - in ``temperature_range``. If ``schedule == 'geometric'``, then the - cooling schedule will be a geometric interpolation between the values - in ``temperature_range``. Otherwise, you can supply an explicit - schedule. In this case, ``schedule`` should be an iterable of tuples, - where each tuple is a ``(T, n)`` pair, where ``T`` denotes the - temperature to update the simulation, and ``n`` denote the number of - times to update the simulation at that temperature. This schedule - will be sent directly into the - ``qubovert.sim.PUBOSimulation.schedule_update`` method. + ``T0, Tf = qubovert.sim.anneal_temperature_range(L, spin=True)``. + schedule : str, or list of floats (optional, defaults to ``'geometric'``). + What type of cooling schedule to use. If ``schedule == 'linear'``, + then the cooling schedule will be a linear interpolation between the + values in ``temperature_range``. If ``schedule == 'geometric'``, then + the cooling schedule will be a geometric interpolation between the + values in ``temperature_range``. Otherwise, ``schedule`` must be an + iterable of floats being the explicit temperature schedule for the + anneal to follow. in_order : bool (optional, defaults to True). Whether to iterate through the variables in order or randomly during an update step. When ``in_order`` is False, the simulation @@ -454,6 +378,8 @@ def anneal_pubo(P, num_anneals=1, anneal_duration=1000, initial_state=None, Parameters section. ValueError If the initial temperature is less than the final temperature. + ValueError + If ``L`` is not degree 2 or less. Warns ----- @@ -464,52 +390,116 @@ def anneal_pubo(P, num_anneals=1, anneal_duration=1000, initial_state=None, Example ------- Consider the example of finding the ground state of the 1D - antiferromagnetic Ising chain of length 5 in boolean form. + antiferromagnetic Ising chain of length 5. >>> import qubovert as qv >>> >>> H = sum(qv.spin_var(i) * qv.spin_var(i+1) for i in range(4)) - >>> P = H.to_pubo() - >>> anneal_res = qv.sim.anneal_pubo(P, num_anneals=3) + >>> anneal_res = qv.sim.anneal_quso(H, num_anneals=3) >>> >>> print(anneal_res.best.value) -4 >>> print(anneal_res.best.state) - {0: 0, 1: 1, 2: 0, 3: 1, 4: 0} + {0: 1, 1: -1, 2: 1, 3: -1, 4: 1} >>> # now sort the results >>> anneal_res.sort() >>> >>> # now iterate through all of the results in the sorted order >>> for res in anneal_res: >>> print(res.value, res.state) - -4, {0: 0, 1: 1, 2: 0, 3: 1, 4: 0} - -4, {0: 1, 1: 0, 2: 1, 3: 0, 4: 1} - -4, {0: 0, 1: 1, 2: 0, 3: 1, 4: 0} + -4, {0: 1, 1: -1, 2: 1, 3: -1, 4: 1} + -4, {0: -1, 1: 1, 2: -1, 3: 1, 4: -1} + -4, {0: 1, 1: -1, 2: 1, 3: -1, 4: 1} """ - return anneal_puso( - pubo_to_puso(P), num_anneals, anneal_duration, - None if initial_state is None else boolean_to_spin(initial_state), - temperature_range, schedule, in_order, seed - ).to_boolean() + if num_anneals <= 0: + return AnnealResults(True) + Ts = _create_spin_schedule( + L, anneal_duration, temperature_range, schedule + ) -def anneal_quso(L, num_anneals=1, anneal_duration=1000, initial_state=None, + # must use type since we don't want errors from inheritance + if type(L) == QUSOMatrix: + N = L.max_index + 1 + model = L + reverse_mapping = dict(enumerate(range(N))) + # mapping = reverse_mapping + elif type(L) != QUSO: + L = QUSO(L) + + if type(L) == QUSO: + N = L.num_binary_variables + model = L.to_quso() + # mapping = L.mapping + reverse_mapping = L.reverse_mapping + + # solve `model`, convert solutions back to `L` + + if not N: + return AnnealResults.from_list( + [AnnealResult({}, model.offset, True)] * num_anneals, + True + ) + + if initial_state is not None: + init_state = [1] * N + for k, v in reverse_mapping.items(): + init_state[k] = initial_state[v] + else: + init_state = [] + + # create arguments for the C function + h, num_neighbors = [0.] * N, [0] * N + neighbors, J = [[] for _ in range(N)], [[] for _ in range(N)] + + for k, v in model.items(): + val = float(v) + if len(k) == 1: + h[k[0]] = val + elif len(k) == 2: + i, j = k + neighbors[i].append(j) + neighbors[j].append(i) + num_neighbors[i] += 1 + num_neighbors[j] += 1 + J[i].append(val) + J[j].append(val) + + # flatten the arrays. + J, neighbors = list(chain(*J)), list(chain(*neighbors)) + + states, values = c_anneal_quso( + h, num_neighbors, neighbors, J, # describe the problem + Ts, num_anneals, int(in_order), init_state, # describe the algorithm + seed if seed is not None else -1 + ) + return _package_spin_results( + states, values, model.offset, reverse_mapping + ) + + +# boolean annealing functions + +def anneal_pubo(P, num_anneals=1, anneal_duration=1000, initial_state=None, temperature_range=None, schedule='geometric', in_order=True, seed=None): - """anneal_quso. + """anneal_pubo. - Run a simulated annealing algorithm to try to find the minimum of the QUSO - given by ``L``. ``anneal_quso`` uses a cooling schedule with the - ``qubovert.sim.QUSOSimulation`` object. Please see all of the parameters - for details. + Run a simulated annealing algorithm to try to find the minimum of the PUBO + given by ``P``. ``anneal_pubo`` converts ``P`` to a PUSO and then uses + ``qubovert.sim.anneal_quso``. Please see all the parameters for details. + + **Please note** that the ``qv.sim.anneal_qubo`` function performs + faster than the ``qv.sim.anneal_pubo`` function. If your system has + degree 2 or less, then you should use the ``qv.sim.anneal_qubo`` function. Parameters ---------- - L : dict, ``qubovert.utils.QUSOMatrix`` or ``qubovert.QUSO``. - Maps spin labels to their values in the objective function. - Please see the docstring of ``qubovert.QUSO`` for more info on how to - format ``L``. + P : dict, or any type in ``qubovert.BOOLEAN_MODELS``. + Maps boolean labels to their values in the objective function. + Please see the docstrings of any of the objects in + ``qubovert.BOOLEAN_MODELS`` to see how ``P`` should be formatted. num_anneals : int >= 1 (optional, defaults to 1). The number of times to run the simulated annealing algorithm. anneal_duration : int >= 1 (optional, defaults to 1000). @@ -519,7 +509,7 @@ def anneal_quso(L, num_anneals=1, anneal_duration=1000, initial_state=None, ignored. initial_state : dict (optional, defaults to None). The initial state to start the anneal in. ``initial_state`` must map - the spin label names to their values in {1, -1}. If ``initial_state`` + the spin label names to their values in {0, 1}. If ``initial_state`` is None, then a random state will be chosen to start each anneal. Otherwise, ``initial_state`` will be the starting state for all of the anneals. @@ -529,7 +519,7 @@ def anneal_quso(L, num_anneals=1, anneal_duration=1000, initial_state=None, details on picking a temperature range, please see the function ``qubovert.sim.anneal_temperature_range``. If ``temperature_range`` is None, then it will by default be set to - ``T0, Tf = qubovert.sim.anneal_temperature_range(L, spin=True)``. + ``T0, Tf = qubovert.sim.anneal_temperature_range(P, spin=False)``. schedule : str or iterable of tuple (optional, defaults to ``'geometric'``) What type of cooling schedule to use. If ``schedule == 'linear'``, then the cooling schedule will be a linear interpolation between the values @@ -541,7 +531,7 @@ def anneal_quso(L, num_anneals=1, anneal_duration=1000, initial_state=None, temperature to update the simulation, and ``n`` denote the number of times to update the simulation at that temperature. This schedule will be sent directly into the - ``qubovert.sim.PUSOSimulation.schedule_update`` method. + ``qubovert.sim.PUBOSimulation.schedule_update`` method. in_order : bool (optional, defaults to True). Whether to iterate through the variables in order or randomly during an update step. When ``in_order`` is False, the simulation @@ -575,32 +565,34 @@ def anneal_quso(L, num_anneals=1, anneal_duration=1000, initial_state=None, Example ------- Consider the example of finding the ground state of the 1D - antiferromagnetic Ising chain of length 5. + antiferromagnetic Ising chain of length 5 in boolean form. >>> import qubovert as qv >>> >>> H = sum(qv.spin_var(i) * qv.spin_var(i+1) for i in range(4)) - >>> anneal_res = qv.sim.anneal_quso(H, num_anneals=3) + >>> P = H.to_pubo() + >>> anneal_res = qv.sim.anneal_pubo(P, num_anneals=3) >>> >>> print(anneal_res.best.value) -4 >>> print(anneal_res.best.state) - {0: 1, 1: -1, 2: 1, 3: -1, 4: 1} + {0: 0, 1: 1, 2: 0, 3: 1, 4: 0} >>> # now sort the results >>> anneal_res.sort() >>> >>> # now iterate through all of the results in the sorted order >>> for res in anneal_res: >>> print(res.value, res.state) - -4, {0: 1, 1: -1, 2: 1, 3: -1, 4: 1} - -4, {0: -1, 1: 1, 2: -1, 3: 1, 4: -1} - -4, {0: 1, 1: -1, 2: 1, 3: -1, 4: 1} + -4, {0: 0, 1: 1, 2: 0, 3: 1, 4: 0} + -4, {0: 1, 1: 0, 2: 1, 3: 0, 4: 1} + -4, {0: 0, 1: 1, 2: 0, 3: 1, 4: 0} """ - return _anneal_spin( - L, QUSOSimulation, num_anneals, anneal_duration, initial_state, + return anneal_puso( + pubo_to_puso(P), num_anneals, anneal_duration, + boolean_to_spin(initial_state) if initial_state is not None else None, temperature_range, schedule, in_order, seed - ) + ).to_boolean() def anneal_qubo(Q, num_anneals=1, anneal_duration=1000, initial_state=None, @@ -609,9 +601,9 @@ def anneal_qubo(Q, num_anneals=1, anneal_duration=1000, initial_state=None, """anneal_qubo. Run a simulated annealing algorithm to try to find the minimum of the QUBO - given by ``Q``. ``anneal_qubo`` converts ``Q`` to a QUSO and then uses a - cooling schedule with the ``qubovert.sim.QUSOSimulation`` object. Please - see all of the parameters for details. + given by ``Q``. ``anneal_qubo`` converts ``Q`` to a QUSO and then uses + ``qubovert.sim.anneal_quso``. + Please see all of the parameters for details. Parameters ---------- @@ -628,7 +620,7 @@ def anneal_qubo(Q, num_anneals=1, anneal_duration=1000, initial_state=None, ignored. initial_state : dict (optional, defaults to None). The initial state to start the anneal in. ``initial_state`` must map - the boolean label names to their values in {0, 1}. If ``initial_state`` + the spin label names to their values in {0, 1}. If ``initial_state`` is None, then a random state will be chosen to start each anneal. Otherwise, ``initial_state`` will be the starting state for all of the anneals. @@ -709,6 +701,6 @@ def anneal_qubo(Q, num_anneals=1, anneal_duration=1000, initial_state=None, """ return anneal_quso( qubo_to_quso(Q), num_anneals, anneal_duration, - None if initial_state is None else boolean_to_spin(initial_state), + boolean_to_spin(initial_state) if initial_state is not None else None, temperature_range, schedule, in_order, seed ).to_boolean() diff --git a/qubovert/sim/_anneal_temperature_range.py b/qubovert/sim/_anneal_temperature_range.py new file mode 100644 index 0000000..624366e --- /dev/null +++ b/qubovert/sim/_anneal_temperature_range.py @@ -0,0 +1,112 @@ +# 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. + +"""_anneal_temperature_range.py. + +This file contains the function to determine the temperature range for +annealing a spin model. + +""" + +from qubovert.utils import pubo_to_puso +from math import log + +__all__ = "anneal_temperature_range", + + +# anneal temperature range function + +def anneal_temperature_range(model, start_flip_prob=0.5, + end_flip_prob=0.01, spin=False): + """anneal_temperature_range. + + Calculate the temperature to start and end an anneal of ``model``, such + that at the start of the anneal there is a ``start_flip_prob`` probability + that a bit is flipped despite it being energetically unfavorable, and at + the end of the anneal there is a ``end_flip_prob`` probability that a bit + is flipped despite it being energetically unfavorable. + + Parameters + ---------- + model : dict, or any type in ``qubovert.SPIN_MODELS`` or ``BOOLEAN_MODELS`` + Dictionary mapping tuples of binary labels to their values. See any of + the docstrings of a type in ``qubovert.SPIN_MODELS`` or + ``BOOLEAN_MODELS`` for more info. + start_flip_prob : float in [0, 1) (optional, defaults to 0.5). + The desired probability that a bit flips despite it being energetically + unfavorable at the start of the anneal. ``start_flip_prob`` must be + greater than ``end_flip_prob``. + end_flip_prob : float in [0, 1) (optional, defaults to 0.01). + The desired probability that a bit flips despite it being energetically + unfavorable at the end of the anneal. ``end_flip_prob`` must be + less than ``start_flip_prob``. + spin : bool (optional, default to False). + ``spin`` should be True if ``model`` is a spin model (ie + ``isinstance(model, qubovert.SPIN_MODELS)``) and should be False if + ``model`` is a boolean model (ie + ``isinstance(model, qubovert.BOOLEAN_MODELS)``). + + Returns + ------- + temp_range : tuple (hot, cold). + The ``hot`` temperature is the temperature to start the anneal at, and + the ``cold`` temperature is the temperature to end the anneal at. + Note that ``hot >= cold``. + + """ + # slight modification of _default_ising_beta_range in + # https://github.com/dwavesystems/dwave-neal/blob/master/neal/sampler.py + + # raise exception if invalid probabilities + if any(( + start_flip_prob < 0, start_flip_prob >= 1, + end_flip_prob < 0, end_flip_prob >= 1, + )): + raise ValueError("Flip probabilities must be in [0, 1)") + elif end_flip_prob > start_flip_prob: + raise ValueError("The starting flip probability must be greater than " + "the ending flip probability.") + + if not spin: + model = pubo_to_puso(model) + + # if D is a Matrix object or QUBO, PUBO, etc, then variables are defined + try: + # don't waste time copying (model.variables), since we never mutate it. + variables = model._variables + except AttributeError: + variables = set(v for k in model for v in k) + + # if the model is empty or just an offset + if not variables: + return 0, 0 + + factor = 2 # should be this (I think) + # factor = 1 # D-Wave neal does this. + + # calculate the approximate minimum possible change in energy by flipping + # a single bit. + min_del_energy = factor * min(abs(c) for k, c in model.items() if k) + # calculate the approximate maximum possible change in energy by flipping + # a single bit. + max_del_energy = factor * max( + sum(abs(c) for k, c in model.items() if v in k) + for v in variables + ) + + # now ensure that the bolzmann weight satisfy the desired probabilities. + # ie exp(-del_energy / T) = prob + T0 = -max_del_energy / log(start_flip_prob) if start_flip_prob else 0. + Tf = -min_del_energy / log(end_flip_prob) if end_flip_prob else 0. + return T0, Tf diff --git a/qubovert/sim/_canneal.c b/qubovert/sim/_canneal.c new file mode 100644 index 0000000..4d9cc33 --- /dev/null +++ b/qubovert/sim/_canneal.c @@ -0,0 +1,354 @@ +#include "Python.h" +#include "anneal_quso.h" +#include "anneal_puso.h" + + +/* +Here we wrap the source code in the src folder so that it can be called +directly from Python. This file creates the ``qubovert.sim._canneal`` module. +*/ + +// Module info +static char _canneal_name[] = "_canneal"; + +static char _canneal_docstring[] = + "``qubovert.sim._canneal`` is a module for annealing\n" + "with the C source code."; + + +// helper code for the module functions below. +PyObject *build_py_states_values( + int num_anneals, int len_state, int *states, double *values +) { + /* + Build a Python tuple of ``py_states, py_values``, where ``py_states`` is + a list of lists, where each list represents a state, and ``py_values`` is + a list of floats, where each float is the energy of that state. We build + these from the states in values in ``states``, and ``values``. ``states`` + is an array of length ``len_state * num_anneals``. So + ``states[i * len_state + j]`` is the sign that the jth spin took on the + ith anneal. ``values`` is an array of length ``num_anneals``. + + Parameters + ---------- + num_anneals : int. + len_state : int. + states : points to an int array of size ``num_anneals * len_state``. + values : points to a double array of size ``num_anneals``. + + Returns + ------- + res : a Python tuple. + The first element of the tuple is a list of lists of ints, and the + second element is a list of floats. + + */ + PyObject *py_states = PyList_New(num_anneals); + PyObject *py_values = PyList_New(num_anneals); + PyObject *py_state; int i, j; + for(i=0; i>> import qubovert as qv - >>> - >>> # create the objective function. - >>> x = [qv.boolean_var(i) for i in range(10)] - >>> model = sum(x) ** 2 - >>> model.add_constraint_le_zero(x[0] + x[2] - 3 * x[5] - 1, lam=3) - >>> - >>> # initial state is all variables equal to 1 - >>> initial_state = {i: 11 for i in range(length)} - >>> sim = qv.sim.PUBOSimulation(model, initial_state) - >>> - >>> # define a schedule. here we simulate at temperature 4 for 25 time - >>> # steps, then temperature 2 for 25 time steps, then temperature 1 for - >>> # 10 time steps. - >>> schedule = (4, 25), (2, 25), (1, 10) - >>> sim.schedule_update(schedule) - >>> - >>> print("final state", sim.state) - - See Also - -------- - ``qv.sim.PUSOSimulation``, ``qv.sim.QUSOSimulation``, - ``qv.sim.QUBOSimulation``. - - """ - - def __init__(self, P, initial_state=None, memory=0): - """__init__. - - Parameters - ---------- - P : dict or type in ``qubovert.BOOLEAN_MODELS``. - The PUBO to simulate. This should map tuples of boolean variable - labels to their respective coefficient in the objective function. - For more information, see the docstrings for any of the models in - ``qubovert.BOOLEAN_MODELS``. - initial_state : dict (optional, defaults to None). - The initial state to start the simulation in. ``initial_state`` - should map boolean label names to their initial values, where each - value is either 0 or 1. If ``initial_state`` is None, then it - will be initialized to all 0s. - memory : int >= 0 (optional, defaults to 0). - During the simulation, we keep a list of the most recent ``memory`` - states that the simulation was in. These can be accessed with - ``self.get_past_states(number_of_states)``. - - """ - model = pubo_to_puso(P) - if initial_state is None: - initial_state = {v: 0 for v in model._variables} - super().__init__(model, initial_state, memory) - - @property - def state(self): - """state. - - A copy of the current state of the system. - - Returns - ------- - state : dict. - Dictionary that maps boolean labels to their values in {0, 1}. - - """ - return spin_to_boolean(self._state) - - def set_state(self, state): - """set_state. - - Set the state of the spin system to ``state``. - - Parameters - ---------- - state : dict or iterable. - ``state`` maps the spin variable labels to their corresponding - values. In other words ``state[v]`` is the value of variable ``v``. - A value must be either 0 or 1. - - """ - # if we call super then we get the wrong errors - state = {v: state[v] for v in self._variables} - if any(v not in {0, 1} for v in state.values()): - raise ValueError("State must contain only 0's and 1's") - self._state = boolean_to_spin(state) diff --git a/qubovert/sim/_puso_simulation.py b/qubovert/sim/_puso_simulation.py deleted file mode 100644 index e13753d..0000000 --- a/qubovert/sim/_puso_simulation.py +++ /dev/null @@ -1,320 +0,0 @@ -# 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. - -"""_puso_simulation.py. - -This file contains the ``PUSOSimulation`` object, which deals with using a -Metropolis algorithm to simulate PUSOs. - -""" - -from qubovert.utils import puso_value -import random -from math import exp - - -__all__ = 'PUSOSimulation', - - -class PUSOSimulation: - """PUSOSimulation. - - ``PUSOSimulation`` uses a Metropolis algorithm to simulate a PUSO. - The spin system can be "updated" at a temperature ``T``. Thus, we can get - an idea of the time evolution of a spin system by creating a temperature - schedule and seeing how the system evolves. Please note that the - ``qv.sim.QUSOSimulation`` performs the simulation much faster than the - ``qv.sim.PUSOSimulation`` object, but of course only works with degree 1 or - 2 models. If your PUSO is degree two (thus a QUSO), then you should use - the ``QUSOSimulation`` object. - - Examples - -------- - Consider the example of the ferromagnetic chain. - - >>> import qubovert as qv - >>> - >>> length = 50 - >>> spin_system = sum( - >>> -qv.spin_var(i) * qv.spin_var(i+1) * qv.spin_var(i+2) - >>> for i in range(length-2) - >>> ) - >>> - >>> # initial state is all spin down - >>> initial_state = {i: -1 for i in range(length)} - >>> sim = qv.sim.PUSOSimulation(spin_system, initial_state) - >>> - >>> # define a schedule. here we simulate at temperature 4 for 25 time - >>> # steps, then temperature 2 for 25 time steps, then temperature 1 for - >>> # 10 time steps. - >>> schedule = (4, 25), (2, 25), (1, 10) - >>> sim.schedule_update(schedule) - >>> - >>> print("final state", sim.state) - >>> print("last 30 states", sim.get_past_states(30)) - - See Also - -------- - ``qv.sim.PUBOSimulation``, ``qv.sim.QUSOSimulation``, - ``qv.sim.QUBOSimulation``. - - """ - - def __init__(self, H, initial_state=None, memory=0): - """__init__. - - Parameters - ---------- - H : dict or type in ``qubovert.SPIN_MODELS``. - The PUSO to simulate. This should map tuples of spin variable - labels to their respective coefficient in the Hamiltonian. For more - information, see the docstrings for any of the models in - ``qubovert.SPIN_MODELS``. - initial_state : dict (optional, defaults to None). - The initial state to start the simulation in. ``initial_state`` - should map spin label names to their initial values, where each - value is either 1 or -1. If ``initial_state`` is None, then it - will be initialized to all 1s. - memory : int >= 0 (optional, defaults to 0). - During the simulation, we keep a list of the most recent ``memory`` - states that the simulation was in. These can be accessed with - ``self.get_past_states(number_of_states)``. - - """ - # if model is a Matrix object or PUSO, etc, - # then variables will be defined. - # variables must be a list so it can be used with random.choices in - # the update method. - try: - self._variables = list(H._variables) - except AttributeError: - if isinstance(initial_state, dict): - self._variables = list(initial_state.keys()) - else: - self._variables = list({v for k in H for v in k}) - - self._initial_state = ( - initial_state.copy() if initial_state is not None else - {v: 1 for v in self._variables} - ) - self.set_state(self._initial_state) - - # keep track of the most recent states - self._past_states = [] - # how many previous states to remember - self._memory = memory - - # create a dictionary mapping each bit to the graph that it affects. - self._subgraphs = {v: {} for v in self._variables} - for k, c in H.items(): - for v in k: - self._subgraphs[v][k] = c - - def __str__(self): - """__str__. - - Return - ------ - s : str. - - """ - return self.__class__.__name__ + "(memory=%d)" % self._memory - - @property - def memory(self): - """memory. - - Returns - ------- - memory : int >= 0 (optional, defaults to 0). - During the simulation, we keep a list of the most recent ``memory`` - states that the simulation was in. These can be accessed with - ``self.get_past_states(number_of_states)``. - - """ - return self._memory - - @property - def state(self): - """state. - - A copy of the current state of the system. - - Returns - ------- - state : dict. - Dictionary that maps spin labels to their values in {1, -1}. - - """ - return self._state.copy() - - @property - def initial_state(self): - """initial_state. - - A copy of the initial state of the system. - - Returns - ------- - initial_state : dict. - Dictionary that maps binary labels to their values. - - """ - return self._initial_state.copy() - - def set_state(self, state): - """set_state. - - Set the state of the spin system to ``state``. - - Parameters - ---------- - state : dict or iterable. - ``state`` maps the spin variable labels to their corresponding - values. In other words ``state[v]`` is the value of variable ``v``. - A value must be either 1 or -1. - - """ - self._state = {v: state[v] for v in self._variables} - if any(v not in {1, -1} for v in self._state.values()): - raise ValueError("State must contain only 1's and -1's") - - def reset(self): - """reset. - - Reset the simulation back to its original state. - - """ - self._past_states = [] - self.set_state(self._initial_state) - - def get_past_states(self, num_states=None): - """get_past_states. - - Return the previous ``num_states`` states of the system (if that many - exist; ``self`` only stores up the previous ``self.memory`` states). - - Parameters - ---------- - num_states : int (optional, defaults to None). - The number of previous update steps to include. If ``num_states`` - is None, then all the previous states in memory will be returned. - - Returns - ------- - states : list of dicts. - Each dict maps binary labels to their values. - - """ - if num_states == 1: - return [self.state] - elif num_states is None: - num_states = self._memory - return [ - s.copy() for s in self._past_states[-num_states+1:] - ] + [self.state] - - def _add_past_state(self): - """_add_past_state. - - Add the current state to the ``past_states`` memory. If there is no - more memory left (see ``self.memory``) then remove the oldest state. - - Parameters - ---------- - state : dict. - Maps binary labels to their values. - - """ - if self._memory: - self._past_states.append(self.state) - if len(self._past_states) > self._memory: - self._past_states.pop(0) - - def update(self, T, num_updates=1, in_order=False, seed=None): - """update. - - Update the simulation at temperature ``T``. Updates the internal state. - - Parameters - ---------- - T : number >= 0. - Temperature. - num_updates : int >= 1 (optional, defaults to 1). - The number of times to update the simulation at the temperature. - in_order : bool (optional, defaults to False). - Whether to iterate through the variables in order or randomly - during an update step. When ``in_order`` is False, the simulation - is more physically realistic, but when using the Simulation for - annealing, often it is better to have ``in_order = True``. - seed : number (optional, defaults to None). - The number to seed ``random`` with. If ``seed is None``, then - ``random.seed`` will not be called. - - """ - # self.schedule_update is much faster when it is self-contained, e.g. - # never calls self.update. That's why we format it this way. - self.schedule_update([(T, num_updates)], in_order, seed) - - def schedule_update(self, schedule, in_order=False, seed=None): - """schedule_update. - - Update the simulation with a schedule. - - Parameters - ---------- - schedule : iterable of tuples. - Each element in ``schedule`` is a pair ``(T, n)`` which designates - a temperature and a number of updates. See `Notes` below. - in_order : bool (optional, defaults to False). - Whether to iterate through the variables in order or randomly - during an update step. When ``in_order`` is False, the simulation - is more physically realistic, but when using the Simulation for - annealing, often it is better to have ``in_order = True``. - seed : number (optional, defaults to None). - The number to seed ``random`` with. If ``seed is None``, then - ``random.seed`` will not be called. - - Notes - ----- - The following two code blocks perform exactly the same thing. - - >>> sim = PUSOSimulation(10) - >>> for T in (3, 2): - >>> sim.update(T, 100) - >>> sim.update(1, 50) - - >>> sim = PUSOSimulation(10) - >>> schedule = (3, 100), (2, 100), (1, 50) - >>> sim.schedule_update(schedule) - - """ - if seed is not None: - random.seed(seed) - - for T, n in schedule: - for _ in range(n): - self._add_past_state() - - vars_to_update = ( - self._variables if in_order else - random.choices(self._variables, k=len(self._variables)) - ) - - for i in vars_to_update: - # the change in energy from flipping variable i is equal - # to -2 * (the energy of the subgraph depending on i) - dE = -2 * puso_value(self._state, self._subgraphs[i]) - if dE <= 0 or (T and random.random() < exp(-dE / T)): - self._state[i] *= -1 diff --git a/qubovert/sim/_qubo_simulation.py b/qubovert/sim/_qubo_simulation.py deleted file mode 100644 index 7d7444e..0000000 --- a/qubovert/sim/_qubo_simulation.py +++ /dev/null @@ -1,132 +0,0 @@ -# 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. - -"""_qubo_simulation.py. - -This file contains the ``QUBOSimulation`` object, which deals with using a -Metropolis algorithm to simulate QUBOs. - -""" - -from qubovert.utils import ( - qubo_to_quso, spin_to_boolean, boolean_to_spin, QUSOMatrix -) -from . import QUSOSimulation - - -__all__ = 'QUBOSimulation', - - -class QUBOSimulation(QUSOSimulation): - """QUBOSimulation. - - ``QUBOSimulation`` uses a Metropolis algorithm to simulate a QUBO. - The QUBO can be "updated" at a temperature ``T``. Thus, - we can get an idea of the time evolution of a QUBO by creating a - temperature schedule and seeing how the system evolves. - - ``QUBOSimulation`` inherits from ``QUSOSimulation``. In fact, - ``QUBOSimulation`` just deals internally with converting to and from - a spin system; all the simulation is done with ``QUSOSimulation``. See - ``help(qubovert.sim.QUSOSimulation)`` for more details. - - Examples - -------- - Consider the following example where we minimize an objective function. - - >>> import qubovert as qv - >>> - >>> # create the objective function. - >>> x = [qv.boolean_var(i) for i in range(10)] - >>> model = sum(x) - >>> model.add_constraint_le_zero(x[0] + x[2] - 3 * x[5] - 1, lam=3) - >>> - >>> # initial state is all variables equal to 1 - >>> initial_state = {i: 11 for i in range(length)} - >>> sim = qv.sim.QUBOSimulation(model, initial_state) - >>> - >>> # define a schedule. here we simulate at temperature 4 for 25 time - >>> # steps, then temperature 2 for 25 time steps, then temperature 1 for - >>> # 10 time steps. - >>> schedule = (4, 25), (2, 25), (1, 10) - >>> sim.schedule_update(schedule) - >>> - >>> print("final state", sim.state) - - See Also - -------- - ``qv.sim.PUSOSimulation``, ``qv.sim.QUSOSimulation``, - ``qv.sim.PUBOSimulation``. - - """ - - def __init__(self, Q, initial_state=None): - """__init__. - - Parameters - ---------- - Q : dict, ``qubovert.utils.QUBOMatrix``, or ``qubovert.QUBO`` object. - The QUBO to simulate. This should map tuples of boolean variable - labels to their respective coefficient in the objective function. - For more information, see the docstrings for - ``qubovert.utils.QUBOMatrix`` and ``qubovert.QUBO``. - initial_state : dict (optional, defaults to None). - The initial state to start the simulation in. ``initial_state`` - should map boolean label names to their initial values, where each - value is either 0 or 1. If ``initial_state`` is None, then it - will be initialized to all 0s. - - """ - model = qubo_to_quso(Q) - if initial_state is None: - var = ( - range(model.max_index + 1) - if type(model) == QUSOMatrix - else model._variables - ) - initial_state = {v: 0 for v in var} - super().__init__(model, initial_state) - - @property - def state(self): - """state. - - A copy of the current state of the system. - - Returns - ------- - state : dict. - Dictionary that maps boolean labels to their values in {0, 1}. - - """ - return spin_to_boolean(super().state) - - def set_state(self, state): - """set_state. - - Set the state of the spin system to ``state``. - - Parameters - ---------- - state : dict or iterable. - ``state`` maps the spin variable labels to their corresponding - values. In other words ``state[v]`` is the value of variable ``v``. - A value must be either 0 or 1. - - """ - # if we call super then we get the wrong errors - state = {v: state[v] for v in self._variables} - if any(v not in {0, 1} for v in state.values()): - raise ValueError("State must contain only 0's and 1's") - super().set_state(boolean_to_spin(state)) diff --git a/qubovert/sim/_quso_simulation.py b/qubovert/sim/_quso_simulation.py deleted file mode 100644 index bcd0ff0..0000000 --- a/qubovert/sim/_quso_simulation.py +++ /dev/null @@ -1,261 +0,0 @@ -# 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. - -"""_quso_simulation.py. - -This file contains the ``QUSOSimulation`` object, which deals with using a -Metropolis algorithm to simulate QUSOs. The ``QUSOSimulation`` object deals -with interfacing with the C code for simulating QUSOs. - -""" - -from ._simulate_quso import c_simulate_quso as simulate_quso -from itertools import chain -from qubovert import QUSO -from qubovert.utils import QUSOMatrix - - -__all__ = 'QUSOSimulation', - - -class QUSOSimulation: - """QUSOSimulation. - - ``QUSOSimulation`` uses a Metropolis algorithm implemnted in C to simulate - a QUSO. The QUSO can be "updated" at a temperature ``T``. Thus, we can get - an idea of the time evolution of a QUSO by creating a temperature - schedule and seeing how the system evolves. - - Examples - -------- - Consider the example of the ferromagnetic chain. - - >>> import qubovert as qv - >>> - >>> length = 50 - >>> spin_system = sum( - >>> -qv.spin_var(i) * qv.spin_var(i+1) * qv.spin_var(i+2) - >>> for i in range(length-2) - >>> ) - >>> - >>> # initial state is all spin down - >>> initial_state = {i: -1 for i in range(length)} - >>> sim = qv.sim.QUSOSimulation(spin_system, initial_state) - >>> - >>> # define a schedule. here we simulate at temperature 4 for 25 time - >>> # steps, then temperature 2 for 25 time steps, then temperature 1 for - >>> # 10 time steps. - >>> schedule = (4, 25), (2, 25), (1, 10) - >>> sim.schedule_update(schedule) - >>> - >>> print("final state", sim.state) - >>> print("last 30 states", sim.get_past_states(30)) - - See Also - -------- - ``qv.sim.PUBOSimulation``, ``qv.sim.PUSOSimulation``, - ``qv.sim.QUBOSimulation``. - - """ - - def __init__(self, L, initial_state=None): - """__init__. - - Parameters - ---------- - L : dict, ``qubovert.utils.QUSOMatrix``, or ``qubovert.QUSO`` object. - The QUSO to simulate. This should map tuples of spin variable - labels to their respective coefficient in the Hamiltonian. - For more information, see the docstrings for - ``qubovert.utils.QUSOMatrix`` and ``qubovert.QUSO``. - initial_state : dict (optional, defaults to None). - The initial state to start the simulation in. ``initial_state`` - should map spin label names to their initial values, where each - value is either 1 or -1. If ``initial_state`` is None, then it - will be initialized to all 1s. - - """ - # must use type since we don't want errors from inheritance - if type(L) == QUSOMatrix: - N = L.max_index + 1 - model = L - self._mapping = dict(enumerate(range(N))) - self._reverse_mapping = self._mapping - self._variables = set(self._mapping.keys()) - elif type(L) != QUSO: - L = QUSO(L) - - if type(L) == QUSO: - N = L.num_binary_variables - model = L.to_quso() - self._mapping = L.mapping - self._reverse_mapping = L.reverse_mapping - self._variables = L.variables - - self._initial_state = ( - initial_state.copy() if initial_state is not None else - {v: 1 for v in self._variables} - ) - self._state = [1] * N - self.set_state(self._initial_state) - - # C arguments - # create model arrays - h, num_neighbors = [0.] * N, [0] * N - neighbors, J = [[] for _ in range(N)], [[] for _ in range(N)] - - for k, v in model.items(): - val = float(v) - if len(k) == 1: - h[k[0]] = val - elif len(k) == 2: - i, j = k - neighbors[i].append(j) - neighbors[j].append(i) - num_neighbors[i] += 1 - num_neighbors[j] += 1 - J[i].append(val) - J[j].append(val) - # ignore offset. - # flatten the arrays. - J, neighbors = list(chain(*J)), list(chain(*neighbors)) - - self._c_args = h, num_neighbors, neighbors, J - - def __str__(self): - """__str__. - - Return - ------ - s : str. - - """ - return self.__class__.__name__ - - @property - def state(self): - """state. - - A copy of the current state of the system. - - Returns - ------- - state : dict. - Dictionary that maps spin labels to their values in {1, -1}. - - """ - return {self._reverse_mapping[k]: v for k, v in enumerate(self._state)} - - @property - def initial_state(self): - """initial_state. - - A copy of the initial state of the system. - - Returns - ------- - initial_state : dict. - Dictionary that maps binary labels to their values. - - """ - return self._initial_state.copy() - - def set_state(self, state): - """set_state. - - Set the state of the spin system to ``state``. - - Parameters - ---------- - state : dict or iterable. - ``state`` maps the spin variable labels to their corresponding - values. In other words ``state[v]`` is the value of variable ``v``. - A value must be either 1 or -1. - - """ - for v in self._variables: - self._state[self._mapping[v]] = state[v] - if any(v not in {1, -1} for v in self._state): - raise ValueError("State must contain only 1's and -1's") - - def reset(self): - """reset. - - Reset the simulation back to its original state. - - """ - self.set_state(self._initial_state) - - def update(self, T, num_updates=1, in_order=False, seed=None): - """update. - - Update the simulation at temperature ``T``. Updates the internal state. - - Parameters - ---------- - T : number >= 0. - Temperature. - num_updates : int >= 1 (optional, defaults to 1). - The number of times to update the simulation at the temperature. - in_order : bool (optional, defaults to False). - Whether to iterate through the variables in order or randomly - during an update step. When ``in_order`` is False, the simulation - is more physically realistic, but when using the Simulation for - annealing, often it is better to have ``in_order = True``. - seed : number (optional, defaults to None). - The number to seed ``random`` with. - - """ - self.schedule_update([(T, num_updates)], in_order, seed) - - def schedule_update(self, schedule, in_order=False, seed=None): - """schedule_update. - - Update the simulation with a schedule. This function wraps the C - implementation of the simulation. - - Parameters - ---------- - schedule : iterable of tuples. - Each element in ``schedule`` is a pair ``(T, n)`` which designates - a temperature and a number of updates. See `Notes` below. - in_order : bool (optional, defaults to False). - Whether to iterate through the variables in order or randomly - during an update step. When ``in_order`` is False, the simulation - is more physically realistic, but when using the Simulation for - annealing, often it is better to have ``in_order = True``. - seed : number (optional, defaults to None). - The number to seed ``random`` with. - - Notes - ----- - The following two code blocks perform exactly the same thing, although - the second code block will be faster. - - >>> sim = QUSOSimulation(10) - >>> for T in (3, 2): - >>> sim.update(T, 100) - >>> sim.update(1, 50) - - >>> sim = QUSOSimulation(10) - >>> schedule = (3, 100), (2, 100), (1, 50) - >>> sim.schedule_update(schedule) - - """ - # call the C function - self._state = simulate_quso( - self._state, *self._c_args, - *zip(*schedule), int(in_order), - seed if seed is not None else -1 - ) diff --git a/qubovert/sim/_simulate_quso.c b/qubovert/sim/_simulate_quso.c deleted file mode 100644 index 94ee745..0000000 --- a/qubovert/sim/_simulate_quso.c +++ /dev/null @@ -1,186 +0,0 @@ -// 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. - -#include "Python.h" -#include "simulate_quso.h" - - -/* -Here we wrap the source code in src/simulate_quso.c so that it can be called -directly from Python. This file creates the ``qubovert.sim._simulate_quso`` -module with the function ``c_simulate_quso``. -*/ - -// Module info -static char _simulate_quso_name[] = "_simulate_quso"; - -static char _simulate_quso_docstring[] = - "``qubovert.sim._simulate_quso`` is a module for simulating a QUSO\n" - "with the C source code."; - - -// Define module functions; wrap the source code. - -static char c_simulate_quso_docstring[] = - "c_simulate_quso.\n\n" - "Simulate a QUSO with the C source.\n\n" - "Parameters\n" - "----------\n" - "state: list of ints.\n" - " ``state[i]`` is the value of the ith spin, either 1 or -1.\n" - "h : list of floats.\n" - " ``h[i]`` is the field value on spin ``i``.\n" - "num_neighbors : list of ints.\n" - " ``num_neighbors[i]`` is the number of neighbors that spin i has.\n" - "neighbors : list of ints.\n" - " ``neighbors[i]`` is the jth neighbor of spin ``k``, where\n" - " ``j = i - num_neighbors[k-1] - num_neighbors[k-2] - ...``\n" - "J : list of doubles.\n" - " ``J[i]`` is the coupling value between spin ``k`` and\n" - " ``neighbors[i]``.\n" - "Ts : tuple of floats\n" - " Each ``T`` in ``Ts`` indicates a temperature to update the\n" - " simulation at.\n" - "num_updates : tuple of ints\n" - " ``num_updates[i]`` indicates how many times to update the\n" - " simulation at temperature ``T[i]``." - "in_order : int.\n" - " ``in_order`` indicates whether to iterate through the variables in\n" - " order ``in_order=1`` or randomly ``in_order=0`` during an\n" - " update step.\n" - "seed : int.\n" - " seeds the random number generator If ``seed`` is a negative integer,\n" - " then we seed the random number generator with ``time(NULL)``.\n" - " Otherwise, we use ``seed``.\n\n" - "Returns\n" - "-------\n" - "new_state : list of ints.\n\n" - "Example\n" - "-------\n" - "``neighbors`` and ``J`` are basically flattened arrays.\n" - "In other words, we flatten the arrays ``temp_neighbors`` and\n" - "``temp_J``, where ``temp_neighbors`` points to an array where\n" - "``temp_neighbors[i][j]`` is the jth neighbor of spin i,\n" - "for j=0,...,num_neighbors[i]-1, and similarly, ``temp_J`` points to an\n" - "array where ``temp_J[i][j]`` is the coupling value between\n" - "spin i and spin ``neighbors[i][j]``, for j=0,...,num_neighbors[i]-1.\n\n" - "A spin model such as\n" - " :math:`-z_0 z_1 + 2*z_1*z_2 + z_0`\n" - "must be represented as\n" - " ``h = {1., 0, 0}``\n" - " ``num_neighbors = {1, 2, 1}``\n" - " ``temp_neighbors = {{1}, {0, 2}, {1}}``\n" - " ``temp_J = {{-1.},\n" - " {-1, 2},\n" - " {2}}``\n" - " ``neighbors = {1, 0, 2, 1}``\n" - " ``J = {-1.,\n" - " -1, 2,\n" - " 2}``\n"; - - -static PyObject* c_simulate_quso(PyObject* self, PyObject* args) { - /* - This is the function that we call from python with - ``qubovert.sim._simulate_quso.c_simulate_quso``. See the docstring above - for details on what ``args`` should be. - */ - PyObject *state, *h, *num_neighbors, *neighbors, *J, *Ts, *num_updates; - int in_order, seed; - - if (!PyArg_ParseTuple(args, "OOOOOOOii", - &state, &h, &num_neighbors, &neighbors, - &J, &Ts, &num_updates, &in_order, &seed)) { - return NULL; - } - - int len_state = (int)PyList_Size(state); - int len_J = (int)PyList_Size(J); - int len_Ts = (int)PyTuple_Size(Ts); - int *c_state = (int*)malloc(len_state * sizeof(int)); - double *c_h = (double*)malloc(len_state * sizeof(double)); - int *c_num_neighbors = (int*)malloc(len_state * sizeof(int)); - int *c_neighbors = (int*)malloc(len_J * sizeof(int)); - double *c_J = (double*)malloc(len_J * sizeof(double)); - double *c_Ts = (double*)malloc(len_Ts * sizeof(double)); - int *c_num_updates = (int*)malloc(len_Ts * sizeof(int)); - - int i; // iterator - - for(i=0; i +#include + + +double puso_subgraph_value( + int *state, int spin, + int *num_couplings, int *terms, double *couplings, + long *index, long **subgraphs +) { + /* + Find the value of the PUSO defined by the subgraph of terms containing + spin ``spin``. + + Parameters + ---------- + state : points to an integer array. + `state[i]` is either 1 or -1 representing the state of spin ``i``. + spin : int. + The spin defining the subgraph. + num_couplings : points to an int array. + ``num_couplings[i]`` is the number of spins in the ith term. + terms : points to an int array. + ``terms`` contains all the terms in the PUSO. + couplings : points to a double array. + The coupling value for each term. + index : points to a long array. + ``index[i]`` points to where the ith term starts in the + ``terms`` array. + subgraphs : a bunch of pointers to long arrays. + ``subgraphs[i][0]`` is the number of terms that spin ``i`` is involved + in. ``subgraphs[i][j + 1]`` is the jth term that spin ``i`` is + involved in, for ``j = 0`` to ``j = subgraphs[i][0] - 1``. + + Return + ------ + value : double. + + Example + ------- + Consider a PUSO + ``z_0 z_1 - z_1 z_2 z_3 + 3 z_2``. + Then we would have the following arguments: + ``terms = {0, 1, 1, 2, 3, 2}``. + ``num_couplings = {2, 3, 1}``. + ``couplings = {1, -1, 3}``. + Note that I add those big spaces in the ``terms`` array for the visuals + so that you can see how these terms are separated. + + In the example above: + ``index = {0, 2, 5}`` + ``subgraphs = { + {1, 0}, // spin 0 is involved in term 0 + {2, 0, 1}, // spin 1 is involved in term 0 and 1 + {2, 1, 2}, // spin 2 is involved in term 1 and 2 + {1, 1} // spin 3 is involved in term 1 + }`` + + Notice how the zeroth element of the array ``subgraphs[i]`` + is the number of terms that spin ``i`` is involved in. + + */ + double value = 0.; int product; long i, term; int j; + for(i=1; i<=subgraphs[spin][0]; i++) { + term = subgraphs[spin][i]; + product = 1; + for(j=0; j 0 && rand_double(rng) < exp(-dE / T))) { + state[i] *= -1; + } + } + } +} + + +double puso_value( + int *state, + long num_terms, int *num_couplings, int *terms, double *couplings +) { + /* + Find the PUSOs value with the spin state `state`. + + Parameters + ---------- + state : points to an integer array. + `state[i]` is either 1 or -1 representing the state of spin ``i``. + num_terms : long int. + The number of terms in the PUSO. + num_couplings : points to an int array. + ``num_couplings[i]`` is the number of spins in the ith term. + terms : points to an int array. + ``terms`` contains all the terms in the PUSO. + couplings : points to a double array. + The coupling value for each term. + + Returns + ------- + value : double + + Example + ------- + Consider a PUSO + ``z_0 z_1 - z_1 z_2 z_3 + 3 z_2``. + Then we would have the following arguments: + ``num_terms = 3``. + ``terms = {0, 1, 1, 2, 3, 2}``. + ``num_couplings = {2, 3, 1}``. + ``couplings = {1, -1, 3}``. + Note that I add those big spaces in the ``terms`` array for the visuals + so that you can see how these terms are separated. + + */ + long index = 0; double value = 0.; int _, product; + for(long term=0; term +#include + + +void compute_flip_dE( + double *flip_spin_dE, + int len_state, int *state, double *h, + int *num_neighbors, int *neighbors, double *J, + long *index +) { + /* + Determine the energy of the subgraph around a certain spin. + + Parameters + ---------- + `flip_spin_dE` points to an array with memory allocated for + `len_state` doubles. + `state` points to an array where `state[i]` is the value + of the ith spin, either 1 or -1. + `h` points to an array such that `h[i]` is the field value + for spin i. + `num_neighbors` points to an array such that `num_neighbors[i]` is + the length of the array `neighbors[i]`. + `neighbors` points to an array where `neighbors[index[i]+j]` is + one of the neighboring spins of spin i, for j + in {0, 1, ..., num_neighbors[i]-1}. + `J` points to an array where `J[index[i]+j]` is the coupling value + between the spin i and spin `neighbors[index[i]+j]`, for j + in {0, 1, ..., num_neighbors[i]-1}. + `index` points to an array such that `index[i]` is the index of + `neighbors` and `J` where the information for spin `i` starts. + See the `anneal_quso` function for more info. + + */ + int i, j, neighbor; + double subgraph_energy; + for(i=0; i 0 && rand_double(rng) < exp(-dE / T))) { + recompute_flip_dE( + i, flip_spin_dE, state, + num_neighbors, neighbors, J, + index + ); + state[i] *= -1; + } + } + } + free(flip_spin_dE); +} + + +double quso_value( + int len_state, int *state, double *h, + int *num_neighbors, int *neighbors, double *J, + long *index +) { + /* + Determine the energy of the QUSO when the state is `state`. + + Parameters + ---------- + `len_state` is the length of `state`. + `state` points to an array where `state[i]` is the value + of the ith spin, either 1 or -1. + `h` points to an array such that `h[i]` is the field value + for spin i. + `num_neighbors` points to an array such that `num_neighbors[i]` is + the length of the array `neighbors[i]`. + `neighbors` points to an array where `neighbors[index[i]+j]` is + one of the neighboring spins of spin i, for j + in {0, 1, ..., num_neighbors[i]-1}. + `J` points to an array where `J[index[i]+j]` is the coupling value + between the spin i and spin `neighbors[index[i]+j]`, for j + in {0, 1, ..., num_neighbors[i]-1}. + `index` points to an array such that `index[i]` is the index of + `neighbors` and `J` where the information for spin `i` starts. + See the `anneal_quso` function for more info. + + */ + int i, j, neighbor; + double value = 0.; double subgraph_energy; + for(i=0; i= i) { + subgraph_energy += J[index[i] + j] * state[neighbor]; + } + } + value += state[i] * subgraph_energy; + } + return value; +} + + +void anneal_quso( // updates states and values in place + int num_anneals, int *states, double *values, int len_state, + double *h, int *num_neighbors, int *neighbors, double *J, + int len_Ts, double *Ts, int in_order, int initial_state_provided, int seed +) { + /* + Anneal a QUSO ``num_anneals`` times. + + Parameters + ---------- + `num_anneals` is the number of times to run simulated annealing. + `states` points to a buffer array to build the resulting states. + It will be of dimension `states[num_anneals * len_state]`. + The jth spin in the ith state can be accessed with + `states[i * len_state + j]`. + `values` points to a buffer array to store the resulting values. + It will be of dimension `values[num_anneals]`. + `len_state` is the length of `state`, ie the number of spin. + `h` points to an array where `h[i]` is the field value on spin `i`. + `num_neighbors` points to an array where `num_neighbors[i]` is + number of neighbors that spin i has. + `neighbors` points to an array where `neighbors[i]` is the jth neighbor of + spin `k`, where `j = i - num_neighbors[k-1] - num_neighbors[k-2] - ...` + `J` points to an array where `J[i]` is the coupling value between + spin `k` and `neighbors[i]`. + `index` points to an array such that `index[i]` is the index of + `neighbors` and `J` where the information for spin `i` starts. + See the `anneal_quso` function for more info. + `len_Ts` is the length of `Ts` and the length of `num_updates`. + `Ts` points to an array where `Ts[j]` is the jth temperature to simulate + the QUSO at. + `in_order` indicates whether to iterate through the variables in order + initial_state_provided : bool. + If ``initial_state_provided == 0``, then we randomly initiate each + initial state for each anneal. Otherwise, we assume that the desired + starting states are encoded in the buffer ``states``. + `seed` is the value to seed the random number generator. + If `seed < 0`, then the random number generator will be seeded with + the internal clock. + + Returns + ------- + None. + This function updates `state` and does not return anything. + + Example + ------- + `neighbors` and `J` are basically flattened arrays. + In other words, we flatten the arrays `temp_neighbors` and + `temp_J`, where `temp_neighbors` points to an array where `temp_neighbors[i][j]` + is the jth neighbor of spin i, for j=0,...,num_neighbors[i]-1, and similarly, + `temp_J` points to an array where `temp_J[i][j]` is the coupling value between + spin i and spin `neighbors[i][j]`, for j=0,...,num_neighbors[i]-1. + + A spin model such as + -z_0 z_1 + 2*z_1*z_2 + z_0 + must be represented as + `h = {1., 0, 0}` + `num_neighbors = {1, 2, 1}` + `temp_neighbors = {{1}, {0, 2}, {1}}` + `temp_J = {{-1.}, + {-1, 2}, + {2}}` + `neighbors = {1, 0, 2, 1}` + `J = {-1., + -1, 2, + 2}` + */ + int i, j; + + rng_t rng = rand_init(seed); + + // index[i] points to where the information for spin i starts + // in the J and neighbor arrays. + long *index; index = (long*)malloc(len_state * sizeof(long)); + index[0] = 0; + for(i=1; i +#include + + +// We use the minimal C random number generator from +// http://www.pcg-random.org. +// Here we just wrap the pcg_basic.c and pcg_basic.h code with what we +// need it for. + + +void rand_seed(rng_t *rng, int seed) { + // If seed is negative, then we will seed with time, otherwise, + // seed with seed. + if(seed < 0) { + pcg32_srandom_r(rng, (unsigned int)time(NULL), (intptr_t)rng); + } else { + pcg32_srandom_r(rng, (unsigned)seed, 54u); + } +} + + +rng_t rand_init(int seed) { + // If seed is negative, then we will seed with time, otherwise, + // seed with seed. + rng_t rng; + rand_seed(&rng, seed); + return rng; +} + + +double rand_double(rng_t *rng) { + // random double in [0, 1) + return ldexp((double)pcg32_random_r(rng), -32); +} + + +int rand_int(rng_t *rng, int stop) { + //random integer in [0, top) + return (int)pcg32_boundedrand_r(rng, stop); +} diff --git a/qubovert/sim/src/random.h b/qubovert/sim/src/random.h new file mode 100644 index 0000000..011c1ed --- /dev/null +++ b/qubovert/sim/src/random.h @@ -0,0 +1,18 @@ +#ifndef RANDOM_H_INCLUDED +#define RANDOM_H_INCLUDED + +// We use the minimal C random number generator from +// http://www.pcg-random.org. +// Here we just wrap the pcg_basic.c and pcg_basic.h code with what we +// need it for. + +#include "pcg_basic.h" + +#define rng_t pcg32_random_t + +void rand_seed(rng_t *rng, int seed); +rng_t rand_init(int seed); +double rand_double(rng_t *rng); +int rand_int(rng_t *rng, int stop); + +#endif diff --git a/qubovert/sim/src/simulate_quso.c b/qubovert/sim/src/simulate_quso.c deleted file mode 100644 index 8a4922a..0000000 --- a/qubovert/sim/src/simulate_quso.c +++ /dev/null @@ -1,248 +0,0 @@ -// 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. - -#include "pcg_basic.h" -#include "simulate_quso.h" -#include -#include -#include - -/* -a few slight modifications and one major modification from - https://github.com/dwavesystems/dwave-neal/blob/master/neal/src/cpu_sa.cpp -We use the minimal C random number generator from - http://www.pcg-random.org. -*/ - - -double rand_double(pcg32_random_t *rng) { - // random double in [0, 1) - return ldexp((double)pcg32_random_r(rng), -32); -} - - -int rand_int(pcg32_random_t *rng, int stop) { - //random integer in [0, top) - return (int)pcg32_boundedrand_r(rng, stop); -} - - -void compute_flip_dE( - double *flip_spin_dE, - int len_state, int *state, double *h, - int *num_neighbors, int *neighbors, double *J, - long *index -) { - /* - Determine the energy of the subgraph around a certain spin. - - Parameters - ---------- - `flip_spin_dE` points to an array with memory allocated for - `len_state` doubles. - `state` points to an array where `state[i]` is the value - of the ith spin, either 1 or -1. - `h` points to an array such that `h[i]` is the field value - for spin i. - `num_neighbors` points to an array such that `num_neighbors[i]` is - the length of the array `neighbors[i]`. - `neighbors` points to an array where `neighbors[index[i]+j]` is - one of the neighboring spins of spin i, for j - in {0, 1, ..., num_neighbors[i]-1}. - `J` points to an array where `J[index[i]+j]` is the coupling value - between the spin i and spin `neighbors[index[i]+j]`, for j - in {0, 1, ..., num_neighbors[i]-1}. - `index` points to an array such that `index[i]` is the index of - `neighbors` and `J` where the information for spin `i` starts. - See the `simulate_quso` function for more info. - - */ - int i, j, neighbor; - double subgraph_energy; - for(i=0; i 0 && rand_double(&rng) < exp(-dE / T))) { - recompute_flip_dE( - i, flip_spin_dE, state, - num_neighbors, neighbors, J, - index - ); - state[i] *= -1; - } - } - } - } - free(flip_spin_dE); free(index); -} diff --git a/qubovert/sim/src/simulate_quso.h b/qubovert/sim/src/simulate_quso.h deleted file mode 100644 index c6c5993..0000000 --- a/qubovert/sim/src/simulate_quso.h +++ /dev/null @@ -1,25 +0,0 @@ -// 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. - -#ifndef simulate_quso_INCLUDED -#define simulate_quso_INCLUDED - -void simulate_quso( - int len_state, int *state, double *h, - int *num_neighbors, int *neighbors, double *J, - int len_Ts, double *Ts, int *num_updates, - int in_order, int seed -); - -#endif diff --git a/qubovert/utils/_binary_helpers.py b/qubovert/utils/_binary_helpers.py index bfcb29f..02a388e 100644 --- a/qubovert/utils/_binary_helpers.py +++ b/qubovert/utils/_binary_helpers.py @@ -20,11 +20,11 @@ from math import ceil -__all__ = 'solution_type', 'num_bits' +__all__ = 'is_solution_spin', 'num_bits' -def solution_type(solution, default='bool'): - """solution_type. +def is_solution_spin(solution, default=False): + """is_solution_spin. Figure out if the ``solution`` is a solution to a boolean or a spin model. If it cannot be determined (ie if ``solution`` is all 1s), then return @@ -37,41 +37,47 @@ def solution_type(solution, default='bool'): with values. If ``solution`` is a bin type, then all the values should be either a 0 or 1. If ``solution`` is a spin type, then all the values should be either a 1 or -1. - default : str (optional, defaults to ``'bool'``). + default : bool (optional, defaults to ``False``). The default answer to return if the solution type cannot be determined. Returns ------- - res : str. + res : bool. If it is determined that ``solution`` is the solution to a spin model, - then ``res`` will be ``'spin'``. If it is determined that ``solution`` - is the solution to a boolean model, then `res`` will be ``'bool'``. + then ``res`` will be ``True``. If it is determined that ``solution`` + is the solution to a boolean model, then `res`` will be ``False``. Otherwise, ``res`` will be ``default``. Examples -------- - >>> solution_type((0, 1, 1, 0)) - 'bool' + >>> is_solution_spin((0, 1, 1, 0)) + False - >>> solution_type((1, -1, -1, 1)) - 'spin' + >>> is_solution_spin((1, -1, -1, 1)) + True + + >>> is_solution_spin(dict(enumerate((0, 1, 1, 0)))) + False + + >>> is_solution_spin(dict(enumerate((1, -1, -1, 1)))) + True In these cases, the default is invoked. - >>> solution_type((1, 1, 1, 1)) - 'bool' - >>> solution_type((1, 1, 1, 1), default='bool') - 'bool' - >>> solution_type((1, 1, 1, 1), default=='spin') - 'spin' + >>> is_solution_spin((1, 1, 1, 1)) + False + >>> is_solution_spin((1, 1, 1, 1), default=False) + False + >>> is_solution_spin((1, 1, 1, 1), default=True) + True """ sol = solution.values() if isinstance(solution, dict) else solution for v in sol: if v == 0: - return 'bool' + return False elif v == -1: - return 'spin' + return True return default diff --git a/qubovert/utils/_pubomatrix.py b/qubovert/utils/_pubomatrix.py index 7b2cf4d..59e1363 100644 --- a/qubovert/utils/_pubomatrix.py +++ b/qubovert/utils/_pubomatrix.py @@ -473,3 +473,45 @@ def value(self, x): """ return pubo_value(x, self) + + def pretty_str(self, var_prefix='x'): + """pretty_str. + + Return a pretty string representation of the boolean model. + ``x`` indicates variables in {0, 1}. + + Parameters + ---------- + var_prefix : str. + The prefix for the variables. + + Return + ------ + res : str. + + """ + res, first = "", True + for prod, coef in self.items(): + try: + if not coef: + continue + elif coef > 0 and (coef != 1 or not prod): + res += "%s " % coef + elif coef < 0: + if coef == -1: + if first: + res += "-" if prod else "-1 " + else: + res = res[:-2] + ('- ' if prod else "- 1 ") + else: + if first: + res += "%s " % coef + else: + res = res[:-2] + '- %s ' % abs(coef) + except TypeError: # coef must be sympy symbolic + res += "(%s) " % str(coef) + for x in prod: + res += "%s(%s) " % (var_prefix, x) + res += "+ " + first = False + return res[:-2].strip() diff --git a/qubovert/utils/_pusomatrix.py b/qubovert/utils/_pusomatrix.py index 2fb9faf..ab8cbc0 100644 --- a/qubovert/utils/_pusomatrix.py +++ b/qubovert/utils/_pusomatrix.py @@ -235,3 +235,21 @@ def value(self, x): """ return puso_value(x, self) + + def pretty_str(self, var_prefix='z'): + """pretty_str. + + Return a pretty string representation of the spin model. + ``z`` indicates variables in {1, -1}. + + Parameters + ---------- + var_prefix : str. + The prefix for the variables. + + Return + ------ + res : str. + + """ + return PUBOMatrix.pretty_str(self, var_prefix) diff --git a/setup.py b/setup.py index 2016e15..9506857 100644 --- a/setup.py +++ b/setup.py @@ -37,10 +37,12 @@ # create the extension for the C file in qubovert.sim.src extensions = [ setuptools.Extension( - name='qubovert.sim._simulate_quso', - sources=['./qubovert/sim/_simulate_quso.c', - './qubovert/sim/src/simulate_quso.c', - './qubovert/sim/src/pcg_basic.c'], + name='qubovert.sim._canneal', + sources=['./qubovert/sim/_canneal.c', + './qubovert/sim/src/pcg_basic.c', + './qubovert/sim/src/random.c', + './qubovert/sim/src/anneal_quso.c', + './qubovert/sim/src/anneal_puso.c'], include_dirs=['./qubovert/sim/src/'], language='c' ) diff --git a/tests/sim/test_anneal.py b/tests/sim/test_anneal.py index 2423dfc..a0efaab 100644 --- a/tests/sim/test_anneal.py +++ b/tests/sim/test_anneal.py @@ -18,79 +18,28 @@ from qubovert.sim import ( anneal_qubo, anneal_quso, anneal_pubo, anneal_puso, - anneal_temperature_range, AnnealResults + AnnealResults, SCHEDULES ) -from qubovert.utils import puso_to_pubo, quso_to_qubo, QUBOVertWarning -from numpy.testing import assert_raises, assert_allclose, assert_warns +from qubovert.utils import ( + puso_to_pubo, quso_to_qubo, QUBOVertWarning, + QUBOMatrix, QUSOMatrix, PUBOMatrix, PUSOMatrix +) +from qubovert import QUBO, QUSO, PUBO, PUSO, PCBO, PCSO +from numpy.testing import assert_raises, assert_warns import numpy as np -def test_temperature_range(): +def test_anneal_puso(): - with assert_raises(ValueError): - anneal_temperature_range({}, end_flip_prob=-.3) - with assert_raises(ValueError): - anneal_temperature_range({}, end_flip_prob=2) - with assert_raises(ValueError): - anneal_temperature_range({}, end_flip_prob=1) - with assert_raises(ValueError): - anneal_temperature_range({}, start_flip_prob=-.3) - with assert_raises(ValueError): - anneal_temperature_range({}, start_flip_prob=2) - with assert_raises(ValueError): - anneal_temperature_range({}, start_flip_prob=1) - with assert_raises(ValueError): - anneal_temperature_range({}, .3, .9) - - assert anneal_temperature_range({}) == (0, 0) - assert anneal_temperature_range({(): 3}) == (0, 0) - - H = {(0, 1, 2): 2, (3,): -1, (4, 5): 5, (): -2} - probs = .1, .25, .57, .7 - for i, end_flip_prob in enumerate(probs): - for start_flip_prob in probs[i:]: - # spin model - T0, Tf = anneal_temperature_range( - H, start_flip_prob, end_flip_prob, True - ) - assert_allclose(T0, -10 / np.log(start_flip_prob)) - assert_allclose(Tf, -2 / np.log(end_flip_prob)) - - # boolean model - assert_allclose( - (T0, Tf), - anneal_temperature_range( - puso_to_pubo(H), start_flip_prob, end_flip_prob, False - ) - ) - - H = {(0, 1): 1, (1, 2,): -2, (1, 2, 3): 6, (): 11} - probs = 0, .16, .56, .98 - for i, end_flip_prob in enumerate(probs): - for start_flip_prob in probs[i:]: - # spin model - T0, Tf = anneal_temperature_range( - H, start_flip_prob, end_flip_prob, True - ) - assert_allclose( - T0, -18 / np.log(start_flip_prob) if start_flip_prob else 0 - ) - assert_allclose( - Tf, -2 / np.log(end_flip_prob) if end_flip_prob else 0 - ) - - # boolean model - assert_allclose( - (T0, Tf), - anneal_temperature_range( - puso_to_pubo(H), start_flip_prob, end_flip_prob, False - ) - ) + _anneal_puso(dict) + _anneal_puso(PUSOMatrix) + _anneal_puso(PUSO) + _anneal_puso(PCSO) -def test_anneal_puso(): +def _anneal_puso(type_): - H = {(i, i+1, i+2): -1 for i in range(3)} + H = type_({(i, i+1, i+2): -1 for i in range(3)}) with assert_raises(ValueError): anneal_puso(H, anneal_duration=-1) @@ -99,7 +48,7 @@ def test_anneal_puso(): anneal_puso(H, anneal_duration=-2) with assert_warns(QUBOVertWarning): - anneal_puso(H, temperature_range=(1, 2), schedule=[(3, 10), (2, 15)]) + anneal_puso(H, temperature_range=(1, 2), schedule=[3, 2]) with assert_raises(ValueError): anneal_puso(H, temperature_range=(1, 2)) @@ -117,10 +66,12 @@ def test_anneal_puso(): # just make sure everything runs anneal_puso(H, schedule='linear') - anneal_puso(H, initial_state=[1]*5) + res = anneal_puso(H, initial_state=[1] * 5) + for x in res: + assert all(i in (1, -1) for i in x.state.values()) # check to see if we find the groundstate of a simple but largeish model. - H = {(i, i+1): -1 for i in range(30)} + H = type_({(i, i+1): -1 for i in range(30)}) res = anneal_puso(H, num_anneals=4, seed=0) assert res.best.state in ( dict(enumerate([1]*31)), dict(enumerate([-1]*31)) @@ -138,12 +89,44 @@ def test_anneal_puso(): # make sure we run branch where an explicit schedule is given and no # temperature range is supplied - anneal_puso(H, schedule=[(3, 10), (2, 15)]) + anneal_puso(H, schedule=[3, 2]) + + # make sure it works with fields + res = anneal_puso( + type_({(0, 1, 2): 1, (1,): -1, (): 2}), + num_anneals=10 + ) + assert len(res) == 10 + res.sort() + for i in range(9): + assert res[i].value <= res[i + 1].value + + # bigish ordering + res = anneal_puso( + type_( + {(i, j, j + 1): 1 for i in range(100) for j in range(i+1, 100)} + ), + num_anneals=50 + ) + assert len(res) == 50 + res.sort() + for i in range(49): + assert res[i].value <= res[i + 1].value def test_anneal_quso(): - L = {(i, i+1): -1 for i in range(3)} + _anneal_quso(dict) + _anneal_quso(QUSOMatrix) + _anneal_quso(PUSOMatrix) + _anneal_quso(QUSO) + _anneal_quso(PUSO) + _anneal_quso(PCSO) + + +def _anneal_quso(type_): + + L = type_({(i, i+1): -1 for i in range(3)}) with assert_raises(ValueError): anneal_quso(L, anneal_duration=-1) @@ -152,7 +135,7 @@ def test_anneal_quso(): anneal_quso(L, anneal_duration=-2) with assert_warns(QUBOVertWarning): - anneal_quso(L, temperature_range=(1, 2), schedule=[(3, 10), (2, 15)]) + anneal_quso(L, temperature_range=(1, 2), schedule=[3, 15]) with assert_raises(ValueError): anneal_quso(L, temperature_range=(1, 2)) @@ -170,10 +153,12 @@ def test_anneal_quso(): # just make sure everything runs anneal_quso(L, schedule='linear') - anneal_quso(L, initial_state=[1]*4) + res = anneal_quso(L, initial_state=[1] * 5) + for x in res: + assert all(i in (1, -1) for i in x.state.values()) # check to see if we find the groundstate of a simple but largeish model. - L = {(i, i+1): -1 for i in range(30)} + L = type_({(i, i+1): -1 for i in range(30)}) res = anneal_quso(L, num_anneals=4, seed=0) assert res.best.state in ( dict(enumerate([1]*31)), dict(enumerate([-1]*31)) @@ -191,12 +176,37 @@ def test_anneal_quso(): # make sure we run branch where an explicit schedule is given and no # temperature range is supplied - anneal_quso(L, schedule=[(3, 10), (2, 15)]) + anneal_quso(L, schedule=[3] * 10 + [2] * 15) + + # make sure it works with fields + res = anneal_quso(type_({(0, 1): 1, (1,): -1, (): 2}), num_anneals=10) + assert len(res) == 10 + res.sort() + for i in range(9): + assert res[i].value <= res[i + 1].value + + # big ordering + res = anneal_quso( + type_({(i, j): 1 for i in range(100) for j in range(i+1, 100)}), + num_anneals=50 + ) + assert len(res) == 50 + res.sort() + for i in range(49): + assert res[i].value <= res[i + 1].value def test_anneal_pubo(): - P = puso_to_pubo({(i, i+1, i+2): -1 for i in range(3)}) + _anneal_pubo(dict) + _anneal_pubo(PUBOMatrix) + _anneal_pubo(PUBO) + _anneal_pubo(PCBO) + + +def _anneal_pubo(type_): + + P = type_(puso_to_pubo({(i, i+1, i+2): -1 for i in range(3)})) with assert_raises(ValueError): anneal_pubo(P, anneal_duration=-1) @@ -205,7 +215,7 @@ def test_anneal_pubo(): anneal_pubo(P, anneal_duration=-2) with assert_warns(QUBOVertWarning): - anneal_pubo(P, temperature_range=(1, 2), schedule=[(3, 10), (2, 15)]) + anneal_pubo(P, temperature_range=(1, 2), schedule=[3, 2]) with assert_raises(ValueError): anneal_pubo(P, temperature_range=(1, 2)) @@ -223,10 +233,12 @@ def test_anneal_pubo(): # just make sure everything runs anneal_pubo(P, schedule='linear') - anneal_pubo(P, initial_state=[1]*5) + res = anneal_pubo(P, initial_state=[1] * 5) + for x in res: + assert all(i in (0, 1) for i in x.state.values()) # check to see if we find the groundstate of a simple but largeish model. - P = puso_to_pubo({(i, i+1): -1 for i in range(30)}) + P = type_(puso_to_pubo({(i, i+1): -1 for i in range(30)})) res = anneal_pubo(P, num_anneals=4, seed=0) assert res.best.state in ( dict(enumerate([0]*31)), dict(enumerate([1]*31)) @@ -244,12 +256,41 @@ def test_anneal_pubo(): # 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)]) + 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) + assert len(res) == 10 + res.sort() + for i in range(9): + assert res[i].value <= res[i + 1].value + + # bigish ordering + res = anneal_pubo( + type_( + {(i, j, j + 1): 1 for i in range(100) for j in range(i+1, 100)} + ), + num_anneals=50 + ) + assert len(res) == 50 + res.sort() + for i in range(49): + assert res[i].value <= res[i + 1].value def test_anneal_qubo(): - Q = quso_to_qubo({(i, i+1): -1 for i in range(3)}) + _anneal_qubo(dict) + _anneal_qubo(QUBOMatrix) + _anneal_qubo(PUBOMatrix) + _anneal_qubo(QUBO) + _anneal_qubo(PUBO) + _anneal_qubo(PCBO) + + +def _anneal_qubo(type_): + + Q = type_(quso_to_qubo({(i, i+1): -1 for i in range(3)})) with assert_raises(ValueError): anneal_qubo(Q, anneal_duration=-1) @@ -258,7 +299,7 @@ def test_anneal_qubo(): anneal_qubo(Q, anneal_duration=-2) with assert_warns(QUBOVertWarning): - anneal_qubo(Q, temperature_range=(1, 2), schedule=[(3, 10), (2, 15)]) + anneal_qubo(Q, temperature_range=(1, 2), schedule=[3, 2]) with assert_raises(ValueError): anneal_qubo(Q, temperature_range=(1, 2)) @@ -276,10 +317,12 @@ def test_anneal_qubo(): # just make sure everything runs anneal_qubo(Q, schedule='linear') - anneal_qubo(Q, initial_state=[1]*4) + res = anneal_qubo(Q, initial_state=[1] * 5) + for x in res: + assert all(i in (0, 1) for i in x.state.values()) # check to see if we find the groundstate of a simple but largeish model. - Q = quso_to_qubo({(i, i+1): -1 for i in range(30)}) + Q = type_(quso_to_qubo({(i, i+1): -1 for i in range(30)})) res = anneal_qubo(Q, num_anneals=4, seed=0) assert res.best.state in ( dict(enumerate([0]*31)), dict(enumerate([1]*31)) @@ -297,4 +340,71 @@ def test_anneal_qubo(): # make sure we run branch where an explicit schedule is given and no # temperature range is supplied - anneal_qubo(Q, schedule=[(3, 10), (2, 15)]) + anneal_qubo(Q, schedule=[3] * 10 + [2] * 15) + + # make sure it works with fields + res = anneal_qubo(type_({(0, 1): 1, (1,): -1, (): 2}), num_anneals=10) + assert len(res) == 10 + res.sort() + for i in range(9): + assert res[i].value <= res[i + 1].value + + # big ordering + res = anneal_qubo( + type_({(i, j): 1 for i in range(100) for j in range(i+1, 100)}), + num_anneals=50 + ) + assert len(res) == 50 + res.sort() + for i in range(49): + assert res[i].value <= res[i + 1].value + + +def test_anneal_quso_vs_anneal_puso(): + + L = {(i, j): 1 for i in range(10) for j in range(i+1, 10)} + L.update({(i,): 1 for i in range(10)}) + kwargs = {} + for seed in range(10): + kwargs['seed'] = seed + for schedule in SCHEDULES: + kwargs['schedule'] = schedule + for in_order in range(2): + kwargs['in_order'] = in_order + for anneal_duration in (10, 100, 1000): + kwargs['anneal_duration'] = anneal_duration + for num_anneals in range(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 + + +def test_anneal_qubo_vs_anneal_pubo(): + + Q = {(i, j): 1 for i in range(10) for j in range(i+1, 10)} + Q.update({(i,): 1 for i in range(10)}) + kwargs = {} + for seed in range(10): + kwargs['seed'] = seed + for schedule in SCHEDULES: + kwargs['schedule'] = schedule + for in_order in range(2): + kwargs['in_order'] = in_order + for anneal_duration in (10, 100, 1000): + kwargs['anneal_duration'] = anneal_duration + for num_anneals in range(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 diff --git a/tests/sim/test_anneal_temperature_range.py b/tests/sim/test_anneal_temperature_range.py new file mode 100644 index 0000000..93c97cc --- /dev/null +++ b/tests/sim/test_anneal_temperature_range.py @@ -0,0 +1,86 @@ +# 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 the functions in the +``qubovert.sim._anneal_temperature_range`` file. +""" + +from qubovert.sim import anneal_temperature_range +from qubovert.utils import puso_to_pubo +from numpy.testing import assert_raises, assert_allclose +from math import log + + +def test_anneal_temperature_range(): + + with assert_raises(ValueError): + anneal_temperature_range({}, end_flip_prob=-.3) + with assert_raises(ValueError): + anneal_temperature_range({}, end_flip_prob=2) + with assert_raises(ValueError): + anneal_temperature_range({}, end_flip_prob=1) + with assert_raises(ValueError): + anneal_temperature_range({}, start_flip_prob=-.3) + with assert_raises(ValueError): + anneal_temperature_range({}, start_flip_prob=2) + with assert_raises(ValueError): + anneal_temperature_range({}, start_flip_prob=1) + with assert_raises(ValueError): + anneal_temperature_range({}, .3, .9) + + assert anneal_temperature_range({}) == (0, 0) + assert anneal_temperature_range({(): 3}) == (0, 0) + + H = {(0, 1, 2): 2, (3,): -1, (4, 5): 5, (): -2} + probs = .1, .25, .57, .7 + for i, end_flip_prob in enumerate(probs): + for start_flip_prob in probs[i:]: + # spin model + T0, Tf = anneal_temperature_range( + H, start_flip_prob, end_flip_prob, True + ) + assert_allclose(T0, -10 / log(start_flip_prob)) + assert_allclose(Tf, -2 / log(end_flip_prob)) + + # boolean model + assert_allclose( + (T0, Tf), + anneal_temperature_range( + puso_to_pubo(H), start_flip_prob, end_flip_prob, False + ) + ) + + H = {(0, 1): 1, (1, 2,): -2, (1, 2, 3): 6, (): 11} + probs = 0, .16, .56, .98 + for i, end_flip_prob in enumerate(probs): + for start_flip_prob in probs[i:]: + # spin model + T0, Tf = anneal_temperature_range( + H, start_flip_prob, end_flip_prob, True + ) + assert_allclose( + T0, -18 / log(start_flip_prob) if start_flip_prob else 0 + ) + assert_allclose( + Tf, -2 / log(end_flip_prob) if end_flip_prob else 0 + ) + + # boolean model + assert_allclose( + (T0, Tf), + anneal_temperature_range( + puso_to_pubo(H), start_flip_prob, end_flip_prob, False + ) + ) diff --git a/tests/sim/test_pubo_simulation.py b/tests/sim/test_pubo_simulation.py deleted file mode 100644 index 132d24e..0000000 --- a/tests/sim/test_pubo_simulation.py +++ /dev/null @@ -1,106 +0,0 @@ -# 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 the PUBOSimulation functionality in the -``qubovert.sim`` library. -""" - -from qubovert.sim import PUSOSimulation, PUBOSimulation -from qubovert import spin_var -from qubovert.utils import boolean_to_spin, puso_to_pubo -from numpy.testing import assert_raises - - -def test_pubosimulation_str(): - - for memory in range(5): - s = PUBOSimulation({}, memory=memory) - assert str(s) == "PUBOSimulation(memory=%d)" % memory - - -def test_pubosimulation_set_state(): - - ising = puso_to_pubo(sum(-spin_var(i) * spin_var(i+1) for i in range(9))) - - sim = PUBOSimulation(ising) - assert sim.state == {i: 0 for i in ising.variables} - - sim = PUBOSimulation(ising, {i: 1 for i in ising.variables}) - assert sim.state == {i: 1 for i in ising.variables} - - with assert_raises(ValueError): - sim.set_state({i: 3 for i in ising.variables}) - - with assert_raises(ValueError): - PUBOSimulation(ising, {i: -1 for i in ising.variables}) - - sim = PUBOSimulation({(0,): 1}) - with assert_raises(ValueError): - sim.set_state([-1]) - - sim.set_state([1]) - assert sim.state == {0: 1} - - -def test_pubosimulation_paststates_reset(): - - ising = sum(-spin_var(i) * spin_var(i+1) for i in range(3)) - initial_state = {0: 0, 1: 0, 2: 1, 3: 0} - sim = PUBOSimulation(ising, initial_state, 1000) - - assert sim.memory == 1000 - assert sim.get_past_states() == [sim.state] - - states = [sim.state] - for _ in range(100): - sim.update(2) - states.append(sim.state) - assert states == sim.get_past_states() - assert states[-50:] == sim.get_past_states(50) - assert sim.get_past_states(1) == [sim.state] - - sim.update(2, 2000) - assert len(sim.get_past_states()) == 1000 - - sim.reset() - assert sim.state == initial_state == sim.initial_state - assert sim.get_past_states() == [initial_state] - - -def test_pubosimulation_vs_pusosimulation(): - - ising = sum(-spin_var(i) * spin_var(i+1) for i in range(15)) - qubo = puso_to_pubo(ising) - - schedule = [(T, 20) for T in range(3, 0, -1)] - - spin = PUSOSimulation(ising) - boolean = PUBOSimulation(qubo) - - assert spin.initial_state == boolean_to_spin(boolean.initial_state) - - spin.schedule_update(schedule, seed=4) - boolean.schedule_update(schedule, seed=4) - assert spin.state == boolean_to_spin(boolean.state) - - initial_state = [0] * 8 + [1] * 8 - spin = PUSOSimulation(ising, boolean_to_spin(initial_state)) - boolean = PUBOSimulation(qubo, initial_state) - - assert spin.initial_state == boolean_to_spin(boolean.initial_state) - - spin.schedule_update(schedule, seed=4) - boolean.schedule_update(schedule, seed=4) - assert spin.state == boolean_to_spin(boolean.state) diff --git a/tests/sim/test_puso_simulation.py b/tests/sim/test_puso_simulation.py deleted file mode 100644 index c5ee7d8..0000000 --- a/tests/sim/test_puso_simulation.py +++ /dev/null @@ -1,119 +0,0 @@ -# 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 the PUSOSimulation functionality in the -``qubovert.sim`` library. -""" - -from qubovert.sim import PUSOSimulation -from qubovert import spin_var -from numpy.testing import assert_raises - - -def test_pusosimulation_str(): - - for memory in range(5): - s = PUSOSimulation({}, memory=memory) - assert str(s) == "PUSOSimulation(memory=%d)" % memory - - -def test_pusosimulation_set_state(): - - ising = sum(-spin_var(i) * spin_var(i+1) for i in range(9)) - - sim = PUSOSimulation(ising) - assert sim.state == {i: 1 for i in ising.variables} - - sim = PUSOSimulation(ising, {i: -1 for i in ising.variables}) - assert sim.state == {i: -1 for i in ising.variables} - - with assert_raises(ValueError): - sim.set_state({i: 3 for i in ising.variables}) - - with assert_raises(ValueError): - PUSOSimulation(ising, {i: 0 for i in ising.variables}) - - sim = PUSOSimulation({(0,): 1}) - with assert_raises(ValueError): - sim.set_state([0]) - - sim.set_state([-1]) - assert sim.state == {0: -1} - - -def test_pusosimulation_paststates_reset(): - - ising = sum(-spin_var(i) * spin_var(i+1) for i in range(3)) - initial_state = {0: 1, 1: 1, 2: -1, 3: 1} - sim = PUSOSimulation(ising, initial_state, 1000) - - assert sim.memory == 1000 - assert sim.get_past_states() == [sim.state] - - states = [sim.state] - for _ in range(100): - sim.update(2) - states.append(sim.state) - assert states == sim.get_past_states() - assert states[-50:] == sim.get_past_states(50) - - sim.update(2, 2000) - assert len(sim.get_past_states()) == 1000 - assert sim.get_past_states(1) == [sim.state] - - sim.reset() - assert sim.state == initial_state == sim.initial_state - assert sim.get_past_states() == [initial_state] - - -def test_pusosimulation_initialstate_variables(): - - ising = dict(sum(-spin_var(i) * spin_var(i+1) for i in range(3))) - initial_state = {0: 1, 1: 1, 2: -1, 3: 1} - sim = PUSOSimulation(ising, initial_state) - assert sim._variables == list(initial_state.keys()) - - -def test_pusosimulation_update_vs_updateschedule(): - - ising = sum(-spin_var(i) * spin_var(i+1) for i in range(3)) - sim = PUSOSimulation(ising) - - sim.update(4, 100, seed=0) - sim.update(2, 23) - sim.update(5, 48) - result1 = sim.state - sim.reset() - sim.schedule_update([(4, 100), (2, 23), (5, 48)], seed=0) - result2 = sim.state - sim.reset() - sim.schedule_update([(4, 100), (2, 23), (5, 48)], seed=1) - result3 = sim.state - assert result1 == result2 - assert result1 != result3 - - -def test_pusosimulation_updates(): - - sim = PUSOSimulation(spin_var(0)) - state = sim.state - sim.update(5, 0) - assert sim.state == state - sim.schedule_update([(5, 0), (3, 0)]) - assert sim.state == state - sim.update(4, -1) - assert sim.state == state - - sim.update(4, 4, in_order=True) diff --git a/tests/sim/test_qubo_simulation.py b/tests/sim/test_qubo_simulation.py deleted file mode 100644 index 16cdf39..0000000 --- a/tests/sim/test_qubo_simulation.py +++ /dev/null @@ -1,151 +0,0 @@ -# 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 the PUSOSimulation functionality in the -``qubovert.sim`` library. -""" - -from qubovert.sim import QUSOSimulation, QUBOSimulation -from qubovert import spin_var, QUBO -from qubovert.utils import ( - boolean_to_spin, quso_to_qubo, QUSOMatrix, QUBOMatrix -) -from numpy.testing import assert_raises - - -def test_qubosimulation_str(): - - assert str(QUBOSimulation({})) == "QUBOSimulation" - - -def test_qubosimulation_set_state(): - - ising = quso_to_qubo(sum(-spin_var(i) * spin_var(i+1) for i in range(9))) - - sim = QUBOSimulation(ising) - assert sim.state == {i: 0 for i in ising.variables} - - sim = QUBOSimulation(ising, {i: 1 for i in ising.variables}) - assert sim.state == {i: 1 for i in ising.variables} - - with assert_raises(ValueError): - sim.set_state({i: 3 for i in ising.variables}) - - with assert_raises(ValueError): - QUBOSimulation(ising, {i: -1 for i in ising.variables}) - - sim = QUBOSimulation({(0,): 1}) - with assert_raises(ValueError): - sim.set_state([-1]) - - sim.set_state([1]) - assert sim.state == {0: 1} - - # test the same thing but wiht matrix - ising = quso_to_qubo(QUSOMatrix( - sum(-spin_var(i) * spin_var(i+1) for i in range(9)) - )) - - sim = QUBOSimulation(ising) - assert sim.state == {i: 0 for i in ising.variables} - - sim = QUBOSimulation(ising, {i: 1 for i in ising.variables}) - assert sim.state == {i: 1 for i in ising.variables} - - with assert_raises(ValueError): - sim.set_state({i: 3 for i in ising.variables}) - - with assert_raises(ValueError): - QUBOSimulation(ising, {i: -1 for i in ising.variables}) - - sim = QUBOSimulation({(0,): 1}) - with assert_raises(ValueError): - sim.set_state([-1]) - - sim.set_state([1]) - assert sim.state == {0: 1} - - # test the same thing but wiht QUBO - ising = quso_to_qubo(QUBO( - sum(-spin_var(i) * spin_var(i+1) for i in range(9)) - )) - - sim = QUBOSimulation(ising) - assert sim.state == {i: 0 for i in ising.variables} - - sim = QUBOSimulation(ising, {i: 1 for i in ising.variables}) - assert sim.state == {i: 1 for i in ising.variables} - - with assert_raises(ValueError): - sim.set_state({i: 3 for i in ising.variables}) - - with assert_raises(ValueError): - QUBOSimulation(ising, {i: -1 for i in ising.variables}) - - sim = QUBOSimulation({(0,): 1}) - with assert_raises(ValueError): - sim.set_state([-1]) - - sim.set_state([1]) - assert sim.state == {0: 1} - - -def test_qubosimulation_reset(): - - ising = sum(-spin_var(i) * spin_var(i+1) for i in range(3)) - initial_state = {0: 0, 1: 0, 2: 1, 3: 0} - sim = QUBOSimulation(ising, initial_state) - sim.schedule_update([(2, 100)]) - sim.update(2, 2000) - sim.reset() - assert sim.state == initial_state == sim.initial_state - - -def test_qubosimulation_vs_qusosimulation(): - - ising = sum(-spin_var(i) * spin_var(i+1) for i in range(15)) - qubo = quso_to_qubo(ising) - - schedule = [(T, 20) for T in range(3, 0, -1)] - - spin = QUSOSimulation(ising) - boolean = QUBOSimulation(qubo) - - assert spin.initial_state == boolean_to_spin(boolean.initial_state) - - spin.schedule_update(schedule, seed=4) - boolean.schedule_update(schedule, seed=4) - assert spin.state == boolean_to_spin(boolean.state) - - initial_state = [0] * 8 + [1] * 8 - spin = QUSOSimulation(ising, boolean_to_spin(initial_state)) - boolean = QUBOSimulation(qubo, initial_state) - - assert spin.initial_state == boolean_to_spin(boolean.initial_state) - - spin.schedule_update(schedule, seed=4) - boolean.schedule_update(schedule, seed=4) - assert spin.state == boolean_to_spin(boolean.state) - - -def test_qubosimulation_bigrun(): - - # test that it runs on a big problem - model = QUBOMatrix( - {(i, j): 1 for i in range(2, 200, 3) for j in range(2, 200, 2)} - ) - sim = QUBOSimulation(model) - sim.update(3, 1000) - sim.update(3, 1000, in_order=True) diff --git a/tests/sim/test_quso_simulation.py b/tests/sim/test_quso_simulation.py deleted file mode 100644 index ff7f4ad..0000000 --- a/tests/sim/test_quso_simulation.py +++ /dev/null @@ -1,166 +0,0 @@ -# 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 the QUSOSimulation functionality in the -``qubovert.sim`` library. -""" - -from qubovert.sim import QUSOSimulation -from qubovert import spin_var, QUSO -from qubovert.utils import QUSOMatrix -from numpy.testing import assert_raises - - -def test_qusosimulation_str(): - - assert str(QUSOSimulation({})) == "QUSOSimulation" - - -def test_qusosimulation_set_state(): - - ising = sum(-spin_var(i) * spin_var(i+1) for i in range(9)) - - sim = QUSOSimulation(ising) - assert sim.state == {i: 1 for i in ising.variables} - - sim = QUSOSimulation(ising, {i: -1 for i in ising.variables}) - assert sim.state == {i: -1 for i in ising.variables} - - with assert_raises(ValueError): - sim.set_state({i: 3 for i in ising.variables}) - - with assert_raises(ValueError): - QUSOSimulation(ising, {i: 0 for i in ising.variables}) - - sim = QUSOSimulation({(0,): 1}) - with assert_raises(ValueError): - sim.set_state([0]) - - sim.set_state([-1]) - assert sim.state == {0: -1} - - # test the same but with matrix - ising = QUSOMatrix( - sum(-spin_var(i) * spin_var(i+1) for i in range(9)) - ) - - sim = QUSOSimulation(ising) - assert sim.state == {i: 1 for i in ising.variables} - - sim = QUSOSimulation(ising, {i: -1 for i in ising.variables}) - assert sim.state == {i: -1 for i in ising.variables} - - with assert_raises(ValueError): - sim.set_state({i: 3 for i in ising.variables}) - - with assert_raises(ValueError): - QUSOSimulation(ising, {i: 0 for i in ising.variables}) - - sim = QUSOSimulation({(0,): 1}) - with assert_raises(ValueError): - sim.set_state([0]) - - sim.set_state([-1]) - assert sim.state == {0: -1} - - # test the same for QUSO - ising = QUSO( - sum(-spin_var(i) * spin_var(i+1) for i in range(9)) - ) - sim = QUSOSimulation(ising) - assert sim.state == {i: 1 for i in ising.variables} - - sim = QUSOSimulation(ising, {i: -1 for i in ising.variables}) - assert sim.state == {i: -1 for i in ising.variables} - - with assert_raises(ValueError): - sim.set_state({i: 3 for i in ising.variables}) - - with assert_raises(ValueError): - QUSOSimulation(ising, {i: 0 for i in ising.variables}) - - sim = QUSOSimulation({(0,): 1}) - with assert_raises(ValueError): - sim.set_state([0]) - - sim.set_state([-1]) - assert sim.state == {0: -1} - - -def test_qusosimulation_reset(): - - ising = sum(-spin_var(i) * spin_var(i+1) for i in range(3)) - initial_state = {0: 1, 1: 1, 2: -1, 3: 1} - sim = QUSOSimulation(ising, initial_state) - - sim.schedule_update([(2, 100)]) - sim.update(2, 2000) - sim.reset() - assert sim.state == initial_state == sim.initial_state - - # test the same thing but with matrix - ising = QUSOMatrix( - sum(-spin_var(i) * spin_var(i+1) for i in range(3)) - ) - initial_state = {0: 1, 1: 1, 2: -1, 3: 1} - sim = QUSOSimulation(ising, initial_state) - - sim.schedule_update([(2, 100)]) - sim.update(2, 2000) - sim.reset() - assert sim.state == initial_state == sim.initial_state - - # test the same thing but with QUSO - ising = QUSO( - sum(-spin_var(i) * spin_var(i+1) for i in range(3)) - ) - initial_state = {0: 1, 1: 1, 2: -1, 3: 1} - sim = QUSOSimulation(ising, initial_state) - - sim.schedule_update([(2, 100)]) - sim.update(2, 2000) - sim.reset() - assert sim.state == initial_state == sim.initial_state - - -def test_qusosimulation_initialstate_variables(): - - ising = dict(sum(-spin_var(i) * spin_var(i+1) for i in range(3))) - initial_state = {0: 1, 1: 1, 2: -1, 3: 1} - sim = QUSOSimulation(ising, initial_state) - assert sim._variables == set(initial_state.keys()) - - -def test_qusosimulation_updates(): - - sim = QUSOSimulation(spin_var(0)) - state = sim.state - sim.update(5, 0) - assert sim.state == state - sim.schedule_update([(5, 0), (3, 0)]) - assert sim.state == state - sim.update(4, -1) - assert sim.state == state - - -def test_qusosimulation_bigrun(): - - # test that it runs on a big problem - model = QUSOMatrix( - {(i, j): 1 for i in range(2, 200, 3) for j in range(2, 200, 2)} - ) - sim = QUSOSimulation(model) - sim.update(3, 1000) - sim.update(3, 1000, in_order=True) diff --git a/tests/test_pcbo.py b/tests/test_pcbo.py index 04ad050..258a08c 100644 --- a/tests/test_pcbo.py +++ b/tests/test_pcbo.py @@ -27,6 +27,29 @@ from numpy.testing import assert_raises, assert_warns +def test_pretty_str(): + + def equal(expression, string): + assert expression.pretty_str() == string + assert PCBO.pretty_str(dict(expression)) == string + + x = [PCBO() + {(i,): 1} for i in range(3)] + a, b = Symbol('a'), Symbol('b') + + equal(x[0], "x(0)") + equal(-x[0], "-x(0)") + equal(x[0] * 0, "") + equal(2*x[0]*x[1] - 3*x[2], "2 x(0) x(1) - 3 x(2)") + equal(0*x[0] + 1, "1") + equal(0*x[0] - 1, "-1") + equal(0*x[0] + a, "(a)") + equal(0*x[0] + a * b, "(a*b)") + equal((a+b)*(x[0]*x[1] - x[2]), "(a + b) x(0) x(1) + (-a - b) x(2)") + equal(2*x[0]*x[1] - x[2], "2 x(0) x(1) - x(2)") + equal(-x[2] + x[0]*x[1], "-x(2) + x(0) x(1)") + equal(-2*x[2] + 2*x[0]*x[1], "-2 x(2) + 2 x(0) x(1)") + + """ TESTS FOR THE METHODS THAT PCBO INHERITS FROM PUBO """ diff --git a/tests/test_pcso.py b/tests/test_pcso.py index 40bfa3a..2e30e8e 100644 --- a/tests/test_pcso.py +++ b/tests/test_pcso.py @@ -28,6 +28,29 @@ from numpy.testing import assert_raises, assert_warns +def test_pretty_str(): + + def equal(expression, string): + assert expression.pretty_str() == string + assert PCSO.pretty_str(dict(expression)) == string + + z = [PCSO() + {(i,): 1} for i in range(3)] + a, b = Symbol('a'), Symbol('b') + + equal(z[0], "z(0)") + equal(-z[0], "-z(0)") + equal(z[0] * 0, "") + equal(2*z[0]*z[1] - 3*z[2], "2 z(0) z(1) - 3 z(2)") + equal(0*z[0] + 1, "1") + equal(0*z[0] - 1, "-1") + equal(0*z[0] + a, "(a)") + equal(0*z[0] + a * b, "(a*b)") + equal((a+b)*(z[0]*z[1] - z[2]), "(a + b) z(0) z(1) + (-a - b) z(2)") + equal(2*z[0]*z[1] - z[2], "2 z(0) z(1) - z(2)") + equal(-z[2] + z[0]*z[1], "-z(2) + z(0) z(1)") + equal(-2*z[2] + 2*z[0]*z[1], "-2 z(2) + 2 z(0) z(1)") + + """ TESTS FOR THE METHODS THAT PCSO INHERITS FROM PUSO """ diff --git a/tests/test_pubo.py b/tests/test_pubo.py index 3c92d96..fa804c6 100644 --- a/tests/test_pubo.py +++ b/tests/test_pubo.py @@ -27,6 +27,29 @@ from numpy.testing import assert_raises +def test_pretty_str(): + + def equal(expression, string): + assert expression.pretty_str() == string + assert PUBO.pretty_str(dict(expression)) == string + + x = [PUBO() + {(i,): 1} for i in range(3)] + a, b = Symbol('a'), Symbol('b') + + equal(x[0], "x(0)") + equal(-x[0], "-x(0)") + equal(x[0] * 0, "") + equal(2*x[0]*x[1] - 3*x[2], "2 x(0) x(1) - 3 x(2)") + equal(0*x[0] + 1, "1") + equal(0*x[0] - 1, "-1") + equal(0*x[0] + a, "(a)") + equal(0*x[0] + a * b, "(a*b)") + equal((a+b)*(x[0]*x[1] - x[2]), "(a + b) x(0) x(1) + (-a - b) x(2)") + equal(2*x[0]*x[1] - x[2], "2 x(0) x(1) - x(2)") + equal(-x[2] + x[0]*x[1], "-x(2) + x(0) x(1)") + equal(-2*x[2] + 2*x[0]*x[1], "-2 x(2) + 2 x(0) x(1)") + + class Problem: def __init__(self, problem, solution, obj): diff --git a/tests/test_puso.py b/tests/test_puso.py index 1b6887d..a33356e 100644 --- a/tests/test_puso.py +++ b/tests/test_puso.py @@ -25,6 +25,29 @@ from numpy.testing import assert_raises +def test_pretty_str(): + + def equal(expression, string): + assert expression.pretty_str() == string + assert PUSO.pretty_str(dict(expression)) == string + + z = [PUSO() + {(i,): 1} for i in range(3)] + a, b = Symbol('a'), Symbol('b') + + equal(z[0], "z(0)") + equal(-z[0], "-z(0)") + equal(z[0] * 0, "") + equal(2*z[0]*z[1] - 3*z[2], "2 z(0) z(1) - 3 z(2)") + equal(0*z[0] + 1, "1") + equal(0*z[0] - 1, "-1") + equal(0*z[0] + a, "(a)") + equal(0*z[0] + a * b, "(a*b)") + equal((a+b)*(z[0]*z[1] - z[2]), "(a + b) z(0) z(1) + (-a - b) z(2)") + equal(2*z[0]*z[1] - z[2], "2 z(0) z(1) - z(2)") + equal(-z[2] + z[0]*z[1], "-z(2) + z(0) z(1)") + equal(-2*z[2] + 2*z[0]*z[1], "-2 z(2) + 2 z(0) z(1)") + + class Problem: def __init__(self, problem, solution, obj): diff --git a/tests/test_qubo.py b/tests/test_qubo.py index e27dc27..0355dcb 100644 --- a/tests/test_qubo.py +++ b/tests/test_qubo.py @@ -27,6 +27,29 @@ from numpy.testing import assert_raises +def test_pretty_str(): + + def equal(expression, string): + assert expression.pretty_str() == string + assert QUBO.pretty_str(dict(expression)) == string + + x = [QUBO() + {(i,): 1} for i in range(3)] + a, b = Symbol('a'), Symbol('b') + + equal(x[0], "x(0)") + equal(-x[0], "-x(0)") + equal(x[0] * 0, "") + equal(2*x[0]*x[1] - 3*x[2], "2 x(0) x(1) - 3 x(2)") + equal(0*x[0] + 1, "1") + equal(0*x[0] - 1, "-1") + equal(0*x[0] + a, "(a)") + equal(0*x[0] + a * b, "(a*b)") + equal((a+b)*(x[0]*x[1] - x[2]), "(a + b) x(0) x(1) + (-a - b) x(2)") + equal(2*x[0]*x[1] - x[2], "2 x(0) x(1) - x(2)") + equal(-x[2] + x[0]*x[1], "-x(2) + x(0) x(1)") + equal(-2*x[2] + 2*x[0]*x[1], "-2 x(2) + 2 x(0) x(1)") + + problem = QUBO({('a',): -1, ('b',): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2}) solution = {'c': 1, 'b': 1, 'a': 1} obj = -8 diff --git a/tests/test_quso.py b/tests/test_quso.py index 823cf5c..17dce2f 100644 --- a/tests/test_quso.py +++ b/tests/test_quso.py @@ -27,6 +27,29 @@ from numpy.testing import assert_raises +def test_pretty_str(): + + def equal(expression, string): + assert expression.pretty_str() == string + assert QUSO.pretty_str(dict(expression)) == string + + z = [QUSO() + {(i,): 1} for i in range(3)] + a, b = Symbol('a'), Symbol('b') + + equal(z[0], "z(0)") + equal(-z[0], "-z(0)") + equal(z[0] * 0, "") + equal(2*z[0]*z[1] - 3*z[2], "2 z(0) z(1) - 3 z(2)") + equal(0*z[0] + 1, "1") + equal(0*z[0] - 1, "-1") + equal(0*z[0] + a, "(a)") + equal(0*z[0] + a * b, "(a*b)") + equal((a+b)*(z[0]*z[1] - z[2]), "(a + b) z(0) z(1) + (-a - b) z(2)") + equal(2*z[0]*z[1] - z[2], "2 z(0) z(1) - z(2)") + equal(-z[2] + z[0]*z[1], "-z(2) + z(0) z(1)") + equal(-2*z[2] + 2*z[0]*z[1], "-2 z(2) + 2 z(0) z(1)") + + problem = QUSO({('a',): -1, ('b',): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2}) solution = {'c': -1, 'b': -1, 'a': -1} diff --git a/tests/utils/test_binary_helpers.py b/tests/utils/test_binary_helpers.py index 51c07d0..2a37fd3 100644 --- a/tests/utils/test_binary_helpers.py +++ b/tests/utils/test_binary_helpers.py @@ -16,23 +16,23 @@ Contains tests for functions in the _binary_helpers.py file. """ -from qubovert.utils import solution_type, num_bits +from qubovert.utils import is_solution_spin, num_bits from numpy.testing import assert_raises -def test_solution_type(): +def test_is_solution_spin(): - assert solution_type((0, 1, 1, 0)) == 'bool' - assert solution_type((1, -1, -1, 1)) == 'spin' - assert solution_type((1, 1, 1, 1), 'testing') == 'testing' - assert solution_type((1, 1, 1, 1)) == 'bool' - assert solution_type((1, 1, 1, 1), 'spin') == 'spin' + assert not is_solution_spin((0, 1, 1, 0)) + assert is_solution_spin((1, -1, -1, 1)) + assert is_solution_spin((1, 1, 1, 1), None) is None + assert not is_solution_spin((1, 1, 1, 1)) + assert is_solution_spin((1, 1, 1, 1), True) - assert solution_type(dict(enumerate((0, 1, 1, 0)))) == 'bool' - assert solution_type(dict(enumerate((1, -1, -1, 1)))) == 'spin' - assert solution_type(dict(enumerate((1, 1, 1, 1))), 'testing') == 'testing' - assert solution_type(dict(enumerate((1, 1, 1, 1)))) == 'bool' - assert solution_type(dict(enumerate((1, 1, 1, 1))), 'spin') == 'spin' + assert not is_solution_spin(dict(enumerate((0, 1, 1, 0)))) + assert is_solution_spin(dict(enumerate((1, -1, -1, 1)))) + assert is_solution_spin(dict(enumerate((1, 1, 1, 1))), None) is None + assert not is_solution_spin(dict(enumerate((1, 1, 1, 1)))) + assert is_solution_spin(dict(enumerate((1, 1, 1, 1))), True) def test_num_bits(): diff --git a/tests/utils/test_pubomatrix.py b/tests/utils/test_pubomatrix.py index 65e00a8..ff1f178 100644 --- a/tests/utils/test_pubomatrix.py +++ b/tests/utils/test_pubomatrix.py @@ -22,6 +22,32 @@ from numpy.testing import assert_raises +def test_pretty_str(): + + def equal(expression, string): + assert expression.pretty_str() == string + assert PUBOMatrix.pretty_str(dict(expression)) == string + + x = [PUBOMatrix() + {(i,): 1} for i in range(3)] + a, b = Symbol('a'), Symbol('b') + + equal(x[0], "x(0)") + equal(-x[0], "-x(0)") + equal(x[0] * 0, "") + equal(2*x[0]*x[1] - 3*x[2], "2 x(0) x(1) - 3 x(2)") + equal(0*x[0] + 1, "1") + equal(0*x[0] - 1, "-1") + equal(0*x[0] + a, "(a)") + equal(0*x[0] + a * b, "(a*b)") + equal((a+b)*(x[0]*x[1] - x[2]), "(a + b) x(0) x(1) + (-a - b) x(2)") + equal(2*x[0]*x[1] - x[2], "2 x(0) x(1) - x(2)") + equal(-x[2] + x[0]*x[1], "-x(2) + x(0) x(1)") + equal(-2*x[2] + 2*x[0]*x[1], "-2 x(2) + 2 x(0) x(1)") + + # test when there is a zero element + assert PUBOMatrix.pretty_str({(0,): 1, (1, 2): 0}, 'y') == "y(0)" + + def test_pubo_checkkey(): with assert_raises(KeyError): diff --git a/tests/utils/test_pusomatrix.py b/tests/utils/test_pusomatrix.py index b2403df..6bf3a06 100644 --- a/tests/utils/test_pusomatrix.py +++ b/tests/utils/test_pusomatrix.py @@ -22,6 +22,29 @@ from numpy.testing import assert_raises +def test_pretty_str(): + + def equal(expression, string): + assert expression.pretty_str() == string + assert PUSOMatrix.pretty_str(dict(expression)) == string + + z = [PUSOMatrix() + {(i,): 1} for i in range(3)] + a, b = Symbol('a'), Symbol('b') + + equal(z[0], "z(0)") + equal(-z[0], "-z(0)") + equal(z[0] * 0, "") + equal(2*z[0]*z[1] - 3*z[2], "2 z(0) z(1) - 3 z(2)") + equal(0*z[0] + 1, "1") + equal(0*z[0] - 1, "-1") + equal(0*z[0] + a, "(a)") + equal(0*z[0] + a * b, "(a*b)") + equal((a+b)*(z[0]*z[1] - z[2]), "(a + b) z(0) z(1) + (-a - b) z(2)") + equal(2*z[0]*z[1] - z[2], "2 z(0) z(1) - z(2)") + equal(-z[2] + z[0]*z[1], "-z(2) + z(0) z(1)") + equal(-2*z[2] + 2*z[0]*z[1], "-2 z(2) + 2 z(0) z(1)") + + def test_qubo_checkkey(): with assert_raises(KeyError): diff --git a/tests/utils/test_qubomatrix.py b/tests/utils/test_qubomatrix.py index a1114cc..aa3e61c 100644 --- a/tests/utils/test_qubomatrix.py +++ b/tests/utils/test_qubomatrix.py @@ -22,6 +22,29 @@ from numpy.testing import assert_raises +def test_pretty_str(): + + def equal(expression, string): + assert expression.pretty_str() == string + assert QUBOMatrix.pretty_str(dict(expression)) == string + + x = [QUBOMatrix() + {(i,): 1} for i in range(3)] + a, b = Symbol('a'), Symbol('b') + + equal(x[0], "x(0)") + equal(-x[0], "-x(0)") + equal(x[0] * 0, "") + equal(2*x[0]*x[1] - 3*x[2], "2 x(0) x(1) - 3 x(2)") + equal(0*x[0] + 1, "1") + equal(0*x[0] - 1, "-1") + equal(0*x[0] + a, "(a)") + equal(0*x[0] + a * b, "(a*b)") + equal((a+b)*(x[0]*x[1] - x[2]), "(a + b) x(0) x(1) + (-a - b) x(2)") + equal(2*x[0]*x[1] - x[2], "2 x(0) x(1) - x(2)") + equal(-x[2] + x[0]*x[1], "-x(2) + x(0) x(1)") + equal(-2*x[2] + 2*x[0]*x[1], "-2 x(2) + 2 x(0) x(1)") + + def test_qubo_checkkey(): with assert_raises(KeyError): diff --git a/tests/utils/test_qusomatrix.py b/tests/utils/test_qusomatrix.py index 7be3a8c..223965f 100644 --- a/tests/utils/test_qusomatrix.py +++ b/tests/utils/test_qusomatrix.py @@ -22,6 +22,29 @@ from numpy.testing import assert_raises +def test_pretty_str(): + + def equal(expression, string): + assert expression.pretty_str() == string + assert QUSOMatrix.pretty_str(dict(expression)) == string + + z = [QUSOMatrix() + {(i,): 1} for i in range(3)] + a, b = Symbol('a'), Symbol('b') + + equal(z[0], "z(0)") + equal(-z[0], "-z(0)") + equal(z[0] * 0, "") + equal(2*z[0]*z[1] - 3*z[2], "2 z(0) z(1) - 3 z(2)") + equal(0*z[0] + 1, "1") + equal(0*z[0] - 1, "-1") + equal(0*z[0] + a, "(a)") + equal(0*z[0] + a * b, "(a*b)") + equal((a+b)*(z[0]*z[1] - z[2]), "(a + b) z(0) z(1) + (-a - b) z(2)") + equal(2*z[0]*z[1] - z[2], "2 z(0) z(1) - z(2)") + equal(-z[2] + z[0]*z[1], "-z(2) + z(0) z(1)") + equal(-2*z[2] + 2*z[0]*z[1], "-2 z(2) + 2 z(0) z(1)") + + def test_checkkey(): with assert_raises(KeyError):