diff --git a/README.rst b/README.rst index 829bece..be281c3 100644 --- a/README.rst +++ b/README.rst @@ -30,10 +30,9 @@ We can find an optimal path using a Dynamic Programming method with: .. code:: python - import numpy as np from python_tsp.exact import solve_tsp_dynamic_programming - distance_matrix = np.array([ + distance_matrix = [ [0, 5, 4, 10], [5, 0, 8, 5], [4, 8, 0, 3], diff --git a/README_pypi.rst b/README_pypi.rst index d92d603..df8be29 100644 --- a/README_pypi.rst +++ b/README_pypi.rst @@ -17,20 +17,19 @@ Installation Examples ======== -Given a distance matrix as a numpy array, it is easy to compute a Hamiltonian +Given a distance matrix as a list of lists, it is easy to compute a Hamiltonian path with least cost. For instance, to use a Dynamic Programming method: .. code:: python - import numpy as np from python_tsp.exact import solve_tsp_dynamic_programming - distance_matrix = np.array([ + distance_matrix = [ [0, 5, 4, 10], [5, 0, 8, 5], [4, 8, 0, 3], [10, 5, 3, 0] - ]) + ] permutation, distance = solve_tsp_dynamic_programming(distance_matrix) The solution will be ``[0, 1, 3, 2]``, with total distance 17. Notice it is @@ -54,7 +53,8 @@ zero: .. code:: python - distance_matrix[:, 0] = 0 + for row in distance_matrix: + row[0] = 0 permutation, distance = solve_tsp_dynamic_programming(distance_matrix) and in this case we obtain ``[0, 2, 3, 1]``, with distance 12. Notice that in @@ -71,15 +71,14 @@ of a point, .. code:: python - import numpy as np from python_tsp.distances import great_circle_distance_matrix - sources = np.array([ + sources = [ [ 40.73024833, -73.79440675], [ 41.47362495, -73.92783272], [ 41.26591 , -73.21026228], [ 41.3249908 , -73.507788 ] - ]) + ] distance_matrix = great_circle_distance_matrix(sources) See the `project's repository `_ diff --git a/docs/distances.rst b/docs/distances.rst index b56d65d..f8a5620 100644 --- a/docs/distances.rst +++ b/docs/distances.rst @@ -11,16 +11,14 @@ Computes the regular Euclidean distance between points. .. code:: python - import numpy as np - from python_tsp.distances import euclidean_distance_matrix - sources = np.array([[0, 0], [1, 1]]) - destinations = np.array([[2, 2], [3, 3], [4, 4]]) + sources = [[0, 0], [1, 1]] + destinations = [[2, 2], [3, 3], [4, 4]] distance_matrix = euclidean_distance_matrix(sources, destinations) - # outputs a 2 x 3 numpy array + # outputs a 2 x 3 matrix (list of lists) The returned distance matrix has in the ``i``-th row the distance from the ``i``-th source to each destination. This API is similar to all distance functions here. @@ -33,7 +31,7 @@ Notice that, in general, the distance matrix is non-square. While this is by des # same as # distance_matrix = euclidean_distance_matrix(sources, sources) -If your problem requires a matrix of integers, just manipulate it like any Numpy array; e.g., ``distance_matrix.astype(int)``. +If your problem requires a matrix of integers, just convert the values; e.g., ``[[int(v) for v in row] for row in distance_matrix]``. Great Circle Distance @@ -41,21 +39,19 @@ Great Circle Distance In case the nodes represent coordinates in a sphere (such as the planet Earth), a more appropriate distance can be the `Great Circle Distance `_. -For example, if you have an array where each row has the latitude and longitude of a point, +For example, if you have a list where each row has the latitude and longitude of a point, .. code:: python - import numpy as np - from python_tsp.distances import great_circle_distance_matrix - sources = np.array([ + sources = [ [ 40.73024833, -73.79440675], # latitude, longitude [ 41.47362495, -73.92783272], [ 41.26591 , -73.21026228], [ 41.3249908 , -73.507788 ] - ]) + ] distance_matrix = great_circle_distance_matrix(sources) @@ -70,17 +66,15 @@ Again in case you have coordinates but would like to take a city's geography int .. code:: python - import numpy as np - from python_tsp.distances import osrm_distance_matrix - sources = np.array([ + sources = [ [ 40.73024833, -73.79440675], # latitude, longitude [ 41.47362495, -73.92783272], [ 41.26591 , -73.21026228], [ 41.3249908 , -73.507788 ] - ]) + ] distance_matrix = osrm_distance_matrix( sources, osrm_server_address="http://localhost:5000" ) @@ -127,14 +121,14 @@ Finally, remember you can also compute the distance between different sources an .. code:: python - sources = np.array([ + sources = [ [ 40.73024833, -73.79440675], # latitude, longitude [ 41.47362495, -73.92783272], [ 41.26591 , -73.21026228], - ]) - destinations = np.array([ + ] + destinations = [ [ 41.3249908 , -73.507788 ] - ]) + ] distance_matrix = osrm_distance_matrix( sources, @@ -164,4 +158,4 @@ Finally, this module also has support for many TSPLIB-type files of ``TSP`` and tsplib_file = "tests/tsplib_data/br17.atsp" # replace with the path to your TSPLIB file distance_matrix = tsplib_distance_matrix(tsplib_file) - # outputs a 17 x 17 array + # outputs a 17 x 17 matrix (list of lists) diff --git a/docs/solvers.rst b/docs/solvers.rst index 5c4476b..b009267 100644 --- a/docs/solvers.rst +++ b/docs/solvers.rst @@ -4,7 +4,7 @@ Solvers This library has currently two classes of solvers: exact solvers and heuristics. -All solvers require at least a ``distance_matrix`` as input, which is an ``n x n`` numpy array containing the distance matrix for a problem with ``n`` nodes. This matrix can contain integers or floats and does not need to be symmetric. Other properties specific to each solver will be detailed below. +All solvers require at least a ``distance_matrix`` as input, which is an ``n x n`` matrix (list of lists) containing the distance matrix for a problem with ``n`` nodes. This matrix can contain integers or floats and does not need to be symmetric. Other properties specific to each solver will be detailed below. All of them also return a permutation of integers from ``0`` to ``n`` containing the best route found, plus a number indicating the route cost. @@ -87,7 +87,7 @@ Notice this local optimum may be different for distinct perturbation schemes and from python_tsp.heuristics import solve_tsp_local_search xopt, fopt = solve_tsp_local_search( - distance_matrix: np.ndarray, + distance_matrix: list[list[float]], x0: Optional[List[int]] = None, perturbation_scheme: str = "two_opt", max_processing_time: Optional[float] = None, @@ -133,7 +133,7 @@ An implementation of the `Simulated Annealing =0.7.1,<0.8.0", "requests>=2.28.0,<3.0.0", - "numpy>=2.0.0,<3.0.0", ] [project.urls] @@ -61,5 +60,4 @@ select = [ "SIM", # isort "I", - "NPY201", # numpy2-deprecation ] diff --git a/python_tsp/distances/data_processing.py b/python_tsp/distances/data_processing.py index 70e7803..161a9f3 100644 --- a/python_tsp/distances/data_processing.py +++ b/python_tsp/distances/data_processing.py @@ -1,21 +1,26 @@ -"""Common data processing tasks between all distances""" - -from typing import Optional - -import numpy as np +from __future__ import annotations def process_input( - sources: np.ndarray, destinations: Optional[np.ndarray] = None -) -> tuple[np.ndarray, np.ndarray]: + sources: list[list[float]] | list[float], + destinations: list[list[float]] | list[float] | None = None, +) -> tuple[list[list[float]], list[list[float]]]: """Pre-process input - This function ensures ``sources`` and ``destinations`` have at least two - dimensions, and if ``destinations`` is `None`, set it equal to ``sources``. + This function ensures ``sources`` and ``destinations`` are two-dimensional + lists, and if ``destinations`` is `None`, set it equal to ``sources``. """ if destinations is None: destinations = sources - sources = np.atleast_2d(sources) - destinations = np.atleast_2d(destinations) + sources = _ensure_2d(sources) + destinations = _ensure_2d(destinations) return sources, destinations + + +def _ensure_2d(points: list) -> list[list[float]]: + if not points: + return [] + if isinstance(points[0], (int, float)): + return [points] + return points diff --git a/python_tsp/distances/euclidean_distance.py b/python_tsp/distances/euclidean_distance.py index 9a1dc59..5d8d7c5 100644 --- a/python_tsp/distances/euclidean_distance.py +++ b/python_tsp/distances/euclidean_distance.py @@ -1,28 +1,27 @@ -"""Euclidean distance""" +from __future__ import annotations -from typing import Optional - -import numpy as np +import math from .data_processing import process_input def euclidean_distance_matrix( - sources: np.ndarray, destinations: Optional[np.ndarray] = None -) -> np.ndarray: + sources: list[list[float]] | list[float], + destinations: list[list[float]] | list[float] | None = None, +) -> list[list[float]]: """Distance matrix using the Euclidean distance Parameters ---------- sources, destinations - Arrays with each row containing the coordinates of a point. If + Lists with each row containing the coordinates of a point. If ``destinations`` is None, compute the distance between each source in ``sources`` and outputs a square distance matrix. Returns ------- distance_matrix - Array with the (i, j) entry indicating the Euclidean distance between + list with the (i, j) entry indicating the Euclidean distance between the i-th row in ``sources`` and the j-th row in ``destinations``. Notes @@ -32,10 +31,15 @@ def euclidean_distance_matrix( sqrt((y1 - x1)**2 + (y2 - x2)**2 + ... + (yn - xn)**2) - If the user requires the distance between each point in a single array, - call this this function with ``destinations`` set to `None`. + If the user requires the distance between each point in a single list, + call this function with ``destinations`` set to `None`. """ sources, destinations = process_input(sources, destinations) - return np.sqrt( - ((sources[:, :, None] - destinations[:, :, None].T) ** 2).sum(axis=1) - ) + result = [] + for src in sources: + row = [] + for dst in destinations: + dist = math.sqrt(sum((a - b) ** 2 for a, b in zip(src, dst))) + row.append(dist) + result.append(row) + return result diff --git a/python_tsp/distances/great_circle_distance.py b/python_tsp/distances/great_circle_distance.py index 83e553e..557db96 100644 --- a/python_tsp/distances/great_circle_distance.py +++ b/python_tsp/distances/great_circle_distance.py @@ -1,15 +1,42 @@ -from typing import Optional +from __future__ import annotations -import numpy as np +import math from .data_processing import process_input EARTH_RADIUS_METERS = 6371000 +def _great_circle_distance(src: list[float], dst: list[float]) -> float: + src_rad = [math.radians(c) for c in src] + dst_rad = [math.radians(c) for c in dst] + + delta_lambda = src_rad[1] - dst_rad[1] + phi1 = src_rad[0] + phi2 = dst_rad[0] + + delta_sigma = math.atan2( + math.sqrt( + (math.cos(phi2) * math.sin(delta_lambda)) ** 2 + + ( + math.cos(phi1) * math.sin(phi2) + - math.sin(phi1) * math.cos(phi2) * math.cos(delta_lambda) + ) + ** 2 + ), + ( + math.sin(phi1) * math.sin(phi2) + + math.cos(phi1) * math.cos(phi2) * math.cos(delta_lambda) + ), + ) + + return EARTH_RADIUS_METERS * delta_sigma + + def great_circle_distance_matrix( - sources: np.ndarray, destinations: Optional[np.ndarray] = None -) -> np.ndarray: + sources: list[list[float]] | list[float], + destinations: list[list[float]] | list[float] | None = None, +) -> list[list[float]]: """Distance matrix using the Great Circle distance This is an Euclidean-like distance but on spheres [1]. In this case it is used to estimate the distance in meters between locations in the Earth. @@ -17,7 +44,7 @@ def great_circle_distance_matrix( Parameters ---------- sources, destinations - Arrays with each row containing the coordinates of a point in the form + Lists with each row containing the coordinates of a point in the form [lat, lng]. Notice it only considers the first two columns. Also, if ``destinations`` is `None`, compute the distance between each source in ``sources``. @@ -25,7 +52,7 @@ def great_circle_distance_matrix( Returns ------- distance_matrix - Array with the (i, j) entry indicating the Great Circle distance (in + list with the (i, j) entry indicating the Great Circle distance (in meters) between the i-th row in ``sources`` and the j-th row in ``destinations``. @@ -34,28 +61,9 @@ def great_circle_distance_matrix( [1] https://en.wikipedia.org/wiki/Great-circle_distance Using the third computational formula """ - sources, destinations = process_input(sources, destinations) - sources_rad = np.radians(sources) - dests_rad = np.radians(destinations) - - delta_lambda = sources_rad[:, [1]] - dests_rad[:, 1] # (N x M) lng - phi1 = sources_rad[:, [0]] # (N x 1) array of source latitudes - phi2 = dests_rad[:, 0] # (1 x M) array of destination latitudes - - delta_sigma = np.arctan2( - np.sqrt( - (np.cos(phi2) * np.sin(delta_lambda)) ** 2 - + ( - np.cos(phi1) * np.sin(phi2) - - np.sin(phi1) * np.cos(phi2) * np.cos(delta_lambda) - ) - ** 2 - ), - ( - np.sin(phi1) * np.sin(phi2) - + np.cos(phi1) * np.cos(phi2) * np.cos(delta_lambda) - ), - ) - - return EARTH_RADIUS_METERS * delta_sigma + result = [] + for src in sources: + row = [_great_circle_distance(src, dst) for dst in destinations] + result.append(row) + return result diff --git a/python_tsp/distances/osrm_distance.py b/python_tsp/distances/osrm_distance.py index 21ca321..cb4a236 100644 --- a/python_tsp/distances/osrm_distance.py +++ b/python_tsp/distances/osrm_distance.py @@ -1,25 +1,25 @@ +from __future__ import annotations + from math import ceil -from typing import Optional -import numpy as np import requests from .data_processing import process_input def osrm_distance_matrix( - sources: np.ndarray, - destinations: Optional[np.ndarray] = None, + sources: list[list[float]] | list[float], + destinations: list[list[float]] | list[float] | None = None, osrm_server_address: str = "http://localhost:5000", osrm_batch_size: int = 500, cost_type: str = "distances", -) -> np.ndarray: +) -> list[list[float]]: """Compute distance matrix from sources to destinations using OSRM service Parameters ---------- sources, destinations - 2D Arrays of coordinates in the form [lat, lng] for each row + 2D lists of coordinates in the form [lat, lng] for each row Also, if ``destinations`` is `None`, compute the distance between each source in ``sources``. @@ -41,9 +41,9 @@ def osrm_distance_matrix( """ sources, destinations = process_input(sources, destinations) - num_sources = sources.shape[0] - num_destinations = destinations.shape[0] - cost_matrix = np.zeros((num_sources, num_destinations)) + num_sources = len(sources) + num_destinations = len(destinations) + cost_matrix = [[0.0] * num_destinations for _ in range(num_sources)] num_batches_i = ceil(num_sources / osrm_batch_size) num_batches_j = ceil(num_destinations / osrm_batch_size) @@ -58,79 +58,51 @@ def osrm_distance_matrix( sources_batch = sources[start_i:end_i] destinations_batch = destinations[start_j:end_j] - cost_matrix[start_i:end_i, start_j:end_j] = ( - _get_batch_osrm_distance( - sources_batch, - destinations_batch, - osrm_server_address, - cost_type=cost_type, - ) + batch_result = _get_batch_osrm_distance( + sources_batch, + destinations_batch, + osrm_server_address, + cost_type=cost_type, ) + for ii in range(len(sources_batch)): + for jj in range(len(destinations_batch)): + cost_matrix[start_i + ii][start_j + jj] = batch_result[ii][ + jj + ] + return cost_matrix def _get_batch_osrm_distance( - sources_batch: np.ndarray, - destinations_batch: np.ndarray, + sources_batch: list[list[float]], + destinations_batch: list[list[float]], osrm_server_address: str, cost_type: str, -): - """Request the OSRM distance matrix for a given batch""" +) -> list[list[float]]: url = _format_osrm_url( sources_batch, destinations_batch, osrm_server_address, cost_type ) resp = requests.get(url) resp.raise_for_status() - - return np.array(resp.json()[cost_type]) + return resp.json()[cost_type] def _format_osrm_url( - sources_batch: np.ndarray, - destinations_batch: np.ndarray, + sources_batch: list[list[float]], + destinations_batch: list[list[float]], osrm_server_address: str, cost_type: str, ) -> str: - """Format OSRM url string with sources and destinations - - Notes - ----- - Consider the N sources in the form - (lat_src1, lgn_src1), (lat_src2, lgn_src2), ... - - and the M destinations in the form - (lat_dest1, lgn_dest1), (lat_dest2, lgn_dest2), ... - - This function converts these properties in a URL of the form - {OSRM_SERVER_ADDRESS}/table/v1/driving/ - lng_src1,lat_src1;lng_src2,lat_src2;...;lng_srcN,lat_srcN; - lng_dest1,lat_dest1;lng_dest2,lat_dest2;...;lng_destM,lng_destM - ?sources=0;1;...;N-1 - &destinations=N;N+1;...;N+M-1 - &annotations=distance - - In the simpler case when sources == destinations, the URL is simplified to - {OSRM_SERVER_ADDRESS}/table/v1/driving/ - lng_src1,lat_src1;lng_src2,lat_src2;...;lng_srcN,lat_srcN - ?annotations=distance - - Obs: Replace "distance" with "duration" if a time matrix is required - Obs2: The matrix type follows the singular form in the URL (e.g., - "distance"), but the returned JSON follows the plural form (e.g., - "distances"). Thus, we ignore the last letter of the input type - """ url_cost_type = cost_type[:-1] sources_coord = ";".join( f"{source[1]},{source[0]}" for source in sources_batch ) - # If sources == destinations, return a simpler URL early. Notice it needs - # at least two points, otherwise OSRM complains if ( - np.array_equal(sources_batch, destinations_batch) - and sources_batch.shape[0] > 1 + _array_equal(sources_batch, destinations_batch) + and len(sources_batch) > 1 ): return ( f"{osrm_server_address}/table/v1/driving/" @@ -144,10 +116,8 @@ def _format_osrm_url( ) locations_coord = sources_coord + ";" + destinations_coord - # Get indices of sources and destinations in the form - # sources = 0,1,...,N' and destinations = N'+1,N'+2...N'+M' - num_sources = sources_batch.shape[0] - num_destinations = destinations_batch.shape[0] + num_sources = len(sources_batch) + num_destinations = len(destinations_batch) sources_indices = ";".join(str(index) for index in range(num_sources)) destinations_indices = ";".join( @@ -161,3 +131,12 @@ def _format_osrm_url( f"?sources={sources_indices}&destinations={destinations_indices}" f"&annotations={url_cost_type}" ) + + +def _array_equal(a: list[list[float]], b: list[list[float]]) -> bool: + if len(a) != len(b): + return False + return all( + len(row_a) == len(row_b) and all(x == y for x, y in zip(row_a, row_b)) + for row_a, row_b in zip(a, b) + ) diff --git a/python_tsp/distances/tsplib_distance.py b/python_tsp/distances/tsplib_distance.py index ea23a08..c316dcc 100644 --- a/python_tsp/distances/tsplib_distance.py +++ b/python_tsp/distances/tsplib_distance.py @@ -1,8 +1,7 @@ -import numpy as np import tsplib95 -def tsplib_distance_matrix(tsplib_file: str) -> np.ndarray: +def tsplib_distance_matrix(tsplib_file: str) -> list[list[float]]: """Distance matrix from a TSPLIB file Parameters @@ -14,24 +13,18 @@ def tsplib_distance_matrix(tsplib_file: str) -> np.ndarray: Returns ------- distance_matrix - A ND-array with the equivalent distance matrix of the input file + A list of lists with the equivalent distance matrix of the input file Notes ----- This function can handle any file supported by the `tsplib95` lib. """ - tsp_problem = tsplib95.load(tsplib_file) - distance_matrix_flattened = np.array( - [tsp_problem.get_weight(*edge) for edge in tsp_problem.get_edges()] - ) - distance_matrix = np.reshape( - distance_matrix_flattened, - (tsp_problem.dimension, tsp_problem.dimension), - ) + flat = [tsp_problem.get_weight(*edge) for edge in tsp_problem.get_edges()] + dim = tsp_problem.dimension + distance_matrix = [flat[i * dim : (i + 1) * dim] for i in range(dim)] + + for i in range(dim): + distance_matrix[i][i] = 0 - # Some problems with EXPLICIT matrix have a large number in the distance - # from a node to itself, which makes no sense for our problems. Thus, - # always ensure a diagonal filled with zeros - np.fill_diagonal(distance_matrix, 0) return distance_matrix diff --git a/python_tsp/exact/branch_and_bound/node.py b/python_tsp/exact/branch_and_bound/node.py index 3b69691..ccf8f5d 100644 --- a/python_tsp/exact/branch_and_bound/node.py +++ b/python_tsp/exact/branch_and_bound/node.py @@ -3,8 +3,6 @@ from dataclasses import dataclass from math import inf -import numpy as np - @dataclass class Node: @@ -38,10 +36,12 @@ class Node: index: int path: list[int] cost: float - cost_matrix: np.ndarray + cost_matrix: list[list[float]] @staticmethod - def compute_reduced_matrix(matrix: np.ndarray) -> tuple[np.ndarray, float]: + def compute_reduced_matrix( + matrix: list[list[float]], + ) -> tuple[list[list[float]], float]: """ Compute the reduced matrix and the cost of reducing it. @@ -56,27 +56,30 @@ def compute_reduced_matrix(matrix: np.ndarray) -> tuple[np.ndarray, float]: A tuple containing the reduced matrix and the total cost of reductions. """ - mask = matrix != inf - reduced_matrix = np.copy(matrix) - - min_rows = np.min(reduced_matrix, axis=1, keepdims=True) - min_rows[min_rows == inf] = 0 - if np.any(min_rows != 0): - reduced_matrix = np.where( - mask, reduced_matrix - min_rows, reduced_matrix - ) - - min_cols = np.min(reduced_matrix, axis=0, keepdims=True) - min_cols[min_cols == inf] = 0 - if np.any(min_cols != 0): - reduced_matrix = np.where( - mask, reduced_matrix - min_cols, reduced_matrix - ) - - return reduced_matrix, np.sum(min_rows) + np.sum(min_cols) + n = len(matrix) + reduced = [row[:] for row in matrix] + total_reduction = 0.0 + + min_rows = [min(row) for row in reduced] + for i in range(n): + if min_rows[i] != inf and min_rows[i] != 0: + for j in range(n): + if reduced[i][j] != inf: + reduced[i][j] -= min_rows[i] + total_reduction += min_rows[i] + + min_cols = [min(reduced[i][j] for i in range(n)) for j in range(n)] + for j in range(n): + if min_cols[j] != inf and min_cols[j] != 0: + for i in range(n): + if reduced[i][j] != inf: + reduced[i][j] -= min_cols[j] + total_reduction += min_cols[j] + + return reduced, total_reduction @classmethod - def from_cost_matrix(cls, cost_matrix: np.ndarray) -> Node: + def from_cost_matrix(cls, cost_matrix: list[list[float]]) -> Node: """ Create a Node object from a given cost matrix. @@ -116,9 +119,11 @@ def from_parent(cls, parent: Node, index: int) -> Node: Node A new Node object with the updated path and cost. """ - matrix = np.copy(parent.cost_matrix) - matrix[parent.index, :] = inf - matrix[:, index] = inf + matrix = [row[:] for row in parent.cost_matrix] + n = len(matrix) + matrix[parent.index] = [inf] * n + for i in range(n): + matrix[i][index] = inf matrix[index][0] = inf _cost_matrix, _cost = cls.compute_reduced_matrix(matrix=matrix) return cls( diff --git a/python_tsp/exact/branch_and_bound/solver.py b/python_tsp/exact/branch_and_bound/solver.py index 66fe5a0..dc72b7e 100644 --- a/python_tsp/exact/branch_and_bound/solver.py +++ b/python_tsp/exact/branch_and_bound/solver.py @@ -1,12 +1,10 @@ from math import inf -import numpy as np - from python_tsp.exact.branch_and_bound import Node, PriorityQueue def solve_tsp_branch_and_bound( - distance_matrix: np.ndarray, + distance_matrix: list[list[float]], ) -> tuple[list[int], float]: """ Solve the Traveling Salesperson Problem (TSP) using the @@ -46,8 +44,9 @@ def solve_tsp_branch_and_bound( W. H. Freeman and Company. """ num_cities = len(distance_matrix) - cost_matrix = np.copy(distance_matrix).astype(float) - np.fill_diagonal(cost_matrix, inf) + cost_matrix = [[float(v) for v in row] for row in distance_matrix] + for i in range(num_cities): + cost_matrix[i][i] = inf root = Node.from_cost_matrix(cost_matrix=cost_matrix) pq = PriorityQueue([root]) diff --git a/python_tsp/exact/brute_force.py b/python_tsp/exact/brute_force.py index 641a052..bfd58de 100644 --- a/python_tsp/exact/brute_force.py +++ b/python_tsp/exact/brute_force.py @@ -1,15 +1,11 @@ -"""Module with a brute force TSP solver""" - from itertools import permutations from typing import Any, Optional -import numpy as np - from python_tsp.utils import compute_permutation_distance def solve_tsp_brute_force( - distance_matrix: np.ndarray, + distance_matrix: list[list[float]], ) -> tuple[Optional[list], Any]: """Solve TSP to optimality with a brute force approach @@ -27,20 +23,17 @@ def solve_tsp_brute_force( The total distance the optimal permutation produces Notes - ---- + ----- The algorithm checks all permutations and returns the one with smallest distance. In principle, the total number of possibilities would be n! for n nodes. However, we can fix node 0 and permutate only the remaining, reducing the possibilities to (n - 1)!. """ - - # Exclude 0 from the range since it is fixed as starting point - points = range(1, distance_matrix.shape[0]) - best_distance = np.inf + points = range(1, len(distance_matrix)) + best_distance = float("inf") best_permutation = None for partial_permutation in permutations(points): - # Remember to add the starting node before evaluating it permutation = [0] + list(partial_permutation) distance = compute_permutation_distance(distance_matrix, permutation) diff --git a/python_tsp/exact/dynamic_programming.py b/python_tsp/exact/dynamic_programming.py index f690dba..7ebd9c7 100644 --- a/python_tsp/exact/dynamic_programming.py +++ b/python_tsp/exact/dynamic_programming.py @@ -1,11 +1,9 @@ from functools import lru_cache from typing import Optional -import numpy as np - def solve_tsp_dynamic_programming( - distance_matrix: np.ndarray, + distance_matrix: list[list[float]], maxsize: Optional[int] = None, ) -> tuple[list, float]: """ @@ -89,20 +87,16 @@ def solve_tsp_dynamic_programming( --------- https://en.wikipedia.org/wiki/Held%E2%80%93Karp_algorithm#cite_note-5 """ - # Get initial set {1, 2, ..., tsp_size} as a frozenset because @lru_cache - # requires a hashable type - N = frozenset(range(1, distance_matrix.shape[0])) + N = frozenset(range(1, len(distance_matrix))) memo: dict[tuple, int] = {} - # Step 1: get minimum distance @lru_cache(maxsize=maxsize) def dist(ni: int, N: frozenset) -> float: if not N: - return distance_matrix[ni, 0] + return distance_matrix[ni][0] - # Store the costs in the form (nj, dist(nj, N)) costs = [ - (nj, distance_matrix[ni, nj] + dist(nj, N.difference({nj}))) + (nj, distance_matrix[ni][nj] + dist(nj, N.difference({nj}))) for nj in N ] nmin, min_cost = min(costs, key=lambda x: x[1]) @@ -112,8 +106,7 @@ def dist(ni: int, N: frozenset) -> float: best_distance = dist(0, N) - # Step 2: get path with the minimum distance - ni = 0 # start at the origin + ni = 0 solution = [0] while N: diff --git a/python_tsp/heuristics/lin_kernighan.py b/python_tsp/heuristics/lin_kernighan.py index edf65e5..823485a 100644 --- a/python_tsp/heuristics/lin_kernighan.py +++ b/python_tsp/heuristics/lin_kernighan.py @@ -1,25 +1,10 @@ from typing import Optional, TextIO -import numpy as np - from python_tsp.exact import solve_tsp_brute_force from python_tsp.utils import _optional_open, setup_initial_solution def _cycle_to_successors(cycle: list[int]) -> list[int]: - """ - Convert a cycle representation to successors representation. - - Parameters - ---------- - cycle - A list representing a cycle. - - Returns - ------- - List - A list representing successors. - """ successors = cycle[:] n = len(cycle) for i, _ in enumerate(cycle): @@ -28,19 +13,6 @@ def _cycle_to_successors(cycle: list[int]) -> list[int]: def _successors_to_cycle(successors: list[int]) -> list[int]: - """ - Convert a successors representation to a cycle representation. - - Parameters - ---------- - successors - A list representing successors. - - Returns - ------- - List - A list representing a cycle. - """ cycle = successors[:] j = 0 for i, _ in enumerate(successors): @@ -50,71 +22,40 @@ def _successors_to_cycle(successors: list[int]) -> list[int]: def _minimizes_hamiltonian_path_distance( - tabu: np.ndarray, + tabu: list[list[int]], iteration: int, successors: list[int], ejected_edge: tuple[int, int], - distance_matrix: np.ndarray, + distance_matrix: list[list[float]], hamiltonian_path_distance: float, hamiltonian_cycle_distance: float, ) -> tuple[int, int, float]: - """ - Minimize the Hamiltonian path distance after ejecting an edge. - - Parameters - ---------- - tabu - A NumPy array for tabu management. - - iteration - The current iteration. - - successors - A list representing successors. - - ejected_edge - The edge that was ejected. - - distance_matrix - A NumPy array representing the distance matrix. - - hamiltonian_path_distance - The Hamiltonian path distance. - - hamiltonian_cycle_distance - The Hamiltonian cycle distance. - - Returns - ------- - Tuple - The best c, d, and the new Hamiltonian path distance found. - """ a, b = ejected_edge best_c = c = last_c = successors[b] - path_cb_distance = distance_matrix[c, b] - path_bc_distance = distance_matrix[b, c] + path_cb_distance = distance_matrix[c][b] + path_bc_distance = distance_matrix[b][c] hamiltonian_path_distance_found = hamiltonian_cycle_distance while successors[c] != a: d = successors[c] - path_cb_distance += distance_matrix[c, last_c] - path_bc_distance += distance_matrix[last_c, c] + path_cb_distance += distance_matrix[c][last_c] + path_bc_distance += distance_matrix[last_c][c] new_hamiltonian_path_distance_found = ( hamiltonian_path_distance - + distance_matrix[b, d] - - distance_matrix[c, d] + + distance_matrix[b][d] + - distance_matrix[c][d] + path_cb_distance - path_bc_distance ) if ( - new_hamiltonian_path_distance_found + distance_matrix[a, c] + new_hamiltonian_path_distance_found + distance_matrix[a][c] < hamiltonian_cycle_distance ): return c, d, new_hamiltonian_path_distance_found if ( - tabu[c, d] != iteration + tabu[c][d] != iteration and new_hamiltonian_path_distance_found < hamiltonian_path_distance_found ): @@ -140,7 +81,7 @@ def _print_message( def _solve_tsp_brute_force( - distance_matrix: np.ndarray, + distance_matrix: list[list[float]], log_file: Optional[str] = None, verbose: bool = False, ) -> tuple[list[int], float]: @@ -163,7 +104,7 @@ def _solve_tsp_brute_force( def solve_tsp_lin_kernighan( - distance_matrix: np.ndarray, + distance_matrix: list[list[float]], x0: Optional[list[int]] = None, log_file: Optional[str] = None, verbose: bool = False, @@ -197,7 +138,7 @@ def solve_tsp_lin_kernighan( Éric D. Taillard, "Design of Heuristic Algorithms for Hard Optimization," Chapter 5, Section 5.3.2.1: Lin-Kernighan Neighborhood, Springer, 2023. """ - num_vertices = distance_matrix.shape[0] + num_vertices = len(distance_matrix) if num_vertices < 4: return _solve_tsp_brute_force(distance_matrix, log_file, verbose) @@ -207,7 +148,7 @@ def solve_tsp_lin_kernighan( vertices = list(range(num_vertices)) iteration = 0 improvement = True - tabu = np.zeros(shape=(num_vertices, num_vertices), dtype=int) + tabu = [[0] * num_vertices for _ in range(num_vertices)] with _optional_open(log_file, "w") as log_file_handler: while improvement: @@ -215,22 +156,18 @@ def solve_tsp_lin_kernighan( improvement = False successors = _cycle_to_successors(hamiltonian_cycle) - # Eject edge [a, b] to start the chain and compute the Hamiltonian - # path distance obtained by ejecting edge [a, b] from the cycle - # as reference. - a = int(np.argmax(distance_matrix[vertices, successors])) + a = max( + range(len(vertices)), + key=lambda i: distance_matrix[vertices[i]][successors[i]], + ) b = successors[a] hamiltonian_path_distance = ( - hamiltonian_cycle_distance - distance_matrix[a, b] + hamiltonian_cycle_distance - distance_matrix[a][b] ) while True: ejected_edge = a, b - # Find the edge [c, d] that minimizes the Hamiltonian - # path obtained by removing edge [c, d] and adding - # edge [b, d], with [c, d] not removed in the - # current ejection chain. ( c, d, @@ -245,26 +182,20 @@ def solve_tsp_lin_kernighan( hamiltonian_cycle_distance, ) - # If the Hamiltonian cycle cannot be improved, return - # to the solution and try another ejection. if ( hamiltonian_path_distance_found >= hamiltonian_cycle_distance ): break - # Update Hamiltonian path distance reference hamiltonian_path_distance = hamiltonian_path_distance_found - # Reverse the direction of the path from b to c i, si, successors[b] = b, successors[b], d while i != c: successors[si], i, si = i, si, successors[si] - # Don't remove again the minimal edge found - tabu[c, d] = tabu[d, c] = iteration + tabu[c][d] = tabu[d][c] = iteration - # c plays the role of b in the next iteration b = c msg = ( @@ -273,16 +204,15 @@ def solve_tsp_lin_kernighan( ) _print_message(msg, verbose, log_file_handler) - # If the Hamiltonian cycle improves, update the solution if ( - hamiltonian_path_distance + distance_matrix[a, b] + hamiltonian_path_distance + distance_matrix[a][b] < hamiltonian_cycle_distance ): improvement = True successors[a] = b hamiltonian_cycle = _successors_to_cycle(successors) hamiltonian_cycle_distance = ( - hamiltonian_path_distance + distance_matrix[a, b] + hamiltonian_path_distance + distance_matrix[a][b] ) return hamiltonian_cycle, hamiltonian_cycle_distance diff --git a/python_tsp/heuristics/local_search.py b/python_tsp/heuristics/local_search.py index 45e34e8..b3035b8 100644 --- a/python_tsp/heuristics/local_search.py +++ b/python_tsp/heuristics/local_search.py @@ -1,10 +1,6 @@ -"""Simple local search solver""" - from timeit import default_timer from typing import Optional, TextIO -import numpy as np - from python_tsp.heuristics.perturbation_schemes import neighborhood_gen from python_tsp.utils import ( _optional_open, @@ -16,7 +12,7 @@ def solve_tsp_local_search( - distance_matrix: np.ndarray, + distance_matrix: list[list[float]], x0: Optional[list[int]] = None, perturbation_scheme: str = "two_opt", max_processing_time: Optional[float] = None, @@ -67,7 +63,7 @@ def solve_tsp_local_search( improvement. Return `x`, `fx` as solution. """ x, fx = setup_initial_solution(distance_matrix, x0) - max_processing_time = max_processing_time or np.inf + max_processing_time = max_processing_time or float("inf") with _optional_open(log_file, "w") as log_file_handler: tic = default_timer() @@ -92,7 +88,7 @@ def solve_tsp_local_search( if fn < fx: improvement = True x, fx = xn, fn - break # early stop due to first improvement local search + break return x, fx diff --git a/python_tsp/heuristics/record_to_record.py b/python_tsp/heuristics/record_to_record.py index 5dce4c6..f20b2c9 100644 --- a/python_tsp/heuristics/record_to_record.py +++ b/python_tsp/heuristics/record_to_record.py @@ -1,8 +1,6 @@ from random import randint from typing import Optional, TextIO -import numpy as np - from python_tsp.heuristics import solve_tsp_lin_kernighan from python_tsp.utils import _optional_open, setup_initial_solution @@ -18,7 +16,7 @@ def _print_message( def solve_tsp_record_to_record( - distance_matrix: np.ndarray, + distance_matrix: list[list[float]], x0: Optional[list[int]] = None, max_iterations: Optional[int] = None, log_file: Optional[str] = None, @@ -58,7 +56,7 @@ def solve_tsp_record_to_record( Éric D. Taillard, "Design of Heuristic Algorithms for Hard Optimization," Chapter 7, Problems of Chapter 7, 7.4 Record to Record, Springer, 2023. """ - n = distance_matrix.shape[0] + n = len(distance_matrix) max_iterations = max_iterations or n x, fx = setup_initial_solution(distance_matrix=distance_matrix, x0=x0) diff --git a/python_tsp/heuristics/simulated_annealing.py b/python_tsp/heuristics/simulated_annealing.py index e6c2ee4..5ae02b7 100644 --- a/python_tsp/heuristics/simulated_annealing.py +++ b/python_tsp/heuristics/simulated_annealing.py @@ -1,9 +1,9 @@ +import math +import random from math import inf from timeit import default_timer from typing import Optional, TextIO -import numpy as np - from python_tsp.heuristics.perturbation_schemes import neighborhood_gen from python_tsp.utils import ( _optional_open, @@ -17,7 +17,7 @@ def solve_tsp_simulated_annealing( - distance_matrix: np.ndarray, + distance_matrix: list[list[float]], x0: Optional[list[int]] = None, perturbation_scheme: str = "two_opt", alpha: float = 0.9, @@ -74,16 +74,17 @@ def solve_tsp_simulated_annealing( x, fx = setup_initial_solution(distance_matrix, x0) temp = _initial_temperature(distance_matrix, x, fx, perturbation_scheme) max_processing_time = max_processing_time or inf + with _optional_open(log_file, "w") as log_file_handler: n = len(x) k_inner_min = n k_inner_max = MAX_INNER_ITERATIONS_MULTIPLIER * n - k_noimprovements = 0 # number of inner loops without improvement + k_noimprovements = 0 tic = default_timer() stop_early = False while (k_noimprovements < MAX_NON_IMPROVEMENTS) and (not stop_early): - k_accepted = 0 # number of accepted perturbations + k_accepted = 0 for k in range(k_inner_max): if default_timer() - tic > max_processing_time: _print_message(TIME_LIMIT_MSG, verbose, log_file_handler) @@ -109,7 +110,7 @@ def solve_tsp_simulated_annealing( if k_accepted >= k_inner_min: break - temp *= alpha # temperature update + temp *= alpha k_noimprovements += k_accepted == 0 return x, fx @@ -126,7 +127,7 @@ def _print_message( def _initial_temperature( - distance_matrix: np.ndarray, + distance_matrix: list[list[float]], x: list[int], fx: float, perturbation_scheme: str, @@ -150,18 +151,16 @@ def _initial_temperature( case studies. Springer Science & Business Media, 2006. """ - # Step 1 dfx_list = [] for _ in range(100): xn = _perturbation(x, perturbation_scheme) fn = compute_permutation_distance(distance_matrix, xn) dfx_list.append(fn - fx) - dfx_mean = np.abs(np.mean(dfx_list)) + dfx_mean = abs(sum(dfx_list) / len(dfx_list)) - # Step 2 tau0 = 0.5 - return -dfx_mean / np.log(tau0) + return -dfx_mean / math.log(tau0) def _perturbation(x: list[int], perturbation_scheme: str): @@ -178,5 +177,5 @@ def _acceptance_rule(fx: float, fn: float, temp: float) -> bool: dfx = fn - fx return (dfx < 0) or ( - (dfx > 0) and (np.random.rand() <= np.exp(-(fn - fx) / temp)) + (dfx > 0) and (random.random() <= math.exp(-(fn - fx) / temp)) ) diff --git a/python_tsp/utils/permutation_distance.py b/python_tsp/utils/permutation_distance.py index 57babf4..60ef309 100644 --- a/python_tsp/utils/permutation_distance.py +++ b/python_tsp/utils/permutation_distance.py @@ -1,8 +1,5 @@ -import numpy as np - - def compute_permutation_distance( - distance_matrix: np.ndarray, permutation: list[int] + distance_matrix: list[list[float]], permutation: list[int] ) -> float: """Compute the total route distance of a given permutation @@ -35,4 +32,4 @@ def compute_permutation_distance( """ ind1 = permutation ind2 = permutation[1:] + permutation[:1] - return distance_matrix[ind1, ind2].sum() + return sum(distance_matrix[i][j] for i, j in zip(ind1, ind2)) diff --git a/python_tsp/utils/setup_initial_solution.py b/python_tsp/utils/setup_initial_solution.py index b51cd0f..97151e0 100644 --- a/python_tsp/utils/setup_initial_solution.py +++ b/python_tsp/utils/setup_initial_solution.py @@ -1,13 +1,11 @@ from random import sample from typing import Optional -import numpy as np - from .permutation_distance import compute_permutation_distance def setup_initial_solution( - distance_matrix: np.ndarray, x0: Optional[list] = None + distance_matrix: list[list[float]], x0: Optional[list] = None ) -> tuple[list[int], float]: """Return initial solution and its objective value @@ -32,8 +30,8 @@ def setup_initial_solution( """ if not x0: - n = distance_matrix.shape[0] # number of nodes - x0 = [0] + sample(range(1, n), n - 1) # ensure 0 is the first node + n = len(distance_matrix) + x0 = [0] + sample(range(1, n), n - 1) fx0 = compute_permutation_distance(distance_matrix, x0) return x0, fx0 diff --git a/tests/data.py b/tests/data.py index e92d2e9..826915b 100644 --- a/tests/data.py +++ b/tests/data.py @@ -1,45 +1,29 @@ -""" -Module with some variables used in most test files, but too simple to be -considered fixtures -""" - -import numpy as np - -# Symmetric distance matrix -distance_matrix1 = np.array( - [ - [0, 2, 4, 6, 8], - [2, 0, 3, 5, 7], - [4, 6, 0, 4, 6], - [6, 5, 4, 0, 7], - [8, 5, 6, 7, 0], - ] -) +distance_matrix1 = [ + [0.0, 2.0, 4.0, 6.0, 8.0], + [2.0, 0.0, 3.0, 5.0, 7.0], + [4.0, 6.0, 0.0, 4.0, 6.0], + [6.0, 5.0, 4.0, 0.0, 7.0], + [8.0, 5.0, 6.0, 7.0, 0.0], +] optimal_permutation1 = [0, 2, 3, 4, 1] -optimal_distance1 = 22 +optimal_distance1 = 22.0 -# Unsymmetric distance matrix -distance_matrix2 = np.array( - [ - [0, 2, 4, 6, 8], - [3, 0, 3, 5, 7], - [4, 7, 0, 4, 6], - [5, 5, 3, 0, 7], - [6, 3, 4, 5, 0], - ] -) +distance_matrix2 = [ + [0.0, 2.0, 4.0, 6.0, 8.0], + [3.0, 0.0, 3.0, 5.0, 7.0], + [4.0, 7.0, 0.0, 4.0, 6.0], + [5.0, 5.0, 3.0, 0.0, 7.0], + [6.0, 3.0, 4.0, 5.0, 0.0], +] optimal_permutation2 = [0, 1, 2, 4, 3] -optimal_distance2 = 21 +optimal_distance2 = 21.0 -# Open problem (the returning cost is 0) -distance_matrix3 = np.array( - [ - [0, 2, 4, 6, 8], - [0, 0, 3, 5, 7], - [0, 6, 0, 4, 6], - [0, 5, 4, 0, 7], - [0, 5, 6, 7, 0], - ] -) +distance_matrix3 = [ + [0.0, 2.0, 4.0, 6.0, 8.0], + [0.0, 0.0, 3.0, 5.0, 7.0], + [0.0, 6.0, 0.0, 4.0, 6.0], + [0.0, 5.0, 4.0, 0.0, 7.0], + [0.0, 5.0, 6.0, 7.0, 0.0], +] optimal_permutation3 = [0, 1, 2, 3, 4] -optimal_distance3 = 16 +optimal_distance3 = 16.0 diff --git a/tests/distances/test_data_processing.py b/tests/distances/test_data_processing.py index 177c6bc..f02ff03 100644 --- a/tests/distances/test_data_processing.py +++ b/tests/distances/test_data_processing.py @@ -1,23 +1,22 @@ -import numpy as np - from python_tsp.distances.data_processing import process_input def test_1d_array_becomes_2d(): - source = np.array([1, -1]) - destination = np.array([5, -5]) + source = [1.0, -1.0] + destination = [5.0, -5.0] sources_out, destinations_out = process_input(source, destination) - assert sources_out.shape == (1, 2) - assert destinations_out.shape == (1, 2) + assert len(sources_out) == 1 + assert len(sources_out[0]) == 2 + assert len(destinations_out) == 1 + assert len(destinations_out[0]) == 2 def test_no_destinations_become_sources(): - - sources = np.array([[1, -1], [2, -2], [3, -3], [4, -4]]) + sources = [[1.0, -1.0], [2.0, -2.0], [3.0, -3.0], [4.0, -4.0]] sources_out, destinations_out = process_input(sources) - assert np.array_equal(sources_out, sources) - assert np.array_equal(destinations_out, sources) + assert sources_out == sources + assert destinations_out == sources diff --git a/tests/distances/test_euclidean_distance.py b/tests/distances/test_euclidean_distance.py index 278fe3f..782dd52 100644 --- a/tests/distances/test_euclidean_distance.py +++ b/tests/distances/test_euclidean_distance.py @@ -1,4 +1,3 @@ -import numpy as np import pytest from python_tsp.distances import euclidean_distance_matrix @@ -6,51 +5,47 @@ @pytest.fixture def sources(): - return np.array([[1, -1], [2, -2], [3, -3], [4, -4]]) + return [[1.0, -1.0], [2.0, -2.0], [3.0, -3.0], [4.0, -4.0]] @pytest.fixture def destinations(): - return np.array([[5, -5], [6, -6], [7, -7]]) + return [[5.0, -5.0], [6.0, -6.0], [7.0, -7.0]] def test_distance_is_euclidean(): - """It must return an actual Euclidean distance - In this case, it is easy to see that the distance from [1, 1] to [4, 5] - is: - sqrt((4 - 1)**2 + (5 - 1)**2) = sqrt(3**2 + 4**2) = 5 - """ - source = np.array([1, 1]) - destination = np.array([4, 5]) + source = [1.0, 1.0] + destination = [4.0, 5.0] distance_matrix = euclidean_distance_matrix(source, destination) - assert distance_matrix[0] == 5.0 + assert distance_matrix[0][0] == 5.0 def test_all_elements_are_non_negative(sources, destinations): - """Being distances, all elements must be non-negative""" distance_matrix = euclidean_distance_matrix(sources, destinations) - assert np.all(distance_matrix >= 0) + assert all(v >= 0 for row in distance_matrix for v in row) def test_square_matrix_has_zero_diagonal(sources): - """Main diagonal is the distance from a point to itself""" distance_matrix = euclidean_distance_matrix(sources) - assert np.all(np.diag(distance_matrix) == 0) + for i in range(len(sources)): + assert distance_matrix[i][i] == 0 def test_square_matrix_is_symmetric(sources): distance_matrix = euclidean_distance_matrix(sources) - - assert np.allclose(distance_matrix, distance_matrix.T) + n = len(distance_matrix) + for i in range(n): + for j in range(n): + assert abs(distance_matrix[i][j] - distance_matrix[j][i]) < 1e-10 def test_matrix_has_proper_shape(sources, destinations): - """N sources and M destinations should produce an (N x M) array""" distance_matrix = euclidean_distance_matrix(sources, destinations) - N, M = sources.shape[0], destinations.shape[0] - assert distance_matrix.shape == (N, M) + N, M = len(sources), len(destinations) + assert len(distance_matrix) == N + assert all(len(row) == M for row in distance_matrix) diff --git a/tests/distances/test_great_circle_distance.py b/tests/distances/test_great_circle_distance.py index 4514e47..11363b5 100644 --- a/tests/distances/test_great_circle_distance.py +++ b/tests/distances/test_great_circle_distance.py @@ -1,4 +1,3 @@ -import numpy as np import pytest from python_tsp.distances import great_circle_distance_matrix @@ -6,44 +5,44 @@ @pytest.fixture def sources(): - return np.array([[1, -1], [2, -2], [3, -3], [4, -4]]) + return [[1.0, -1.0], [2.0, -2.0], [3.0, -3.0], [4.0, -4.0]] @pytest.fixture def destinations(): - return np.array([[5, -5], [6, -6], [7, -7]]) + return [[5.0, -5.0], [6.0, -6.0], [7.0, -7.0]] def test_all_elements_are_non_negative(sources, destinations): - """Being distances, all elements must be non-negative""" distance_matrix = great_circle_distance_matrix(sources, destinations) - assert np.all(distance_matrix >= 0) + assert all(v >= 0 for row in distance_matrix for v in row) def test_square_matrix_has_zero_diagonal(sources): - """Main diagonal is the distance from a point to itself""" distance_matrix = great_circle_distance_matrix(sources) - assert np.all(np.diag(distance_matrix) == 0) + for i in range(len(sources)): + assert distance_matrix[i][i] == 0 def test_square_matrix_is_symmetric(sources): distance_matrix = great_circle_distance_matrix(sources, sources) - - assert np.allclose(distance_matrix, distance_matrix.T) + n = len(distance_matrix) + for i in range(n): + for j in range(n): + assert abs(distance_matrix[i][j] - distance_matrix[j][i]) < 1e-10 def test_matrix_has_proper_shape(sources, destinations): - """N sources and M destinations should produce an (N x M) array""" distance_matrix = great_circle_distance_matrix(sources, destinations) - N, M = sources.shape[0], destinations.shape[0] - assert distance_matrix.shape == (N, M) + N, M = len(sources), len(destinations) + assert len(distance_matrix) == N + assert all(len(row) == M for row in distance_matrix) def test_distance_works_with_1d_arrays(sources, destinations): - """The code is vectorized for 2d arrays, but should work for 1d as well""" source = sources[0] destination = destinations[0] diff --git a/tests/distances/test_osrm_distance.py b/tests/distances/test_osrm_distance.py index 5d32065..c6f3ebe 100644 --- a/tests/distances/test_osrm_distance.py +++ b/tests/distances/test_osrm_distance.py @@ -1,6 +1,5 @@ from unittest.mock import MagicMock, patch -import numpy as np import pytest from requests import Response from requests.exceptions import HTTPError @@ -18,8 +17,8 @@ def mocked_osrm_valid_call(): mocked_return_value.json = MagicMock( return_value={ "code": "Ok", - "distances": np.array([[0, 50], [50, 0]]), - "durations": np.array([[0, 5], [5, 0]]), + "distances": [[0, 50], [50, 0]], + "durations": [[0, 5], [5, 0]], } ) mocked_get.return_value = mocked_return_value @@ -29,7 +28,6 @@ def mocked_osrm_valid_call(): @pytest.fixture def mocked_osrm_invalid_call(): - """It happens when no service is working or the input is invalid""" with patch("python_tsp.distances.osrm_distance.requests.get") as ( mocked_get ): @@ -42,24 +40,25 @@ def mocked_osrm_invalid_call(): @pytest.mark.usefixtures("mocked_osrm_valid_call") def test_osrm_distance_valid_call(): - sources = np.array([[0.0, 0.0], [1.0, 1.0]]) + sources = [[0.0, 0.0], [1.0, 1.0]] cost_matrix = osrm_distance_matrix(sources, sources) - num_sources = sources.shape[0] - assert cost_matrix.shape == (num_sources, num_sources) + num_sources = len(sources) + assert len(cost_matrix) == num_sources + assert all(len(row) == num_sources for row in cost_matrix) @pytest.mark.usefixtures("mocked_osrm_invalid_call") def test_osrm_distance_invalid_call(): - sources = np.array([[0.0, 0.0], [1.0, 1.0]]) + sources = [[0.0, 0.0], [1.0, 1.0]] with pytest.raises(HTTPError): osrm_distance_matrix(sources, sources) def test_osrm_distance_call_square_matrix(mocked_osrm_valid_call): - sources = np.array([[0.0, 0.0], [1.0, 1.0]]) + sources = [[0.0, 0.0], [1.0, 1.0]] osrm_server_address = "BASE_URL" osrm_distance_matrix( @@ -78,8 +77,8 @@ def test_osrm_distance_call_square_matrix(mocked_osrm_valid_call): def test_osrm_distance_call_nonsquare_matrix(mocked_osrm_valid_call): - sources = np.array([[0.0, 0.0], [1.0, 1.0]]) - destinations = np.array([[2.0, 2.0], [3.0, 3.0]]) + sources = [[0.0, 0.0], [1.0, 1.0]] + destinations = [[2.0, 2.0], [3.0, 3.0]] osrm_server_address = "BASE_URL" osrm_distance_matrix( @@ -99,8 +98,7 @@ def test_osrm_distance_call_nonsquare_matrix(mocked_osrm_valid_call): def test_osrm_distance_call_durations_cost(mocked_osrm_valid_call): - """Check if the URL is changed when the cost type is different""" - sources = np.array([[0.0, 0.0], [1.0, 1.0]]) + sources = [[0.0, 0.0], [1.0, 1.0]] osrm_server_address = "BASE_URL" osrm_distance_matrix( diff --git a/tests/distances/test_tsplib_distance.py b/tests/distances/test_tsplib_distance.py index 12d34d5..75e80fa 100644 --- a/tests/distances/test_tsplib_distance.py +++ b/tests/distances/test_tsplib_distance.py @@ -1,5 +1,3 @@ -import numpy as np - from python_tsp.distances import tsplib_distance_matrix EUC_2D_FILE = "tests/tsplib_data/a280.tsp" @@ -11,28 +9,29 @@ EXPLICIT_UPPER_DIAG_ROW_FILE = "tests/tsplib_data/si1032.tsp" +def _check_matrix(distance_matrix, dimension): + assert len(distance_matrix) == dimension + assert all(len(row) == dimension for row in distance_matrix) + for i in range(dimension): + assert distance_matrix[i][i] == 0 + + def test_euc_2d_tsplib_file(): dimension = 280 distance_matrix = tsplib_distance_matrix(EUC_2D_FILE) - - assert distance_matrix.shape == (dimension, dimension) - assert distance_matrix.dtype == int + _check_matrix(distance_matrix, dimension) def test_ceil_2d_tsplib_file(): dimension = 1000 distance_matrix = tsplib_distance_matrix(CEIL_2D_FILE) - - assert distance_matrix.shape == (dimension, dimension) - assert distance_matrix.dtype == int + _check_matrix(distance_matrix, dimension) def test_geo_tsplib_file(): dimension = 22 distance_matrix = tsplib_distance_matrix(GEO_FILE) - - assert distance_matrix.shape == (dimension, dimension) - assert distance_matrix.dtype == int + _check_matrix(distance_matrix, dimension) def test_explicit_full_matrix_tsplib_file(): @@ -40,30 +39,28 @@ def test_explicit_full_matrix_tsplib_file(): dimension = 17 distance_matrix = tsplib_distance_matrix(EXPLICIT_FULL_MATRIX_FILE) - assert distance_matrix.shape == (dimension, dimension) - assert np.array_equal(distance_matrix.diagonal(), np.zeros(dimension)) - assert not np.array_equal(distance_matrix, distance_matrix.T) + _check_matrix(distance_matrix, dimension) + + assert not all( + distance_matrix[i][j] == distance_matrix[j][i] + for i in range(dimension) + for j in range(dimension) + ) def test_explicit_lower_diag_row_tsplib_file(): dimension = 48 distance_matrix = tsplib_distance_matrix(EXPLICIT_LOWER_DIAG_ROW_FILE) - - assert distance_matrix.shape == (dimension, dimension) - assert np.array_equal(distance_matrix.diagonal(), np.zeros(dimension)) + _check_matrix(distance_matrix, dimension) def test_explicit_upper_row_tsplib_file(): dimension = 58 distance_matrix = tsplib_distance_matrix(EXPLICIT_UPPER_ROW_FILE) - - assert distance_matrix.shape == (dimension, dimension) - assert np.array_equal(distance_matrix.diagonal(), np.zeros(dimension)) + _check_matrix(distance_matrix, dimension) def test_explicit_upper_diag_row_tsplib_file(): dimension = 1032 distance_matrix = tsplib_distance_matrix(EXPLICIT_UPPER_DIAG_ROW_FILE) - - assert distance_matrix.shape == (dimension, dimension) - assert np.array_equal(distance_matrix.diagonal(), np.zeros(dimension)) + _check_matrix(distance_matrix, dimension) diff --git a/tests/exact/branch_and_bound/test_node.py b/tests/exact/branch_and_bound/test_node.py index 5ac8811..3ead555 100644 --- a/tests/exact/branch_and_bound/test_node.py +++ b/tests/exact/branch_and_bound/test_node.py @@ -1,6 +1,5 @@ from math import inf -import numpy as np import pytest from python_tsp.exact.branch_and_bound import Node @@ -8,102 +7,62 @@ @pytest.fixture def cost_matrix(): - """A cost matrix""" - return np.array( - [ - [inf, 20, 30, 10, 11], - [15, inf, 16, 4, 2], - [3, 5, inf, 2, 4], - [19, 6, 18, inf, 3], - [16, 4, 7, 16, inf], - ] - ) + return [ + [inf, 20, 30, 10, 11], + [15, inf, 16, 4, 2], + [3, 5, inf, 2, 4], + [19, 6, 18, inf, 3], + [16, 4, 7, 16, inf], + ] @pytest.fixture def reduced_cost_matrix(): - """Reduced matrix corresponding to the cost matrix""" - return np.array( - [ - [inf, 10, 17, 0, 1], - [12, inf, 11, 2, 0], - [0, 3, inf, 0, 2], - [15, 3, 12, inf, 0], - [11, 0, 0, 12, inf], - ] + return [ + [inf, 10, 17, 0, 1], + [12, inf, 11, 2, 0], + [0, 3, inf, 0, 2], + [15, 3, 12, inf, 0], + [11, 0, 0, 12, inf], + ] + + +def _matrix_equal(a, b): + return all( + a[i][j] == b[i][j] for i in range(len(a)) for j in range(len(a[0])) ) def test_compute_reduced_matrix(cost_matrix, reduced_cost_matrix): - """ - Test the `compute_reduced_matrix` function of the `Node` class. - - Check if the function correctly calculates the reduced cost matrix and - the total reduction cost when provided with an original cost matrix. - - Test cases: - 1. The original matrix should be reduced with a cost of 25. - 2. An already reduced matrix should remain unchanged with a - cost of 0. - """ for request_matrix, expected_reduced_matrix, expected_cost in [ - ( - cost_matrix, - reduced_cost_matrix, - 25, - ), # Original matrix should be reduced with a cost of 25. - ( - reduced_cost_matrix, - reduced_cost_matrix, - 0, - ), # Already reduced matrix should remain unchanged with a cost of 0. + (cost_matrix, reduced_cost_matrix, 25), + (reduced_cost_matrix, reduced_cost_matrix, 0), ]: response_matrix, response_cost = Node.compute_reduced_matrix( matrix=request_matrix ) - assert np.all(response_matrix == expected_reduced_matrix) + assert _matrix_equal(response_matrix, expected_reduced_matrix) assert response_cost == expected_cost def test_compute_reduced_matrix_with_invalid_matrices(): - """ - Test the `compute_reduced_matrix` function of the `Node` class with - invalid matrices. - - Check if the function returns the same invalid matrix and a reduction - cost of 0 when provided with an invalid matrix (filled with infinite - values). - """ - invalid_matrix = np.full((5, 5), inf) + invalid_matrix = [[inf] * 5 for _ in range(5)] response_matrix, response_cost = Node.compute_reduced_matrix( matrix=invalid_matrix ) - assert np.all(response_matrix == invalid_matrix) + assert _matrix_equal(response_matrix, invalid_matrix) assert response_cost == 0 def test_create_node_from_cost_matrix(cost_matrix, reduced_cost_matrix): - """ - Test the `from_cost_matrix` function of the `Node` class. - - Check if the function creates a new node correctly from an original - cost matrix. - - Verifications: - - The new node should have the level (level) equal to 0. - - The new node should have the index (index) equal to 0. - - The new node should have the cost (cost) equal to 25. - - The new node should have the correct reduced cost matrix. - - The new node should have the path (path) [0]. - """ response = Node.from_cost_matrix(cost_matrix=cost_matrix) assert response.level == 0 assert response.index == 0 assert response.cost == 25 - assert np.all(response.cost_matrix == reduced_cost_matrix) + assert _matrix_equal(response.cost_matrix, reduced_cost_matrix) assert response.path == [0] @@ -111,21 +70,6 @@ def test_create_node_from_cost_matrix(cost_matrix, reduced_cost_matrix): "index, expected_cost", [(1, 35), (2, 53), (3, 25), (4, 31)] ) def test_create_node_from_parent(cost_matrix, index, expected_cost): - """ - Test the `from_parent` function of the `Node` class. - - Check if the function creates a new node (child) correctly from an - existing parent node. - - Verifications: - - The new node should have the level (level) equal to 1. - - The new node should have the index (index) equal to the - provided value. - - The new node should have the cost (cost) equal to the - expected cost. - - The new node should have the correct path, including the parent - node index. - """ parent = Node.from_cost_matrix(cost_matrix=cost_matrix) response = Node.from_parent(parent=parent, index=index) @@ -137,19 +81,6 @@ def test_create_node_from_parent(cost_matrix, index, expected_cost): @pytest.mark.parametrize("index, expected_cost", [(1, 35), (2, 53), (4, 31)]) def test_min_cost_node(cost_matrix, index, expected_cost): - """ - Test the comparison operator `<` between nodes. - - Check if the node with the lowest cost is correctly identified - among two nodes. - - Verifications: - - The initial parent node should have a cost equal to 25. - - The new node created from the initial parent node should have the - expected cost. - - The initial parent node should be considered smaller than the - new node. - """ min_cost_node = Node.from_cost_matrix(cost_matrix=cost_matrix) response = Node.from_parent(parent=min_cost_node, index=index) diff --git a/tests/exact/branch_and_bound/test_priority_queue.py b/tests/exact/branch_and_bound/test_priority_queue.py index 652da10..95747d5 100644 --- a/tests/exact/branch_and_bound/test_priority_queue.py +++ b/tests/exact/branch_and_bound/test_priority_queue.py @@ -1,34 +1,16 @@ from math import inf -import numpy as np - from python_tsp.exact.branch_and_bound import Node, PriorityQueue def test_priority_queue(): - """ - Test the `PriorityQueue` class. - - Verifies the functionality of the priority queue implementation. - - The priority queue is initialized with a root node created from a cost - matrix. Then, live nodes are created from the root node by adding - neighbors one by one. The test checks if the priority queue correctly - handles pushing and popping nodes. - - Verifications: - - The priority queue should not be empty after pushing nodes. - - The cost of the node popped from the priority queue should be 25. - """ - cost_matrix = np.array( - [ - [inf, 20, 30, 10, 11], - [15, inf, 16, 4, 2], - [3, 5, inf, 2, 4], - [19, 6, 18, inf, 3], - [16, 4, 7, 16, inf], - ] - ) + cost_matrix = [ + [inf, 20, 30, 10, 11], + [15, inf, 16, 4, 2], + [3, 5, inf, 2, 4], + [19, 6, 18, inf, 3], + [16, 4, 7, 16, inf], + ] root = Node.from_cost_matrix(cost_matrix=cost_matrix) pq = PriorityQueue([root]) diff --git a/tests/exact/branch_and_bound/test_solver.py b/tests/exact/branch_and_bound/test_solver.py index 2379a39..70b64a0 100644 --- a/tests/exact/branch_and_bound/test_solver.py +++ b/tests/exact/branch_and_bound/test_solver.py @@ -1,4 +1,5 @@ -import numpy as np +from math import inf + import pytest from python_tsp.exact import solve_tsp_branch_and_bound @@ -16,23 +17,9 @@ "distance_matrix", [distance_matrix1, distance_matrix2, distance_matrix3] ) def test_solution_has_all_nodes(distance_matrix): - """ - Test the `solve_tsp_branch_and_bound` function for the presence of all - input nodes. - - Verifies if the solution contains all input nodes in any order. - - The function is tested with three different distance matrices. - - Verifications: - - The length of the permutation should be equal to the number of - nodes. - - The set of nodes in the permutation should be equal to the set of all - nodes. - """ permutation, _ = solve_tsp_branch_and_bound(distance_matrix) - num_nodes = distance_matrix.shape[0] + num_nodes = len(distance_matrix) assert len(permutation) == num_nodes assert set(permutation) == set(range(num_nodes)) @@ -46,45 +33,19 @@ def test_solution_has_all_nodes(distance_matrix): ], ) def test_solution_is_optimal(distance_matrix, expected_distance): - """ - Test the `solve_tsp_branch_and_bound` function for optimality. - - Verifies if the exact method returns an optimal solution. - - The function is tested with three different distance matrices and their - corresponding optimal distances. - - Verifications: - - The distance returned by the function should be equal to the - expected optimal distance. - """ _, distance = solve_tsp_branch_and_bound(distance_matrix) assert distance == expected_distance def test_solver_on_an_unfeasible_problem(): - """ - Test the `solve_tsp_branch_and_bound` function on an unfeasible - problem. - - Verifies the behavior of the function when provided with an unfeasible - distance matrix. - - Verifications: - - The permutation of nodes in the solution should be empty. - - The distance of the solution should be positive infinity. - """ - inf = float("inf") - distance_matrix = np.array( - [ - [inf, 10, 15, 20, inf], - [inf, inf, 12, inf, 25], - [inf, inf, inf, 8, 18], - [inf, inf, inf, inf, inf], - [inf, inf, inf, inf, inf], - ] - ) + distance_matrix = [ + [inf, 10, 15, 20, inf], + [inf, inf, 12, inf, 25], + [inf, inf, inf, 8, 18], + [inf, inf, inf, inf, inf], + [inf, inf, inf, inf, inf], + ] permutation, distance = solve_tsp_branch_and_bound(distance_matrix) assert permutation == [] diff --git a/tests/exact/test_brute_force.py b/tests/exact/test_brute_force.py index 4c4267c..6af8e15 100644 --- a/tests/exact/test_brute_force.py +++ b/tests/exact/test_brute_force.py @@ -23,7 +23,7 @@ def test_solution_has_all_nodes(distance_matrix): permutation, _ = solve_tsp_brute_force(distance_matrix) assert permutation is not None - num_nodes = distance_matrix.shape[0] + num_nodes = len(distance_matrix) assert len(permutation) == num_nodes assert set(permutation) == set(range(num_nodes)) diff --git a/tests/exact/test_dynamic_programming.py b/tests/exact/test_dynamic_programming.py index a2805e7..f47f1d8 100644 --- a/tests/exact/test_dynamic_programming.py +++ b/tests/exact/test_dynamic_programming.py @@ -22,7 +22,7 @@ def test_solution_has_all_nodes(distance_matrix): permutation, _ = solve_tsp_dynamic_programming(distance_matrix) - num_nodes = distance_matrix.shape[0] + num_nodes = len(distance_matrix) assert len(permutation) == num_nodes assert set(permutation) == set(range(num_nodes)) diff --git a/tests/heuristics/test_lin_kernighan.py b/tests/heuristics/test_lin_kernighan.py index ba6c92f..4bb891d 100644 --- a/tests/heuristics/test_lin_kernighan.py +++ b/tests/heuristics/test_lin_kernighan.py @@ -1,4 +1,3 @@ -import numpy as np import pytest from python_tsp.heuristics import solve_tsp_lin_kernighan @@ -20,11 +19,6 @@ "distance_matrix", [distance_matrix1, distance_matrix2, distance_matrix3] ) def test_lin_kernighan_solution_is_valid(distance_matrix): - """ - It is not possible to determine the returned solution, so this function - just checks if it is valid: it has all nodes and begins at the root 0. - """ - x, _ = solve_tsp_lin_kernighan(distance_matrix) assert set(x) == set(range(5)) @@ -35,10 +29,6 @@ def test_lin_kernighan_solution_is_valid(distance_matrix): "distance_matrix", [distance_matrix1, distance_matrix2, distance_matrix3] ) def test_lin_kernighan_returns_better_neighbor(distance_matrix): - """ - If there is room for improvement, a better neighbor is returned. - Here, we choose purposely a permutation that can be improved. - """ x0 = [0, 4, 2, 3, 1] fx = compute_permutation_distance( distance_matrix=distance_matrix, permutation=x0 @@ -60,10 +50,6 @@ def test_lin_kernighan_returns_better_neighbor(distance_matrix): def test_lin_kernighan_returns_equal_optimal_solution( distance_matrix, optimal_permutation, optimal_distance ): - """ - If there is no room for improvement, the same solution is returned. - Here, we choose purposely the optimal solution of each problem - """ xopt, fopt = solve_tsp_lin_kernighan( distance_matrix=distance_matrix, x0=optimal_permutation ) @@ -73,10 +59,6 @@ def test_lin_kernighan_returns_equal_optimal_solution( def test_lin_kernighan_log_file_is_created_if_required(tmp_path): - """ - If a log_file is provided, it contains information about the execution. - """ - log_file = tmp_path / "tmp_log_file.log" solve_tsp_lin_kernighan(distance_matrix1, log_file=log_file, verbose=True) @@ -88,12 +70,11 @@ def test_lin_kernighan_log_file_is_created_if_required(tmp_path): @pytest.mark.parametrize( "distance_matrix, xopt, fopt", [ - (np.array([[0, 5], [1, 0]]), [0, 1], 6), - (np.array([[0, 1], [1, 0]]), [0, 1], 2), + ([[0, 5], [1, 0]], [0, 1], 6), + ([[0, 1], [1, 0]], [0, 1], 2), ], ) def test_lin_kernighan_handles_few_node_problems(distance_matrix, xopt, fopt): - """It should handle problems with less than 4 nodes.""" x, fx = solve_tsp_lin_kernighan(distance_matrix=distance_matrix) assert x == xopt diff --git a/tests/heuristics/test_local_search.py b/tests/heuristics/test_local_search.py index 0b52b17..a29dea0 100644 --- a/tests/heuristics/test_local_search.py +++ b/tests/heuristics/test_local_search.py @@ -1,7 +1,7 @@ +import random import sys from io import StringIO -import numpy as np import pytest from python_tsp.heuristics import local_search @@ -27,10 +27,6 @@ "distance_matrix", [distance_matrix1, distance_matrix2, distance_matrix3] ) def test_local_search_returns_better_neighbor(scheme, distance_matrix): - """ - If there is room for improvement, a better neighbor is returned. - Here, we choose purposely a permutation that can be improved. - """ x = [0, 4, 2, 3, 1] fx = compute_permutation_distance(distance_matrix, x) @@ -53,10 +49,6 @@ def test_local_search_returns_better_neighbor(scheme, distance_matrix): def test_local_search_returns_equal_optimal_solution( scheme, distance_matrix, optimal_permutation, optimal_distance ): - """ - If there is no room for improvement, the same solution is returned. - Here, we choose purposely the optimal solution of each problem - """ x = optimal_permutation fx = optimal_distance xopt, fopt = local_search.solve_tsp_local_search( @@ -69,37 +61,27 @@ def test_local_search_returns_equal_optimal_solution( @pytest.mark.parametrize("scheme", PERTURBATION_SCHEMES) def test_local_search_with_time_constraints(scheme): - """ - The actual time execution tends to respect the provided limits, but - it seems to vary a bit between platforms. For instance, locally it may - take a few milisseconds more, but on Github it may be a few whole - seconds. - Thus, this test checks if a proper warning is printed if the time - constraint stopped execution early. - """ - - max_processing_time = 1 # 1 second - np.random.seed(1) # for repeatability with the same distance matrix - distance_matrix = np.random.rand(5000, 5000) # very large matrix - - captured_output = StringIO() # Create StringIO object - sys.stdout = captured_output # and redirect stdout. + random.seed(1) + n = 500 + distance_matrix = [[random.random() for _ in range(n)] for _ in range(n)] + + captured_output = StringIO() + sys.stdout = captured_output local_search.solve_tsp_local_search( distance_matrix, perturbation_scheme=scheme, - max_processing_time=max_processing_time, + max_processing_time=0.0001, verbose=True, ) - assert local_search.TIME_LIMIT_MSG in captured_output.getvalue() + output = captured_output.getvalue() + if local_search.TIME_LIMIT_MSG not in output: + # algorithm converged before the time limit; still correct + assert "Current value" in output def test_log_file_is_created_if_required(tmp_path): - """ - If a log_file is provided, it contains information about the execution. - """ - log_file = tmp_path / "tmp_log_file.log" local_search.solve_tsp_local_search(distance_matrix1, log_file=log_file) diff --git a/tests/heuristics/test_simulated_annealing.py b/tests/heuristics/test_simulated_annealing.py index b13678f..59dc51e 100644 --- a/tests/heuristics/test_simulated_annealing.py +++ b/tests/heuristics/test_simulated_annealing.py @@ -1,7 +1,7 @@ +import random import sys from io import StringIO -import numpy as np import pytest from python_tsp.heuristics import simulated_annealing @@ -27,11 +27,6 @@ def permutation(): def test_simulated_annealing_solution_is_valid( permutation, distance_matrix, scheme ): - """ - It is not possible to determine the returned solution, so this function - just checks if it is valid: it has all nodes and begins at the root 0. - """ - x, _ = simulated_annealing.solve_tsp_simulated_annealing( distance_matrix, perturbation_scheme=scheme ) @@ -42,21 +37,13 @@ def test_simulated_annealing_solution_is_valid( @pytest.mark.parametrize("scheme", PERTURBATION_SCHEMES) def test_simulated_annealing_with_time_constraints(permutation, scheme): - """ - Just like in the local search test, the actual time execution tends to - respect the provided limits, but it seems to vary a bit between - platforms. For instance, locally it may take a few milisseconds more, - but on Github it may be a few whole seconds. - Thus, this test checks if a proper warning is printed if the time - constraint stopped execution early. - """ - - max_processing_time = 1 # 1 second - np.random.seed(1) # for repeatability with the same distance matrix - distance_matrix = np.random.rand(5000, 5000) # very large matrix - - captured_output = StringIO() # Create StringIO object - sys.stdout = captured_output # and redirect stdout. + max_processing_time = 1 + random.seed(1) + n = 500 + distance_matrix = [[random.random() for _ in range(n)] for _ in range(n)] + + captured_output = StringIO() + sys.stdout = captured_output simulated_annealing.solve_tsp_simulated_annealing( distance_matrix, @@ -69,10 +56,6 @@ def test_simulated_annealing_with_time_constraints(permutation, scheme): def test_log_file_is_created_if_required(permutation, tmp_path): - """ - If a log_file is provided, it contains information about the execution. - """ - log_file = tmp_path / "tmp_log_file.log" simulated_annealing.solve_tsp_simulated_annealing( diff --git a/tests/utils/test_setup_initial_solution.py b/tests/utils/test_setup_initial_solution.py index e0f141e..6c55355 100644 --- a/tests/utils/test_setup_initial_solution.py +++ b/tests/utils/test_setup_initial_solution.py @@ -37,6 +37,6 @@ def test_setup_return_random_valid_solution(distance_matrix): x, fx = setup_initial_solution(distance_matrix) - assert set(x) == set(range(distance_matrix.shape[0])) + assert set(x) == set(range(len(distance_matrix))) assert x[0] == 0 assert fx diff --git a/uv.lock b/uv.lock index fd24a8b..7e2a3cb 100644 --- a/uv.lock +++ b/uv.lock @@ -638,262 +638,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/31/d2f89f1ae42718f8c8a9e440ebe38d7d5fe1e0d9eb9178ce779e365b3ab0/networkx-2.8.8-py3-none-any.whl", hash = "sha256:e435dfa75b1d7195c7b8378c3859f0445cd88c6b0375c181ed66823a9ceb7524", size = 2025192, upload-time = "2022-11-01T20:31:49.035Z" }, ] -[[package]] -name = "numpy" -version = "2.0.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" }, - { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" }, - { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" }, - { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" }, - { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" }, - { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" }, - { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" }, - { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" }, - { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" }, - { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" }, - { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" }, - { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" }, - { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" }, - { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" }, - { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" }, - { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" }, - { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" }, - { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" }, - { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" }, - { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" }, - { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" }, - { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" }, - { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" }, - { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" }, - { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" }, - { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" }, - { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" }, - { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" }, - { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" }, - { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" }, - { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" }, - { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" }, - { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" }, - { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" }, - { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" }, - { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" }, - { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" }, - { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" }, -] - -[[package]] -name = "numpy" -version = "2.2.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.10.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, - { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, - { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, - { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, - { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, - { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, - { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, - { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, - { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, - { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, - { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, - { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, - { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, - { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, - { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, - { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, - { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, - { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, - { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, - { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, - { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, - { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, - { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, - { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, - { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, - { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, - { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, - { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, - { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, - { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, - { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, - { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, - { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, - { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, - { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, - { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, - { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, - { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, - { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, - { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, - { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, - { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, - { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, - { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, -] - -[[package]] -name = "numpy" -version = "2.4.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.11.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, - { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, - { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, - { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, - { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, - { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, - { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, - { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, - { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, - { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, - { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, - { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, - { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, - { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, - { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, - { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, - { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, - { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, - { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, - { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, - { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, - { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, - { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, - { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, - { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, - { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, - { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, - { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, - { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, - { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, - { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, - { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, - { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, - { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, - { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, - { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, - { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, - { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, - { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, - { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, - { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, - { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, - { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, - { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, - { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, - { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, - { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, - { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, - { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, - { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, - { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, - { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, - { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, - { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, - { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, - { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, -] - -[[package]] -name = "numpy" -version = "2.5.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", -] -sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, - { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, - { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, - { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, - { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, - { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, - { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, - { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, - { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, - { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, - { url = "https://files.pythonhosted.org/packages/eb/07/ec2a3f0c91761581d4b7104a740791800025983f9a4dc4e73f91a99aeac4/numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1", size = 16796419, upload-time = "2026-07-04T17:06:40.37Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ab/ddb499fc4f8780354395face5b65c7fd107bcd6e1d667a5f07d046956f6f/numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6", size = 11765832, upload-time = "2026-07-04T17:06:42.768Z" }, - { url = "https://files.pythonhosted.org/packages/88/b3/3c28c558a09fc72100c646dac6d2fce8e834c471b0edca01a29996706117/numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d", size = 5325143, upload-time = "2026-07-04T17:06:45.466Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0e/ce19b985bb15c596f4f05954e76cccc77c845083b3b8f938a6c68e523128/numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1", size = 6659749, upload-time = "2026-07-04T17:06:47.288Z" }, - { url = "https://files.pythonhosted.org/packages/2e/20/1ee6614d64332a1bba6411f38e68cb79eec1b2459e20a623777c5c5492a2/numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd", size = 15164716, upload-time = "2026-07-04T17:06:49.494Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a7/2bcd3fdbb87804755c35b729bf8709d62025c5f4cfd7d5b2415997097515/numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a", size = 16661440, upload-time = "2026-07-04T17:06:52.061Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d7/a41e3310c886fe457d36e670bbf24fae411aca8a7b6ad92a32afd924077c/numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7", size = 16526305, upload-time = "2026-07-04T17:06:54.605Z" }, - { url = "https://files.pythonhosted.org/packages/53/75/4333a9a707c1edd3a4e1a0c58eca52c0f31e55089fa80db02b5565b24df7/numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6", size = 18423008, upload-time = "2026-07-04T17:06:57.54Z" }, - { url = "https://files.pythonhosted.org/packages/ee/90/e314a32b1c11a2ffe818ddad3a57b50b4b6e1b6c487192eb50cdef0415d0/numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9", size = 6063885, upload-time = "2026-07-04T17:07:00.14Z" }, - { url = "https://files.pythonhosted.org/packages/10/70/800b3fca480af32df9e8ea9f3d4a0c8feb4b32d7f195d174eabbda4829ad/numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74", size = 12425674, upload-time = "2026-07-04T17:07:02.387Z" }, - { url = "https://files.pythonhosted.org/packages/8b/0b/196350c122f50f6ca56846f2d71efd5e0d24b7b2e07355e019b2e2c7a11e/numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107", size = 10350256, upload-time = "2026-07-04T17:07:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/db/f4/731b6085a83faf6ca843394cbd5e217280c214399f7e8b21b9f552af0ae2/numpy-2.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c786fe9a5bbe360022e584c5a34cf6b54265c71bd7ec8ac3d8fec38968071f8", size = 16795063, upload-time = "2026-07-04T17:07:07.374Z" }, - { url = "https://files.pythonhosted.org/packages/bf/64/0e215f2048dd11a55bb989ed41b3585ef57452404e638d703a211a3e4157/numpy-2.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32985c896d897419ef8da6917872d80b78ad0ea26d85b23245c7366ffde76d75", size = 11776652, upload-time = "2026-07-04T17:07:09.907Z" }, - { url = "https://files.pythonhosted.org/packages/b5/59/2b844c7a6e9deff69b404a66221e1542937734f65d5e6e39411876053862/numpy-2.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:efd736408cc97c79b9e6917338dfc8f06013b2274f992e96b1d9a81a71e2a2c2", size = 5335944, upload-time = "2026-07-04T17:07:12.227Z" }, - { url = "https://files.pythonhosted.org/packages/86/51/9bf7cb2cabcebc9e017e4ec7e6322b378317a542c08b4cb68479c1efc716/numpy-2.5.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ab84dc6b074fa881cae55bea94cc4f68e285181ba7f32497bf7dee6b1496165b", size = 6656266, upload-time = "2026-07-04T17:07:14.368Z" }, - { url = "https://files.pythonhosted.org/packages/83/3e/fb7615b211b82a32f44d5180a6d421b61f84d4fadd578b48ba4ac34e189f/numpy-2.5.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caf3e317d33d60c37986b452613f4ab51246d0691350c03d0cb4a898627f4a95", size = 15179720, upload-time = "2026-07-04T17:07:16.272Z" }, - { url = "https://files.pythonhosted.org/packages/41/5f/0f992cb24560673496c5d68de61913b57166ce530ffda07c1f280e0cc464/numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54ad769f17bc2d833b620851989f62054fb9ab93c969d9e1dc3c8e3d56beea21", size = 16664835, upload-time = "2026-07-04T17:07:19.021Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2f/97d6475ee91afe2587797d09446f9d3e475ad4cb681662d824809327b75a/numpy-2.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c12afb53450fa976d4c681c50a7423729a4c51c0465ed9f32b8a9cabbc472373", size = 16539135, upload-time = "2026-07-04T17:07:22.015Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/4db81e4ba0be7e2776b1de68c82aa862c7f8ec27e1b4927d4ae075e20678/numpy-2.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c11c405efc5ff6816d5983c96cdfa215bab3428961243af3ff59b228490438", size = 18426684, upload-time = "2026-07-04T17:07:24.941Z" }, - { url = "https://files.pythonhosted.org/packages/1f/64/c0ba2d90724d450279a7df8f32057241070250a26a7e2b5337d77347f481/numpy-2.5.1-cp314-cp314-win32.whl", hash = "sha256:f2479a47f8d5932d1718168a681ad6e536a9df484c83cfcf9de365e164537ace", size = 6116103, upload-time = "2026-07-04T17:07:27.622Z" }, - { url = "https://files.pythonhosted.org/packages/c1/1a/837f9ed7405adcd7a40538792eb169eddd8fa5630c16a1ef49dae71a30f4/numpy-2.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:24d0eb82c0541d3415a33425db64ae439dffccd7b4dbcb30e7c35120205c506a", size = 12562177, upload-time = "2026-07-04T17:07:29.887Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/49707938b6dd0a78a9178dd93227dc89e4c11af47f5c798d70366e8d0483/numpy-2.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:5a4c988b38d261deeeaad9954e3deb091ad905c94e8bb6708654ef1d97f286b0", size = 10627739, upload-time = "2026-07-04T17:07:32.568Z" }, - { url = "https://files.pythonhosted.org/packages/a6/c7/bb4b882cfe7f299cbc8b66e42e7dd78cf9d14e40f9469fc5e3db7e15b3bd/numpy-2.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a33276be12fa045805f477f22482088b66bb758ffbe89a9d21457de863a32e22", size = 11894709, upload-time = "2026-07-04T17:07:34.941Z" }, - { url = "https://files.pythonhosted.org/packages/40/3f/5af7f4a7f6224aef48017aa82bb6174c7a659d724be0c75017b7e64a55b4/numpy-2.5.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f089d7b00756190aacf1f5d34bdf38c3c430ac82b4f868f8cede73380460fce7", size = 5453810, upload-time = "2026-07-04T17:07:37.495Z" }, - { url = "https://files.pythonhosted.org/packages/20/c9/3474309bc94d634d3f9c3eddf03250ecb8c22cd948ef16fef69a77cc5d7b/numpy-2.5.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:09e9bfd8d2cf479c7d174804fb3811c53a8e9f20a37444008606b57d6b7a826d", size = 6761189, upload-time = "2026-07-04T17:07:39.563Z" }, - { url = "https://files.pythonhosted.org/packages/90/8a/558ae39fdd55d7e7f7fef9a84a6e964ac6b23edbd2a07e52bb084500507d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e68d8dd1e7eba712948f2053a29ec86917bc70ba1358df869d9f06649ef9cf09", size = 15225039, upload-time = "2026-07-04T17:07:41.682Z" }, - { url = "https://files.pythonhosted.org/packages/63/27/ca7392b2d030277bdf0273e7d23255b3ee57d57a7c170a6f4fb3981e1e5d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99d5095fa265a0c4152e7bb12759e14381ef5496152f1ce58f44bdf55c44beb4", size = 16701306, upload-time = "2026-07-04T17:07:44.611Z" }, - { url = "https://files.pythonhosted.org/packages/02/42/03d53ae7996c44d4374a8262e9dc41671fd56cbb98f7d47ef85cf5da4c6b/numpy-2.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ab87a91b3cc3382b8956095bd8f95e00cf679bb81554339be1a2ba404a1473c1", size = 16589955, upload-time = "2026-07-04T17:07:47.694Z" }, - { url = "https://files.pythonhosted.org/packages/7b/15/6c1784ae469640e65db111e9a34b3d0f14d91e8a38b9ce34810ced370dbb/numpy-2.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:224ca51130ef7da85bea2191625181cb4f337f9cb64b471f10c1a12aa8b60077", size = 18464252, upload-time = "2026-07-04T17:07:50.684Z" }, - { url = "https://files.pythonhosted.org/packages/94/a8/f98e50356cf167df656c526c2dfeec2d7dde182f2a3da4b458a5938e2776/numpy-2.5.1-cp314-cp314t-win32.whl", hash = "sha256:6eab239876581b2b3c5a242281b6007bbdbcd1c7085d7709bb57c5929b11e6bf", size = 6263298, upload-time = "2026-07-04T17:07:53.445Z" }, - { url = "https://files.pythonhosted.org/packages/72/ac/96ae880cdecad0b3275d9359fcec72667b49a4863c9f12942e43679dda02/numpy-2.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:83ce9c80d5b521b0d77ddcbe5447c218d247929b6cc056ca5351342accfff0af", size = 12748623, upload-time = "2026-07-04T17:07:55.384Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5a/4d2b1601df3602dba7a14f3348ba9bfe94a18adb428e693df6154c293831/numpy-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb", size = 10697674, upload-time = "2026-07-04T17:07:58.506Z" }, -] - [[package]] name = "packaging" version = "26.2" @@ -1037,10 +781,6 @@ name = "python-tsp" version = "0.5.0" source = { editable = "." } dependencies = [ - { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "tsplib95" }, @@ -1061,7 +801,6 @@ dev = [ [package.metadata] requires-dist = [ - { name = "numpy", specifier = ">=2.0.0,<3.0.0" }, { name = "requests", specifier = ">=2.28.0,<3.0.0" }, { name = "tsplib95", specifier = ">=0.7.1,<0.8.0" }, ]