From 892c18d3c78c461c3c04db9dd81a5693ff7f2e01 Mon Sep 17 00:00:00 2001 From: James Gaboardi Date: Wed, 12 Oct 2022 09:16:32 -0400 Subject: [PATCH 1/4] add drop_islands kw (pysal/libpysal#478) --- spopt/region/region_k_means.py | 36 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/spopt/region/region_k_means.py b/spopt/region/region_k_means.py index 973c4bc6..93a9b716 100644 --- a/spopt/region/region_k_means.py +++ b/spopt/region/region_k_means.py @@ -24,7 +24,7 @@ ) -def region_k_means(X, n_clusters, w): +def region_k_means(X, n_clusters, w, drop_islands=True): """Solve the region-K-means problem with the constraint that each cluster forms a spatially connected component. @@ -33,12 +33,13 @@ def region_k_means(X, n_clusters, w): X : {numpy.ndarray, list} The observations to cluster shaped ``(n_samples, n_features)``. - n_clusters : int The number of clusters to form. - w : libpysal.weights.W - ... + Weights object created from given data. + drop_islands : bool + Drop observations that are islands (``True``) or keep them (``False``). + Default is ``True``. Returns ------- @@ -46,18 +47,16 @@ def region_k_means(X, n_clusters, w): label : numpy.ndarray Integer array with shape ``(n_samples,)``, where ``label[i]`` is the code or index of the centroid the ``i``th observation is closest to. - centroid : numpy.ndarray Floating point array of centroids in the shape of ``(k, n_features)`` found at the last iteration of ``region_k_means``. - iters : int The number of iterations for the reassignment phase. """ data = X - a_list = w.to_adjlist(remove_symmetric=False) + a_list = w.to_adjlist(remove_symmetric=False, drop_islands=drop_islands) areas = numpy.arange(w.n).astype(int) k = n_clusters seeds = _seeds(areas, k) @@ -138,37 +137,38 @@ class RegionKMeansHeuristic(BaseSpOptHeuristicSolver): ---------- data : {numpy.ndarray, list}, required The observations to cluster shaped ``(n_samples, n_features)``. - n_clusters : int The number of clusters to form. - w : libpysal.weights.W, required Weights object created from given data. - + drop_islands : bool + Drop observations that are islands (``True``) or keep them (``False``). + Default is ``True``. Attributes ---------- - - labels_ : numpy.array - Region IDs for observations - + + labels_ : numpy.array + Region IDs for observations. centroids_ : numpy.ndarray Floating point array of centroids in the shape of ``(k, n_features)`` found at the last iteration of ``region_k_means``. - - iters : int + iters_ : int The number of iterations for the reassignment phase. """ - def __init__(self, data, n_clusters, w): + def __init__(self, data, n_clusters, w, drop_islands=True): self.data = data self.w = w self.n_clusters = n_clusters + self.drop_islands = drop_islands def solve(self): """Solve the region k-means heuristic.""" - centroid, label, iters = region_k_means(self.data, self.n_clusters, self.w) + centroid, label, iters = region_k_means( + self.data, self.n_clusters, self.w, self.drop_islands + ) self.labels_ = label self.centroids_ = centroid self.iters_ = iters From 493ca086dd58d807c3ff8b2a0dfbe73f565c4829 Mon Sep 17 00:00:00 2001 From: James Gaboardi Date: Fri, 14 Oct 2022 12:03:04 -0400 Subject: [PATCH 2/4] black format region/* --- .coveragerc | 1 + spopt/region/azp.py | 10 +- spopt/region/base.py | 224 +++++++++++++++-------------- spopt/region/components.py | 13 +- spopt/region/csgraph_utils.py | 6 +- spopt/region/maxp.py | 10 +- spopt/region/objective_function.py | 27 ++-- spopt/region/random_region.py | 8 +- spopt/region/spenc.py | 6 +- spopt/region/util.py | 15 +- 10 files changed, 163 insertions(+), 157 deletions(-) diff --git a/.coveragerc b/.coveragerc index 6e3682d9..989a4f67 100644 --- a/.coveragerc +++ b/.coveragerc @@ -25,6 +25,7 @@ ignore_errors = True omit = */tests/* *__init__.py + *_version.py [html] directory = coverage_html_report diff --git a/spopt/region/azp.py b/spopt/region/azp.py index 3c66c166..91389fd3 100755 --- a/spopt/region/azp.py +++ b/spopt/region/azp.py @@ -162,7 +162,7 @@ class AZP_orig: labels_ : numpy.ndarray Each element is a region label specifying to which region the corresponding area was assigned to by the last run of a fit-method. - + """ def __init__(self, allow_move_strategy=None, random_state=None): @@ -175,7 +175,7 @@ def __init__(self, allow_move_strategy=None, random_state=None): instance can be passed as argument. Default is ``None``. random_state : None, int, str, bytes, or bytearray Random seed. Default is ``None``. - + """ self.n_regions = None @@ -225,7 +225,7 @@ def fit_from_scipy_sparse_matrix( objective_func : region.objective_function.ObjectiveFunction The objective function to use. Default is ``ObjectiveFunctionPairwise()``. - + """ if attr.ndim == 1: @@ -375,7 +375,7 @@ def fit_from_geodataframe( ``fit_from_scipy_sparse_matrix``. contiguity : str Defines the contiguity relationship between areas. - Default is ``'rook'``. Possible contiguity definitions are: + Default is ``'rook'``. Possible contiguity definitions are: * "rook" - Rook contiguity. * "queen" - Queen contiguity. @@ -750,7 +750,7 @@ def fit_from_networkx( Refer to the corresponding argument in ``AZP.fit_from_networkx``. Default is ``ObjectiveFunctionPairwise()``. - + """ adj = nx.to_scipy_sparse_matrix(graph) diff --git a/spopt/region/base.py b/spopt/region/base.py index 44e978ca..d1e31fea 100644 --- a/spopt/region/base.py +++ b/spopt/region/base.py @@ -14,22 +14,22 @@ class RegionMixin(object): def solve_assign(self, X, adjacency): """ - + Parameters ---------- - - X : + + X : ... - - adjacency : + + adjacency : ... - + Returns ------- - - _labels_ : + + _labels_ : ... - + """ self.solve(X, adjacency) @@ -39,19 +39,19 @@ def solve_assign(self, X, adjacency): def w_to_g(w): """Get a ``networkx`` graph from a PySAL W. - + Parameters ---------- - + w : libpysal.weights.W ... - + Returns ------- - + g : networkx.Graph ... - + """ g = networkx.Graph() for ego, alters in w.neighbors.items(): @@ -62,31 +62,31 @@ def w_to_g(w): def move_ok(area, source, destination, g, w): """Check if area can move from source region to destination region. - + Parameters ---------- - - area : + + area : ... - - source : + + source : ... - - destination : + + destination : ... - + g : networkx.Graph ... - + w : libpysal.weights.W ... - + Returns ------- - + _move_ok_ : bool ``True`` if the move is acceptable otherwise ``False``. - + """ _move_ok_ = False @@ -105,37 +105,37 @@ def move_ok(area, source, destination, g, w): def ok_moves(candidates, regions, labels_, closest, g, w, areas): """Check a sequence of candidate moves. - + Parameters ---------- - - candidates : + + candidates : ... - - regions : + + regions : ... - - labels_ : + + labels_ : ... - - closest : + + closest : ... - + g : networkx.Graph ... - + w : libpysal.weights.W ... - - areas : + + areas : ... - + Returns ------- - + keep : list ... - + """ keep = [] @@ -149,22 +149,22 @@ def ok_moves(candidates, regions, labels_, closest, g, w, areas): def region_neighbors(a_list, region): """Get neighbors for members of a region. - + Parameters ---------- - - a_list : + + a_list : ... - - region : + + region : ... - + Returns ------- - + _region_neighbors_ : list ... - + """ neighbors = a_list[a_list["focal"].isin(region)].neighbor.values @@ -174,22 +174,22 @@ def region_neighbors(a_list, region): def _centroid(regions, data): """Get centroids for all regions. - + Parameters ---------- - - regions : + + regions : ... - - data : + + data : ... - + Returns ------- - + _centroid_ : numpy.array ... - + """ _centroid_ = numpy.array([data[region, :].mean(axis=0) for region in regions]) @@ -198,22 +198,22 @@ def _centroid(regions, data): def _closest(data, centroids): """For each row in data, find the closest row in centroids. - + Parameters ---------- - - data : + + data : ... - - centroids : + + centroids : ... - + Returns ------- - + _closest_ : list ... - + """ _closest_ = [numpy.argmin(((row - centroids) ** 2).sum(axis=1)) for row in data] @@ -222,22 +222,22 @@ def _closest(data, centroids): def _seeds(areas, k): """Randomly select `k` seeds from a sequence of areas. - + Parameters ---------- - - areas : + + areas : ... - + k : int The number of desired seeds. - + Returns ------- - + _seeds_ : numpy.array ... - + """ _seeds_ = numpy.random.choice(areas, size=k, replace=False) @@ -246,26 +246,26 @@ def _seeds(areas, k): def is_neighbor(area, region, w): """Check if area is a neighbor of any member of region. - + Parameters ---------- - - area : + + area : ... - - region : + + region : ... - + w : libpysal.weights.W ... - + Returns ------- - + neighboring : bool ``True`` if area is a neighbor of any member of region otherwise ``False``. - + """ neighboring = False @@ -276,8 +276,6 @@ def is_neighbor(area, region, w): return neighboring - - def infeasible_components(gdf, w, threshold_var, threshold): """Identify infeasible components. @@ -303,11 +301,11 @@ def infeasible_components(gdf, w, threshold_var, threshold): ------- list of infeasible components """ - gdf['_components'] = w.component_labels - gb = gdf.groupby(by='_components').sum() + gdf["_components"] = w.component_labels + gb = gdf.groupby(by="_components").sum(numeric_only=True) gdf.drop(columns="_components", inplace=True) if gb[threshold_var].min() < threshold: - l = gb[gb[threshold_var]< threshold] + l = gb[gb[threshold_var] < threshold] return l.index.values.tolist() return [] @@ -327,11 +325,11 @@ def plot_components(gdf, w): """ cgdf = gdf.copy() - cgdf['component'] = w.component_labels - return cgdf.explore(column='component', categorical=True) + cgdf["component"] = w.component_labels + return cgdf.explore(column="component", categorical=True) -def modify_components(gdf, w, threshold_var, threshold, policy='single'): +def modify_components(gdf, w, threshold_var, threshold, policy="single"): """Modify infeasible components. Parameters @@ -376,18 +374,18 @@ def modify_components(gdf, w, threshold_var, threshold, policy='single'): if ifcs == numpy.unique(w.component_labels).tolist(): raise Exception("No feasible components found in input.") policy = policy.lower() - if not ifcs or policy == 'keep': + if not ifcs or policy == "keep": return gdf, w - elif policy == 'single': - w = form_single_component(gdf, w, linkage='single') + elif policy == "single": + w = form_single_component(gdf, w, linkage="single") return gdf, w - elif policy == 'multiple': - w = form_single_component(gdf, w, linkage='multiple') + elif policy == "multiple": + w = form_single_component(gdf, w, linkage="multiple") return gdf, w - elif policy == 'drop': + elif policy == "drop": keep_ids = numpy.where(~numpy.isin(w.component_labels, ifcs))[0] gdf = gdf.iloc[keep_ids] - cw = libpysal.weights.w_subset(w, keep_ids) + cw = libpysal.weights.w_subset(w, keep_ids) new_neigh = {} old_new = dict([(o, n) for n, o in enumerate(keep_ids)]) for old in keep_ids: @@ -397,10 +395,10 @@ def modify_components(gdf, w, threshold_var, threshold, policy='single'): gdf.reset_index(inplace=True) return gdf, new_w else: - raise Exception(f'Undefined components policy: {policy}') + raise Exception(f"Undefined components policy: {policy}") -def form_single_component(gdf, w, linkage='single'): +def form_single_component(gdf, w, linkage="single"): """Ensure the connectivity forms a signal connected component. Parameters @@ -422,7 +420,7 @@ def form_single_component(gdf, w, linkage='single'): w : libpysal.weights.W """ data = numpy.unique(w.component_labels, return_counts=True) - if len(data[0])== 1: + if len(data[0]) == 1: return w else: # largest component label @@ -430,8 +428,14 @@ def form_single_component(gdf, w, linkage='single'): # form tree on largest component wcl = w.component_labels - tree = KDTree(list(zip(gdf.iloc[wcl == lcl].geometry.centroid.x, - gdf.iloc[wcl == lcl].geometry.centroid.y))) + tree = KDTree( + list( + zip( + gdf.iloc[wcl == lcl].geometry.centroid.x, + gdf.iloc[wcl == lcl].geometry.centroid.y, + ) + ) + ) # small component labels scl = [cl for cl in data[0] if cl != lcl] @@ -444,8 +448,12 @@ def form_single_component(gdf, w, linkage='single'): for cl in data[0]: if cl == lcl: continue - query_pnts = list(zip(gdf.iloc[wcl == cl].geometry.centroid.x, - gdf.iloc[wcl == cl].geometry.centroid.y)) + query_pnts = list( + zip( + gdf.iloc[wcl == cl].geometry.centroid.x, + gdf.iloc[wcl == cl].geometry.centroid.y, + ) + ) dd, jj = tree.query(query_pnts, k=1) clas = numpy.where(numpy.isin(w.component_labels, [cl]))[0] @@ -453,16 +461,16 @@ def form_single_component(gdf, w, linkage='single'): ll = linkage.lower() - if ll == 'single': + if ll == "single": min_idx = numpy.argmin(dd) j = jj[min_idx] i = clas[min_idx] - joins.append((i,j)) - elif ll == 'multiple': + joins.append((i, j)) + elif ll == "multiple": pairs = zip(clas, jj) joins.extend(list(pairs)) else: - raise Exception(f'Unknown linkage: {linkage}') + raise Exception(f"Unknown linkage: {linkage}") neighbors = copy.deepcopy(w.neighbors) for join in joins: diff --git a/spopt/region/components.py b/spopt/region/components.py index 89d7fd4d..b7e74f03 100644 --- a/spopt/region/components.py +++ b/spopt/region/components.py @@ -43,8 +43,7 @@ def is_component(w, ids): while q: node = q.pop() marks[node] = components - others = [neighbor for neighbor in w.neighbors[node] - if neighbor in ids] + others = [neighbor for neighbor in w.neighbors[node] if neighbor in ids] for other in others: if marks[other] == 0 and other not in q: q.append(other) @@ -129,8 +128,7 @@ def connected_components(self, threshold=0.9, op=lt): nodes = set(self.nodes) components, visited = [], set() while len(nodes) > 0: - connected, visited = self.dfs( - nodes.pop(), visited, threshold, op) + connected, visited = self.dfs(nodes.pop(), visited, threshold, op) connected = set(connected) for node in connected: if node in nodes: @@ -152,8 +150,11 @@ def dfs(self, v, visited, threshold, op=lt, first=None): visited.add(v) if first is None: first = v - for i in (n for n, w in self.edges.get(v, {}).iteritems() - if op(w, threshold) and n not in visited): + for i in ( + n + for n, w in self.edges.get(v, {}).iteritems() + if op(w, threshold) and n not in visited + ): x, y = self.dfs(i, visited, threshold, op, first) aux.extend(x) visited = visited.union(y) diff --git a/spopt/region/csgraph_utils.py b/spopt/region/csgraph_utils.py index 364b079c..666ebda8 100755 --- a/spopt/region/csgraph_utils.py +++ b/spopt/region/csgraph_utils.py @@ -35,8 +35,9 @@ def is_connected(adj): >>> is_connected(disconnected) False """ - n_connected_components = csg.connected_components(adj, directed=False, - return_labels=False) + n_connected_components = csg.connected_components( + adj, directed=False, return_labels=False + ) return True if n_connected_components == 1 else False @@ -124,4 +125,3 @@ def sub_adj_matrix(adj, nodes, wo_nodes=None): nodes = nodes[mask] nodes = nodes[:, None] return csr_matrix(adj[nodes, nodes.T]) - diff --git a/spopt/region/maxp.py b/spopt/region/maxp.py index 3b2a6455..49204bad 100644 --- a/spopt/region/maxp.py +++ b/spopt/region/maxp.py @@ -16,8 +16,7 @@ import libpysal import numpy as np from copy import deepcopy -from .base import (infeasible_components, plot_components, - modify_components) +from .base import infeasible_components, plot_components, modify_components ITERCONSTRUCT = 999 ITERSA = 10 @@ -33,7 +32,7 @@ def maxp( max_iterations_construction=ITERCONSTRUCT, max_iterations_sa=ITERSA, verbose=False, - policy='single', + policy="single", ): """The max-p-regions involves the aggregation of n areas into an unknown maximum number of homogeneous regions, while ensuring that each region is contiguous and satisfies a minimum @@ -87,8 +86,7 @@ def maxp( Region IDs for observations. """ - gdf, w = modify_components(gdf, w, threshold_name, - threshold, policy=policy) + gdf, w = modify_components(gdf, w, threshold_name, threshold, policy=policy) attr = np.atleast_2d(gdf[attrs_name].values) if attr.shape[0] == 1: attr = attr.T @@ -785,7 +783,7 @@ def __init__( max_iterations_construction=99, max_iterations_sa=ITERSA, verbose=False, - policy='attach', + policy="attach", ): self.gdf = gdf diff --git a/spopt/region/objective_function.py b/spopt/region/objective_function.py index 14affd86..ff5e60cc 100755 --- a/spopt/region/objective_function.py +++ b/spopt/region/objective_function.py @@ -76,19 +76,20 @@ def __call__(self, labels, attr): obj_val = sum( self.metric(attr[i].reshape(1, -1), attr[j].reshape(1, -1)) for r in regions_set - for i, j in itertools.combinations(np.where(labels == r)[0], 2)) + for i, j in itertools.combinations(np.where(labels == r)[0], 2) + ) return obj_val def update(self, moving_area, recipient_region, labels, attr): donor_region = labels[moving_area] attr_donor = attr[labels == donor_region] - donor_diff = sum( - self.metric(attr_donor, attr[moving_area].reshape(1, -1))) + donor_diff = sum(self.metric(attr_donor, attr[moving_area].reshape(1, -1))) attr_recipient = attr[labels == recipient_region] recipient_diff = sum( - self.metric(attr_recipient, attr[moving_area].reshape(1, -1))) + self.metric(attr_recipient, attr[moving_area].reshape(1, -1)) + ) return recipient_diff - donor_diff @@ -140,22 +141,24 @@ def _intraregional_heterogeneity(self, labels, region, attr): return self.reduction( self.metric( attr[labels == region], - self.center(attr[labels == region], axis=0).reshape(1, -1)), - axis=0) + self.center(attr[labels == region], axis=0).reshape(1, -1), + ), + axis=0, + ) def update(self, moving_area, recipient_region, labels, attr): donor_region = labels[moving_area] - donor_before = self._intraregional_heterogeneity( - labels, donor_region, attr) + donor_before = self._intraregional_heterogeneity(labels, donor_region, attr) recipient_before = self._intraregional_heterogeneity( - labels, recipient_region, attr) + labels, recipient_region, attr + ) labels[moving_area] = recipient_region - donor_after = self._intraregional_heterogeneity( - labels, donor_region, attr) + donor_after = self._intraregional_heterogeneity(labels, donor_region, attr) recipient_after = self._intraregional_heterogeneity( - labels, recipient_region, attr) + labels, recipient_region, attr + ) labels[moving_area] = donor_region overall_before = self.reduction((donor_before, recipient_before)) diff --git a/spopt/region/random_region.py b/spopt/region/random_region.py index 1dd2d0c3..c9774d18 100644 --- a/spopt/region/random_region.py +++ b/spopt/region/random_region.py @@ -387,12 +387,12 @@ def __init__( self.build_noncontig_regions(num_regions, region_breaks) def get_num_regions(self): - return np.random.random_integers(2, self.n) + return np.random.randint(2, self.n) def get_region_breaks(self, num_regions): region_breaks = set([]) while len(region_breaks) < num_regions - 1: - region_breaks.add(np.random.random_integers(1, self.n - 1)) + region_breaks.add(np.random.randint(1, self.n - 1)) region_breaks = list(region_breaks) region_breaks.sort() return region_breaks @@ -428,7 +428,7 @@ def grow_compact(self, w, test_card, region, candidates, potential): # potential areas before adding new potential areas add_areas = [] while potential and len(region) < test_card: - pot_index = np.random.random_integers(0, len(potential) - 1) + pot_index = np.random.randint(0, len(potential)) add_area = potential[pot_index] region.append(add_area) candidates.remove(add_area) @@ -447,7 +447,7 @@ def grow_compact(self, w, test_card, region, candidates, potential): def grow_free(self, w, test_card, region, candidates, potential): # increment potential areas after each new area is # added to the region (faster than the grow_compact) - pot_index = np.random.random_integers(0, len(potential) - 1) + pot_index = np.random.randint(0, len(potential)) add_area = potential[pot_index] region.append(add_area) candidates.remove(add_area) diff --git a/spopt/region/spenc.py b/spopt/region/spenc.py index a1c4bc4f..44ab80ab 100644 --- a/spopt/region/spenc.py +++ b/spopt/region/spenc.py @@ -16,7 +16,7 @@ def __init__(self, gdf, w, attrs_name, n_clusters=5, random_state=None, gamma=1) gdf : geopandas.GeoDataFrame Input data. - + w : libpywal.weights.W Spatial weights matrix. @@ -26,11 +26,11 @@ def __init__(self, gdf, w, attrs_name, n_clusters=5, random_state=None, gamma=1) n_clusters : int The number of clusters to form. Default is ``5``. - + random_state : int Random seed to set for reproducible results. Default is ``None``. - + gamma: int Default is ``1``. diff --git a/spopt/region/util.py b/spopt/region/util.py index be9686c1..9dec2707 100755 --- a/spopt/region/util.py +++ b/spopt/region/util.py @@ -6,7 +6,7 @@ import scipy.sparse.csgraph as csg from sklearn.metrics.pairwise import distance_metrics -from scipy.sparse.dok import dok_matrix +from scipy.sparse import dok_matrix import numpy as np import networkx as nx from libpysal import weights @@ -18,7 +18,7 @@ "A named tuple representing a move from `old_region` to `new_region`." # sphinx -def array_from_dict_values(dct, sorted_keys=None, flat_output=False, dtype=np.float): +def array_from_dict_values(dct, sorted_keys=None, flat_output=False, dtype=float): """ Return values of the dictionary passed as `dct` argument as an numpy array. The values in the returned array are sorted by the keys of `dct`. @@ -889,15 +889,10 @@ def count(arr, el): def check_solver(solver): - if not isinstance(solver, str) or solver.lower() not in [ - "cbc", - "cplex", - "glpk", - "gurobi", - ]: + solvers = ["cbc", "cplex", "glpk", "gurobi"] + if not isinstance(solver, str) or solver.lower() not in solvers: raise ValueError( - "The solver argument must be one of the following" - ' strings: "cbc", "cplex", "glpk", or "gurobi".' + f"The solver must be one of {solvers} but '{solver}' was given." ) From c6ed1bcdca1b24b686770bcdbe2862fee2ea5676 Mon Sep 17 00:00:00 2001 From: James Gaboardi Date: Fri, 14 Oct 2022 13:18:58 -0400 Subject: [PATCH 3/4] black format locate/* --- spopt/locate/base.py | 28 +++++++++++++------- spopt/locate/coverage.py | 51 +++++++++++++++++------------------- spopt/locate/p_center.py | 21 +++++++-------- spopt/locate/p_dispersion.py | 8 ++++-- spopt/locate/p_median.py | 21 +++++++-------- 5 files changed, 68 insertions(+), 61 deletions(-) diff --git a/spopt/locate/base.py b/spopt/locate/base.py index eb26bff9..46a88e2a 100644 --- a/spopt/locate/base.py +++ b/spopt/locate/base.py @@ -76,7 +76,8 @@ def client_facility_array(self) -> None: self.cli2fac[fac_site].append(i) else: raise AttributeError( - "The attribute `fac2cli` is not set. See `facility_client_array` method to set the attribute" + "The attribute `fac2cli` is not set. " + "See `facility_client_array` method to set the attribute" ) @@ -311,7 +312,7 @@ def add_set_covering_constraint( ) else: raise AttributeError( - "before setting constraints must set facility variable" + "Before setting coverage constraints facility variables must be set." ) @staticmethod @@ -364,7 +365,8 @@ def add_backup_covering_constraint( ) else: raise AttributeError( - "before setting constraints must set facility variable" + "Before setting backup coverage constraints " + "facility variables must be set." ) @staticmethod @@ -393,7 +395,7 @@ def add_facility_constraint( model += pulp.lpSum(fac_vars) == p_facilities else: raise AttributeError( - "before setting constraints must set facility variable" + "Before setting facility constraint facility variables must be set." ) @staticmethod @@ -424,7 +426,8 @@ def add_predefined_facility_constraint( fac_vars[ind].fixValue() else: raise AttributeError( - "before predefined facility must set facility variable" + "Before setting predefined facility constraints " + "facility variables must be set." ) @staticmethod @@ -463,7 +466,8 @@ def add_maximal_coverage_constraint( ) else: raise AttributeError( - "before setting constraints must set facility and demand variable" + "Before setting maximal coverage constraints facility " + "and demand variables must be set." ) @staticmethod @@ -496,7 +500,8 @@ def add_assignment_constraint( model += pulp.lpSum([cli_assgn_vars[i][j] for j in range_facility]) == 1 else: raise AttributeError( - "before setting constraints must set client assignment variable" + "Before setting assignment constraints " + "client assignment variables must be set." ) @staticmethod @@ -531,7 +536,8 @@ def add_opening_constraint( model += fac_vars[j] - cli_assgn_vars[i][j] >= 0 else: raise AttributeError( - "before setting constraints must set client assignment variable" + "Before setting opening constraints " + "client assignment variables must be set." ) @staticmethod @@ -579,7 +585,8 @@ def add_minimized_maximum_constraint( ) else: raise AttributeError( - "before setting constraints must set weight and client assignment variables" + "Before setting minimized maximum constraints weight " + "and client assignment variables must be set." ) @staticmethod @@ -621,5 +628,6 @@ def add_p_dispersion_interfacility_constraint( ) else: raise AttributeError( - "before setting constraints must set dispersion objective value and facility assignment variables" + "Before setting interfacility distance constraints dispersion " + "objective value and facility assignment variables must be set." ) diff --git a/spopt/locate/coverage.py b/spopt/locate/coverage.py index 7b242a9d..e63d96da 100644 --- a/spopt/locate/coverage.py +++ b/spopt/locate/coverage.py @@ -237,18 +237,16 @@ def from_geodataframe( dem_type_geom = dem.geom_type.unique() fac_type_geom = fac.geom_type.unique() + _msg = ( + " geodataframe contains mixed type geometries or is not a point. Be " + "sure deriving centroid from geometries doesn't affect the results." + ) if len(dem_type_geom) > 1 or not "Point" in dem_type_geom: - warnings.warn( - "Demand geodataframe contains mixed type geometries or is not a point. Be sure deriving centroid from geometries doesn't affect the results.", - Warning, - ) + warnings.warn(f"Demand{_msg}", UserWarning) dem = dem.centroid if len(fac_type_geom) > 1 or not "Point" in fac_type_geom: - warnings.warn( - "Facility geodataframe contains mixed type geometries or is not a point. Be sure deriving centroid from geometries doesn't affect the results.", - Warning, - ) + warnings.warn(f"Facility{_msg}", UserWarning) fac = fac.centroid dem_data = np.array([dem.x.to_numpy(), dem.y.to_numpy()]).T @@ -256,7 +254,8 @@ def from_geodataframe( if gdf_demand.crs != gdf_fac.crs: raise ValueError( - f"geodataframes crs are different: gdf_demand-{gdf_demand.crs}, gdf_fac-{gdf_fac.crs}" + "Geodataframes crs are different: " + f"gdf_demand-{gdf_demand.crs}, gdf_fac-{gdf_fac.crs}" ) distances = cdist(dem_data, fac_data, distance_metric) @@ -554,18 +553,16 @@ def from_geodataframe( dem_type_geom = dem.geom_type.unique() fac_type_geom = fac.geom_type.unique() + _msg = ( + " geodataframe contains mixed type geometries or is not a point. Be " + "sure deriving centroid from geometries doesn't affect the results." + ) if len(dem_type_geom) > 1 or not "Point" in dem_type_geom: - warnings.warn( - "Demand geodataframe contains mixed type geometries or is not a point. Be sure deriving centroid from geometries doesn't affect the results.", - Warning, - ) + warnings.warn(f"Demand{_msg}", UserWarning) dem = dem.centroid if len(fac_type_geom) > 1 or not "Point" in fac_type_geom: - warnings.warn( - "Facility geodataframe contains mixed type geometries or is not a point. Be sure deriving centroid from geometries doesn't affect the results.", - Warning, - ) + warnings.warn(f"Facility{_msg}", UserWarning) fac = fac.centroid dem_data = np.array([dem.x.to_numpy(), dem.y.to_numpy()]).T @@ -573,7 +570,8 @@ def from_geodataframe( if gdf_demand.crs != gdf_fac.crs: raise ValueError( - f"geodataframes crs are different: gdf_demand-{gdf_demand.crs}, gdf_fac-{gdf_fac.crs}" + "Geodataframes crs are different: " + f"gdf_demand-{gdf_demand.crs}, gdf_fac-{gdf_fac.crs}" ) distances = cdist(dem_data, fac_data, distance_metric) @@ -898,18 +896,16 @@ def from_geodataframe( dem_type_geom = dem.geom_type.unique() fac_type_geom = fac.geom_type.unique() + _msg = ( + " geodataframe contains mixed type geometries or is not a point. Be " + "sure deriving centroid from geometries doesn't affect the results." + ) if len(dem_type_geom) > 1 or not "Point" in dem_type_geom: - warnings.warn( - "Demand geodataframe contains mixed type geometries or is not a point. Be sure deriving centroid from geometries doesn't affect the results.", - Warning, - ) + warnings.warn(f"Demand{_msg}", UserWarning) dem = dem.centroid if len(fac_type_geom) > 1 or not "Point" in fac_type_geom: - warnings.warn( - "Facility geodataframe contains mixed type geometries or is not a point. Be sure deriving centroid from geometries doesn't affect the results.", - Warning, - ) + warnings.warn(f"Facility{_msg}", UserWarning) fac = fac.centroid dem_data = np.array([dem.x.to_numpy(), dem.y.to_numpy()]).T @@ -917,7 +913,8 @@ def from_geodataframe( if gdf_demand.crs != gdf_fac.crs: raise ValueError( - f"geodataframes crs are different: gdf_demand-{gdf_demand.crs}, gdf_fac-{gdf_fac.crs}" + "Geodataframes crs are different: " + f"gdf_demand-{gdf_demand.crs}, gdf_fac-{gdf_fac.crs}" ) distances = cdist(dem_data, fac_data, distance_metric) diff --git a/spopt/locate/p_center.py b/spopt/locate/p_center.py index 08913ff8..024569bd 100644 --- a/spopt/locate/p_center.py +++ b/spopt/locate/p_center.py @@ -33,7 +33,7 @@ class PCenter(LocateSolver, BaseOutputMixin): cli2fac: np.array 2-d MxN, where m is number of clients and n is number of facilities. Each row represent a client and has an array containing facility index meaning that the client is covered by the facility ith. aij: np.array - Cost matrix 2-d array + Cost matrix 2-d array """ @@ -244,18 +244,16 @@ def from_geodataframe( dem_type_geom = dem.geom_type.unique() fac_type_geom = fac.geom_type.unique() + _msg = ( + " geodataframe contains mixed type geometries or is not a point. Be " + "sure deriving centroid from geometries doesn't affect the results." + ) if len(dem_type_geom) > 1 or not "Point" in dem_type_geom: - warnings.warn( - "Demand geodataframe contains mixed type geometries or is not a point. Be sure deriving centroid from geometries doesn't affect the results.", - Warning, - ) + warnings.warn(f"Demand{_msg}", UserWarning) dem = dem.centroid if len(fac_type_geom) > 1 or not "Point" in fac_type_geom: - warnings.warn( - "Facility geodataframe contains mixed type geometries or is not a point. Be sure deriving centroid from geometries doesn't affect the results.", - Warning, - ) + warnings.warn(f"Facility{_msg}", UserWarning) fac = fac.centroid dem_data = np.array([dem.x.to_numpy(), dem.y.to_numpy()]).T @@ -263,7 +261,8 @@ def from_geodataframe( if gdf_demand.crs != gdf_fac.crs: raise ValueError( - f"geodataframes crs are different: gdf_demand-{gdf_demand.crs}, gdf_fac-{gdf_fac.crs}" + "Geodataframes crs are different: " + f"gdf_demand-{gdf_demand.crs}, gdf_fac-{gdf_fac.crs}" ) distances = cdist(dem_data, fac_data, distance_metric) @@ -301,7 +300,7 @@ def solve(self, solver: pulp.LpSolver, results: bool = True): ---------- solver: pulp.LpSolver solver supported by pulp package - + results: bool if True it will create metainfo - which facilities cover which demand and vice-versa, and the uncovered demand - about the model results diff --git a/spopt/locate/p_dispersion.py b/spopt/locate/p_dispersion.py index edd39e76..f9613ec4 100644 --- a/spopt/locate/p_dispersion.py +++ b/spopt/locate/p_dispersion.py @@ -215,8 +215,12 @@ def from_geodataframe( if len(fac_type_geom) > 1 or not "Point" in fac_type_geom: warnings.warn( - "Facility geodataframe contains mixed type geometries or is not a point. Be sure deriving centroid from geometries doesn't affect the results.", - Warning, + ( + "Facility geodataframe contains mixed type geometries " + "or is not a point. Be sure deriving centroid from " + "geometries doesn't affect the results." + ), + UserWarning, ) fac = fac.centroid diff --git a/spopt/locate/p_median.py b/spopt/locate/p_median.py index 36b6178e..1227c911 100644 --- a/spopt/locate/p_median.py +++ b/spopt/locate/p_median.py @@ -38,7 +38,7 @@ class PMedian(LocateSolver, BaseOutputMixin, MeanDistanceMixin): cli2fac: np.array 2-d MxN, where m is number of clients and n is number of facilities. Each row represent a client and has an array containing facility index meaning that the client is covered by the facility ith. aij: np.array - Cost matrix 2-d array + Cost matrix 2-d array """ @@ -280,18 +280,16 @@ def from_geodataframe( dem_type_geom = dem.geom_type.unique() fac_type_geom = fac.geom_type.unique() + _msg = ( + " geodataframe contains mixed type geometries or is not a point. Be " + "sure deriving centroid from geometries doesn't affect the results." + ) if len(dem_type_geom) > 1 or not "Point" in dem_type_geom: - warnings.warn( - "Demand geodataframe contains mixed type geometries or is not a point. Be sure deriving centroid from geometries doesn't affect the results.", - Warning, - ) + warnings.warn(f"Demand{_msg}", UserWarning) dem = dem.centroid if len(fac_type_geom) > 1 or not "Point" in fac_type_geom: - warnings.warn( - "Facility geodataframe contains mixed type geometries or is not a point. Be sure deriving centroid from geometries doesn't affect the results.", - Warning, - ) + warnings.warn(f"Facility{_msg}", UserWarning) fac = fac.centroid dem_data = np.array([dem.x.to_numpy(), dem.y.to_numpy()]).T @@ -299,7 +297,8 @@ def from_geodataframe( if gdf_demand.crs != gdf_fac.crs: raise ValueError( - f"geodataframes crs are different: gdf_demand-{gdf_demand.crs}, gdf_fac-{gdf_fac.crs}" + "Geodataframes crs are different: " + f"gdf_demand-{gdf_demand.crs}, gdf_fac-{gdf_fac.crs}" ) distances = cdist(dem_data, fac_data, distance_metric) @@ -329,7 +328,7 @@ def facility_client_array(self) -> None: self.fac2cli.append(array_cli) - def solve(self, solver: pulp.LpSolver, results: bool=True): + def solve(self, solver: pulp.LpSolver, results: bool = True): """ Solve the PMedian model From 0d7da9ebe6c917eda67fafccadf5fe50eb33171d Mon Sep 17 00:00:00 2001 From: James Gaboardi Date: Fri, 14 Oct 2022 13:20:16 -0400 Subject: [PATCH 4/4] update tests --- spopt/BaseClass.py | 6 +- spopt/tests/test_azp.py | 6 +- spopt/tests/test_locate.py | 312 ++++++++++++++++++----------- spopt/tests/test_lscpb.py | 109 +++++----- spopt/tests/test_maxp.py | 61 +++--- spopt/tests/test_p_dispersion.py | 41 ++-- spopt/tests/test_random_regions.py | 71 ++++--- spopt/tests/test_region_k_means.py | 10 +- spopt/tests/test_region_util.py | 22 +- spopt/tests/test_skater.py | 26 ++- spopt/tests/test_spenc.py | 6 +- spopt/tests/test_ward.py | 6 +- 12 files changed, 383 insertions(+), 293 deletions(-) diff --git a/spopt/BaseClass.py b/spopt/BaseClass.py index 331a0e2d..37e90087 100644 --- a/spopt/BaseClass.py +++ b/spopt/BaseClass.py @@ -2,9 +2,7 @@ class BaseSpOptSolver(ABC): - """Base class for all spatial optimization model solvers. - - """ + """Base class for all spatial optimization model solvers.""" @abstractmethod def solve(self): @@ -56,4 +54,4 @@ class BaseSpOptHeuristicSolver(BaseSpOptSolver): @abstractmethod def solve(self): """Solve the optimization model.""" - pass \ No newline at end of file + pass diff --git a/spopt/tests/test_azp.py b/spopt/tests/test_azp.py index 8b205f4d..eb33d696 100644 --- a/spopt/tests/test_azp.py +++ b/spopt/tests/test_azp.py @@ -1,7 +1,7 @@ import libpysal import geopandas import numpy -import unittest +import pytest import spopt from spopt.region import AZP @@ -14,8 +14,8 @@ MEXICO = geopandas.read_file(pth) -class TestAZP(unittest.TestCase): - def setUp(self): +class TestAZP: + def setup_method(self): self.mexico = MEXICO.copy() diff --git a/spopt/tests/test_locate.py b/spopt/tests/test_locate.py index 0e13d9c2..74959785 100644 --- a/spopt/tests/test_locate.py +++ b/spopt/tests/test_locate.py @@ -9,10 +9,11 @@ from spopt.locate import LSCP, MCLP, PCenter, PMedian from spopt.locate.util import simulated_geo_points -import unittest import os import pickle import platform +import pytest +import warnings operating_system = platform.platform()[:7].lower() if operating_system == "windows": @@ -21,8 +22,8 @@ WINDOWS = False -class TestSyntheticLocate(unittest.TestCase): - def setUp(self) -> None: +class TestSyntheticLocate: + def setup_method(self) -> None: self.dirpath = os.path.join(os.path.dirname(__file__), "./data/") lattice = spaghetti.regular_lattice((0, 0, 10, 10), 9, exterior=True) @@ -67,7 +68,7 @@ def setUp(self) -> None: def test_lscp_from_cost_matrix(self): lscp = LSCP.from_cost_matrix(self.cost_matrix, 10) result = lscp.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertIsInstance(result, LSCP) + assert isinstance(result, LSCP) def test_lscp_facility_client_array_from_cost_matrix(self): with open(self.dirpath + "lscp_fac2cli.pkl", "rb") as f: @@ -77,7 +78,10 @@ def test_lscp_facility_client_array_from_cost_matrix(self): lscp = lscp.solve(pulp.PULP_CBC_CMD(msg=False)) lscp.facility_client_array() - numpy.testing.assert_array_equal(lscp.fac2cli, lscp_objective) + numpy.testing.assert_array_equal( + numpy.array(lscp.fac2cli, dtype=object), + numpy.array(lscp_objective, dtype=object), + ) def test_lscp_client_facility_array_from_cost_matrix(self): with open(self.dirpath + "lscp_cli2fac.pkl", "rb") as f: @@ -88,14 +92,17 @@ def test_lscp_client_facility_array_from_cost_matrix(self): lscp.facility_client_array() lscp.client_facility_array() - numpy.testing.assert_array_equal(lscp.cli2fac, lscp_objective) + numpy.testing.assert_array_equal( + numpy.array(lscp.cli2fac, dtype=object), + numpy.array(lscp_objective, dtype=object), + ) def test_lscp_from_geodataframe(self): lscp = LSCP.from_geodataframe( self.clients_snapped, self.facilities_snapped, "geometry", "geometry", 10 ) result = lscp.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertIsInstance(result, LSCP) + assert isinstance(result, LSCP) def test_lscp_facility_client_array_from_geodataframe(self): with open(self.dirpath + "lscp_geodataframe_fac2cli.pkl", "rb") as f: @@ -111,7 +118,10 @@ def test_lscp_facility_client_array_from_geodataframe(self): lscp = lscp.solve(pulp.PULP_CBC_CMD(msg=False)) lscp.facility_client_array() - numpy.testing.assert_array_equal(lscp.fac2cli, lscp_objective) + numpy.testing.assert_array_equal( + numpy.array(lscp.fac2cli, dtype=object), + numpy.array(lscp_objective, dtype=object), + ) def test_lscp_client_facility_array_from_geodataframe(self): with open(self.dirpath + "lscp_geodataframe_cli2fac.pkl", "rb") as f: @@ -128,7 +138,10 @@ def test_lscp_client_facility_array_from_geodataframe(self): lscp.facility_client_array() lscp.client_facility_array() - numpy.testing.assert_array_equal(lscp.cli2fac, lscp_objective) + numpy.testing.assert_array_equal( + numpy.array(lscp.cli2fac, dtype=object), + numpy.array(lscp_objective, dtype=object), + ) def test_lscp_preselected_facility_client_array_from_geodataframe(self): with open( @@ -151,14 +164,17 @@ def test_lscp_preselected_facility_client_array_from_geodataframe(self): lscp = lscp.solve(pulp.PULP_CBC_CMD(msg=False, warmStart=True)) lscp.facility_client_array() - numpy.testing.assert_array_equal(lscp.fac2cli, lscp_objective) + numpy.testing.assert_array_equal( + numpy.array(lscp.fac2cli, dtype=object), + numpy.array(lscp_objective, dtype=object), + ) def test_mclp_from_cost_matrix(self): mclp = MCLP.from_cost_matrix( self.cost_matrix, self.ai, service_radius=7, p_facilities=4 ) result = mclp.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertIsInstance(result, MCLP) + assert isinstance(result, MCLP) def test_mclp_facility_client_array_from_cost_matrix(self): with open(self.dirpath + "mclp_fac2cli.pkl", "rb") as f: @@ -173,7 +189,10 @@ def test_mclp_facility_client_array_from_cost_matrix(self): mclp = mclp.solve(pulp.PULP_CBC_CMD(msg=False)) mclp.facility_client_array() - numpy.testing.assert_array_equal(mclp.fac2cli, mclp_objective) + numpy.testing.assert_array_equal( + numpy.array(mclp.fac2cli, dtype=object), + numpy.array(mclp_objective, dtype=object), + ) def test_mclp_client_facility_array_from_cost_matrix(self): with open(self.dirpath + "mclp_cli2fac.pkl", "rb") as f: @@ -189,7 +208,10 @@ def test_mclp_client_facility_array_from_cost_matrix(self): mclp.facility_client_array() mclp.client_facility_array() - numpy.testing.assert_array_equal(mclp.cli2fac, mclp_objective) + numpy.testing.assert_array_equal( + numpy.array(mclp.cli2fac, dtype=object), + numpy.array(mclp_objective, dtype=object), + ) def test_mclp_from_geodataframe(self): @@ -203,7 +225,7 @@ def test_mclp_from_geodataframe(self): p_facilities=4, ) result = mclp.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertIsInstance(result, MCLP) + assert isinstance(result, MCLP) def test_mclp_facility_client_array_from_geodataframe(self): with open(self.dirpath + "mclp_geodataframe_fac2cli.pkl", "rb") as f: @@ -221,7 +243,10 @@ def test_mclp_facility_client_array_from_geodataframe(self): mclp = mclp.solve(pulp.PULP_CBC_CMD(msg=False)) mclp.facility_client_array() - numpy.testing.assert_array_equal(mclp.fac2cli, mclp_objective) + numpy.testing.assert_array_equal( + numpy.array(mclp.fac2cli, dtype=object), + numpy.array(mclp_objective, dtype=object), + ) def test_mclp_preselected_facility_client_array_from_geodataframe(self): with open( @@ -246,7 +271,10 @@ def test_mclp_preselected_facility_client_array_from_geodataframe(self): mclp = mclp.solve(pulp.PULP_CBC_CMD(msg=False, warmStart=True)) mclp.facility_client_array() - numpy.testing.assert_array_equal(mclp.fac2cli, mclp_objective) + numpy.testing.assert_array_equal( + numpy.array(mclp.fac2cli, dtype=object), + numpy.array(mclp_objective, dtype=object), + ) def test_mclp_client_facility_array_from_geodataframe(self): with open(self.dirpath + "mclp_geodataframe_cli2fac.pkl", "rb") as f: @@ -265,12 +293,15 @@ def test_mclp_client_facility_array_from_geodataframe(self): mclp.facility_client_array() mclp.client_facility_array() - numpy.testing.assert_array_equal(mclp.cli2fac, mclp_objective) + numpy.testing.assert_array_equal( + numpy.array(mclp.cli2fac, dtype=object), + numpy.array(mclp_objective, dtype=object), + ) def test_p_median_from_cost_matrix(self): p_median = PMedian.from_cost_matrix(self.cost_matrix, self.ai, p_facilities=4) result = p_median.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertIsInstance(result, PMedian) + assert isinstance(result, PMedian) def test_pmedian_facility_client_array_from_cost_matrix(self): with open(self.dirpath + "pmedian_fac2cli.pkl", "rb") as f: @@ -280,7 +311,10 @@ def test_pmedian_facility_client_array_from_cost_matrix(self): pmedian = pmedian.solve(pulp.PULP_CBC_CMD(msg=False)) pmedian.facility_client_array() - numpy.testing.assert_array_equal(pmedian.fac2cli, pmedian_objective) + numpy.testing.assert_array_equal( + numpy.array(pmedian.fac2cli, dtype=object), + numpy.array(pmedian_objective, dtype=object), + ) def test_pmedian_client_facility_array_from_cost_matrix(self): with open(self.dirpath + "pmedian_cli2fac.pkl", "rb") as f: @@ -291,7 +325,10 @@ def test_pmedian_client_facility_array_from_cost_matrix(self): pmedian.facility_client_array() pmedian.client_facility_array() - numpy.testing.assert_array_equal(pmedian.cli2fac, pmedian_objective) + numpy.testing.assert_array_equal( + numpy.array(pmedian.cli2fac, dtype=object), + numpy.array(pmedian_objective, dtype=object), + ) def test_p_median_from_geodataframe(self): p_median = PMedian.from_geodataframe( @@ -303,7 +340,7 @@ def test_p_median_from_geodataframe(self): p_facilities=4, ) result = p_median.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertIsInstance(result, PMedian) + assert isinstance(result, PMedian) def test_pmedian_facility_client_array_from_geodataframe(self): with open(self.dirpath + "pmedian_geodataframe_fac2cli.pkl", "rb") as f: @@ -320,7 +357,10 @@ def test_pmedian_facility_client_array_from_geodataframe(self): pmedian = pmedian.solve(pulp.PULP_CBC_CMD(msg=False)) pmedian.facility_client_array() - numpy.testing.assert_array_equal(pmedian.fac2cli, pmedian_objective) + numpy.testing.assert_array_equal( + numpy.array(pmedian.fac2cli, dtype=object), + numpy.array(pmedian_objective, dtype=object), + ) def test_pmedian_client_facility_array_from_geodataframe(self): with open(self.dirpath + "pmedian_geodataframe_cli2fac.pkl", "rb") as f: @@ -338,12 +378,15 @@ def test_pmedian_client_facility_array_from_geodataframe(self): pmedian.facility_client_array() pmedian.client_facility_array() - numpy.testing.assert_array_equal(pmedian.cli2fac, pmedian_objective) + numpy.testing.assert_array_equal( + numpy.array(pmedian.cli2fac, dtype=object), + numpy.array(pmedian_objective, dtype=object), + ) def test_p_center_from_cost_matrix(self): p_center = PCenter.from_cost_matrix(self.cost_matrix, p_facilities=4) result = p_center.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertIsInstance(result, PCenter) + assert isinstance(result, PCenter) def test_pcenter_facility_client_array_from_cost_matrix(self): with open(self.dirpath + "pcenter_fac2cli.pkl", "rb") as f: @@ -353,7 +396,10 @@ def test_pcenter_facility_client_array_from_cost_matrix(self): pcenter = pcenter.solve(pulp.PULP_CBC_CMD(msg=False)) pcenter.facility_client_array() - numpy.testing.assert_array_equal(pcenter.fac2cli, pcenter_objective) + numpy.testing.assert_array_equal( + numpy.array(pcenter.fac2cli, dtype=object), + numpy.array(pcenter_objective, dtype=object), + ) def test_pcenter_client_facility_array_from_cost_matrix(self): with open(self.dirpath + "pcenter_cli2fac.pkl", "rb") as f: @@ -364,7 +410,10 @@ def test_pcenter_client_facility_array_from_cost_matrix(self): pcenter.facility_client_array() pcenter.client_facility_array() - numpy.testing.assert_array_equal(pcenter.cli2fac, pcenter_objective) + numpy.testing.assert_array_equal( + numpy.array(pcenter.cli2fac, dtype=object), + numpy.array(pcenter_objective, dtype=object), + ) def test_p_center_from_geodataframe(self): p_center = PCenter.from_geodataframe( @@ -375,7 +424,7 @@ def test_p_center_from_geodataframe(self): p_facilities=4, ) result = p_center.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertIsInstance(result, PCenter) + assert isinstance(result, PCenter) def test_pcenter_facility_client_array_from_geodataframe(self): with open(self.dirpath + "pcenter_geodataframe_fac2cli.pkl", "rb") as f: @@ -391,7 +440,10 @@ def test_pcenter_facility_client_array_from_geodataframe(self): pcenter = pcenter.solve(pulp.PULP_CBC_CMD(msg=False)) pcenter.facility_client_array() - numpy.testing.assert_array_equal(pcenter.fac2cli, pcenter_objective) + numpy.testing.assert_array_equal( + numpy.array(pcenter.fac2cli, dtype=object), + numpy.array(pcenter_objective, dtype=object), + ) def test_pcenter_client_facility_array_from_geodataframe(self): with open(self.dirpath + "pcenter_geodataframe_cli2fac.pkl", "rb") as f: @@ -408,11 +460,14 @@ def test_pcenter_client_facility_array_from_geodataframe(self): pcenter.facility_client_array() pcenter.client_facility_array() - numpy.testing.assert_array_equal(pcenter.cli2fac, pcenter_objective) + numpy.testing.assert_array_equal( + numpy.array(pcenter.cli2fac, dtype=object), + numpy.array(pcenter_objective, dtype=object), + ) -class TestRealWorldLocate(unittest.TestCase): - def setUp(self) -> None: +class TestRealWorldLocate: + def setup_method(self) -> None: self.dirpath = os.path.join(os.path.dirname(__file__), "./data/") network_distance = pandas.read_csv( self.dirpath @@ -460,11 +515,11 @@ def test_optimality_lscp_from_cost_matrix(self): lscp = LSCP.from_cost_matrix(self.cost_matrix, self.service_dist) lscp = lscp.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertEqual(lscp.problem.status, pulp.LpStatusOptimal) + assert lscp.problem.status == pulp.LpStatusOptimal def test_infeasibility_lscp_from_cost_matrix(self): lscp = LSCP.from_cost_matrix(self.cost_matrix, 20) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError, match="Model is not solved: Infeasible."): lscp.solve(pulp.PULP_CBC_CMD(msg=False)) def test_optimality_lscp_from_geodataframe(self): @@ -476,7 +531,7 @@ def test_optimality_lscp_from_geodataframe(self): self.service_dist, ) lscp = lscp.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertEqual(lscp.problem.status, pulp.LpStatusOptimal) + assert lscp.problem.status == pulp.LpStatusOptimal def test_infeasibility_lscp_from_geodataframe(self): lscp = LSCP.from_geodataframe( @@ -486,7 +541,7 @@ def test_infeasibility_lscp_from_geodataframe(self): "geometry", 0, ) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError, match="Model is not solved: Infeasible."): lscp.solve(pulp.PULP_CBC_CMD(msg=False)) def test_optimality_mclp_from_cost_matrix(self): @@ -497,7 +552,7 @@ def test_optimality_mclp_from_cost_matrix(self): p_facilities=self.p_facility, ) mclp = mclp.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertEqual(mclp.problem.status, pulp.LpStatusOptimal) + assert mclp.problem.status == pulp.LpStatusOptimal def test_infeasibility_mclp_from_cost_matrix(self): mclp = MCLP.from_cost_matrix( @@ -506,7 +561,7 @@ def test_infeasibility_mclp_from_cost_matrix(self): service_radius=self.service_dist, p_facilities=1000, ) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError, match="Model is not solved: Infeasible."): mclp.solve(pulp.PULP_CBC_CMD(msg=False)) def test_mixin_mclp_get_uncovered_clients(self): @@ -521,7 +576,7 @@ def test_mixin_mclp_get_uncovered_clients(self): mclp.facility_client_array() mclp.uncovered_clients() - self.assertEqual(mclp.n_cli_uncov, uncovered_clients_expected) + assert mclp.n_cli_uncov == uncovered_clients_expected def test_mixin_mclp_get_percentage(self): percentage_expected = 0.8975609756097561 @@ -536,7 +591,7 @@ def test_mixin_mclp_get_percentage(self): mclp.uncovered_clients() mclp.get_percentage() - self.assertEqual(mclp.percentage, percentage_expected) + assert mclp.percentage == percentage_expected def test_optimality_mclp_from_geodataframe(self): mclp = MCLP.from_geodataframe( @@ -549,7 +604,7 @@ def test_optimality_mclp_from_geodataframe(self): p_facilities=self.p_facility, ) mclp = mclp.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertEqual(mclp.problem.status, pulp.LpStatusOptimal) + assert mclp.problem.status == pulp.LpStatusOptimal def test_infeasibility_mclp_from_geodataframe(self): mclp = MCLP.from_geodataframe( @@ -561,7 +616,7 @@ def test_infeasibility_mclp_from_geodataframe(self): service_radius=self.service_dist, p_facilities=1000, ) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError, match="Model is not solved: Infeasible."): mclp.solve(pulp.PULP_CBC_CMD(msg=False)) def test_optimality_pcenter_from_cost_matrix(self): @@ -569,11 +624,11 @@ def test_optimality_pcenter_from_cost_matrix(self): self.cost_matrix, p_facilities=self.p_facility ) pcenter = pcenter.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertEqual(pcenter.problem.status, pulp.LpStatusOptimal) + assert pcenter.problem.status == pulp.LpStatusOptimal def test_infeasibility_pcenter_from_cost_matrix(self): pcenter = PCenter.from_cost_matrix(self.cost_matrix, p_facilities=0) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError, match="Model is not solved: Infeasible."): pcenter.solve(pulp.PULP_CBC_CMD(msg=False)) def test_optimality_pcenter_from_geodataframe(self): @@ -585,7 +640,7 @@ def test_optimality_pcenter_from_geodataframe(self): p_facilities=self.p_facility, ) pcenter = pcenter.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertEqual(pcenter.problem.status, pulp.LpStatusOptimal) + assert pcenter.problem.status == pulp.LpStatusOptimal def test_infeasibility_pcenter_from_geodataframe(self): pcenter = PCenter.from_geodataframe( @@ -595,7 +650,7 @@ def test_infeasibility_pcenter_from_geodataframe(self): "geometry", p_facilities=0, ) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError, match="Model is not solved: Infeasible."): pcenter.solve(pulp.PULP_CBC_CMD(msg=False)) def test_optimality_pmedian_from_cost_matrix(self): @@ -603,11 +658,11 @@ def test_optimality_pmedian_from_cost_matrix(self): self.cost_matrix, self.ai, p_facilities=self.p_facility ) pmedian = pmedian.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertEqual(pmedian.problem.status, pulp.LpStatusOptimal) + assert pmedian.problem.status == pulp.LpStatusOptimal def test_infeasibility_pmedian_from_cost_matrix(self): pmedian = PMedian.from_cost_matrix(self.cost_matrix, self.ai, p_facilities=0) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError, match="Model is not solved: Infeasible."): pmedian.solve(pulp.PULP_CBC_CMD(msg=False)) def test_mixin_mean_distance(self): @@ -618,7 +673,7 @@ def test_mixin_mean_distance(self): pmedian = pmedian.solve(pulp.PULP_CBC_CMD(msg=False)) pmedian.get_mean_distance(self.ai) - self.assertEqual(pmedian.mean_dist, mean_distance_expected) + assert pmedian.mean_dist == mean_distance_expected def test_optimality_pmedian_from_geodataframe(self): pmedian = PMedian.from_geodataframe( @@ -630,7 +685,7 @@ def test_optimality_pmedian_from_geodataframe(self): p_facilities=self.p_facility, ) pmedian = pmedian.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertEqual(pmedian.problem.status, pulp.LpStatusOptimal) + assert pmedian.problem.status == pulp.LpStatusOptimal def test_infeasibility_pmedian_from_geodataframe(self): pmedian = PMedian.from_geodataframe( @@ -641,9 +696,9 @@ def test_infeasibility_pmedian_from_geodataframe(self): "POP2000", p_facilities=0, ) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError, match="Model is not solved: Infeasible."): pmedian.solve(pulp.PULP_CBC_CMD(msg=False)) - + def test_attribute_error_fac2cli_MCLP_facility_client_array(self): mclp = MCLP.from_geodataframe( self.demand_points_gdf, @@ -655,9 +710,11 @@ def test_attribute_error_fac2cli_MCLP_facility_client_array(self): p_facilities=self.p_facility, ) mclp = mclp.solve(pulp.PULP_CBC_CMD(msg=False), results=False) - - with self.assertRaises(AttributeError): - _ = self.fac2cli + + with pytest.raises( + AttributeError, match="'MCLP' object has no attribute 'fac2cli'" + ): + mclp.fac2cli def test_attribute_error_cli2fac_MCLP_facility_client_array(self): mclp = MCLP.from_geodataframe( @@ -670,9 +727,11 @@ def test_attribute_error_cli2fac_MCLP_facility_client_array(self): p_facilities=self.p_facility, ) mclp = mclp.solve(pulp.PULP_CBC_CMD(msg=False), results=False) - - with self.assertRaises(AttributeError): - _ = self.cli2fac + + with pytest.raises( + AttributeError, match="'MCLP' object has no attribute 'cli2fac'" + ): + mclp.cli2fac def test_attribute_error_ncliuncov_MCLP_facility_client_array(self): mclp = MCLP.from_geodataframe( @@ -685,9 +744,11 @@ def test_attribute_error_ncliuncov_MCLP_facility_client_array(self): p_facilities=self.p_facility, ) mclp = mclp.solve(pulp.PULP_CBC_CMD(msg=False), results=False) - - with self.assertRaises(AttributeError): - _ = self.n_cli_uncov + + with pytest.raises( + AttributeError, match="'MCLP' object has no attribute 'n_cli_uncov'" + ): + mclp.n_cli_uncov def test_attribute_error_percentage_MCLP_facility_client_array(self): mclp = MCLP.from_geodataframe( @@ -700,14 +761,15 @@ def test_attribute_error_percentage_MCLP_facility_client_array(self): p_facilities=self.p_facility, ) mclp = mclp.solve(pulp.PULP_CBC_CMD(msg=False), results=False) - - with self.assertRaises(AttributeError): - _ = mclp.get_percentage() - - -class TestErrorsWarnings(unittest.TestCase): - def setUp(self) -> None: + with pytest.raises( + AttributeError, match="The attribute `n_cli_uncov` is not set." + ): + mclp.get_percentage() + + +class TestErrorsWarnings: + def setup_method(self) -> None: pol1 = Polygon([(0, 0), (1, 0), (1, 1)]) pol2 = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) @@ -723,10 +785,16 @@ def setUp(self) -> None: self.gdf_dem_crs = self.gdf_dem.to_crs("EPSG:3857") self.gdf_dem_buffered = self.gdf_dem.copy() - self.gdf_dem_buffered["geometry"] = self.gdf_dem.buffer(2) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + category=UserWarning, + message="Geometry is in a geographic CRS", + ) + self.gdf_dem_buffered["geometry"] = self.gdf_dem.buffer(2) def test_attribute_error_add_set_covering_constraint(self): - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError, match="Before setting coverage constraints"): dummy_class = LSCP("dummy", pulp.LpProblem("name")) dummy_matrix = numpy.array([]) dummy_range = range(1) @@ -735,7 +803,7 @@ def test_attribute_error_add_set_covering_constraint(self): ) def test_attribute_error_add_facility_constraint(self): - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError, match="Before setting facility constraint"): dummy_class = LSCP("dummy", pulp.LpProblem("name")) dummy_p_facility = 1 FacilityModelBuilder.add_facility_constraint( @@ -743,7 +811,9 @@ def test_attribute_error_add_facility_constraint(self): ) def test_attribute_error_add_maximal_coverage_constraint(self): - with self.assertRaises(AttributeError): + with pytest.raises( + AttributeError, match="Before setting maximal coverage constraints" + ): dummy_class = LSCP("dummy", pulp.LpProblem("name")) dummy_matrix = numpy.array([]) dummy_range = range(1) @@ -752,7 +822,9 @@ def test_attribute_error_add_maximal_coverage_constraint(self): ) def test_attribute_error_add_assignment_constraint(self): - with self.assertRaises(AttributeError): + with pytest.raises( + AttributeError, match="Before setting assignment constraints" + ): dummy_class = LSCP("dummy", pulp.LpProblem("name")) dummy_range = range(1) FacilityModelBuilder.add_assignment_constraint( @@ -760,7 +832,7 @@ def test_attribute_error_add_assignment_constraint(self): ) def test_attribute_error_add_opening_constraint(self): - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError, match="Before setting opening constraints"): dummy_class = LSCP("dummy", pulp.LpProblem("name")) dummy_range = range(1) FacilityModelBuilder.add_opening_constraint( @@ -768,7 +840,9 @@ def test_attribute_error_add_opening_constraint(self): ) def test_attribute_error_add_minimized_maximum_constraint(self): - with self.assertRaises(AttributeError): + with pytest.raises( + AttributeError, match="Before setting minimized maximum constraints" + ): dummy_class = LSCP("dummy", pulp.LpProblem("name")) dummy_matrix = numpy.array([]) dummy_range = range(1) @@ -777,50 +851,56 @@ def test_attribute_error_add_minimized_maximum_constraint(self): ) def test_error_lscp_different_crs(self): - with self.assertRaises(ValueError): - dummy_class = LSCP.from_geodataframe( - self.gdf_dem_crs, self.gdf_fac, "geometry", "geometry", 10 - ) + with pytest.warns( + UserWarning, match="Facility geodataframe contains mixed type" + ): + with pytest.raises(ValueError, match="Geodataframes crs are different: "): + LSCP.from_geodataframe( + self.gdf_dem_crs, self.gdf_fac, "geometry", "geometry", 10 + ) def test_error_mclp_different_crs(self): - with self.assertRaises(ValueError): - dummy_class = MCLP.from_geodataframe( - self.gdf_dem_crs, self.gdf_fac, "geometry", "geometry", "weight", 10, 2 - ) + with pytest.warns( + UserWarning, match="Facility geodataframe contains mixed type" + ): + with pytest.raises(ValueError, match="Geodataframes crs are different: "): + MCLP.from_geodataframe( + self.gdf_dem_crs, + self.gdf_fac, + "geometry", + "geometry", + "weight", + 10, + 2, + ) def test_error_pmedian_different_crs(self): - with self.assertRaises(ValueError): - dummy_class = PMedian.from_geodataframe( - self.gdf_dem_crs, self.gdf_fac, "geometry", "geometry", "weight", 2 - ) + with pytest.warns( + UserWarning, match="Facility geodataframe contains mixed type" + ): + with pytest.raises(ValueError, match="Geodataframes crs are different: "): + PMedian.from_geodataframe( + self.gdf_dem_crs, self.gdf_fac, "geometry", "geometry", "weight", 2 + ) def test_error_pcenter_different_crs(self): - with self.assertRaises(ValueError): - dummy_class = PCenter.from_geodataframe( - self.gdf_dem_crs, self.gdf_fac, "geometry", "geometry", 2 - ) - - def test_warning_lscp_facility_geodataframe(self): - with self.assertWarns(Warning): - dummy_class = LSCP.from_geodataframe( - self.gdf_dem, self.gdf_fac, "geometry", "geometry", 10 - ) + with pytest.warns( + UserWarning, match="Facility geodataframe contains mixed type" + ): + with pytest.raises(ValueError, match="Geodataframes crs are different: "): + PCenter.from_geodataframe( + self.gdf_dem_crs, self.gdf_fac, "geometry", "geometry", 2 + ) def test_warning_lscp_demand_geodataframe(self): - with self.assertWarns(Warning): - dummy_class = LSCP.from_geodataframe( + with pytest.warns(UserWarning, match="Demand geodataframe contains mixed type"): + LSCP.from_geodataframe( self.gdf_dem_buffered, self.gdf_fac, "geometry", "geometry", 10 ) - def test_warning_mclp_facility_geodataframe(self): - with self.assertWarns(Warning): - dummy_class = MCLP.from_geodataframe( - self.gdf_dem, self.gdf_fac, "geometry", "geometry", "weight", 10, 2 - ) - def test_warning_mclp_demand_geodataframe(self): - with self.assertWarns(Warning): - dummy_class = MCLP.from_geodataframe( + with pytest.warns(UserWarning, match="Demand geodataframe contains mixed type"): + MCLP.from_geodataframe( self.gdf_dem_buffered, self.gdf_fac, "geometry", @@ -830,26 +910,14 @@ def test_warning_mclp_demand_geodataframe(self): 2, ) - def test_warning_pmedian_facility_geodataframe(self): - with self.assertWarns(Warning): - dummy_class = PMedian.from_geodataframe( - self.gdf_dem, self.gdf_fac, "geometry", "geometry", "weight", 2 - ) - def test_warning_pmedian_demand_geodataframe(self): - with self.assertWarns(Warning): - dummy_class = PMedian.from_geodataframe( + with pytest.warns(UserWarning, match="Demand geodataframe contains mixed type"): + PMedian.from_geodataframe( self.gdf_dem_buffered, self.gdf_fac, "geometry", "geometry", "weight", 2 ) - def test_warning_pcenter_facility_geodataframe(self): - with self.assertWarns(Warning): - dummy_class = PCenter.from_geodataframe( - self.gdf_dem, self.gdf_fac, "geometry", "geometry", 2 - ) - def test_warning_pcenter_demand_geodataframe(self): - with self.assertWarns(Warning): - dummy_class = PCenter.from_geodataframe( + with pytest.warns(UserWarning, match="Demand geodataframe contains mixed type"): + PCenter.from_geodataframe( self.gdf_dem_buffered, self.gdf_fac, "geometry", "geometry", 2 ) diff --git a/spopt/tests/test_lscpb.py b/spopt/tests/test_lscpb.py index 4ab8fffd..c143055c 100644 --- a/spopt/tests/test_lscpb.py +++ b/spopt/tests/test_lscpb.py @@ -8,20 +8,15 @@ from spopt.locate import LSCPB from spopt.locate.base import FacilityModelBuilder, LocateSolver from spopt.locate.util import simulated_geo_points -import unittest + import os import pickle -import platform - -operating_system = platform.platform()[:7].lower() -if operating_system == "windows": - WINDOWS = True -else: - WINDOWS = False +import warnings +import pytest -class TestSyntheticLocate(unittest.TestCase): - def setUp(self) -> None: +class TestSyntheticLocate: + def setup_method(self) -> None: self.dirpath = os.path.join(os.path.dirname(__file__), "./data/") lattice = spaghetti.regular_lattice((0, 0, 10, 10), 9, exterior=True) @@ -68,7 +63,8 @@ def test_lscpb_from_cost_matrix(self): self.cost_matrix, 10, pulp.PULP_CBC_CMD(msg=False) ) result = lscpb.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertIsInstance(result, LSCPB) + + assert isinstance(result, LSCPB) def test_lscpb_facility_client_array_from_cost_matrix(self): with open(self.dirpath + "lscpb_fac2cli.pkl", "rb") as f: @@ -80,7 +76,10 @@ def test_lscpb_facility_client_array_from_cost_matrix(self): lscpb = lscpb.solve(pulp.PULP_CBC_CMD(msg=False)) lscpb.facility_client_array() - numpy.testing.assert_array_equal(lscpb.fac2cli, lscpb_objective) + numpy.testing.assert_array_equal( + numpy.array(lscpb.fac2cli, dtype=object), + numpy.array(lscpb_objective, dtype=object), + ) def test_lscpb_client_facility_array_from_cost_matrix(self): with open(self.dirpath + "lscpb_cli2fac.pkl", "rb") as f: @@ -93,7 +92,10 @@ def test_lscpb_client_facility_array_from_cost_matrix(self): lscpb.facility_client_array() lscpb.client_facility_array() - numpy.testing.assert_array_equal(lscpb.cli2fac, lscpb_objective) + numpy.testing.assert_array_equal( + numpy.array(lscpb.cli2fac, dtype=object), + numpy.array(lscpb_objective, dtype=object), + ) def test_lscpb_from_geodataframe(self): lscpb = LSCPB.from_geodataframe( @@ -105,7 +107,8 @@ def test_lscpb_from_geodataframe(self): pulp.PULP_CBC_CMD(msg=False), ) result = lscpb.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertIsInstance(result, LSCPB) + + assert isinstance(result, LSCPB) def test_lscpb_facility_client_array_from_geodataframe(self): with open(self.dirpath + "lscpb_geodataframe_fac2cli.pkl", "rb") as f: @@ -122,7 +125,10 @@ def test_lscpb_facility_client_array_from_geodataframe(self): lscpb = lscpb.solve(pulp.PULP_CBC_CMD(msg=False)) lscpb.facility_client_array() - numpy.testing.assert_array_equal(lscpb.fac2cli, lscpb_objective) + numpy.testing.assert_array_equal( + numpy.array(lscpb.fac2cli, dtype=object), + numpy.array(lscpb_objective, dtype=object), + ) def test_lscpb_client_facility_array_from_geodataframe(self): with open(self.dirpath + "lscpb_geodataframe_cli2fac.pkl", "rb") as f: @@ -140,7 +146,10 @@ def test_lscpb_client_facility_array_from_geodataframe(self): lscpb.facility_client_array() lscpb.client_facility_array() - numpy.testing.assert_array_equal(lscpb.cli2fac, lscpb_objective) + numpy.testing.assert_array_equal( + numpy.array(lscpb.cli2fac, dtype=object), + numpy.array(lscpb_objective, dtype=object), + ) def test_lscpb_preselected_facility_client_array_from_geodataframe(self): with open( @@ -163,11 +172,14 @@ def test_lscpb_preselected_facility_client_array_from_geodataframe(self): lscpb = lscpb.solve(pulp.PULP_CBC_CMD(msg=False, warmStart=True)) lscpb.facility_client_array() - numpy.testing.assert_array_equal(lscpb.fac2cli, lscpb_objective) + numpy.testing.assert_array_equal( + numpy.array(lscpb.fac2cli, dtype=object), + numpy.array(lscpb_objective, dtype=object), + ) -class TestRealWorldLocate(unittest.TestCase): - def setUp(self) -> None: +class TestRealWorldLocate: + def setup_method(self) -> None: self.dirpath = os.path.join(os.path.dirname(__file__), "./data/") network_distance = pandas.read_csv( self.dirpath @@ -217,10 +229,10 @@ def test_optimality_lscpb_from_cost_matrix(self): ) lscpb = lscpb.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertEqual(lscpb.problem.status, pulp.LpStatusOptimal) + assert lscpb.problem.status == pulp.LpStatusOptimal def test_infeasibility_lscpb_from_cost_matrix(self): - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError, match="Model is not solved"): lscpb = LSCPB.from_cost_matrix( self.cost_matrix, 20, pulp.PULP_CBC_CMD(msg=False) ) @@ -236,10 +248,11 @@ def test_optimality_lscpb_from_geodataframe(self): pulp.PULP_CBC_CMD(msg=False), ) lscpb = lscpb.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertEqual(lscpb.problem.status, pulp.LpStatusOptimal) + + assert lscpb.problem.status == pulp.LpStatusOptimal def test_infeasibility_lscpb_from_geodataframe(self): - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError, match="Model is not solved"): lscpb = LSCPB.from_geodataframe( self.demand_points_gdf, self.facility_points_gdf, @@ -251,8 +264,8 @@ def test_infeasibility_lscpb_from_geodataframe(self): lscpb.solve(pulp.PULP_CBC_CMD(msg=False)) -class TestErrorsWarnings(unittest.TestCase): - def setUp(self) -> None: +class TestErrorsWarnings: + def setup_method(self) -> None: pol1 = Polygon([(0, 0), (1, 0), (1, 1)]) pol2 = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) @@ -268,33 +281,31 @@ def setUp(self) -> None: self.gdf_dem_crs = self.gdf_dem.to_crs("EPSG:3857") self.gdf_dem_buffered = self.gdf_dem.copy() - self.gdf_dem_buffered["geometry"] = self.gdf_dem.buffer(2) - - def test_error_lscpb_different_crs(self): - with self.assertRaises(ValueError): - dummy_class = LSCPB.from_geodataframe( - self.gdf_dem_crs, - self.gdf_fac, - "geometry", - "geometry", - 10, - pulp.PULP_CBC_CMD(msg=False), + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + category=UserWarning, + message="Geometry is in a geographic CRS", ) + self.gdf_dem_buffered["geometry"] = self.gdf_dem.buffer(2) - def test_warning_lscpb_facility_geodataframe(self): - with self.assertWarns(Warning): - dummy_class = LSCPB.from_geodataframe( - self.gdf_dem, - self.gdf_fac, - "geometry", - "geometry", - 100, - pulp.PULP_CBC_CMD(msg=False), - ) + def test_error_lscpb_different_crs(self): + with pytest.warns( + UserWarning, match="Facility geodataframe contains mixed type" + ): + with pytest.raises(ValueError, match="Geodataframes crs are different: "): + LSCPB.from_geodataframe( + self.gdf_dem_crs, + self.gdf_fac, + "geometry", + "geometry", + 10, + pulp.PULP_CBC_CMD(msg=False), + ) def test_warning_lscpb_demand_geodataframe(self): - with self.assertWarns(Warning): - dummy_class = LSCPB.from_geodataframe( + with pytest.warns(UserWarning, match="Demand geodataframe contains mixed type"): + LSCPB.from_geodataframe( self.gdf_dem_buffered, self.gdf_fac, "geometry", @@ -304,7 +315,7 @@ def test_warning_lscpb_demand_geodataframe(self): ) def test_attribute_error_add_facility_constraint(self): - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError, match="Before setting backup coverage"): dummy_class = LSCPB("dummy", pulp.LpProblem("name")) dummy_p_facility = 1 dummy_fac_r = 0 diff --git a/spopt/tests/test_maxp.py b/spopt/tests/test_maxp.py index 42e8bb33..9fcd5491 100644 --- a/spopt/tests/test_maxp.py +++ b/spopt/tests/test_maxp.py @@ -1,12 +1,10 @@ import geopandas import libpysal import numpy -import unittest +import pytest from shapely.geometry import Polygon, box from spopt.region import MaxPHeuristic -from spopt.region.maxp import (infeasible_components, - modify_components, - plot_components) +from spopt.region.maxp import infeasible_components, modify_components, plot_components # Mexican states @@ -14,11 +12,11 @@ MEXICO = geopandas.read_file(pth) -class TestMaxPHeuristic(unittest.TestCase): - def setUp(self): +class TestMaxPHeuristic: + def setup_method(self): self.mexico = MEXICO.copy() - self.mexico['count']=1 + self.mexico["count"] = 1 self.w = libpysal.weights.Queen.from_dataframe(self.mexico) # labels for non-verbose and verbose: # count=1, threshold=4, top_n=2 @@ -28,8 +26,7 @@ def setUp(self): # labels for: # count=4, threshold=10, top_n=5 self.complex_labels = [2, 2, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 3, 3, 3] - self.complex_labels += [1, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 2, - 2, 2] + self.complex_labels += [1, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2] # labels for one variable column: # count=1, threshold=5, top_n=5 @@ -41,14 +38,20 @@ def setUp(self): n_rows = 10 b = 0 h = w = 10 - component_0 = [box(l*w, b, l*w+w, b+h) for l in range(n_cols)] - b = b + h*2 - component_1 = [box(l*w, b + h * r, l * w + w, b + h+ h * r) for r in range(n_rows) for l in range(n_cols) ] + component_0 = [box(l * w, b, l * w + w, b + h) for l in range(n_cols)] + b = b + h * 2 + component_1 = [ + box(l * w, b + h * r, l * w + w, b + h + h * r) + for r in range(n_rows) + for l in range(n_cols) + ] geometries = component_0 + component_1 - self.gdf = geopandas.GeoDataFrame(geometry=geometries, - data = numpy.ones((n_cols*n_rows+n_cols,1), int), - columns=['var']) + self.gdf = geopandas.GeoDataFrame( + geometry=geometries, + data=numpy.ones((n_cols * n_rows + n_cols, 1), int), + columns=["var"], + ) def test_maxp_heuristic_basic_nonverbose(self): attrs_name = [f"PCGDP{year}" for year in range(1950, 2010, 10)] @@ -99,40 +102,26 @@ def test_maxp_one_var(self): numpy.testing.assert_array_equal(model.labels_, self.var1_labels) def test_infeasible_components(self): - ifcs = infeasible_components(self.mexico, self.w, - 'count', - 35) + ifcs = infeasible_components(self.mexico, self.w, "count", 35) print(ifcs) numpy.testing.assert_array_equal(ifcs, [0]) def test_plot_components(self): - nt = type(plot_components(self.mexico, - self.w)).__name__ - assert nt == 'Map' + nt = type(plot_components(self.mexico, self.w)).__name__ + assert nt == "Map" + @pytest.mark.filterwarnings("ignore:The weights matrix is not fully") def test_modify_components(self): w = libpysal.weights.Queen.from_dataframe(self.gdf) - gdf1, w1 = modify_components(self.gdf, - w, - 'var', - 6, - policy='drop') + gdf1, w1 = modify_components(self.gdf, w, "var", 6, policy="drop") assert gdf1.shape[0] == 50 - gdf1, w1 = modify_components(self.gdf, - w, - 'var', - 6, - policy='single') + gdf1, w1 = modify_components(self.gdf, w, "var", 6, policy="single") assert gdf1.shape[0] == 55 assert w1.neighbors[0] != w.neighbors[0] assert w1.neighbors[1] == w.neighbors[1] - gdf1, w1 = modify_components(self.gdf, - w, - 'var', - 6, - policy='multiple') + gdf1, w1 = modify_components(self.gdf, w, "var", 6, policy="multiple") assert gdf1.shape[0] == 55 assert w1.neighbors[0] != w.neighbors[0] assert w1.neighbors[1] != w.neighbors[1] diff --git a/spopt/tests/test_p_dispersion.py b/spopt/tests/test_p_dispersion.py index 27b8fe3d..7cb21882 100644 --- a/spopt/tests/test_p_dispersion.py +++ b/spopt/tests/test_p_dispersion.py @@ -9,10 +9,11 @@ from spopt.locate import PDispersion from spopt.locate.util import simulated_geo_points -import unittest + import os import pickle import platform +import pytest operating_system = platform.platform()[:7].lower() if operating_system == "windows": @@ -21,8 +22,8 @@ WINDOWS = False -class TestSyntheticLocate(unittest.TestCase): - def setUp(self) -> None: +class TestSyntheticLocate: + def setup_method(self) -> None: self.dirpath = os.path.join(os.path.dirname(__file__), "./data/") lattice = spaghetti.regular_lattice((0, 0, 10, 10), 9, exterior=True) @@ -56,7 +57,7 @@ def setUp(self) -> None: def test_p_dispersion_from_cost_matrix(self): pdispersion = PDispersion.from_cost_matrix(self.cost_matrix, p_fac=2) result = pdispersion.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertIsInstance(result, PDispersion) + assert isinstance(result, PDispersion) def test_p_dispersion_from_geodataframe(self): pdispersion = PDispersion.from_geodataframe( @@ -65,11 +66,11 @@ def test_p_dispersion_from_geodataframe(self): p_fac=2, ) result = pdispersion.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertIsInstance(result, PDispersion) + assert isinstance(result, PDispersion) -class TestRealWorldLocate(unittest.TestCase): - def setUp(self) -> None: +class TestRealWorldLocate: + def setup_method(self) -> None: self.dirpath = os.path.join(os.path.dirname(__file__), "./data/") network_distance = pandas.read_csv( self.dirpath @@ -103,11 +104,11 @@ def test_optimality_p_dispersion_from_cost_matrix(self): self.cost_matrix, p_fac=self.p_facility ) pdispersion = pdispersion.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertEqual(pdispersion.problem.status, pulp.LpStatusOptimal) + assert pdispersion.problem.status == pulp.LpStatusOptimal def test_infeasibility_p_dispersion_from_cost_matrix(self): pdispersion = PDispersion.from_cost_matrix(self.cost_matrix, p_fac=17) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError, match="Model is not solved:"): pdispersion.solve(pulp.PULP_CBC_CMD(msg=False)) def test_optimality_p_dispersion_from_geodataframe(self): @@ -117,7 +118,7 @@ def test_optimality_p_dispersion_from_geodataframe(self): p_fac=self.p_facility, ) pdispersion = pdispersion.solve(pulp.PULP_CBC_CMD(msg=False)) - self.assertEqual(pdispersion.problem.status, pulp.LpStatusOptimal) + assert pdispersion.problem.status == pulp.LpStatusOptimal def test_infeasibility_p_dispersion_from_geodataframe(self): pdispersion = PDispersion.from_geodataframe( @@ -125,12 +126,12 @@ def test_infeasibility_p_dispersion_from_geodataframe(self): "geometry", p_fac=17, ) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError, match="Model is not solved:"): pdispersion.solve(pulp.PULP_CBC_CMD(msg=False)) -class TestErrorsWarnings(unittest.TestCase): - def setUp(self) -> None: +class TestErrorsWarnings: + def setup_method(self) -> None: pol1 = Polygon([(0, 0), (1, 0), (1, 1)]) pol2 = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) @@ -140,7 +141,7 @@ def setUp(self) -> None: self.gdf_fac = geopandas.GeoDataFrame(polygon_dict, crs="EPSG:4326") def test_attribute_error_add_facility_constraint(self): - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError, match="Before setting facility constraint"): dummy_p_facility = 1 dummy_class = PDispersion("dummy", pulp.LpProblem("name"), dummy_p_facility) FacilityModelBuilder.add_facility_constraint( @@ -150,7 +151,9 @@ def test_attribute_error_add_facility_constraint(self): ) def test_attribute_error_add_p_dispersion_interfacility_constraint(self): - with self.assertRaises(AttributeError): + with pytest.raises( + AttributeError, match="Before setting interfacility distance constraints" + ): dummy_p_facility = 1 dummy_matrix = numpy.array([]) dummy_range = range(1) @@ -163,7 +166,7 @@ def test_attribute_error_add_p_dispersion_interfacility_constraint(self): ) def test_attribute_error_add_predefined_facility_constraint(self): - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError, match="Before setting facility constraint"): dummy_p_facility = 1 dummy_matrix = numpy.array([]) dummy_class = PDispersion("dummy", pulp.LpProblem("name"), dummy_p_facility) @@ -172,5 +175,7 @@ def test_attribute_error_add_predefined_facility_constraint(self): ) def test_warning_facility_geodataframe(self): - with self.assertWarns(Warning): - dummy_class = PDispersion.from_geodataframe(self.gdf_fac, "geometry", 1) + with pytest.warns( + UserWarning, match="Facility geodataframe contains mixed type" + ): + PDispersion.from_geodataframe(self.gdf_fac, "geometry", 1) diff --git a/spopt/tests/test_random_regions.py b/spopt/tests/test_random_regions.py index c26ff69b..c8bfdb13 100644 --- a/spopt/tests/test_random_regions.py +++ b/spopt/tests/test_random_regions.py @@ -1,7 +1,7 @@ import geopandas import libpysal import numpy -import unittest +import pytest from spopt.region import RandomRegion, RandomRegions @@ -19,8 +19,8 @@ # Empirical tests ------------------------------------------------------------------------ -class TestRandomRegionEmpirical(unittest.TestCase): - def setUp(self): +class TestRandomRegionEmpirical: + def setup_method(self): self.mexico = MEXICO.copy() self.cards = self.mexico.groupby(by="HANSON03").count().NAME.values.tolist() @@ -40,7 +40,10 @@ def test_random_region_6_card(self): kwargs = {"num_regions": 6, "cardinality": self.cards} model = RandomRegion(self.ids, **kwargs) - numpy.testing.assert_array_equal(known_regions, model.regions) + numpy.testing.assert_array_equal( + numpy.array(known_regions, dtype=object), + numpy.array(model.regions, dtype=object), + ) def test_random_region_6_card_contig_compact(self): known_regions = [ @@ -60,11 +63,14 @@ def test_random_region_6_card_contig_compact(self): } model = RandomRegion(self.ids, **kwargs) - numpy.testing.assert_array_equal(known_regions, model.regions) + numpy.testing.assert_array_equal( + numpy.array(known_regions, dtype=object), + numpy.array(model.regions, dtype=object), + ) -class TestRandomRegionsEmpirical(unittest.TestCase): - def setUp(self): +class TestRandomRegionsEmpirical: + def setup_method(self): self.mexico = MEXICO.copy() self.cards = self.mexico.groupby(by="HANSON03").count().NAME.values.tolist() @@ -83,12 +89,15 @@ def test_random_regions_6_card(self): kwargs = {"num_regions": 6, "cardinality": self.cards, "permutations": 99} model = RandomRegions(self.ids, **kwargs) - numpy.testing.assert_array_equal(known_regions, model.solutions_feas[2].regions) + numpy.testing.assert_array_equal( + numpy.array(known_regions, dtype=object), + numpy.array(model.solutions_feas[2].regions, dtype=object), + ) # Synthetic tests ------------------------------------------------------------------------ -class TestRandomRegionSynthetic(unittest.TestCase): - def setUp(self): +class TestRandomRegionSynthetic: + def setup_method(self): self.nregs = N_REGIONS self.cards = SYNTH_CARDS @@ -99,42 +108,42 @@ def test_random_region_unconstrained(self): known_region_0 = [19, 14, 43, 37, 66, 3, 79, 41, 38, 68, 2, 1, 60] numpy.random.seed(10) model = RandomRegion(self.ids) - self.assertEqual(known_region_0, model.regions[0]) + assert known_region_0 == model.regions[0] def test_random_region_exo_regions(self): known_region_0 = [37, 62, 26, 41, 35, 25, 36] numpy.random.seed(100) kwargs = {"num_regions": self.nregs} model = RandomRegion(self.ids, **kwargs) - self.assertEqual(known_region_0, model.regions[0]) + assert known_region_0 == model.regions[0] def test_random_region_endo_regions_constrained_card(self): known_region_0 = [37, 62] numpy.random.seed(100) kwargs = {"cardinality": self.cards} model = RandomRegion(self.ids, **kwargs) - self.assertEqual(known_region_0, model.regions[0]) + assert known_region_0 == model.regions[0] def test_random_region_exo_regions_constrained_card(self): known_region_0 = [37, 62] numpy.random.seed(100) kwargs = {"num_regions": self.nregs, "cardinality": self.cards} model = RandomRegion(self.ids, **kwargs) - self.assertEqual(known_region_0, model.regions[0]) + assert known_region_0 == model.regions[0] def test_random_region_endo_regions_constrained_contig(self): known_region_5 = [33, 43, 32, 31] numpy.random.seed(100) kwargs = {"contiguity": self.w} model = RandomRegion(self.ids, **kwargs) - self.assertEqual(known_region_5, model.regions[5]) + assert known_region_5 == model.regions[5] def test_random_region_exo_regions_constrained_contig(self): known_region_5 = [92, 93, 91, 81, 71, 70, 90, 80] numpy.random.seed(100) kwargs = {"num_regions": self.nregs, "contiguity": self.w} model = RandomRegion(self.ids, **kwargs) - self.assertEqual(known_region_5, model.regions[5]) + assert known_region_5 == model.regions[5] def test_random_region_exo_regions_constrained_card_contig(self): known_region_0 = [62, 61, 81, 71, 64, 90, 72, 51, 80, 63, 50, 73, 52] @@ -145,21 +154,21 @@ def test_random_region_exo_regions_constrained_card_contig(self): } numpy.random.seed(60) model = RandomRegion(self.ids, **kwargs) - self.assertEqual(known_region_0, model.regions[0]) + assert known_region_0, model.regions[0] def test_random_region_endo_regions_constrained_card_contig(self): known_region_0 = [62, 61, 81, 71, 64, 90, 72, 51, 80, 63, 50, 73, 52] kwargs = {"cardinality": self.cards, "contiguity": self.w} numpy.random.seed(60) model = RandomRegion(self.ids, **kwargs) - self.assertEqual(known_region_0, model.regions[0]) + assert known_region_0 == model.regions[0] def test_random_regions_error_card(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="Number of areas"): RandomRegion([0, 1], cardinality=[4]) def test_random_regions_error_contig(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="Order of 'area_ids'"): class _shell_w_: def __init__(self): @@ -168,12 +177,12 @@ def __init__(self): RandomRegion([0, 1], contiguity=_shell_w_()) def test_random_regions_error_nregs(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="Number of regions"): RandomRegion([0, 1, 2, 3, 4, 5], num_regions=2, cardinality=[1, 2, 3]) -class TestRandomRegionsSynthetic(unittest.TestCase): - def setUp(self): +class TestRandomRegionsSynthetic: + def setup_method(self): self.nregs = N_REGIONS self.cards = SYNTH_CARDS @@ -186,21 +195,21 @@ def test_random_region_unconstrained(self): numpy.random.seed(10) kwargs = {"permutations": self.permutations} model = RandomRegions(self.ids, **kwargs) - self.assertEqual(known_region_0, model.solutions[0].regions[0]) + assert known_region_0 == model.solutions[0].regions[0] def test_random_region_exo_regions(self): known_region_0 = [37, 62, 26, 41, 35, 25, 36] numpy.random.seed(100) kwargs = {"num_regions": self.nregs, "permutations": self.permutations} model = RandomRegions(self.ids, **kwargs) - self.assertEqual(known_region_0, model.solutions[0].regions[0]) + assert known_region_0 == model.solutions[0].regions[0] def test_random_region_endo_regions_constrained_card(self): known_region_0 = [37, 62] numpy.random.seed(100) kwargs = {"cardinality": self.cards, "permutations": self.permutations} model = RandomRegions(self.ids, **kwargs) - self.assertEqual(known_region_0, model.solutions[0].regions[0]) + assert known_region_0 == model.solutions[0].regions[0] def test_random_region_exo_regions_constrained_card(self): known_region_0 = [37, 62] @@ -211,14 +220,14 @@ def test_random_region_exo_regions_constrained_card(self): "permutations": self.permutations, } model = RandomRegions(self.ids, **kwargs) - self.assertEqual(known_region_0, model.solutions[0].regions[0]) + assert known_region_0 == model.solutions[0].regions[0] def test_random_region_endo_regions_constrained_contig(self): known_region_5 = [33, 43, 32, 31] numpy.random.seed(100) kwargs = {"contiguity": self.w, "permutations": self.permutations} model = RandomRegions(self.ids, **kwargs) - self.assertEqual(known_region_5, model.solutions[0].regions[5]) + assert known_region_5 == model.solutions[0].regions[5] def test_random_region_exo_regions_constrained_contig(self): known_region_5 = [92, 93, 91, 81, 71, 70, 90, 80] @@ -229,7 +238,7 @@ def test_random_region_exo_regions_constrained_contig(self): "permutations": self.permutations, } model = RandomRegions(self.ids, **kwargs) - self.assertEqual(known_region_5, model.solutions[0].regions[5]) + assert known_region_5 == model.solutions[0].regions[5] def test_random_region_exo_regions_constrained_card_contig(self): known_region_0 = [62, 61, 81, 71, 64, 90, 72, 51, 80, 63, 50, 73, 52] @@ -241,7 +250,7 @@ def test_random_region_exo_regions_constrained_card_contig(self): } numpy.random.seed(60) model = RandomRegions(self.ids, **kwargs) - self.assertEqual(known_region_0, model.solutions[0].regions[0]) + assert known_region_0 == model.solutions[0].regions[0] def test_random_region_endo_regions_constrained_card_contig(self): known_region_0 = [62, 61, 81, 71, 64, 90, 72, 51, 80, 63, 50, 73, 52] @@ -252,4 +261,4 @@ def test_random_region_endo_regions_constrained_card_contig(self): } numpy.random.seed(60) model = RandomRegions(self.ids, **kwargs) - self.assertEqual(known_region_0, model.solutions[0].regions[0]) + assert known_region_0 == model.solutions[0].regions[0] diff --git a/spopt/tests/test_region_k_means.py b/spopt/tests/test_region_k_means.py index 95268ad9..29ab72b3 100644 --- a/spopt/tests/test_region_k_means.py +++ b/spopt/tests/test_region_k_means.py @@ -1,7 +1,7 @@ import libpysal import numpy +import pandas import pytest -import unittest from spopt.region import RegionKMeansHeuristic from spopt.region.spenclib.utils import lattice @@ -10,8 +10,8 @@ RANDOM_STATE = 12345 -class TestRegionKMeansHeuristic(unittest.TestCase): - def setUp(self): +class TestRegionKMeansHeuristic: + def setup_method(self): # small 3 x 3 example w/ 3 regions dim_small = 3 @@ -27,7 +27,7 @@ def setUp(self): numpy.random.seed(RANDOM_STATE) gdf["data_values_1"] = numpy.random.random(n_polys) gdf["data_values_2"] = numpy.random.random(n_polys) - gdf = gdf[:200].append(gdf[220:]) + gdf = pandas.concat([gdf[:200], gdf[220:]]) self.w_large = libpysal.weights.Rook.from_dataframe(gdf) self.data_large = gdf[["data_values_1", "data_values_2"]].values self.reg_large = 3 @@ -35,6 +35,7 @@ def setUp(self): self.known_labels_large = [1] * self.limit_index @pytest.mark.xfail + @pytest.mark.filterwarnings("ignore:The weights matrix is not fully") def test_region_k_means_heuristic_synth_small(self): numpy.random.seed(RANDOM_STATE) model = RegionKMeansHeuristic(self.data_small, self.reg_small, self.w_small) @@ -42,6 +43,7 @@ def test_region_k_means_heuristic_synth_small(self): numpy.testing.assert_equal(model.labels_, self.known_labels_small) + @pytest.mark.filterwarnings("ignore:The weights matrix is not fully") def test_region_k_means_heuristic_synth_large(self): numpy.random.seed(RANDOM_STATE) model = RegionKMeansHeuristic(self.data_large, self.reg_large, self.w_large) diff --git a/spopt/tests/test_region_util.py b/spopt/tests/test_region_util.py index 69af23fb..59761dda 100644 --- a/spopt/tests/test_region_util.py +++ b/spopt/tests/test_region_util.py @@ -3,7 +3,7 @@ import networkx import numpy import pulp -import unittest +import pytest import spopt.region.util as util @@ -15,9 +15,8 @@ MEXICO = geopandas.read_file(pth) -class TestRegionUtil(unittest.TestCase): - def setUp(self): - +class TestRegionUtil: + def setup_method(self): pass def test_array_from_dict_values(self): @@ -72,7 +71,7 @@ def test_scipy_sparse_matrix_from_dict(self): def test_dict_from_graph_attr(self): desired = {0: [1]} observed = util.dict_from_graph_attr(None, desired) - self.assertEqual(observed, desired) + assert observed == desired edges = [(0, 1), (1, 2), (0, 3), (1, 4), (2, 5), (3, 4), (4, 5)] graph = networkx.Graph(edges) @@ -81,25 +80,20 @@ def test_dict_from_graph_attr(self): desired = {key: [value] for key, value in data_dict.items()} observed = util.dict_from_graph_attr(graph, "test_data") - self.assertEqual(observed, desired) + assert observed == desired desired_array = dict() for node in data_dict: desired_array[node] = numpy.array(data_dict[node]) observed = util.dict_from_graph_attr(graph, "test_data", array_values=True) - self.assertEqual(observed, desired) + assert observed == desired def test_check_solver(self): util.check_solver("cbc") - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="The solver must be one of"): util.check_solver("bcb") def test_get_solver_instance(self): known_name = "PULP_CBC_CMD" observed_name = util.get_solver_instance("cbc").name - self.assertEqual(known_name, observed_name) - - -""" - -""" + assert known_name == observed_name diff --git a/spopt/tests/test_skater.py b/spopt/tests/test_skater.py index e3e47307..cf0460ec 100644 --- a/spopt/tests/test_skater.py +++ b/spopt/tests/test_skater.py @@ -1,8 +1,9 @@ import geopandas import libpysal import numpy +import pytest +from scipy.optimize import OptimizeWarning from sklearn.metrics import pairwise as skm -import unittest from spopt.region import Skater @@ -17,8 +18,8 @@ COLUMBUS = geopandas.read_file(pth) -class TestSkater(unittest.TestCase): - def setUp(self): +class TestSkater: + def setup_method(self): # Mexico self.mexico = MEXICO.copy() @@ -39,6 +40,7 @@ def setUp(self): self.columbus_labels += [3, 2, 3, 2, 2, 2, 2, 4, 3, 2, 4, 2, 4] self.columbus_labels += [5, 2, 4, 3, 3, 4, 5, 5, 5, 4, 3, 5, 5] + @pytest.mark.filterwarnings("ignore:The weights matrix is not fully") def test_skater_defaults(self): self.mexico["count"] = 1 numpy.random.seed(RANDOM_STATE) @@ -47,6 +49,7 @@ def test_skater_defaults(self): numpy.testing.assert_equal(model.labels_, self.default_mexico) + @pytest.mark.filterwarnings("ignore:The weights matrix is not fully") def test_skater_defaults_verbose(self): self.mexico["count"] = 1 sfkws = {"verbose": 1} @@ -61,6 +64,7 @@ def test_skater_defaults_verbose(self): numpy.testing.assert_equal(model.labels_, self.default_mexico) + @pytest.mark.filterwarnings("ignore:The weights matrix is not fully") def test_skater_defaults_super_verbose(self): self.mexico["count"] = 1 sfkws = {"verbose": 2} @@ -75,6 +79,8 @@ def test_skater_defaults_super_verbose(self): numpy.testing.assert_equal(model.labels_, self.default_mexico) + @pytest.mark.filterwarnings("ignore:MSF contains no valid moves after") + @pytest.mark.filterwarnings("ignore:The weights matrix is not fully") def test_skater_defaults_non_defaults(self): self.mexico["count"] = 1 args = self.mexico, self.w_mexico, self.default_attrs_mexico @@ -94,6 +100,8 @@ def test_skater_defaults_non_defaults(self): numpy.testing.assert_equal(model.labels_, self.non_default_mexico) + @pytest.mark.filterwarnings("ignore:MSF contains no valid moves after") + @pytest.mark.filterwarnings("ignore:The weights matrix is not fully") def test_skater_island_pass(self): self.columbus["count"] = 1 args = self.columbus, self.w_columbus, self.attrs_columbus @@ -103,12 +111,15 @@ def test_skater_island_pass(self): kws.update(dict(spanning_forest_kwds=sfkws)) numpy.random.seed(RANDOM_STATE) model = Skater(*args, **kws) - model.solve() + with pytest.warns(OptimizeWarning, match="MSF contains no valid moves after"): + model.solve() numpy.testing.assert_equal(model.labels_, self.columbus_labels) + @pytest.mark.filterwarnings("ignore:By default, the graph is disconnected!") + @pytest.mark.filterwarnings("ignore:The weights matrix is not fully") def test_skater_island_fail(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="Islands must be larger than the quorum"): self.columbus["count"] = 1 args = self.columbus, self.w_columbus, self.attrs_columbus n_clusters, floor, trace, islands = 10, 10, True, "increase" @@ -117,4 +128,7 @@ def test_skater_island_fail(self): kws.update(dict(spanning_forest_kwds=sfkws)) numpy.random.seed(RANDOM_STATE) model = Skater(*args, **kws) - model.solve() + with pytest.warns( + OptimizeWarning, match="By default, the graph is disconnected!" + ): + model.solve() diff --git a/spopt/tests/test_spenc.py b/spopt/tests/test_spenc.py index 88d59f75..392581b7 100644 --- a/spopt/tests/test_spenc.py +++ b/spopt/tests/test_spenc.py @@ -1,7 +1,7 @@ import geopandas import libpysal import numpy -import unittest +import pytest from spopt.region import Spenc @@ -13,8 +13,8 @@ MEXICO = geopandas.read_file(pth) -class TestSpenc(unittest.TestCase): - def setUp(self): +class TestSpenc: + def setup_method(self): # Mexico self.mexico = MEXICO.copy() diff --git a/spopt/tests/test_ward.py b/spopt/tests/test_ward.py index 4c4b1244..d49c22b7 100644 --- a/spopt/tests/test_ward.py +++ b/spopt/tests/test_ward.py @@ -1,7 +1,7 @@ import geopandas import libpysal import numpy -import unittest +import pytest from spopt.region import WardSpatial @@ -12,8 +12,8 @@ MEXICO = geopandas.read_file(pth) -class TestWard(unittest.TestCase): - def setUp(self): +class TestWard: + def setup_method(self): self.mexico = MEXICO.copy() self.mexico["count"] = 1