From 576d96162a3aa24c34228b26a5bff0e58ba4d2ab Mon Sep 17 00:00:00 2001 From: "Stratmann, Philipp" Date: Tue, 11 Jul 2023 15:44:37 +0200 Subject: [PATCH 1/9] revision for clear user API --- .../optimization/solvers/generic/hierarchical_processes.py | 2 ++ src/lava/lib/optimization/solvers/generic/nebm/process.py | 5 ++++- .../lib/optimization/solvers/generic/sub_process_models.py | 5 ++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lava/lib/optimization/solvers/generic/hierarchical_processes.py b/src/lava/lib/optimization/solvers/generic/hierarchical_processes.py index 3a65c745..4dc06193 100644 --- a/src/lava/lib/optimization/solvers/generic/hierarchical_processes.py +++ b/src/lava/lib/optimization/solvers/generic/hierarchical_processes.py @@ -413,6 +413,7 @@ def __init__( name: ty.Optional[str] = None, log_config: ty.Optional[LogConfig] = None, init_value: npt.ArrayLike = 0, + annealing_schedule: str = 'linear', neuron_model: str, ) -> None: """ @@ -459,6 +460,7 @@ def __init__( log_config=log_config, init_value=init_value, neuron_model=neuron_model, + annealing_schedule=annealing_schedule, ) self.added_input = InPort(shape=shape) self.messages = OutPort(shape=shape) diff --git a/src/lava/lib/optimization/solvers/generic/nebm/process.py b/src/lava/lib/optimization/solvers/generic/nebm/process.py index 64e72ac5..b6aae44e 100644 --- a/src/lava/lib/optimization/solvers/generic/nebm/process.py +++ b/src/lava/lib/optimization/solvers/generic/nebm/process.py @@ -20,6 +20,7 @@ def __init__( refract: ty.Optional[ty.Union[int, npty.NDArray]] = 0, init_value=0, init_state=0, + neuron_model: str = 'nebm', ): """ NEBM Process. @@ -81,6 +82,7 @@ def __init__( init_value=0, init_state=None, neuron_model: str, + annealing_schedule: str = 'linear', ): """ SA Process. @@ -109,8 +111,9 @@ def __init__( steps_per_temperature=steps_per_temperature, refract=refract, refract_scaling=refract_scaling, - neuron_model=neuron_model, exp_temperature=exp_temperature, + neuron_model=neuron_model, + annealing_schedule=annealing_schedule, ) self.a_in = InPort(shape=shape) diff --git a/src/lava/lib/optimization/solvers/generic/sub_process_models.py b/src/lava/lib/optimization/solvers/generic/sub_process_models.py index 32befc40..db3a7e5a 100644 --- a/src/lava/lib/optimization/solvers/generic/sub_process_models.py +++ b/src/lava/lib/optimization/solvers/generic/sub_process_models.py @@ -77,7 +77,7 @@ def __init__(self, proc): noise_precision=noise_precision, sustained_on_tau=on_tau, cost_diagonal=diagonal) - elif neuron_model == 'nebm-sa' or 'nebm-sa-balanced': + elif 'nebm-sa' in neuron_model: max_temperature = proc.hyperparameters.get("max_temperature", 10) min_temperature = proc.hyperparameters.get("min_temperature", 0) delta_temperature = proc.hyperparameters.get("delta_temperature", 1) @@ -90,6 +90,8 @@ def __init__(self, proc): np.zeros(shape, dtype=int)) init_state = proc.hyperparameters.get("init_state", np.zeros(shape, dtype=int)) + annealing_schedule = proc.hyperparameters.get("annealing_schedule", + "linear") self.s_bit = \ NEBMSimulatedAnnealingAbstract( shape=shape, @@ -103,6 +105,7 @@ def __init__(self, proc): init_value=init_value, init_state=init_state, neuron_model=neuron_model, + annealing_schedule=annealing_schedule, ) else: AssertionError("Unknown neuron model specified") From 56ffc8a5c62c322f550b76457bb27430d978ea20 Mon Sep 17 00:00:00 2001 From: "Stratmann, Philipp" Date: Wed, 12 Jul 2023 13:46:50 +0200 Subject: [PATCH 2/9] adjust QUBO tutorial --- tutorials/tutorial_02_solving_qubos.ipynb | 152 +++++++++++++++++----- 1 file changed, 118 insertions(+), 34 deletions(-) diff --git a/tutorials/tutorial_02_solving_qubos.ipynb b/tutorials/tutorial_02_solving_qubos.ipynb index 843ac6c8..987ecb6b 100644 --- a/tutorials/tutorial_02_solving_qubos.ipynb +++ b/tutorials/tutorial_02_solving_qubos.ipynb @@ -48,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "a0864507", "metadata": {}, "outputs": [], @@ -69,7 +69,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "f255560d", "metadata": {}, "outputs": [], @@ -89,7 +89,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "3338d93b", "metadata": {}, "outputs": [], @@ -229,12 +229,15 @@ "id": "9a497c70", "metadata": {}, "source": [ - "Lava provides an easy interface to encode QUBO problems, by providing the QUBO matrix $\\mathbf{Q}$." + "Lava provides an easy interface to encode QUBO problems, by providing the QUBO matrix $\\mathbf{Q}$.\n", + "\n", + "Please note:\n", + "As shown below, the QUBO solver can run on CPU and Loihi 2 backend. At the moment, the Loihi 2 model is algorithmically more advanced and more performant. For illustration purposes, the following cell thus instantiates a less complex workload for the CPU backend. In the future, the algorithms for CPU and Loihi 2 will be aligned again. At that point, both backends will be able to solve workloads to the same optimality, while Loihi 2 will remain faster and more energy efficient." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "411835c7", "metadata": {}, "outputs": [], @@ -242,9 +245,12 @@ "# Import utility functions to create and analyze MIS workloads\n", "from lava.lib.optimization.utils.generators.mis import MISProblem\n", "\n", - "# Create an undirected graph with 1000 vertices and a \n", - "# probability of 85% that any two vertices are randomly connected\n", - "mis = MISProblem(num_vertices=955, connection_prob=0.9, seed=44)\n", + "# Create an undirected graph with num_vertices vertices and a \n", + "# probability of connection_prob that any two vertices are randomly connected\n", + "if loihi2_is_available:\n", + " mis = MISProblem(num_vertices=1000, connection_prob=0.15, seed=0)\n", + "else:\n", + " mis = MISProblem(num_vertices=15, connection_prob=0.9, seed=0)\n", "\n", "# Translate the MIS problem for this graph into a QUBO matrix\n", "q = mis.get_qubo_matrix(w_diag=1, w_off=8)\n", @@ -272,26 +278,105 @@ "\n", "Lava will print an obtained solution whenever it found a local minimum. Then it continues its search until either stopping condition is fullfilled.\n", "\n", - "The following cell will run the solver either on a CPU backend, or on a Loihi 2 chip if available. The solutions may differ, due to slightly different noise models." + "The following cell will run the solver either on a CPU backend, or on a Loihi 2 chip if available. As mentioned above, Loihi 2 has a more advanced algorithm, and thus requires a different set of hyperparameters." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "a672adae", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Per core distribution:\n", + "----------------------------------------------------------------\n", + "| AxonIn |NeuronGr| Neurons|Synapses| AxonMap| AxonMem| Cores |\n", + "|--------------------------------------------------------------|\n", + "| 1000| 1| 1| 1000| 2| 0| 1|\n", + "| 1000| 1| 47| 9952| 95| 0| 21|\n", + "|--------------------------------------------------------------|\n", + "| Total | 22|\n", + "----------------------------------------------------------------\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/pstratma/dev/lava_nc/venv/lib/python3.8/site-packages/nxcore/arch/base/nxboard.py:71: UserWarning: Partition None overridden by environment variable PARTITION=oheogulch\n", + " warnings.warn(\"Partition {} overridden by environment variable PARTITION={}\".format(partition,os.environ[\"PARTITION\"]))\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Host: better solution found by network 149296 at step 119351 with cost -39: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0]\n", + "\n", + "Solution of the provided QUBO: [ 40 53 76 84 88 106 142 186 194 205 207 210 230 261 271 272 287 289\n", + " 310 368 431 455 456 490 495 541 575 619 627 710 721 725 758 802 842 867\n", + " 869 893 920].\n" + ] + } + ], "source": [ "solver = OptimizationSolver(qubo_problem)\n", "\n", "# Provide hyperparameters for the solver\n", "# Guidance on the hyperparameter search will be provided in the deep dive tutorial\n", - "np.random.seed(85134)\n", - "hyperparameters = {\n", - " \"temperature\": int(1),\n", - " \"noise_precision\": int(16),\n", - " \"refract\": np.random.randint(5, 20, qubo_problem.num_variables),}\n", + "np.random.seed(0)\n", + "if loihi2_is_available:\n", + " target_cost = int(-50)\n", + " timeout = 200000\n", + " hyperparameters = {\n", + " \"neuron_model\": 'nebm-sa-refract',\n", + " 'annealing_schedule': 'linear',\n", + " \"max_temperature\": int(15),\n", + " \"min_temperature\": int(0),\n", + " \"steps_per_temperature\": int(20),\n", + " \"delta_temperature\": int(1),\n", + " \"refract_scaling\": int(4),\n", + " \"refract\": int(0),\n", + " \"refract_counter\": 0,\n", + " \"init_value\": np.random.randint(0, 2, size=(qubo_problem.num_variables)),}\n", + "else:\n", + " target_cost = int(-2)\n", + " timeout = 100\n", + " hyperparameters = {\n", + " \"neuron_model\": 'nebm',\n", + " \"temperature\": int(1),}\n", "\n", + "# Define the backend\n", "if loihi2_is_available:\n", " backend = 'Loihi2'\n", "else:\n", @@ -301,9 +386,9 @@ "# Change \"backend='Loihi2'\" if your system has physical access to this chip\n", "solver_report = solver.solve(\n", " config=SolverConfig(\n", - " timeout=10000,\n", + " timeout=timeout,\n", " hyperparameters=hyperparameters,\n", - " target_cost=int(-5),\n", + " target_cost=target_cost,\n", " backend=backend\n", " )\n", ")\n", @@ -317,26 +402,27 @@ "id": "9112d43a", "metadata": {}, "source": [ - "The obtained solution is an optimal solution of the provided QUBO problem. To see this, we can compare it with the result of Python's Networkx package, which solves the underlying graph theoretical problem directly." + "To compare the solution with the optimal solution, we can compare it with the result of Python's Networkx package, which solves the underlying graph theoretical problem directly. For Loihi 2, the code is commented out as Networkx takes very long to find a solution to the optimization workload." ] }, { "cell_type": "code", - "execution_count": null, - "id": "12bfe5c2", + "execution_count": 6, + "id": "440201d4", "metadata": {}, "outputs": [], "source": [ - "# Find the optimal solution to the MIS problem\n", - "solution_opt = mis.find_maximum_independent_set()\n", + "if not loihi2_is_available:\n", + " # Find the optimal solution to the MIS problem\n", + " solution_opt = mis.find_maximum_independent_set()\n", "\n", - "# Calculate the QUBO cost of the optimal solution\n", - "cost_opt = qubo_problem.evaluate_cost(solution=solution_opt)\n", + " # Calculate the QUBO cost of the optimal solution\n", + " cost_opt = qubo_problem.evaluate_cost(solution=solution_opt)\n", "\n", - "# Calculate the QUBO cost of Lava's solution\n", - "cost_lava = qubo_problem.evaluate_cost(solution=solution_loihi)\n", + " # Calculate the QUBO cost of Lava's solution\n", + " cost_lava = qubo_problem.evaluate_cost(solution=solution_loihi)\n", "\n", - "print(f'QUBO cost of solution: {cost_lava} (Lava) vs. {cost_opt} (optimal)\\n')" + " print(f'QUBO cost of solution: {cost_lava} (Lava) vs. {cost_opt} (optimal)\\n')" ] }, { @@ -354,9 +440,7 @@ "source": [ "Future releases will provide the following advancements and many more:\n", "- Compiler support for larger problems and faster problem compilation.\n", - "- Performance improvements using advanced hyperparameter search, improved noise annealing schedules, additional algorithms, and parallel runs of solver instances. \n", - "- Utilities to benchmark energy consumption, run time, and accuracy between Loihi1, Loihi2, and CPU-based state-of-the-art solvers.\n", - "- Support for Loihi1." + "- Performance improvements using advanced hyperparameter search, improved noise annealing schedules, additional algorithms, and parallel runs of solver instances." ] }, { @@ -370,7 +454,7 @@ "- the QUBO solver works under the hood.\n", "- optimization tasks can be encoded as QUBO.\n", "- hyperparameters affect the performance of the solver.\n", - "- to benchmark solution accuracy, run time, and energy consumption between Loihi1, Loihi2, and CPU-based solvers.\n", + "- to benchmark solution accuracy, run time, and energy consumption between Loihi2 and CPU-based solvers.\n", "\n", "Watch this [space](https://github.com/lava-nc/lava-optimization) to learn about upcoming developments to the QUBO solver and the optimization toolbox in Lava in general. \n", "\n", @@ -410,9 +494,9 @@ ], "metadata": { "kernelspec": { - "display_name": ".venv", + "display_name": "lava_nc", "language": "python", - "name": "python3" + "name": "lava_nc" }, "language_info": { "codemirror_mode": { From c919711494c7585aa58e3b742d8b0edec06c2576 Mon Sep 17 00:00:00 2001 From: "Stratmann, Philipp" Date: Wed, 12 Jul 2023 16:04:11 +0200 Subject: [PATCH 3/9] adjust QUBO tutorial --- tutorials/tutorial_02_solving_qubos.ipynb | 82 +++-------------------- 1 file changed, 10 insertions(+), 72 deletions(-) diff --git a/tutorials/tutorial_02_solving_qubos.ipynb b/tutorials/tutorial_02_solving_qubos.ipynb index 987ecb6b..2c307980 100644 --- a/tutorials/tutorial_02_solving_qubos.ipynb +++ b/tutorials/tutorial_02_solving_qubos.ipynb @@ -48,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "a0864507", "metadata": {}, "outputs": [], @@ -69,7 +69,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "f255560d", "metadata": {}, "outputs": [], @@ -89,7 +89,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "3338d93b", "metadata": {}, "outputs": [], @@ -237,7 +237,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "411835c7", "metadata": {}, "outputs": [], @@ -283,72 +283,10 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "a672adae", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Per core distribution:\n", - "----------------------------------------------------------------\n", - "| AxonIn |NeuronGr| Neurons|Synapses| AxonMap| AxonMem| Cores |\n", - "|--------------------------------------------------------------|\n", - "| 1000| 1| 1| 1000| 2| 0| 1|\n", - "| 1000| 1| 47| 9952| 95| 0| 21|\n", - "|--------------------------------------------------------------|\n", - "| Total | 22|\n", - "----------------------------------------------------------------\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/pstratma/dev/lava_nc/venv/lib/python3.8/site-packages/nxcore/arch/base/nxboard.py:71: UserWarning: Partition None overridden by environment variable PARTITION=oheogulch\n", - " warnings.warn(\"Partition {} overridden by environment variable PARTITION={}\".format(partition,os.environ[\"PARTITION\"]))\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Host: better solution found by network 149296 at step 119351 with cost -39: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", - " 0]\n", - "\n", - "Solution of the provided QUBO: [ 40 53 76 84 88 106 142 186 194 205 207 210 230 261 271 272 287 289\n", - " 310 368 431 455 456 490 495 541 575 619 627 710 721 725 758 802 842 867\n", - " 869 893 920].\n" - ] - } - ], + "outputs": [], "source": [ "solver = OptimizationSolver(qubo_problem)\n", "\n", @@ -407,7 +345,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "440201d4", "metadata": {}, "outputs": [], @@ -494,9 +432,9 @@ ], "metadata": { "kernelspec": { - "display_name": "lava_nc", + "display_name": "Python 3", "language": "python", - "name": "lava_nc" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -508,7 +446,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.6.3" }, "vscode": { "interpreter": { From d53ccd46bd8c1eab2268c523bda5299bf49c446f Mon Sep 17 00:00:00 2001 From: "Stratmann, Philipp" Date: Thu, 13 Jul 2023 11:09:32 +0200 Subject: [PATCH 4/9] make nebm-sa model choice explicit --- .../lib/optimization/solvers/generic/sub_process_models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lava/lib/optimization/solvers/generic/sub_process_models.py b/src/lava/lib/optimization/solvers/generic/sub_process_models.py index 140a4e16..07053c3c 100644 --- a/src/lava/lib/optimization/solvers/generic/sub_process_models.py +++ b/src/lava/lib/optimization/solvers/generic/sub_process_models.py @@ -49,6 +49,9 @@ def __init__(self, proc): * np.logical_not(np.eye(shape[1] if len(shape) == 2 else 0)), ) neuron_model = proc.hyperparameters.get("neuron_model", "nebm") + available_sa_models = ['nebm-sa', 'nebm-sa-balanced', + 'nebm-sa-refract-approx-unbalanced', + 'nebm-sa-refract-approx', 'nebm-sa-refract'] if neuron_model == "nebm": temperature = proc.hyperparameters.get("temperature", 1) refract = proc.hyperparameters.get("refract", 0) @@ -78,7 +81,7 @@ def __init__(self, proc): noise_precision=noise_precision, sustained_on_tau=on_tau, cost_diagonal=diagonal) - elif 'nebm-sa' in neuron_model: + elif neuron_model in available_sa_models: max_temperature = proc.hyperparameters.get("max_temperature", 10) min_temperature = proc.hyperparameters.get("min_temperature", 0) delta_temperature = proc.hyperparameters.get("delta_temperature", 1) From efb3ac658e5d846b44cfc834a75ff216221e67de Mon Sep 17 00:00:00 2001 From: "Stratmann, Philipp" Date: Wed, 30 Aug 2023 21:08:24 +0200 Subject: [PATCH 5/9] Correct local cost in SA --- .../solvers/generic/hierarchical_processes.py | 85 +++++++++---------- .../solvers/generic/nebm/process.py | 2 + .../solvers/generic/solution_finder/models.py | 7 +- .../solvers/generic/sub_process_models.py | 3 + 4 files changed, 53 insertions(+), 44 deletions(-) diff --git a/src/lava/lib/optimization/solvers/generic/hierarchical_processes.py b/src/lava/lib/optimization/solvers/generic/hierarchical_processes.py index 40c274c9..154ffee9 100644 --- a/src/lava/lib/optimization/solvers/generic/hierarchical_processes.py +++ b/src/lava/lib/optimization/solvers/generic/hierarchical_processes.py @@ -9,7 +9,6 @@ from lava.magma.core.process.ports.ports import InPort, OutPort from lava.magma.core.process.process import AbstractProcess, LogConfig from lava.magma.core.process.variable import Var -from numpy import typing as npt class ContinuousVariablesProcess(AbstractProcess): @@ -21,7 +20,7 @@ def __init__( shape: ty.Tuple[int, ...], problem: OptimizationProblem, backend, - hyperparameters: ty.Dict[str, ty.Union[int, npt.ArrayLike]] = None, + hyperparameters: ty.Dict[str, ty.Union[int, npty.ArrayLike]] = None, name: ty.Optional[str] = None, log_config: ty.Optional[LogConfig] = None, ) -> None: @@ -67,8 +66,8 @@ class DiscreteVariablesProcess(AbstractProcess): def __init__( self, shape: ty.Tuple[int, ...], - cost_diagonal: npt.ArrayLike = None, - hyperparameters: ty.Dict[str, ty.Union[int, npt.ArrayLike]] = None, + cost_diagonal: npty.ArrayLike = None, + hyperparameters: ty.Dict[str, ty.Union[int, npty.ArrayLike]] = None, name: ty.Optional[str] = None, log_config: ty.Optional[LogConfig] = None, ) -> None: @@ -77,7 +76,7 @@ def __init__( ---------- shape: tuple A tuple of the form (number of variables, domain size). - cost_diagonal: npt.ArrayLike + cost_diagonal: npty.ArrayLike The diagonal of the coefficient of the quadratic term on the cost function. hyperparameters: dict, optional @@ -180,7 +179,7 @@ def __init__( shape_out: ty.Tuple[int, ...], problem: OptimizationProblem, backend, - hyperparameters: ty.Dict[str, ty.Union[int, npt.ArrayLike]] = None, + hyperparameters: ty.Dict[str, ty.Union[int, npty.ArrayLike]] = None, name: ty.Optional[str] = None, log_config: ty.Optional[LogConfig] = None, ) -> None: @@ -246,36 +245,36 @@ class StochasticIntegrateAndFire(AbstractProcess): def __init__( self, *, - step_size: npt.ArrayLike, + step_size: npty.ArrayLike, shape: ty.Tuple[int, ...] = (1,), - init_state: npt.ArrayLike = 0, - noise_amplitude: npt.ArrayLike = 1, - noise_precision: npt.ArrayLike = 8, - sustained_on_tau: npt.ArrayLike = -3, - threshold: npt.ArrayLike = 10, - cost_diagonal: npt.ArrayLike = 0, + init_state: npty.ArrayLike = 0, + noise_amplitude: npty.ArrayLike = 1, + noise_precision: npty.ArrayLike = 8, + sustained_on_tau: npty.ArrayLike = -3, + threshold: npty.ArrayLike = 10, + cost_diagonal: npty.ArrayLike = 0, name: ty.Optional[str] = None, log_config: ty.Optional[LogConfig] = None, - init_value: npt.ArrayLike = 0, + init_value: npty.ArrayLike = 0, ) -> None: """ Parameters ---------- shape: tuple The shape of the set of dynamical systems to be created. - init_state: npt.ArrayLike, optional + init_state: npty.ArrayLike, optional The starting value of the state variable. - step_size: npt.ArrayLike, optional + step_size: npty.ArrayLike, optional a value to be added to the state variable at each timestep. - noise_amplitude: npt.ArrayLike, optional + noise_amplitude: npty.ArrayLike, optional The width/range for the stochastic perturbation to the state variable. A random number within this range will be added to the state variable at each timestep. - steps_to_fire: npt.ArrayLike, optional + steps_to_fire: npty.ArrayLike, optional After how many timesteps would the dynamical system fire and reset without stochastic perturbation. Note that if noise_amplitude > 0, the system will stochastically deviate from this value. - cost_diagonal: npt.ArrayLike, optional + cost_diagonal: npty.ArrayLike, optional The linear coefficients on the cost function of the optimization problem where this system will be used. name: str, optional @@ -342,18 +341,18 @@ class NEBMAbstract(AbstractProcess): def __init__( self, *, - temperature: npt.ArrayLike, + temperature: npty.ArrayLike, refract: ty.Optional[ty.Union[int, npty.NDArray]], refract_counter: ty.Optional[ty.Union[int, npty.NDArray]], shape: ty.Tuple[int, ...] = (1,), - init_state: npt.ArrayLike = 0, - input_duration: npt.ArrayLike = 6, - min_state: npt.ArrayLike = 1000, - min_integration: npt.ArrayLike = -1000, - cost_diagonal: npt.ArrayLike = 0, + init_state: npty.ArrayLike = 0, + input_duration: npty.ArrayLike = 6, + min_state: npty.ArrayLike = 1000, + min_integration: npty.ArrayLike = -1000, + cost_diagonal: npty.ArrayLike = 0, name: ty.Optional[str] = None, log_config: ty.Optional[LogConfig] = None, - init_value: npt.ArrayLike = 0, + init_value: npty.ArrayLike = 0, ) -> None: """ @@ -361,26 +360,26 @@ def __init__( ---------- shape: tuple The shape of the set of dynamical systems to be created. - init_state: npt.ArrayLike, optional + init_state: npty.ArrayLike, optional The starting value of the state variable. - temperature: npt.ArrayLike, optional + temperature: npty.ArrayLike, optional the temperature of the systems, defining the level of noise. - input_duration: npt.ArrayLike, optional + input_duration: npty.ArrayLike, optional Number of timesteps by which each input should be preserved. - min_state: npt.ArrayLike, optional + min_state: npty.ArrayLike, optional The minimum value for the state variable. The state variable will be truncated at this value if updating results in a lower value. - min_integration: npt.ArrayLike, optional + min_integration: npty.ArrayLike, optional The minimum value for the total input (addition of all valid inputs at a given timestep). The total input value will be truncated at this value if adding current and preserved inputs results in a lower value. - refract: npt.ArrayLike, optional + refract: npty.ArrayLike, optional Number of timesteps to wait after firing and reset before resuming updating. - refract_counter: npt.ArrayLike, optional + refract_counter: npty.ArrayLike, optional Number of timesteps to initially suppress a unit firing. - cost_diagonal: npt.ArrayLike, optional + cost_diagonal: npty.ArrayLike, optional The linear coefficients on the cost function of the optimization problem where this system will be used. name: str, optional @@ -450,6 +449,7 @@ class NEBMSimulatedAnnealingAbstract(AbstractProcess): def __init__( self, *, + cost_diagonal: npty.ArrayLike, max_temperature: int = 10, min_temperature: int = 0, delta_temperature: int = 1, @@ -458,12 +458,11 @@ def __init__( refract_scaling: int = 14, refract: ty.Optional[ty.Union[int, npty.NDArray]], shape: ty.Tuple[int, ...] = (1,), - init_state: npt.ArrayLike = 0, - min_integration: npt.ArrayLike = -1000, - cost_diagonal: npt.ArrayLike = 0, + init_state: npty.ArrayLike = 0, + min_integration: npty.ArrayLike = -1000, name: ty.Optional[str] = None, log_config: ty.Optional[LogConfig] = None, - init_value: npt.ArrayLike = 0, + init_value: npty.ArrayLike = 0, annealing_schedule: str = 'linear', neuron_model: str, ) -> None: @@ -473,19 +472,19 @@ def __init__( ---------- shape: tuple The shape of the set of dynamical systems to be created. - init_state: npt.ArrayLike, optional + init_state: npty.ArrayLike, optional The starting value of the state variable. - temperature: npt.ArrayLike, optional + temperature: npty.ArrayLike, optional the temperature of the systems, defining the level of noise. - min_integration: npt.ArrayLike, optional + min_integration: npty.ArrayLike, optional The minimum value for the total input (addition of all valid inputs at a given timestep). The total input value will be truncated at this value if adding current and preserved inputs results in a lower value. - refractory_period: npt.ArrayLike, optional + refractory_period: npty.ArrayLike, optional Number of timesteps to wait after firing and reset before resuming updating. - cost_diagonal: npt.ArrayLike, optional + cost_diagonal: npty.ArrayLike, optional The linear coefficients on the cost function of the optimization problem where this system will be used. name: str, optional diff --git a/src/lava/lib/optimization/solvers/generic/nebm/process.py b/src/lava/lib/optimization/solvers/generic/nebm/process.py index 2dd8c867..a5b91626 100644 --- a/src/lava/lib/optimization/solvers/generic/nebm/process.py +++ b/src/lava/lib/optimization/solvers/generic/nebm/process.py @@ -70,6 +70,7 @@ class NEBMSimulatedAnnealing(AbstractProcess): def __init__( self, *, + cost_diagonal: npty.ArrayLike, shape: ty.Tuple[int, ...], max_temperature: int, min_temperature: int, @@ -109,6 +110,7 @@ def __init__( delta_temperature=delta_temperature, steps_per_temperature=steps_per_temperature, refract=refract, + cost_diagonal=cost_diagonal, refract_scaling=refract_scaling, exp_temperature=exp_temperature, neuron_model=neuron_model, diff --git a/src/lava/lib/optimization/solvers/generic/solution_finder/models.py b/src/lava/lib/optimization/solvers/generic/solution_finder/models.py index d2f8b6d0..470b51e6 100644 --- a/src/lava/lib/optimization/solvers/generic/solution_finder/models.py +++ b/src/lava/lib/optimization/solvers/generic/solution_finder/models.py @@ -192,4 +192,9 @@ def _get_init_state( else: q_diag = cost_coefficients[2].init.diagonal() - return q_off_diag @ init_value + q_diag + # nebm-sa-refract neuron models have a different definition of state + neuron_model = hyperparameters.get("neuron_model", "nebm") + if neuron_model == 'nebm-sa-refract': + return q_off_diag @ init_value + else: + return q_off_diag @ init_value + q_diag diff --git a/src/lava/lib/optimization/solvers/generic/sub_process_models.py b/src/lava/lib/optimization/solvers/generic/sub_process_models.py index 16f1ff6a..47d38585 100644 --- a/src/lava/lib/optimization/solvers/generic/sub_process_models.py +++ b/src/lava/lib/optimization/solvers/generic/sub_process_models.py @@ -314,6 +314,7 @@ def __init__(self, proc): steps_per_temperature=steps_per_temperature, refract=refract, refract_scaling=refract_scaling, + cost_diagonal=diagonal, init_value=init_value, init_state=init_state, neuron_model=neuron_model, @@ -457,6 +458,7 @@ class NEBMSimulatedAnnealingAbstractModel(AbstractSubProcessModel): def __init__(self, proc): shape = proc.proc_params.get("shape", (1,)) + cost_diagonal = proc.proc_params.get("cost_diagonal") max_temperature = proc.proc_params.get("max_temperature", 10) min_temperature = proc.proc_params.get("min_temperature", 0) delta_temperature = proc.proc_params.get("delta_temperature", 1) @@ -477,6 +479,7 @@ def __init__(self, proc): exp_temperature=exp_temperature, steps_per_temperature=steps_per_temperature, refract_scaling=refract_scaling, + cost_diagonal=cost_diagonal, refract=refract, init_value=init_value, init_state=init_state, From 6af499450b83c39889d115c94c8fb2d54362f800 Mon Sep 17 00:00:00 2001 From: "Stratmann, Philipp" Date: Mon, 4 Sep 2023 18:22:29 +0200 Subject: [PATCH 6/9] Input validation --- src/lava/lib/optimization/problems/problems.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lava/lib/optimization/problems/problems.py b/src/lava/lib/optimization/problems/problems.py index 986615e5..d4ef580b 100644 --- a/src/lava/lib/optimization/problems/problems.py +++ b/src/lava/lib/optimization/problems/problems.py @@ -116,6 +116,7 @@ def validate_input(self, q): q: Quadratic coefficient of the cost function. """ + m, n = q.shape if m != n: raise ValueError("q matrix is not a square matrix.") @@ -123,6 +124,11 @@ def validate_input(self, q): raise NotImplementedError( "Non integer q matrices are not supported yet." ) + # matrix must be symmetric for current implementation + if not np.allclose(q, q.T, rtol=1e-05, atol=1e-08): + raise NotImplementedError( + "Only symmetric matrixes are currently supported." + ) def verify_solution(self, solution): raise NotImplementedError From a8168d3d186e40e2a49ac78bf3468631492b95b6 Mon Sep 17 00:00:00 2001 From: "Stratmann, Philipp" Date: Mon, 4 Sep 2023 18:24:53 +0200 Subject: [PATCH 7/9] Input validation --- src/lava/lib/optimization/problems/problems.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lava/lib/optimization/problems/problems.py b/src/lava/lib/optimization/problems/problems.py index d4ef580b..e9882aed 100644 --- a/src/lava/lib/optimization/problems/problems.py +++ b/src/lava/lib/optimization/problems/problems.py @@ -65,8 +65,8 @@ def __init__(self, q: npt.ArrayLike): Parameters ---------- - q: squared Q matrix defining the QUBO problem over a binary - vector x as: minimize x^T*Q*x. + q: squared, symmetric, int Q matrix defining the QUBO problem over a + binary vector x as: minimize x^T*Q*x. """ super().__init__() self.validate_input(q) @@ -109,7 +109,7 @@ def evaluate_cost(self, solution: np.ndarray) -> int: return int(self._q_cost(solution)) def validate_input(self, q): - """Validate the cost coefficient is a square matrix. + """Validate that cost coefficient is a square, symmetric, int matrix. Parameters ---------- From ddf16d2e1c0835750c53585cd3579ad8d48f8ecf Mon Sep 17 00:00:00 2001 From: "Stratmann, Philipp" Date: Mon, 4 Sep 2023 19:40:00 +0200 Subject: [PATCH 8/9] added warning to QUBO users of CPU --- .../optimization/solvers/generic/solver.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/lava/lib/optimization/solvers/generic/solver.py b/src/lava/lib/optimization/solvers/generic/solver.py index 79b55998..c99558d8 100644 --- a/src/lava/lib/optimization/solvers/generic/solver.py +++ b/src/lava/lib/optimization/solvers/generic/solver.py @@ -6,6 +6,7 @@ import numpy as np from dataclasses import dataclass +import warnings from lava.lib.optimization.solvers.generic.qp.models import ( PyPIneurPIPGeqModel, @@ -33,6 +34,7 @@ from lava.proc.monitor.process import Monitor from lava.utils.profiler import Profiler +from lava.lib.optimization.problems.problems import QUBO from lava.lib.optimization.problems.problems import OptimizationProblem from lava.lib.optimization.solvers.generic.builder import SolverProcessBuilder from lava.lib.optimization.solvers.generic.cost_integrator.process import ( @@ -271,6 +273,7 @@ def solve(self, config: SolverConfig = SolverConfig()) -> SolverReport: report: SolverReport An object containing all the data generated by the execution. """ + self._validate_input_solve(config) run_condition, run_cfg = self._prepare_solver(config) self.solver_process.run(condition=run_condition, run_cfg=run_cfg) best_state, best_cost, best_timestep = self._get_results(config) @@ -535,3 +538,26 @@ def _get_and_decode_continuous_vars(self, idx: int): self.solver_process.finders[idx].variables_assignment.get() ) return solution + + def _validate_input_solve(self, config: SolverConfig) -> None: + """ + Validates the user input provided to the solve() method of the + OptimizationSolver. + + Parameters + ---------- + config: SolverConfig + Solver configuration used. + + Raises + ---------- + """ + + if (config.backend in CPUS) and isinstance(self.problem, QUBO): + warnings.warn(f"Please note that we are currently advancing only " + f"the Loihi 2 backend of the QUBO solver. Until " + f"further notice, the CPU implementation uses " + f"an outdated algorithm. We thus recommend running " + f"your workloads on Loihi 2 to leverage the latest " + f"algorithms for optimal latency, energy " + f"consumption, and solution accuracy. ") From 3387d0b499b2c24f5f8eb1ec83eab5f2dc9960bc Mon Sep 17 00:00:00 2001 From: "Stratmann, Philipp" Date: Tue, 5 Sep 2023 14:53:48 +0200 Subject: [PATCH 9/9] fix unit test --- tests/lava/lib/optimization/utils/test_report_analyzer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/lava/lib/optimization/utils/test_report_analyzer.py b/tests/lava/lib/optimization/utils/test_report_analyzer.py index 1dee6c09..f4a34d69 100644 --- a/tests/lava/lib/optimization/utils/test_report_analyzer.py +++ b/tests/lava/lib/optimization/utils/test_report_analyzer.py @@ -15,9 +15,9 @@ def prepare_problem_and_report(): np.random.seed(0) size = 5 timeout = 100 - problem = QUBO( - q=np.random.randint(0, 20, size=(size, size), dtype=np.int32) - ) + q = np.random.randint(0, 20, size=(size, size), dtype=np.int32) + q_symm = ((q + q.T) / 2).astype(int) + problem = QUBO(q=q_symm) states = np.random.randint(0, 2, size=(timeout, size), dtype=np.int32) costs = list(map(problem.evaluate_cost, states)) report = SolverReport(