diff --git a/pyproject.toml b/pyproject.toml index 2d5a4816..7db33a4a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,6 +60,7 @@ scipy = "^1.10.1" nbformat = "^5.7.1" seaborn = "^0.12.2" + [tool.poetry.dev-dependencies] bandit = "1.7.4" coverage = "^6.3.2" diff --git a/src/lava/lib/optimization/apps/clustering/problems.py b/src/lava/lib/optimization/apps/clustering/problems.py new file mode 100644 index 00000000..96354138 --- /dev/null +++ b/src/lava/lib/optimization/apps/clustering/problems.py @@ -0,0 +1,157 @@ +# Copyright (C) 2023 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +import networkx as ntx +import numpy as np +import typing as ty + + +class ClusteringProblem: + """Problem specification for a clustering problem. + + N points need to be clustered into M clusters. + + The cluster centers are *given*. Clustering is done to assign cluster IDs + to points based on the closest cluster centers. + """ + def __init__(self, + point_coords: ty.List[ty.Tuple[int, int]], + center_coords: ty.Union[int, ty.List[ty.Tuple[int, int]]], + edges: ty.Optional[ty.List[ty.Tuple[int, int]]] = None): + """ + Parameters + ---------- + point_coords : list(tuple(int, int)) + A list of integer tuples corresponding to the coordinates of + points to be clustered. + center_coords : list(tuple(int, int)) + A list of integer tuples corresponding to the coordinates of + cluster-centers. + edges : (Optional) list(tuple(int, int, float)) + An optional list of edges connecting points and cluster centers, + given as a list of triples (ID1, ID2, weight). See the note + below for ID-scheme. If None, assume all-to-all connectivity + between points, weighted by their pairwise distances. + + Notes + ----- + IDs 1 to M correspond to cluster centers and (M+1) to (M+N) correspond + to the points to be clustered. + """ + super().__init__() + self._point_coords = point_coords + self._center_coords = center_coords + self._num_points = len(self._point_coords) + self._num_clusters = len(self._center_coords) + self._cluster_ids = list(np.arange(1, self._num_clusters + 1)) + self._point_ids = list(np.arange( + self._num_clusters + 1, self._num_clusters + self._num_points + 1)) + self._points = dict(zip(self._point_ids, self._point_coords)) + self._cluster_centers = dict(zip(self._cluster_ids, + self._center_coords)) + if edges: + self._edges = edges + else: + self._edges = [] + + self._problem_graph = None + + @property + def points(self): + return self._points + + @points.setter + def points(self, points: ty.Dict[int, ty.Tuple[int, int]]): + self._points = points + + @property + def point_ids(self): + return self._point_ids + + @property + def point_coords(self): + return self._point_coords + + @property + def num_points(self): + return self._num_points + + @property + def edges(self): + return self._edges + + @property + def cluster_centers(self): + return self._cluster_centers + + @cluster_centers.setter + def cluster_centers(self, cluster_centers: ty.Dict[int, ty.Tuple[int, + int]]): + self._cluster_centers = cluster_centers + + @property + def cluster_ids(self): + return self._cluster_ids + + @property + def center_coords(self): + return self._center_coords + + @property + def num_clusters(self): + return self._num_clusters + + @property + def problem_graph(self): + """NetworkX problem graph is created and returned. + + If edges are specified, they are taken into account. + Returns + ------- + A graph object corresponding to the problem. + """ + if not self._problem_graph: + self._generate_problem_graph() + return self._problem_graph + + def _generate_problem_graph(self): + if len(self.edges) > 0: + gph = ntx.DiGraph() + # Add the nodes to be visited + gph.add_nodes_from(self.point_ids) + # If there are user-provided edges, add them between the nodes + gph.add_edges_from(self.edges) + else: + gph = ntx.complete_graph(self.point_ids, create_using=ntx.DiGraph()) + + node_type_dict = dict(zip(self.point_ids, + ["Point"] * len(self.point_ids))) + # Associate node type as "Node" and node coordinates as attributes + ntx.set_node_attributes(gph, node_type_dict, name="Type") + ntx.set_node_attributes(gph, self.points, name="Coordinates") + + # Add vehicles as nodes + gph.add_nodes_from(self.cluster_ids) + # Associate node type as "Vehicle" and vehicle coordinates as attributes + cluster_center_type_dict = dict(zip(self.cluster_ids, + ["Cluster Center"] * len( + self.cluster_ids))) + ntx.set_node_attributes(gph, cluster_center_type_dict, name="Type") + ntx.set_node_attributes(gph, self.cluster_centers, name="Coordinates") + + # Add edges from initial vehicle positions to all nodes (oneway edges) + for cid in self.cluster_ids: + for pid in self.points: + gph.add_edge(cid, pid) + + # Compute Euclidean distance along all edges and assign them as edge + # weights + # ToDo: Replace the loop with independent distance matrix computation + # and then assign the distances as attributes + for edge in gph.edges.keys(): + gph.edges[edge]["cost"] = np.linalg.norm( + np.array(gph.nodes[edge[1]]["Coordinates"]) - np.array( + gph.nodes[edge[0]]["Coordinates"])) + + self._problem_graph = gph diff --git a/src/lava/lib/optimization/apps/clustering/solver.py b/src/lava/lib/optimization/apps/clustering/solver.py new file mode 100644 index 00000000..5c550fc1 --- /dev/null +++ b/src/lava/lib/optimization/apps/clustering/solver.py @@ -0,0 +1,202 @@ +# Copyright (C) 2023 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + + +import numpy as np +from pprint import pprint +from dataclasses import dataclass + +from lava.lib.optimization.problems.problems import QUBO +from lava.lib.optimization.solvers.generic.solver import OptimizationSolver, \ + SolverReport +from lava.lib.optimization.apps.clustering.problems import ClusteringProblem +from lava.lib.optimization.apps.clustering.utils.q_matrix_generator import \ + QMatrixClust + +import typing as ty +import numpy.typing as npty + +from lava.magma.core.resources import ( + CPU, + Loihi2NeuroCore, + NeuroCore, +) +from lava.lib.optimization.solvers.generic.solver import SolverConfig + +BACKENDS = ty.Union[CPU, Loihi2NeuroCore, NeuroCore, str] +CPUS = [CPU, "CPU"] +NEUROCORES = [Loihi2NeuroCore, NeuroCore, "Loihi2"] + +BACKEND_MSG = f""" was requested as backend. However, +the solver currently supports only Loihi 2 and CPU backends. +These can be specified by calling solve with any of the following: +backend = "CPU" +backend = "Loihi2" +backend = CPU +backend = Loihi2NeuroCore +backend = NeuroCoreS +The explicit resource classes can be imported from +lava.magma.core.resources""" + + +@dataclass +class ClusteringConfig(SolverConfig): + """Solver configuration for VRP solver. + + Parameters + ---------- + core_solver : CoreSolver + Core algorithm that solves a given VRP. Possible values are + CoreSolver.VRPY_CPU or CoreSolver.LAVA_QUBO. + + Notes + ----- + VRPConfig class inherits from `SolverConfig` class at + `lava.lib.optimization.solvers.generic.solver`. Please refer to the + documentation for `SolverConfig` to know more about other arguments that + can be passed. + """ + + do_distance_sparsification: bool = False + sparsification_algo: str = "cutoff" + max_dist_cutoff_fraction: float = 1.0 + profile_q_mat_gen: bool = False + only_gen_q_mat: bool = False + + +@dataclass +class ClusteringSolution: + """Clustering solution holds two dictionaries: + - `clustering_id_map` holds a map from cluster center ID to a list + of point IDs + - `clustering_coords_map` holds a map from the cluster center + coordinates to the point coordinates + """ + clustering_id_map: dict = None + clustering_coords_map: dict = None + + +class ClusteringSolver: + """Solver for clustering problems, given cluster centers. + """ + def __init__(self, clp: ClusteringProblem): + self.problem = clp + self._solver = None + self._profiler = None + self.dist_sparsity = 0. + self.dist_proxy_sparsity = 0. + self.q_gen_time = 0. + self.q_shape = None + self.raw_solution = None + self.solution = ClusteringSolution() + + @property + def solver(self): + return self._solver + + @property + def profiler(self): + return self._profiler + + def solve(self, scfg: ClusteringConfig = ClusteringConfig()): + """ + Solve a clustering problem using a given solver configuration. + + Parameters + ---------- + scfg (ClusteringConfig) : Configuration parameters. + + Notes + ----- + The solver object also stores profiling data as its attributes. + """ + # 1. Generate Q matrix for clustering + node_list_for_clustering = self.problem.center_coords + \ + self.problem.point_coords + # number of binary variables = total_num_nodes * num_clusters + mat_size = len(node_list_for_clustering) * self.problem.num_clusters + q_mat_obj = QMatrixClust( + node_list_for_clustering, + num_clusters=self.problem.num_clusters, + lambda_dist=1, + lambda_points=100, + lambda_centers=100, + fixed_pt=True, + fixed_pt_range=(-128, 127), + clust_dist_sparse_params={ + "do_sparse": scfg.do_distance_sparsification, + "algo": scfg.sparsification_algo, + "max_dist_cutoff_fraction": scfg.max_dist_cutoff_fraction}, + profile_mat_gen=scfg.profile_q_mat_gen) + q_mat = q_mat_obj.matrix.astype(int) + self.dist_sparsity = q_mat_obj.dist_sparsity + self.dist_proxy_sparsity = q_mat_obj.dist_proxy_sparsity + if scfg.profile_q_mat_gen: + self.q_gen_time = q_mat_obj.time_to_gen_mat + self.q_shape = q_mat.shape + # 2. Call Lava QUBO solvers + if not scfg.only_gen_q_mat: + prob = QUBO(q=q_mat) + self._solver = OptimizationSolver(problem=prob) + hparams = { + 'neuron_model': 'nebm-sa-refract', + 'refract': 10, + 'refract_scaling': 6, + 'init_state': np.random.randint(0, 2, size=(mat_size,)), + 'min_temperature': 1, + 'max_temperature': 5, + 'steps_per_temperature': 200 + } + if not scfg.hyperparameters: + scfg.hyperparameters.update(hparams) + report: SolverReport = self._solver.solve(config=scfg) + if report.profiler: + self._profiler = report.profiler + pprint(f"Clustering execution" + f" took {np.sum(report.profiler.execution_time)}s") + # 3. Post process the clustering solution + self.raw_solution: npty.NDArray = \ + report.best_state.reshape((self.problem.num_clusters, + len(node_list_for_clustering))).T + else: + self.raw_solution = -1 * np.ones((self.problem.num_clusters, + len(node_list_for_clustering))).T + + self.post_process_sol() + + def post_process_sol(self): + """ + Post-process the clustering solution returned by `solve()`. + + The clustering solution returned by the `solve` method is a 2-D + binary numpy array, wherein the columns correspond to clusters and + rows correspond to points or cluster centers. entry (i, j) is 1 if + point/cluster center 'i' belongs to cluster 'j'. + """ + + coord_list = (self.problem.center_coords + self.problem.point_coords) + id_map = {} + coord_map = {} + for j, col in enumerate(self.raw_solution.T): + node_idxs = np.nonzero(col) + # ID of "this" cluster is the only nonzero row in this column + # from row 0 to row 'num_clusters' - 1 + this_cluster_id = \ + (node_idxs[0][node_idxs[0] < self.problem.num_clusters] + 1) + if len(this_cluster_id) != 1: + raise ValueError(f"More than one cluster center found in " + f"{j}th cluster. Clustering might not have " + f"converged to a valid solution.") + node_idxs = node_idxs[0][node_idxs[0] >= self.problem.num_clusters] + id_map.update({this_cluster_id.item(): (node_idxs + 1).tolist()}) + + this_center_coords = np.array(coord_list)[this_cluster_id - 1, :] + point_coords_this_cluster = np.array(coord_list)[node_idxs, :] + point_coords_this_cluster = \ + [tuple(point) for point in point_coords_this_cluster.tolist()] + coord_map.update({ + tuple(this_center_coords.flatten()): point_coords_this_cluster}) + + self.solution.clustering_id_map = id_map + self.solution.clustering_coords_map = coord_map diff --git a/src/lava/lib/optimization/apps/clustering/utils/q_matrix_generator.py b/src/lava/lib/optimization/apps/clustering/utils/q_matrix_generator.py new file mode 100644 index 00000000..ee13dec8 --- /dev/null +++ b/src/lava/lib/optimization/apps/clustering/utils/q_matrix_generator.py @@ -0,0 +1,269 @@ +# Copyright (C) 2023 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +import time +import copy + +import numpy as np +import numpy.typing as npty +from scipy.spatial import distance + + +class QMatrixClust: + """Class to generate Q matrix for a clustering problem framed as a QUBO + problem. The matrix values are computed based on the Euclidean distance + between the nodes assuming all-to-all connectivity.""" + + def __init__( + self, + input_nodes, + num_clusters=1, + lambda_dist=1, + lambda_points=100, + lambda_centers=100, + fixed_pt=False, + fixed_pt_range=(0, 127), + clust_dist_sparse_params=None, + profile_mat_gen=False + ) -> None: + """The Constructor of the class generates a Q matrix for clustering + and assigns it the class variables for the matrix. Calls private + functions to initialize Q. The matrix Q is considered to have all-to-all + connectivity between the nodes that are specified. + + Args: + input_nodes (list): Input to matrix generator functions + containing a list of nodes specified as tuples. + + num_clusters (int): Number of clusters to be formed after + clustering is done. The first `num_clusters` nodes in + `input_nodes` correspond to positions of cluster centers. Defaults + to 1. + + lambda_dist (float, optional): relative weight of the + pairwise distance term in the QUBO energy function. Default is 1. + + lambda_points (float, optional): relative weight (in the QUBO + energy function) of the constraint that each point should belong + to exactly one cluster. Higher values signify "hardness" of the + constraint. Default is 100. + + lambda_centers (float, optional): relative weight (in the QUBO + energy function) of the constraint that each cluster center + should belong to exactly one cluster. Higher values signify + "hardness of the constraint". Default is 100. + + fixed_pt (bool, optional): Specifies if the Q matrix should + ultimately be rounded down to integer. If `True`, stochastic + rounding to integer range of Loihi 2 is performed. Defaults to + `False`. + + fixed_pt_range (tuple, optional): Specifies the absolute + value of min and max values that the Q matrix can have if + `fixed_pt =True`. + + clust_dist_sparse_params (dict, optional) : Dictionary of + parameters for sparsification of the distance matrix used in + clustering of the waypoint and the vehicle positions. The + parameters are: + - do_sparse (bool) : a toggle to enable/disable sparsification + (default is False, i.e., disable sparsification) + - algo (string) : the algorithm used for sparsification ( + default is "cutoff", which imposes a maximum cutoff distance + on the distance matrix and subtracts it from the matrix. + Another option is "edge-prune", which prunes edges longer + than a cutoff from the connectivity graph of the entire problem + - max_dist_cutoff_fraction (float) : a fraction between 0 and 1, + which multiplies the max of the distance matrix, and the + result is used as the cutoff in both algorithms. + + profile_mat_gen (bool, optional): Specifies if Q matrix + generation needs to be timed using python's time.time() + """ + if not clust_dist_sparse_params: + self.clust_dist_sparse_params = {"do_sparse": False, + "algo": "cutoff", + "max_dist_cutoff_fraction": 1.0} + else: + self.clust_dist_sparse_params = \ + copy.deepcopy(clust_dist_sparse_params) + self.fixed_pt = fixed_pt + self.min_fixed_pt_mant = fixed_pt_range[0] + self.max_fixed_pt_mant = fixed_pt_range[1] + self.num_clusters = num_clusters + self.max_cutoff_frac = self.clust_dist_sparse_params[ + "max_dist_cutoff_fraction"] + self.dist_sparsity = 0. + self.dist_proxy_sparsity = 0. + self.time_to_gen_mat = 0. + + start_time = time.time() + self.matrix, self.dist_sparsity, self.dist_proxy_sparsity = \ + self._gen_Q_matrix(input_nodes, lambda_dist, lambda_points, + lambda_centers) + if profile_mat_gen: + self.time_to_gen_mat = time.time() - start_time + + @staticmethod + def _compute_matrix_sparsity(mat: npty.NDArray): + return 1 - (np.count_nonzero(mat) / np.prod(mat.shape)) + + def _sparsify_dist_using_cutoff(self, dist): + # The following variants can be used as a proxy for Euclidean + # distance, which may help in sparsifying the Q matrix. + # Dist_proxy = np.zeros_like(Dist) + # inv_dist_mtrx = (1 / Dist) + # log_dist_mtrx = np.log(Dist) + # Dist_proxy[Dist <= 1] = Dist[Dist <= 1] + # Dist_proxy[Dist > 1] = 2 - log_dist_mtrx[Dist > 1] + # Dist_proxy = 100 * (1 - np.exp(-Dist/100)) + if self.max_cutoff_frac == 1.0: + return dist + max_dist_cutoff = self.max_cutoff_frac * np.max(dist) + dist_proxy = dist.copy() + dist_proxy[dist_proxy >= max_dist_cutoff] = max_dist_cutoff + dist_proxy = np.around(dist_proxy - max_dist_cutoff, 2) + return dist_proxy + + def _sparsify_dist_using_edge_pruning(self, dist): + if self.max_cutoff_frac == 1.0: + return dist + dist_proxy = dist.copy() + num_nodes = dist.shape[0] + max_per_row = np.max(dist_proxy, axis=1) + max_per_row = max_per_row.reshape((num_nodes, 1)) + max_per_row = np.tile(max_per_row, (1, num_nodes)) + cut_off = self.max_cutoff_frac * max_per_row + + idxmat = dist_proxy >= cut_off + dist_proxy[idxmat] = cut_off[idxmat] + dist_proxy = dist_proxy - cut_off + + # Zero-out distances between vehicles. Constraints will later take + # care of this + dist_proxy[0:self.num_clusters, 0:self.num_clusters] = np.zeros(( + self.num_clusters, self.num_clusters)) + return dist_proxy + + def _sparsify_dist(self, dist): + if self.clust_dist_sparse_params["algo"] == "cutoff": + return self._sparsify_dist_using_cutoff(dist) + elif self.clust_dist_sparse_params["algo"] == "edge_prune": + return self._sparsify_dist_using_edge_pruning(dist) + else: + raise ValueError("Invalid algorithm chosen for sparsification of " + "the distance matrix in Q-matrix computation for " + "the clustering stage. Choose one of 'cutoff' " + "and 'edge_prune'.") + + def _gen_Q_matrix( + self, input_nodes, lambda_dist, lambda_points, lambda_centers + ): + """Return the Q matrix that sets up the QUBO for a clustering + problem. + + Args: + input_nodes (list[tuples]): Input to matrix generator functions + containing a list of nodes specified as tuples. First + `num_vehicles` tuples correspond to the vehicle nodes. + + lambda_dist (float, optional): relative weight of the + pairwise distance term in the QUBO energy function. + + lambda_points (float, optional): relative weight (in the QUBO + energy function) of the constraint that each point should belong + to exactly one cluster. Higher values signify "hardness" of the + constraint. + + lambda_centers (float, optional): relative weight (in the QUBO + energy function) of the constraint that each cluster center + should belong to exactly one cluster. Higher values signify + "hardness of the constraint". + + Returns: + np.ndarray: Returns a 2 dimension connectivity matrix of size n*n + """ + Dist = distance.cdist(input_nodes, input_nodes, "euclidean") + # Normalize the distance matrix + dist_sparsity = self._compute_matrix_sparsity(Dist) + dist_proxy_sparsity = dist_sparsity + num_nodes = Dist.shape[0] + if self.clust_dist_sparse_params["do_sparse"]: + Dist = self._sparsify_dist(Dist) + dist_proxy_sparsity = self._compute_matrix_sparsity(Dist) + + # TODO: Introduce cut-off distancing later to sparsify distance + # matrix later using one of the proxies above. + # Distance matrix for the encoding + dist_mtrx = np.kron(np.eye(self.num_clusters), Dist) + # Vehicles can only belong to one cluster + # Off-diagonal elements are two, populating matrix with 2 + centers_mat_off_diag = 2 * np.ones( + (self.num_clusters, self.num_clusters) + ) + + # Diag elements of -3 to subtract from earlier matrix and get -1 in the + # diagonal later + centers_mat_diag = -3 * np.eye(self.num_clusters, self.num_clusters) + + # Off-diag elements are two, diagonal elements are -1 + centers_mat = centers_mat_off_diag + centers_mat_diag + + # Only vehicle waypoints get affected by this constraint + cnstrnt_centers = np.pad(centers_mat, ( + (0, num_nodes - self.num_clusters), + (0, num_nodes - self.num_clusters), + ), "constant", constant_values=(0),) + cnstrnt_centers_blck = np.kron( + np.eye(self.num_clusters), cnstrnt_centers + ) + + # waypoints can only belong to one cluster + # Off-diagonal elements are two, populating matrix with 2 + # Encoding waypoint constraints here + qubo_encdng_size = Dist.shape[0] * self.num_clusters + + point_cnstrnt_diag = np.eye(qubo_encdng_size, qubo_encdng_size) + + point_cnstrnt_vector = np.array([1 if i % num_nodes == 0 else 0 for i + in range(qubo_encdng_size)]) + point_constraint_off_diag = point_cnstrnt_vector + for i in range(qubo_encdng_size - 1): + point_constraint_off_diag = np.vstack( + (point_constraint_off_diag, np.roll(point_cnstrnt_vector, + i + 1),)) + cnstrnt_points_blck = \ + -3 * point_cnstrnt_diag + 2 * point_constraint_off_diag + # Combine all matrices to get final Q matrix + Q = (lambda_dist * dist_mtrx + + lambda_centers * cnstrnt_centers_blck + + lambda_points * cnstrnt_points_blck) + if self.fixed_pt: + Q = self._stochastic_rounding(Q) + return Q, dist_sparsity, dist_proxy_sparsity + + def _stochastic_rounding(self, tensor): + """A function to rescale and stochastically round tensor to fixed + point values compatible with Unsigned Mode on Loihi 2. + + Args: + tensor (np.ndarray): floating-point tensor + + Returns: + (np.ndarray): fixed-point version of tensor that is passed as input + """ + tensor_max = np.max(np.abs(tensor)) + tensor_min = np.min(np.abs(tensor)) + + # Get sign mask of tensor to furnish signs for matrix later + tensor_sign_mask = np.sign(tensor) + scaled_tensor = \ + ((self.max_fixed_pt_mant - self.min_fixed_pt_mant) + * (np.abs(tensor) - tensor_min) + / (tensor_max - tensor_min)) + stchstc_rnded_tensor = \ + np.floor(scaled_tensor + + np.random.rand(tensor.shape[0], tensor.shape[1])) + stchstc_rnded_tensor *= tensor_sign_mask + return stchstc_rnded_tensor diff --git a/src/lava/lib/optimization/apps/tsp/problems.py b/src/lava/lib/optimization/apps/tsp/problems.py new file mode 100644 index 00000000..8dede9a4 --- /dev/null +++ b/src/lava/lib/optimization/apps/tsp/problems.py @@ -0,0 +1,127 @@ +# Copyright (C) 2023 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +import networkx as ntx +import numpy as np +import typing as ty + + +class TravellingSalesmanProblem: + """Travelling Salesman Problem specification. + + N customer nodes need to be visited by a travelling salesman, + while minimizing the overall distance of the traversal. + """ + def __init__(self, + waypt_coords: ty.List[ty.Tuple[int, int]], + starting_pt: ty.Tuple[int, int], + edges: ty.Optional[ty.List[ty.Tuple[int, int]]] = None): + """ + Parameters + ---------- + node_coords : list(tuple(int, int)) + A list of integer tuples corresponding to node coordinates. + Nodes signify the "customers" in a VRP, which need to be visited + by the vehicles. + vehicle_coords : list(tuple(int, int)) + A list of integer tuples corresponding to the initial vehicle + coordinates. If the length of the list is 1, then it is + assumed that all vehicles begin from the same depot. + edges: (Optional) list(tuple(int, int)) + An optional list of edges connecting nodes, given as a list of + node ID pairs. If None provided, assume all-to-all connectivity + between nodes. + + Notes + ----- + The vehicle IDs and node IDs are assigned serially. The IDs 1 to M + correspond to vehicles and (M+1) to (M+N) correspond to nodes to be + visited by the vehicles. + """ + super().__init__() + self._waypt_coords = waypt_coords + self._starting_pt_coords = starting_pt + self._num_waypts = len(self._waypt_coords) + self._starting_pt_id = 1 + self._waypt_ids = list(np.arange(2, self._num_waypts + 2)) + self._nodes = {self._starting_pt_id: self._starting_pt_coords} + self._nodes.update(dict(zip(self._waypt_ids, self._waypt_coords))) + if edges: + self._edges = edges + else: + self._edges = [] + + self._problem_graph = None + + @property + def nodes(self): + return self._nodes + + @nodes.setter + def nodes(self, nodes: ty.Dict[int, ty.Tuple[int, int]]): + self._nodes = nodes + + @property + def node_ids(self): + return list(self._nodes.keys()) + + @property + def node_coords(self): + return list(self._nodes.values()) + + @property + def num_nodes(self): + return len(list(self._nodes.keys())) + + @property + def edges(self): + return self._edges + + @property + def waypt_coords(self): + return self._waypt_coords + + @property + def waypt_ids(self): + return self._waypt_ids + + @property + def num_waypts(self): + return len(self._waypt_coords) + + @property + def problem_graph(self): + """NetworkX problem graph is created and returned. + + If edges are specified, they are taken into account. + Returns + ------- + A graph object corresponding to the problem. + """ + if not self._problem_graph: + self._generate_problem_graph() + return self._problem_graph + + def _generate_problem_graph(self): + if len(self.edges) > 0: + gph = ntx.DiGraph() + # Add the nodes to be visited + gph.add_nodes_from(self.node_ids) + # If there are user-provided edges, add them between the nodes + gph.add_edges_from(self.edges) + else: + gph = ntx.complete_graph(self.node_ids, create_using=ntx.DiGraph()) + + ntx.set_node_attributes(gph, self.nodes, name="Coordinates") + + # Compute Euclidean distance along all edges and assign them as edge + # weights + # ToDo: Replace the loop with independent distance matrix computation + # and then assign the distances as attributes + for edge in gph.edges.keys(): + gph.edges[edge]["cost"] = np.linalg.norm( + np.array(gph.nodes[edge[1]]["Coordinates"]) - np.array( + gph.nodes[edge[0]]["Coordinates"])) + + self._problem_graph = gph diff --git a/src/lava/lib/optimization/apps/tsp/solver.py b/src/lava/lib/optimization/apps/tsp/solver.py new file mode 100644 index 00000000..8e734283 --- /dev/null +++ b/src/lava/lib/optimization/apps/tsp/solver.py @@ -0,0 +1,157 @@ +# Copyright (C) 2023 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +import enum +from pprint import pprint + +import numpy as np +import networkx as ntx +import typing as ty +from typing import Tuple, Dict, List +import numpy.typing as npty +from dataclasses import dataclass + +from lava.lib.optimization.problems.problems import QUBO +from lava.lib.optimization.solvers.generic.solver import OptimizationSolver, \ + SolverReport +from lava.lib.optimization.apps.tsp.problems import TravellingSalesmanProblem +from lava.lib.optimization.apps.tsp.utils.q_matrix_generator import QMatrixTSP + +from lava.magma.core.resources import ( + CPU, + Loihi2NeuroCore, + NeuroCore, +) +from lava.lib.optimization.solvers.generic.solver import SolverConfig + +BACKENDS = ty.Union[CPU, Loihi2NeuroCore, NeuroCore, str] +CPUS = [CPU, "CPU"] +NEUROCORES = [Loihi2NeuroCore, NeuroCore, "Loihi2"] + +BACKEND_MSG = f""" was requested as backend. However, +the solver currently supports only Loihi 2 and CPU backends. +These can be specified by calling solve with any of the following: +backend = "CPU" +backend = "Loihi2" +backend = CPU +backend = Loihi2NeuroCore +backend = NeuroCoreS +The explicit resource classes can be imported from +lava.magma.core.resources""" + + +@dataclass +class TSPConfig(SolverConfig): + """Solver configuration for VRP solver. + + Parameters + ---------- + core_solver : CoreSolver + Core algorithm that solves a given VRP. Possible values are + CoreSolver.VRPY_CPU or CoreSolver.LAVA_QUBO. + + Notes + ----- + VRPConfig class inherits from `SolverConfig` class at + `lava.lib.optimization.solvers.generic.solver`. Please refer to the + documentation for `SolverConfig` to know more about other arguments that + can be passed. + """ + + profile_q_mat_gen: bool = False + only_gen_q_mat: bool = False + + +@dataclass +class TSPSolution: + """TSP solution holds two lists: + - `solution_path_ids` holds the ordered list of IDs of the waypoints, + which forms the path obtained from the QUBO solution. It begins and + ends with `1`, the ID of the salesman node. + - `solution_path_coords` holds the ordered list of tuples, which are + the coordinates of the waypoints. + """ + solution_path_ids: list = None + solution_path_coords: list = None + + +class TSPSolver: + """Solver for vehicle routing problems. + """ + def __init__(self, tsp: TravellingSalesmanProblem): + self.problem = tsp + self._solver = None + self._profiler = None + self.q_gen_time = 0. + self.raw_solution = None + self.solution = TSPSolution() + + @property + def solver(self): + return self._solver + + @property + def profiler(self): + return self._profiler + + def solve(self, scfg: TSPConfig = TSPConfig()): + """ + Solve a TSP using a given solver configuration. + + Parameters + ---------- + scfg (TSPConfig) : Configuration parameters. + + """ + q_matsize = self.problem.num_waypts ** 2 + q_mat_obj = QMatrixTSP( + self.problem.waypt_coords, + lamda_dist=1, + lamda_cnstrt=100, + fixed_pt=False, + fixed_pt_range=(-128, 127), + profile_mat_gen=scfg.profile_q_mat_gen + ) + q_mat = q_mat_obj.matrix.astype(int) + if scfg.profile_q_mat_gen: + self.q_gen_time = q_mat_obj.time_to_gen_mat + if not scfg.only_gen_q_mat: + tsp = QUBO(q=q_mat) + tsp_solver = OptimizationSolver(problem=tsp) + hparams = { + 'neuron_model': 'nebm-sa-refract', + 'refract': 50, + 'refract_scaling': 6, + 'init_state': np.random.randint(0, 2, size=(q_matsize,)), + 'min_temperature': 1, + 'max_temperature': 5, + 'steps_per_temperature': 200 + } + if not scfg.hyperparameters: + scfg.hyperparameters.update(hparams) + report: SolverReport = tsp_solver.solve(config=scfg) + if report.profiler: + self._profiler = report.profiler + pprint(f"TSP execution took" + f" {np.sum(report.profiler.execution_time)}s") + self.raw_solution: npty.NDArray = \ + report.best_state.reshape((self.problem.num_waypts, + self.problem.num_waypts)).T + else: + self.raw_solution = -1 * np.ones((self.problem.num_waypts, + self.problem.num_waypts)).T + + self.post_process_sol() + + def post_process_sol(self): + ordered_indices = np.nonzero(self.raw_solution) + ordered_indices = list(zip(ordered_indices[0].tolist(), + ordered_indices[1].tolist())) + ordered_indices.sort(key=lambda x: x[1]) + + self.solution.solution_path_ids = [ + self.problem.waypt_ids[node_id[0]] for node_id in ordered_indices] + self.solution.solution_path_coords = [ + self.problem.waypt_coords[node_id[0]] for node_id in + ordered_indices] diff --git a/src/lava/lib/optimization/apps/tsp/utils/q_matrix_generator.py b/src/lava/lib/optimization/apps/tsp/utils/q_matrix_generator.py new file mode 100644 index 00000000..890959a2 --- /dev/null +++ b/src/lava/lib/optimization/apps/tsp/utils/q_matrix_generator.py @@ -0,0 +1,185 @@ +# INTEL CORPORATION CONFIDENTIAL AND PROPRIETARY +# +# Copyright © 2023 Intel Corporation. +# +# This software and the related documents are Intel copyrighted +# materials, and your use of them is governed by the express +# license under which they were provided to you (License). Unless +# the License provides otherwise, you may not use, modify, copy, +# publish, distribute, disclose or transmit this software or the +# related documents without Intel's prior written permission. +# +# This software and the related documents are provided as is, with +# no express or implied warranties, other than those that are +# expressly stated in the License. +import time +import numpy as np +from scipy.spatial import distance + + +class QMatrixTSP: + """Class to generate Q matrix for travelling salesman problems (TSPs) + framed as QUBO problems. The matrix values are computed based on the + Euclidean distance between the nodes assuming all-to-all connectivity.""" + + def __init__( + self, + input_nodes, + lamda_dist=1, + lamda_cnstrt=1, + fixed_pt=False, + fixed_pt_range=(0, 127), + profile_mat_gen=False + ) -> None: + """ + Parameters + ---------- + input_nodes (list of 2-tuples): Input to matrix generator functions + containing a list of node coordinates specified as 2-tuples. + + lamda_dist (float, optional): Weightage of the pairwise distance + matrix in the Q matrix. + + lamda_cnstrt (float, optional): Weightage of the hard constraints + in the Q matrix. + + fixed_pt (bool, optional): Specifies if the Q matrix should be + rounded down to integer. If `True`, stochastic rounding to + integer range specified `fixed_pt_range` as is performed. + Defaults to `False`. + + fixed_pt_range (tuple, optional): Specifies the absolute + value of min and max values that the Q matrix can have if + `fixed_pt =True`. + + profile_mat_gen (bool, optional): Specifies if Q matrix + generation needs to be timed using python's time.time() + """ + + self.fixed_pt = fixed_pt + self.min_fixed_pt_mant = fixed_pt_range[0] + self.max_fixed_pt_mant = fixed_pt_range[1] + self.time_to_gen_mat = 0. + + start_time = time.time() + self.matrix = self._gen_Q_matrix( + input_nodes, lamda_dist, lamda_cnstrt + ) + if profile_mat_gen: + self.time_to_gen_mat = time.time() - start_time + + def _gen_Q_matrix(self, input_nodes, lamda_dist, lamda_cnstrnt): + """Return the Q matrix that sets up the QUBO for the clustering + problem. The cluster centers are assumed to be uniformly distributed + across the graph. + + Parameters + ---------- + input_nodes (list[tuples]): Input to matrix generator functions + containing a list of nodes specified as tuples. All the nodes + correspond to waypoints relevant to the tsp problem. The + distance between nodes is assumed to be symmetric i.e. A->B = + B->A + + Returns: + np.ndarray: Returns a 2 dimension connectivity matrix of size + n^2 * n^2 + """ + # Euclidean distances between all nodes input to the graph + Dist = distance.cdist(input_nodes, input_nodes, "euclidean") + + # number of waypoints for the salesman + num_wypts = Dist.shape[0] + + # The distance matrix component of the Q matrix + num_wypts_sq = num_wypts ** 2 + Q_dist_mtrx = np.zeros((num_wypts_sq, num_wypts_sq)) + for k in range(num_wypts): + for m in range(num_wypts): + # Sufficent to traverse only lower triangle + if k == m: + break + else: + q_inter_matrx = np.zeros((num_wypts_sq, num_wypts_sq)) + u, v = k, m + for i in range(num_wypts): + for j in range(num_wypts - 1): + v_ind_row = \ + (v + (j + 1) * num_wypts) % num_wypts_sq + u_ind_row = \ + (u + (j + 1) * num_wypts) % num_wypts_sq + q_inter_matrx[v_ind_row, u] = 1 + q_inter_matrx[u_ind_row, v] = 1 + v = (v + num_wypts) % num_wypts_sq + u = (u + num_wypts) % num_wypts_sq + Q_dist_mtrx += Dist[k, m] * q_inter_matrx + # TSP constraint encoding + # Only one vrtx can be selected at a time instant + # Off-diagonal elements are two, populating matrix with 2 + vrtx_mat_off_diag = 2 * np.ones((num_wypts, num_wypts)) + + # Diag elements of -3 to subtract from earlier matrix and get -1 in the + # diagonal later + vrtx_mat_diag = -3 * np.eye(num_wypts, num_wypts) + + # Off-diag elements are two, diagonal elements are -1 + vrtx_mat = vrtx_mat_off_diag + vrtx_mat_diag + vrtx_constraints = np.kron(np.eye(num_wypts), vrtx_mat) + + # Encoding sequence constraints here + seq_constraint_diag = np.eye( + num_wypts * num_wypts, num_wypts * num_wypts + ) + seq_constraint_vector = np.array( + [ + 1 if i % num_wypts == 0 else 0 + for i in range(num_wypts * num_wypts) + ] + ) + seq_constraint_off_diag = seq_constraint_vector + for i in range(num_wypts * num_wypts - 1): + seq_constraint_off_diag = np.vstack( + ( + seq_constraint_off_diag, + np.roll(seq_constraint_vector, i + 1), + ) + ) + + # Off-diag elements are two, diagonal elements are -1 + seq_constraints = \ + (-3 * seq_constraint_diag + 2 * seq_constraint_off_diag) + + # matrix should contain non-zero elements with only value 2 now. + Q_cnstrnts_blck_mtrx = vrtx_constraints + seq_constraints + + # Q_cnstrnts + Q = lamda_dist * Q_dist_mtrx + lamda_cnstrnt * Q_cnstrnts_blck_mtrx + + if self.fixed_pt: + Q = self._stochastic_rounding(Q) + return Q + + def _stochastic_rounding(self, tensor): + """A function to rescale and stochastically round tensor to fixed + point values compatible with Unsigned Mode on Loihi 2. + + Args: + tensor (np.ndarray): floating-point tensor + + Returns: + (np.ndarray): fixed-point version of tensor that is passed as input + """ + tensor_max = np.max(np.abs(tensor)) + tensor_min = np.min(np.abs(tensor)) + + # Get sign mask of tensor to furnish signs for matrix later + tensor_sign_mask = np.sign(tensor) + scaled_tensor = \ + ((self.max_fixed_pt_mant - self.min_fixed_pt_mant) + * (np.abs(tensor) - tensor_min) + / (tensor_max - tensor_min)) + stchstc_rnded_tensor = \ + np.floor(scaled_tensor + + np.random.rand(tensor.shape[0], tensor.shape[1])) + stchstc_rnded_tensor *= tensor_sign_mask + return stchstc_rnded_tensor diff --git a/src/lava/lib/optimization/solvers/generic/read_gate/process.py b/src/lava/lib/optimization/solvers/generic/read_gate/process.py index f0d31987..f0200e1a 100644 --- a/src/lava/lib/optimization/solvers/generic/read_gate/process.py +++ b/src/lava/lib/optimization/solvers/generic/read_gate/process.py @@ -58,6 +58,7 @@ def __init__( log_config=log_config, ) self.target_cost = Var(shape=(1,), init=target_cost) + self.best_solution = Var(shape=shape, init=-1) for id in range(num_in_ports): # Cost is transferred as two separate values @@ -67,6 +68,7 @@ def __init__( setattr(self, f"cost_in_last_bytes_{id}", InPort(shape=(1,))) setattr(self, f"cost_in_first_byte_{id}", InPort(shape=(1,))) self.cost_out = OutPort(shape=(2,)) + self.best_solution = Var(shape=shape, init=-1) self.send_pause_request = OutPort(shape=(1,)) self.solution_out = OutPort(shape=shape) self.solution_reader = RefPort(shape=shape) diff --git a/src/lava/lib/optimization/solvers/generic/scif/models.py b/src/lava/lib/optimization/solvers/generic/scif/models.py index 53f4b0f7..66985f34 100644 --- a/src/lava/lib/optimization/solvers/generic/scif/models.py +++ b/src/lava/lib/optimization/solvers/generic/scif/models.py @@ -383,7 +383,6 @@ class PyModelQuboScifRefracFixed(PyLoihiProcessModel): """***Deprecated*** Concrete implementation of Stochastic Constraint Integrate and Fire (SCIF) neuron for solving QUBO problems. """ - a_in = LavaPyType(PyInPort.VEC_DENSE, int, precision=8) s_sig_out = LavaPyType(PyOutPort.VEC_DENSE, int, precision=8) s_wta_out = LavaPyType(PyOutPort.VEC_DENSE, int, precision=8) @@ -411,7 +410,8 @@ def _prng(self): # need to replace it with Loihi-conformant LFSR function prand = np.zeros(shape=self.state.shape) if prand.size > 0: - rand_nums = np.random.randint(0, (2**16) - 1, size=prand.size) + rand_nums = \ + np.random.randint(0, (2 ** 16) - 1, size=prand.size) # Assign random numbers only to neurons, for which noise is enabled prand = np.right_shift( (rand_nums * self.noise_ampl).astype(int), self.noise_shift @@ -442,12 +442,8 @@ def _integration_dynamics(self, intg_idx): lfsr_to_intg = lfsr[intg_idx] state_to_intg += lfsr_to_intg + cost_diag_intg + a_in_to_intg - np.clip( - state_to_intg, - a_min=-(2**23), - a_max=2**23 - 1, - out=state_to_intg, - ) + np.clip(state_to_intg, a_min=-(2 ** 23), a_max=2 ** 23 - 1, + out=state_to_intg) self.state[intg_idx] = state_to_intg # WTA spike indices when threshold is exceeded @@ -492,10 +488,10 @@ def _gen_sig_spks(self, spk_hist_buffer): s_sig[sig_spk_idx] = ( self.cost_diagonal[sig_spk_idx] + self.a_in_data[sig_spk_idx] ) - return s_sig def _gen_wta_spks(self): + # indices of neurons to be integrated: intg_idx = np.where(self.state >= 0) # indices of neurons in refractory: @@ -520,6 +516,7 @@ def _gen_wta_spks(self): return s_wta def run_spk(self) -> None: + # Receive synaptic input self.a_in_data = self.a_in.recv().astype(int) diff --git a/src/lava/lib/optimization/solvers/lca/v1_neuron/models.py b/src/lava/lib/optimization/solvers/lca/v1_neuron/models.py index a9884dda..9d1b05c4 100644 --- a/src/lava/lib/optimization/solvers/lca/v1_neuron/models.py +++ b/src/lava/lib/optimization/solvers/lca/v1_neuron/models.py @@ -63,6 +63,6 @@ def run_spk(self): activation = apply_activation(self.v, self.vth) bias = np.right_shift(activation * self.tau_int, 24) \ if self.proc_params['two_layer'] else self.bias - self.v = np.right_shift(self.v * (2 ** 24 - self.tau_int), 24) \ - + self.a_in.recv() + bias + self.v = np.right_shift( + self.v * (2 ** 24 - self.tau_int), 24) + self.a_in.recv() + bias self.s_out.send(activation) diff --git a/src/lava/lib/optimization/utils/generators/clustering_tsp_vrp.py b/src/lava/lib/optimization/utils/generators/clustering_tsp_vrp.py new file mode 100644 index 00000000..d3ec5d0e --- /dev/null +++ b/src/lava/lib/optimization/utils/generators/clustering_tsp_vrp.py @@ -0,0 +1,208 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +import typing as ty +import numpy as np +import numpy.typing as npty + + +class AbstractProblem: + def __init__(self, + num_anchors: int = 10, + num_nodes: int = 100, + domain: ty.Union[ + ty.List[ty.List], + ty.List[ty.Tuple], + ty.Tuple[ty.List], + npty.NDArray] = None): + """ + Abstract class for randomly sampling anchor points and nodes in a + given rectangular domain. + + - Clustering problem: the anchor points are cluster centers around + which nodes are to be clustered + - Traveling Salesman Problem: the anchor points can be starting + points for tours to visit the nodes. Typical case would be to have + only one anchor point. + - Vehicle Routing Problem: the anchor points are the vehicle + positions and nodes are the waypoints the vehicles need to visit. + + Parameters + ---------- + num_anchors (int) : number of anchor points to be generated randomly. + num_nodes (int) : number of nodes to be sampled from the `domain`. + domain (int) : domain in which the anchor points and nodes are + generated. Needs to be a rectangle, specified by lower left (LL) corner + coordinates and upper right (UR) coordinates. + """ + self._num_anchors = num_anchors + self._num_nodes = num_nodes + if domain is None: + self._domain = np.array([[0, 0], [100, 100]]) + else: + self._domain = np.array(domain) + + # The following will be populated in the concrete instances + self._anchor_coords = None + self._node_coords = None + + @property + def num_anchors(self): + return self._num_anchors + + @num_anchors.setter + def num_anchors(self, val: int): + self._num_anchors = val + + @property + def num_nodes(self): + return self._num_nodes + + @num_nodes.setter + def num_nodes(self, val: int): + self._num_nodes = val + + @property + def num_pt_per_clust(self): + return int(np.floor(self.num_nodes / self.num_anchors)) + + @property + def residual_num_per_clust(self): + return int(self.num_nodes - self.num_anchors * np.floor( + self.num_nodes / self.num_anchors)) + + @property + def domain(self): + return self._domain + + @domain.setter + def domain(self, d: ty.Union[ty.List[ty.List], ty.List[ty.Tuple], + ty.Tuple[ty.List], npty.NDArray]): + self._domain = d + + @property + def domain_ll(self): + return self.domain[0] + + @property + def domain_ur(self): + return self.domain[1] + + @property + def anchor_coords(self) -> ty.List[ty.Tuple[int, int]]: + return self._anchor_coords + + @property + def node_coords(self) -> ty.List[ty.Tuple[int, int]]: + return self._node_coords + + +class AbstractClusteringProblem(AbstractProblem): + def __init__(self, **kwargs): + num_clusters = kwargs.pop('num_clusters', 10) + num_points = kwargs.pop('num_points', 100) + self.num_clusters = num_clusters + self.num_points = num_points + kwargs.update({'num_anchors': num_clusters, + 'num_nodes': num_points}) + super(AbstractClusteringProblem, self).__init__(**kwargs) + self.center_coords: ty.List[ty.Tuple[int, int]] = self.anchor_coords + self.point_coords: ty.List[ty.Tuple[int, int]] = self.node_coords + + +class AbstractTSP(AbstractProblem): + def __init__(self, **kwargs): + num_starting_pts = kwargs.pop('num_starting_pts', 1) + num_dest_nodes = kwargs.pop('num_dest_nodes', 5) + self.num_starting_pts = num_starting_pts + self.num_dest_nodes = num_dest_nodes + kwargs.update({'num_anchors': num_starting_pts, + 'num_nodes': num_dest_nodes}) + super(AbstractTSP, self).__init__(**kwargs) + self.starting_coords: ty.List[ty.Tuple[int, int]] = self.anchor_coords + self.dest_coords: ty.List[ty.Tuple[int, int]] = self.node_coords + + +class AbstractVRP(AbstractProblem): + def __init__(self, **kwargs): + num_vehicles = kwargs.pop('num_vehicles', 10) + num_waypoints = kwargs.pop('num_waypoints', 100) + self.num_vehicles = num_vehicles + self.num_waypoints = num_waypoints + kwargs.update({'num_anchors': num_vehicles, + 'num_nodes': num_waypoints}) + super(AbstractVRP, self).__init__(**kwargs) + self.vehicle_coords = self.anchor_coords + self.waypoint_coords = self.node_coords + + +class AbstractUniformProblem(AbstractProblem): + def __init__(self, **kwargs): + """Anchor points as well as nodes are uniformly randomly generated + in the specified domain. + """ + super(AbstractUniformProblem, self).__init__(**kwargs) + total_pts_to_sample = self.num_anchors + self.num_nodes + all_coords = np.random.randint(self.domain_ll, self.domain_ur, + size=(total_pts_to_sample, 2)) + self._anchor_coords = all_coords[:self.num_anchors, :] + self._node_coords = all_coords[self.num_anchors:, :] + + +class AbstractGaussianProblem(AbstractProblem): + def __init__(self, **kwargs): + """Anchor points are uniformly randomly generated in the specified + domain. Nodes are generated by sampling from a Gaussian distribution + around the anchor points. + + Parameters + ---------- + variance : (int, float, or list of either) variance for Gaussian + random sampling around each anchor point. If it is a scalar (int + or float), same value is used for all anchor points. If it is a + list, the length of the list must be equal to the number of anchor + points. Each element of the list is used as the variance for random + sampling around the corresponding anchor points. + """ + variance = kwargs.pop('variance', 1) + super(AbstractGaussianProblem, self).__init__(**kwargs) + self._anchor_coords = np.random.randint(self.domain_ll, self.domain_ur, + size=(self.num_anchors, 2)) + self._node_coords = np.zeros((self.num_nodes, 2), dtype=int) + if isinstance(variance, (list, np.ndarray)): + if not len(variance) == self._anchor_coords: + raise AssertionError("The length of variances does not match " + "the number of anchor points.") + else: + variance = [variance] * self.num_anchors + clust_ids_for_extra = [] + if self.residual_num_per_clust > 0: + clust_ids_for_extra = np.random.randint( + 0, self.num_anchors, size=(self.residual_num_per_clust,)) + prev_id = 0 + for j in range(self.num_anchors): + num_to_sample = self.num_pt_per_clust + 1 if ( + j in clust_ids_for_extra) else self.num_pt_per_clust + next_id = prev_id + num_to_sample + self._node_coords[prev_id:next_id, :] = ( + np.random.normal(self._anchor_coords[j, :], variance[j], + size=(num_to_sample, 2)).astype(int)) + prev_id = next_id + + +class UniformlySampledClusteringProblem(AbstractClusteringProblem, + AbstractUniformProblem): + def __init__(self, **kwargs): + super(UniformlySampledClusteringProblem, self).__init__(**kwargs) + + +class GaussianSampledClusteringProblem(AbstractClusteringProblem, + AbstractGaussianProblem): + def __init__(self, **kwargs): + super(GaussianSampledClusteringProblem, self).__init__(**kwargs) + + +class UniformlySampledTSP(AbstractTSP, AbstractUniformProblem): + def __init__(self, **kwargs): + super(UniformlySampledTSP, self).__init__(**kwargs) diff --git a/tests/lava/lib/optimization/apps/__init__.py b/tests/lava/lib/optimization/apps/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/lava/lib/optimization/apps/clustering/__init__.py b/tests/lava/lib/optimization/apps/clustering/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/lava/lib/optimization/apps/clustering/test_problems.py b/tests/lava/lib/optimization/apps/clustering/test_problems.py new file mode 100644 index 00000000..2dbb6722 --- /dev/null +++ b/tests/lava/lib/optimization/apps/clustering/test_problems.py @@ -0,0 +1,29 @@ +import unittest +import numpy as np +from lava.lib.optimization.apps.clustering.problems import ClusteringProblem +from lava.lib.optimization.utils.generators.clustering_tsp_vrp import ( + GaussianSampledClusteringProblem) + + +class TestClusteringProblem(unittest.TestCase): + def setUp(self) -> None: + np.random.seed(2) + self.gcp = GaussianSampledClusteringProblem(num_clusters=4, + num_points=10, + domain=[(0, 0), (25, 25)], + variance=3) + self.cp = ClusteringProblem(point_coords=self.gcp.point_coords, + center_coords=self.gcp.center_coords) + + def test_init(self): + self.assertIsInstance(self.cp, ClusteringProblem) + + def test_properties(self): + self.assertEqual(self.cp.num_clusters, 4) + self.assertEqual(self.cp.num_points, 10) + self.assertListEqual(self.cp.cluster_ids, list(range(1, 5))) + self.assertListEqual(self.cp.point_ids, list(range(5, 15))) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/lava/lib/optimization/apps/clustering/test_solver.py b/tests/lava/lib/optimization/apps/clustering/test_solver.py new file mode 100644 index 00000000..36765e25 --- /dev/null +++ b/tests/lava/lib/optimization/apps/clustering/test_solver.py @@ -0,0 +1,72 @@ +import os +import unittest + +import numpy as np + +from lava.lib.optimization.apps.clustering.problems import ClusteringProblem +from lava.lib.optimization.apps.clustering.solver import (ClusteringConfig, + ClusteringSolver) + + +def get_bool_env_setting(env_var: str): + """Get an environment varible and return + True if the variable is set to 1 else return + false + """ + env_test_setting = os.environ.get(env_var) + test_setting = False + if env_test_setting == "1": + test_setting = True + return test_setting + + +run_loihi_tests: bool = get_bool_env_setting("RUN_LOIHI_TESTS") +run_lib_tests: bool = get_bool_env_setting("RUN_LIB_TESTS") +skip_reason = "Either Loihi or Lib or both tests are disabled." + + +class TestClusteringSolver(unittest.TestCase): + def setUp(self) -> None: + all_coords = [(1, 1), (23, 23), (2, 1), (3, 2), (1, 3), + (4, 2), (22, 21), (20, 23), (21, 21), (24, 25)] + self.center_coords = all_coords[:2] + self.point_coords = all_coords[2:] + self.edges = [(1, 3), (2, 9), (3, 5), (3, 4), (4, 6), (6, 5), + (9, 7), (8, 10), (7, 10), (10, 8), (7, 9)] + self.clustering_prob_instance = ClusteringProblem( + point_coords=self.point_coords, center_coords=self.center_coords) + + def test_init(self): + solver = ClusteringSolver(clp=self.clustering_prob_instance) + self.assertIsInstance(solver, ClusteringSolver) + + @unittest.skipUnless(run_loihi_tests and run_lib_tests, skip_reason) + def test_solve_lava_qubo(self): + solver = ClusteringSolver(clp=self.clustering_prob_instance) + scfg = ClusteringConfig(backend="Loihi2", + hyperparameters={}, + target_cost=-1000000, + timeout=1000, + probe_time=False, + log_level=40) + np.random.seed(0) + solver.solve(scfg=scfg) + gt_id_map = {1: [3, 4, 5, 6], 2: [7, 8, 9, 10]} + self.assertSetEqual(set(gt_id_map.keys()), + set(solver.solution.clustering_id_map.keys())) + for cluster_id, cluster in gt_id_map.items(): + self.assertSetEqual( + set(cluster), + set(solver.solution.clustering_id_map[cluster_id])) + gt_coord_map = {self.center_coords[0]: self.point_coords[:4], + self.center_coords[1]: self.point_coords[4:]} + self.assertSetEqual(set(gt_coord_map.keys()), + set(solver.solution.clustering_coords_map.keys())) + for center_coords, points in gt_coord_map.items(): + self.assertSetEqual( + set(points), + set(solver.solution.clustering_coords_map[center_coords])) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/lava/lib/optimization/apps/clustering/utils/__init__.py b/tests/lava/lib/optimization/apps/clustering/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/lava/lib/optimization/apps/clustering/utils/test_q_matrix_generator.py b/tests/lava/lib/optimization/apps/clustering/utils/test_q_matrix_generator.py new file mode 100644 index 00000000..5d125768 --- /dev/null +++ b/tests/lava/lib/optimization/apps/clustering/utils/test_q_matrix_generator.py @@ -0,0 +1,91 @@ +# INTEL CORPORATION CONFIDENTIAL AND PROPRIETARY +# +# Copyright © 2023 Intel Corporation. +# +# This software and the related documents are Intel copyrighted +# materials, and your use of them is governed by the express +# license under which they were provided to you (License). Unless +# the License provides otherwise, you may not use, modify, copy, +# publish, distribute, disclose or transmit this software or the +# related documents without Intel's prior written permission. +# +# This software and the related documents are provided as is, with +# no express or implied warranties, other than those that are +# expressly stated in the License. +import unittest +import numpy as np +from scipy.spatial import distance +from lava.lib.optimization.apps.clustering.utils.q_matrix_generator import \ + QMatrixClust + + +class TestMatrixGen(unittest.TestCase): + def test_Q_gen(self) -> None: + input_nodes = np.array([(0, 1), (2, 3), (2, 1), (1, 1)]) + lambda_centers = 1.5 + lambda_points = 2.5 + lambda_dist = 2 + num_clusters = 2 + # testing floating point Q matrix + q_obj = QMatrixClust( + input_nodes, + num_clusters=num_clusters, + lambda_dist=lambda_dist, + lambda_centers=lambda_centers, + lambda_points=lambda_points, + ) + Q_clustering_fltg_pt = q_obj.matrix + Q_dist_test = distance.cdist(input_nodes, + input_nodes, + "euclidean") + Q_dist_blck_test = np.kron(np.eye(num_clusters), Q_dist_test) + + Q_wypt_cnst_test = np.array( + [ + [-1., 0., 0., 0., 2., 0., 0., 0.], + [0., -1., 0., 0., 0., 2., 0., 0.], + [0., 0., -1., 0., 0., 0., 2., 0.], + [0., 0., 0., -1., 0., 0., 0., 2.], + [2., 0., 0., 0., -1., 0., 0., 0.], + [0., 2., 0., 0., 0., -1., 0., 0.], + [0., 0., 2., 0., 0., 0., -1., 0.], + [0., 0., 0., 2., 0., 0., 0., -1.] + ] + ) + + Q_vhcle_cnst_test = np.array( + [ + [-1.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [2.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.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, 2.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 2.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.0, 0.0, 0.0, 0.0, 0.0], + ] + ) + Q_test = ( + lambda_dist * Q_dist_blck_test + + lambda_centers * Q_vhcle_cnst_test + + lambda_points * Q_wypt_cnst_test + ) + self.assertEqual(np.all(Q_clustering_fltg_pt == Q_test), True) + + # testing fixed point Q matrix + # individual values not really testsed since the rounding + # is stochastic + q_obj_2 = QMatrixClust( + input_nodes, + num_clusters=2, + fixed_pt=True, + ) + Q_clustering_fixed_pt = q_obj_2.matrix + fixed_pt_Q_max = np.max(np.abs(Q_clustering_fixed_pt)) + fixed_pt_Q_min = np.min(np.abs(Q_clustering_fixed_pt)) + self.assertGreaterEqual(fixed_pt_Q_min == 0, True) + self.assertLessEqual(fixed_pt_Q_max == 127, True) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/lava/lib/optimization/apps/tsp/__init__.py b/tests/lava/lib/optimization/apps/tsp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/lava/lib/optimization/apps/tsp/test_problems.py b/tests/lava/lib/optimization/apps/tsp/test_problems.py new file mode 100644 index 00000000..376d7eed --- /dev/null +++ b/tests/lava/lib/optimization/apps/tsp/test_problems.py @@ -0,0 +1,28 @@ +import unittest +import numpy as np +from lava.lib.optimization.apps.tsp.problems import TravellingSalesmanProblem +from lava.lib.optimization.utils.generators.clustering_tsp_vrp import ( + UniformlySampledTSP) + + +class TestTravellingSalesmanProblem(unittest.TestCase): + def setUp(self) -> None: + np.random.seed(2) + self.utsp = UniformlySampledTSP(num_starting_pts=1, + num_dest_nodes=5, + domain=[(0, 0), (25, 25)]) + self.tsp = TravellingSalesmanProblem( + waypt_coords=self.utsp.dest_coords, + starting_pt=self.utsp.starting_coords[0] + ) + + def test_init(self): + self.assertIsInstance(self.tsp, TravellingSalesmanProblem) + + def test_properties(self): + self.assertEqual(self.tsp.num_waypts, 5) + self.assertListEqual(self.tsp.waypt_ids, list(range(2, 7))) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/lava/lib/optimization/apps/tsp/test_solver.py b/tests/lava/lib/optimization/apps/tsp/test_solver.py new file mode 100644 index 00000000..45aaf3f2 --- /dev/null +++ b/tests/lava/lib/optimization/apps/tsp/test_solver.py @@ -0,0 +1,65 @@ +import os +import pprint +import unittest + +import numpy as np +import networkx as ntx +from lava.lib.optimization.apps.tsp.problems import TravellingSalesmanProblem +from lava.lib.optimization.apps.tsp.solver import TSPConfig, TSPSolver + + +def get_bool_env_setting(env_var: str): + """Get an environment varible and return + True if the variable is set to 1 else return + false + """ + env_test_setting = os.environ.get(env_var) + test_setting = False + if env_test_setting == "1": + test_setting = True + return test_setting + + +run_loihi_tests: bool = get_bool_env_setting("RUN_LOIHI_TESTS") +run_lib_tests: bool = get_bool_env_setting("RUN_LIB_TESTS") +skip_reason = "Either Loihi or Lib or both tests are disabled." + + +class TestTSPSolver(unittest.TestCase): + def setUp(self) -> None: + all_coords = [(1, 1), (2, 1), (16, 1), (16, 15), (2, 15)] + self.center_coords = all_coords[0] + self.point_coords = all_coords[1:] + self.tsp_instance = TravellingSalesmanProblem( + waypt_coords=self.point_coords, starting_pt=self.center_coords) + + def test_init(self): + solver = TSPSolver(tsp=self.tsp_instance) + self.assertIsInstance(solver, TSPSolver) + + @unittest.skipUnless(run_loihi_tests and run_lib_tests, skip_reason) + def test_solve_lava_qubo(self): + solver = TSPSolver(tsp=self.tsp_instance) + scfg = TSPConfig(backend="Loihi2", + hyperparameters={}, + target_cost=-1000000, + timeout=1000, + probe_time=False, + log_level=40) + np.random.seed(0) + solver.solve(scfg=scfg) + gt_indices = np.array([2, 3, 4, 5]) + spidx = solver.solution.solution_path_ids + self.assertEqual(gt_indices.size, len(spidx)) + + gt_graph = ntx.Graph([(2, 3), (3, 4), (4, 5), (5, 2)]) + sol_graph = ntx.Graph([(spidx[0], spidx[1]), + (spidx[1], spidx[2]), + (spidx[2], spidx[3]), + (spidx[3], spidx[0])]) + + self.assertTrue(ntx.utils.graphs_equal(gt_graph, sol_graph)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/lava/lib/optimization/apps/tsp/utils/__init__.py b/tests/lava/lib/optimization/apps/tsp/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/lava/lib/optimization/apps/tsp/utils/test_q_matrix_generator.py b/tests/lava/lib/optimization/apps/tsp/utils/test_q_matrix_generator.py new file mode 100644 index 00000000..2b3d59b2 --- /dev/null +++ b/tests/lava/lib/optimization/apps/tsp/utils/test_q_matrix_generator.py @@ -0,0 +1,76 @@ +# INTEL CORPORATION CONFIDENTIAL AND PROPRIETARY +# +# Copyright © 2023 Intel Corporation. +# +# This software and the related documents are Intel copyrighted +# materials, and your use of them is governed by the express +# license under which they were provided to you (License). Unless +# the License provides otherwise, you may not use, modify, copy, +# publish, distribute, disclose or transmit this software or the +# related documents without Intel's prior written permission. +# +# This software and the related documents are provided as is, with +# no express or implied warranties, other than those that are +# expressly stated in the License. +import unittest +import numpy as np +from lava.lib.optimization.apps.tsp.utils.q_matrix_generator import \ + QMatrixTSP + + +class TestMatrixGen(unittest.TestCase): + def test_Q_gen(self) -> None: + input_nodes = [(0, 1), (0, 0), (0, -1)] + lamda_dist = 2.5 + lamda_cnstrt = 4 # testing floating point Q matrix + q_mat_obj_flp = QMatrixTSP( + input_nodes, + lamda_dist=lamda_dist, + lamda_cnstrt=lamda_cnstrt, + ) + q_mat_flp = q_mat_obj_flp.matrix + q_dist_scaled_test = np.array( + [ + [0, 0, 0, 0, 1, 2, 0, 1, 2], + [0, 0, 0, 1, 0, 1, 1, 0, 1], + [0, 0, 0, 2, 1, 0, 2, 1, 0], + [0, 1, 2, 0, 0, 0, 0, 1, 2], + [1, 0, 1, 0, 0, 0, 1, 0, 1], + [2, 1, 0, 0, 0, 0, 2, 1, 0], + [0, 1, 2, 0, 1, 2, 0, 0, 0], + [1, 0, 1, 1, 0, 1, 0, 0, 0], + [2, 1, 0, 2, 1, 0, 0, 0, 0], + ] + ) + q_cnstrnts_test = 2 * np.array( + [ + [-1, 1, 1, 1, 0, 0, 1, 0, 0], + [1, -1, 1, 0, 1, 0, 0, 1, 0], + [1, 1, -1, 0, 0, 1, 0, 0, 1], + [1, 0, 0, -1, 1, 1, 1, 0, 0], + [0, 1, 0, 1, -1, 1, 0, 1, 0], + [0, 0, 1, 1, 1, -1, 0, 0, 1], + [1, 0, 0, 1, 0, 0, -1, 1, 1], + [0, 1, 0, 0, 1, 0, 1, -1, 1], + [0, 0, 1, 0, 0, 1, 1, 1, -1], + ] + ) + + q_test = ( + lamda_dist * q_dist_scaled_test + lamda_cnstrt * q_cnstrnts_test + ) + self.assertTrue(np.all(q_mat_flp == q_test)) + # testing fixed point Q matrix + # individual values not really testsed since the rounding + # is stochastic + q_mat_obj_fxp = QMatrixTSP(input_nodes, + fixed_pt=True) + q_mat_fxp = q_mat_obj_fxp.matrix + q_max_fxp = np.max(np.abs(q_mat_fxp)) + q_min_fxp = np.min(np.abs(q_mat_fxp)) + self.assertGreaterEqual(q_min_fxp, 0, True) + self.assertLessEqual(q_max_fxp, 127, True) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/lava/lib/optimization/utils/generators/test_clustering_tsp_vrp.py b/tests/lava/lib/optimization/utils/generators/test_clustering_tsp_vrp.py new file mode 100644 index 00000000..1db0a425 --- /dev/null +++ b/tests/lava/lib/optimization/utils/generators/test_clustering_tsp_vrp.py @@ -0,0 +1,127 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +import unittest +import numpy as np + +from lava.lib.optimization.utils.generators.clustering_tsp_vrp import ( + AbstractProblem, AbstractClusteringProblem, AbstractTSP, AbstractVRP, + AbstractUniformProblem, AbstractGaussianProblem, + UniformlySampledClusteringProblem, GaussianSampledClusteringProblem) + + +class TestAbstractProblem(unittest.TestCase): + + def test_abstract_problem_init(self): + ap = AbstractProblem() + self.assertIsInstance(ap, AbstractProblem) + + def test_abstract_problem_properties(self): + dom = np.array([[-5, -5], [5, 5]]) + ap = AbstractProblem(num_anchors=5, num_nodes=20, domain=dom) + self.assertEqual(ap.num_anchors, 5) + self.assertEqual(ap.num_nodes, 20) + self.assertEqual(ap.num_pt_per_clust, 4) # 20 // 5 = 4 + self.assertTrue(np.all(ap.domain == dom)) + self.assertListEqual(ap.domain_ll.tolist(), [-5, -5]) + self.assertListEqual(ap.domain_ur.tolist(), [5, 5]) + self.assertIsNone(ap.anchor_coords) + self.assertIsNone(ap.node_coords) + + def test_abstract_problem_setters(self): + ap = AbstractProblem() + ap.num_anchors = 4 + ap.num_nodes = 36 + ap.domain = [(0, 0), (20, 20)] + self.assertEqual(ap.num_anchors, 4) + self.assertEqual(ap.num_nodes, 36) + self.assertTrue(np.all(ap.domain == np.array([(0, 0), (20, 20)]))) + + +class TestAbstractDerivedProblems(unittest.TestCase): + + def test_abstract_clustering_problem(self): + acp = AbstractClusteringProblem(num_clusters=5, + num_points=20) + self.assertEqual(acp.num_clusters, 5) + self.assertEqual(acp.num_anchors, 5) + self.assertEqual(acp.num_points, 20) + self.assertEqual(acp.num_nodes, 20) + self.assertIsNone(acp.center_coords) + self.assertIsNone(acp.point_coords) + + def test_abstract_tsp(self): + atsp = AbstractTSP(num_starting_pts=5, + num_dest_nodes=20) + self.assertEqual(atsp.num_starting_pts, 5) + self.assertEqual(atsp.num_anchors, 5) + self.assertEqual(atsp.num_dest_nodes, 20) + self.assertEqual(atsp.num_nodes, 20) + self.assertIsNone(atsp.starting_coords) + self.assertIsNone(atsp.dest_coords) + + def test_abstract_vrp(self): + avrp = AbstractVRP(num_vehicles=5, + num_waypoints=20) + self.assertEqual(avrp.num_vehicles, 5) + self.assertEqual(avrp.num_anchors, 5) + self.assertEqual(avrp.num_waypoints, 20) + self.assertEqual(avrp.num_nodes, 20) + self.assertIsNone(avrp.vehicle_coords) + self.assertIsNone(avrp.waypoint_coords) + + def test_abstract_uniform_problem(self): + np.random.seed(2) + aup = AbstractUniformProblem(num_anchors=4, + num_nodes=10, + domain=[(-5, -5), (5, 5)]) + gt_anchor_coords = np.array([[3, 3], [1, -3], [3, 2], [-3, -4]]) + gt_node_coords = np.array([[0, -1], [-1, 0], [2, -2], [1, -1], + [-2, 2], [1, -4], [-2, 0], [3, -1], + [1, -2], [4, -3]]) + self.assertTrue(np.all(aup.anchor_coords == gt_anchor_coords)) + self.assertTrue(np.all(aup.node_coords == gt_node_coords)) + + def test_abstract_gaussian_problem(self): + np.random.seed(2) + agp = AbstractGaussianProblem(num_anchors=4, + num_nodes=7, + domain=[(-3, -3), (3, 3)]) + gt_anchor_coords = np.array([[-3, 2], [-3, 0], [-1, 0], [-3, -1]]) + gt_node_coords = np.array([[-4, 2], [-2, -1], [-4, 0], [-2, 0], + [-2, 0], [-1, -1], [0, 0]]) + self.assertTrue(np.all(agp.anchor_coords == gt_anchor_coords)) + self.assertTrue(np.all(agp.node_coords == gt_node_coords)) + + +class TestClusteringProblems(unittest.TestCase): + + def test_uniform_clustering(self): + np.random.seed(2) + ucp = UniformlySampledClusteringProblem(num_clusters=4, + num_points=10, + domain=[(0, 0), (25, 25)]) + gt_center_coords = np.array([[8, 15], [13, 8], [22, 11], [18, 11]]) + gt_point_coords = np.array([[8, 7], [2, 17], [11, 21], [15, 20], + [20, 5], [7, 3], [6, 4], [10, 11], + [19, 7], [6, 10]]) + self.assertTrue(np.all(ucp.center_coords == gt_center_coords)) + self.assertTrue(np.all(ucp.point_coords == gt_point_coords)) + + def test_gaussian_clustering(self): + np.random.seed(2) + gcp = GaussianSampledClusteringProblem(num_clusters=4, + num_points=10, + domain=[(0, 0), (25, 25)], + variance=3) + gt_center_coords = np.array([[8, 15], [13, 8], [22, 11], [18, 11]]) + gt_point_coords = np.array([[4, 13], [3, 17], [10, 10], [9, 8], + [8, 8], [23, 12], [25, 10], [18, 8], + [17, 8], [13, 12]]) + self.assertTrue(np.all(gcp.center_coords == gt_center_coords)) + self.assertTrue(np.all(gcp.point_coords == gt_point_coords)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tutorials/demo_02_clustering.ipynb b/tutorials/demo_02_clustering.ipynb new file mode 100644 index 00000000..29dbd92c --- /dev/null +++ b/tutorials/demo_02_clustering.ipynb @@ -0,0 +1,287 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Copyright (C) 2023 Intel Corporation*
\n", + "*SPDX-License-Identifier: BSD-3-Clause*
\n", + "*See: https://spdx.org/licenses/*\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Clustering using the Lava QUBO solver\n", + "This notebook demonstrates the usage of a Lava-Optimization QUBO solver to cluster a set of points in 2-dimensional space into clusters with *pre-specified* cluster centers.\n", + "\n", + "We use a problem generator utility, which uniformly samples cluster centers and around them generates Gaussian-distributed points. Such artificially generated problem is then consumed by the clustering solver and a clustering solution is returned." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from lava.lib.optimization.utils.generators.clustering_tsp_vrp import (\n", + " GaussianSampledClusteringProblem) # Problem generation utility\n", + "from lava.lib.optimization.apps.clustering.problems import ClusteringProblem\n", + "from lava.lib.optimization.apps.clustering.solver import (ClusteringConfig,\n", + " ClusteringSolver)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Generate random problem" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "np.random.seed(2)\n", + "# Generate an artificial problem\n", + "domain = [(0, 0), (50, 50)]\n", + "gcp = GaussianSampledClusteringProblem(num_clusters=5,\n", + " num_points=50,\n", + " domain=domain,\n", + " variance=2.5)\n", + "\n", + "# Convert the generated coordinates to lists of tuples\n", + "ct_c = [tuple(coord) for coord in gcp.center_coords]\n", + "pt_c = [tuple(coord) for coord in gcp.point_coords]\n", + "\n", + "# Generate a clustering problem that compatible for solver consumption\n", + "cp = ClusteringProblem(point_coords=pt_c,\n", + " center_coords=ct_c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visualise the problem" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cluter center coordinates: \n", + "[[40 15]\n", + " [45 8]\n", + " [22 43]\n", + " [18 11]\n", + " [40 7]]\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "print(f\"Cluter center coordinates: \\n{gcp.center_coords}\")\n", + "plt.scatter(gcp.center_coords[:, 0], gcp.center_coords[:, 1], s=50, c='b', marker='o')\n", + "plt.scatter(gcp.point_coords[:, 0], gcp.point_coords[:, 1], s=15, c='r', marker='^')\n", + "plt.xlim([domain[0][0], domain[1][1]])\n", + "plt.ylim([domain[0][0], domain[1][1]])\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Solve" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INFO:DRV: SLURM is being run in background\n", + "INFO:DRV: Connecting to 10.54.73.26:40445\n", + "INFO:DRV: Host server up..............Done 0.37s\n", + "INFO:DRV: Mapping chipIds.............Done 0.08ms\n", + "INFO:DRV: Mapping coreIds.............Done 0.26ms\n", + "INFO:DRV: Partitioning neuron groups..Done 3.70ms\n", + "INFO:DRV: Mapping axons...............Done 0.01s\n", + "INFO:DRV: Configuring Spike Block.....Done 0.01ms\n", + "INFO:DRV: Writes SpikeIO Config to FileDone 0.03ms\n", + "INFO:DRV: Initializes Python MQ.......Done 0.01ms\n", + "INFO:DRV: Partitioning MPDS...........Done 1.00ms\n", + "INFO:DRV: Creating Embedded Snips and ChannelsDone 9.16ms\n", + "INFO:DRV: Compiling Embedded snips....Done 0.79s\n", + "INFO:DRV: Compiling Host snips........Done 0.18ms\n", + "INFO:DRV: Compiling Register Probes...Done 0.33ms\n", + "INFO:DRV: Compiling Spike Probes......Done 0.04ms\n", + "INFO:HST: Args chip=0 cpu=0 /home/sumedhrr/frameworks.ai.nx.nxsdk/nxcore/arch/base/pre_execution/../../../../temp/4705e2c0-6948-11ee-bb08-19f77971418b/launcher_chip0_cpu0.bin --chips=1 --remote-relay=0 \n", + "INFO:HST: Args chip=0 cpu=1 /home/sumedhrr/frameworks.ai.nx.nxsdk/nxcore/arch/base/pre_execution/../../../../temp/4705e2c0-6948-11ee-bb08-19f77971418b/launcher_chip0_cpu1.bin --chips=1 --remote-relay=0 \n", + "INFO:HST: Nx...\n", + "INFO:DRV: Booting up..................Done 0.67s\n", + "INFO:DRV: Encoding probes.............Done 0.01ms\n", + "INFO:DRV: Transferring probes.........Done 6.93ms\n", + "INFO:DRV: Configuring registers.......Done 0.10s\n", + "INFO:DRV: Transferring spikes.........Done 0.00ms\n", + "INFO:HST: chip=0 msg=00018114 00ffff00 \n", + "INFO:DRV: Executing...................Done 0.01s\n", + "INFO:DRV: Processing timeseries.......Done 0.05ms\n", + "INFO:DRV: Executor: 1000 timesteps........Done 0.14s\n", + "INFO:HST: Execution has not started yet or has finished.\n", + "INFO:HST: Stopping Execution : at 1000\n", + "INFO:HST: chip=0 cpu=1 halted, status=0x0\n", + "INFO:HST: chip=0 cpu=0 halted, status=0x0\n" + ] + } + ], + "source": [ + "solver = ClusteringSolver(clp=cp)\n", + "scfg = ClusteringConfig(backend=\"Loihi2\",\n", + " hyperparameters={},\n", + " target_cost=-1000000,\n", + " timeout=1000,\n", + " probe_time=False,\n", + " log_level=20) # Change log level to 40 for suppressing the verbose output below\n", + "solver.solve(scfg=scfg)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Maps between cluster centers and points\n", + "`clustering_id_map`: Center IDs -> Point IDs\n", + "\n", + "`clustering_coords_map`: Center coords -> Point coords" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Clustering ID map:\n", + "{4: [36, 37, 38, 39, 40, 41, 42, 43, 44, 45], 2: [16, 18, 19, 20, 22, 23, 25, 50, 51, 53], 3: [26, 27, 28, 29, 30, 31, 33, 34, 35], 1: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 5: [17, 21, 24, 46, 47, 48, 49, 52, 54, 55]}\n", + "Clustering coords map:\n", + "{(18, 11): [(19, 7), (17, 4), (13, 8), (19, 11), (17, 9), (15, 12), (22, 8), (15, 12), (18, 11), (19, 12)], (45, 8): [(44, 7), (46, 5), (47, 0), (48, 6), (47, 8), (47, 3), (45, 11), (45, 9), (41, 9), (47, 5)], (22, 43): [(22, 44), (21, 41), (21, 45), (25, 44), (20, 40), (16, 43), (19, 40), (24, 42), (20, 46)], (40, 15): [(37, 13), (36, 17), (41, 11), (37, 15), (36, 15), (41, 15), (43, 14), (40, 12), (39, 13), (36, 16)], (40, 7): [(43, 2), (42, 8), (40, 3), (36, 7), (40, 8), (36, 4), (39, 9), (40, 3), (37, 9), (39, 6)]}\n" + ] + } + ], + "source": [ + "print(f\"Clustering ID map:\\n{solver.solution.clustering_id_map}\")\n", + "print(f\"Clustering coords map:\\n{solver.solution.clustering_coords_map}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "clustered_coords = list(solver.solution.clustering_coords_map.values())\n", + "clustered_coords = [np.array(coords) for coords in clustered_coords]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visualise the clustering solution\n", + "- Black circles are cluster centers.\n", + "- Red triangles are points which did not get a cluster assignment\n", + "- Other colours represent separate clusters" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.scatter(gcp.center_coords[:, 0], gcp.center_coords[:, 1], s=50, c='k', marker='o')\n", + "plt.scatter(gcp.point_coords[:, 0], gcp.point_coords[:, 1], s=15, c='r', marker='^')\n", + "colours = ['b', 'g', 'y', 'c', 'm']\n", + "for j, cluster in enumerate(clustered_coords):\n", + " plt.scatter(cluster[:, 0], cluster[:, 1], s=15, c=colours[j], marker='^')\n", + "plt.xlim([domain[0][0], domain[1][1]])\n", + "plt.ylim([domain[0][0], domain[1][1]])\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/tutorials/demo_03_tsp.ipynb b/tutorials/demo_03_tsp.ipynb new file mode 100644 index 00000000..a82e53a9 --- /dev/null +++ b/tutorials/demo_03_tsp.ipynb @@ -0,0 +1,199 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Copyright (C) 2023 Intel Corporation*
\n", + "*SPDX-License-Identifier: BSD-3-Clause*
\n", + "*See: https://spdx.org/licenses/*\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Solving a travelling salesman problem using the Lava QUBO solver\n", + "This notebook demonstrates the usage of a Lava-Optimization QUBO solver to solve a travelling salesman problem.\n", + "\n", + "Currently, the solver finds a route connecting way-points without taking the salesman's starting position into account." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from lava.lib.optimization.apps.tsp.problems import TravellingSalesmanProblem\n", + "from lava.lib.optimization.apps.tsp.solver import TSPConfig, TSPSolver" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Generate problem" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "all_coords = [(1, 1), (2, 1), (16, 1), (16, 15), (2, 15)]\n", + "center_coords = [(1, 1)]\n", + "point_coords = [(2, 1), (16, 1), (16, 15), (2, 15)]\n", + "tsp_instance = TravellingSalesmanProblem(\n", + " waypt_coords=point_coords, starting_pt=center_coords)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visualise the problem\n", + "\n", + "- Blue circle: starting position\n", + "- Red triangles: way-points" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.scatter(tsp_instance.nodes[1][0][0], tsp_instance.nodes[1][0][1], s=25, c='b', marker='o')\n", + "plt.scatter(np.array(tsp_instance.waypt_coords)[:, 0], np.array(tsp_instance.waypt_coords)[:, 1], s=25, c='r', marker='^')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Solve" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INFO:DRV: SLURM is being run in background\n", + "INFO:DRV: Connecting to 10.54.73.26:37679\n", + "INFO:DRV: Host server up..............Done 0.39s\n", + "INFO:DRV: Mapping chipIds.............Done 0.01ms\n", + "INFO:DRV: Mapping coreIds.............Done 0.05ms\n", + "INFO:DRV: Partitioning neuron groups..Done 0.67ms\n", + "INFO:DRV: Mapping axons...............Done 0.21ms\n", + "INFO:DRV: Configuring Spike Block.....Done 0.00ms\n", + "INFO:DRV: Writes SpikeIO Config to FileDone 0.01ms\n", + "INFO:DRV: Initializes Python MQ.......Done 0.01ms\n", + "INFO:DRV: Partitioning MPDS...........Done 0.46ms\n", + "INFO:DRV: Creating Embedded Snips and ChannelsDone 5.86ms\n", + "INFO:DRV: Compiling Embedded snips....Done 0.80s\n", + "INFO:DRV: Compiling Host snips........Done 0.22ms\n", + "INFO:DRV: Compiling Register Probes...Done 0.35ms\n", + "INFO:DRV: Compiling Spike Probes......Done 0.04ms\n", + "INFO:HST: Args chip=0 cpu=0 /home/sumedhrr/frameworks.ai.nx.nxsdk/nxcore/arch/base/pre_execution/../../../../temp/ecc88876-6956-11ee-bb08-19f77971418b/launcher_chip0_cpu0.bin --chips=1 --remote-relay=0 \n", + "INFO:HST: Args chip=0 cpu=1 /home/sumedhrr/frameworks.ai.nx.nxsdk/nxcore/arch/base/pre_execution/../../../../temp/ecc88876-6956-11ee-bb08-19f77971418b/launcher_chip0_cpu1.bin --chips=1 --remote-relay=0 \n", + "INFO:HST: Nx...\n", + "INFO:DRV: Booting up..................Done 0.65s\n", + "INFO:DRV: Encoding probes.............Done 0.01ms\n", + "INFO:DRV: Transferring probes.........Done 3.60ms\n", + "INFO:DRV: Configuring registers.......Done 0.05s\n", + "INFO:DRV: Transferring spikes.........Done 0.01ms\n", + "INFO:HST: chip=0 msg=00018114 00ffff00 \n", + "INFO:DRV: Executing...................Done 4.17ms\n", + "INFO:DRV: Processing timeseries.......Done 0.05ms\n", + "INFO:DRV: Executor: 1000 timesteps........Done 0.08s\n", + "INFO:HST: Execution has not started yet or has finished.\n", + "INFO:HST: Stopping Execution : at 1000\n", + "INFO:HST: chip=0 cpu=1 halted, status=0x0\n", + "INFO:HST: chip=0 cpu=0 halted, status=0x0\n" + ] + } + ], + "source": [ + "solver = TSPSolver(tsp=tsp_instance)\n", + "scfg = TSPConfig(backend=\"Loihi2\",\n", + " hyperparameters={},\n", + " target_cost=-1000000,\n", + " timeout=1000,\n", + " probe_time=False,\n", + " log_level=20) # Change log level to 40 for suppressing the verbose output below\n", + "np.random.seed(0)\n", + "solver.solve(scfg=scfg)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "IDs:\n", + "[4, 3, 2, 5]\n", + "Coords:\n", + "[(16, 15), (16, 1), (2, 1), (2, 15)]\n" + ] + } + ], + "source": [ + "print(f\"IDs:\\n{solver.solution.solution_path_ids}\")\n", + "print(f\"Coords:\\n{solver.solution.solution_path_coords}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}