diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 2c21e89..e7f79df 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -45,7 +45,7 @@ jobs: python-version: "3.9" - name: check run: | - poetry run black --check mesh2vec tests scripts docs docs/examples + poetry run black --check mesh2vec tests scripts docs docs/examples scripts pylint: needs: prepare-linux @@ -59,7 +59,7 @@ jobs: python-version: "3.9" - name: check run: | - poetry run pylint mesh2vec tests docs/examples + poetry run pylint mesh2vec tests docs/examples scripts audit: needs: [prepare-linux, prepare-win] diff --git a/.gitignore b/.gitignore index 843e808..b7e0960 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ __pycache__ .DS_Store .venv/ .ipynb_checkpoints -data/data/temp_hg.joblib \ No newline at end of file +data/data/temp_hg.joblib +data/temp_hg.joblib diff --git a/mesh2vec/helpers.py b/mesh2vec/helpers.py index 78f1653..0522a51 100644 --- a/mesh2vec/helpers.py +++ b/mesh2vec/helpers.py @@ -2,14 +2,9 @@ from typing import OrderedDict, List, Dict from collections import deque from abc import ABC, abstractmethod -import time -from numba import jit import numpy as np from scipy.sparse import csr_array, coo_array, eye -import igraph as ip -import numba -import numba.typed # pylint: disable=invalid-name class AbstractAdjacencyStrategy(ABC): @@ -32,7 +27,8 @@ def calc_adjacencies( """ -def _hyper_edges_to_adj_np(hyper_edges_idx): +def _hyper_edges_to_adj_pairs_np(hyper_edges_idx): + """create adjacency list of connection pairs as numpy array (shape (?, 2)) from hyper edges""" adjacency_list = [] for vtxs in hyper_edges_idx.values(): for vtx_a in vtxs: @@ -43,6 +39,7 @@ def _hyper_edges_to_adj_np(hyper_edges_idx): def _hyper_edges_to_adj_list(vtx_count, hyper_edges_idx, include_self=False): + """create adjacency list as list of lists (jagged shape (vtx_count, ?)) from hyper edges""" adjacency_list = [[] for _ in range(vtx_count)] for vtxs in hyper_edges_idx.values(): for vtx_a in vtxs: @@ -54,67 +51,23 @@ def _hyper_edges_to_adj_list(vtx_count, hyper_edges_idx, include_self=False): return adjacency_list -class MatMulAdjacencySmart(AbstractAdjacencyStrategy): - """calc adjacencies using matrix multiplication with low memory footprint""" - - # pylint: disable=too-few-public-methods - def calc_adjacencies( - self, hyper_edges_idx: OrderedDict[str, List[int]], max_distance: int - ) -> Dict[int, List[List[int]]]: - """calc adjacencies using matrix multiplication with low memory footprint""" - adjacency_list_np = _hyper_edges_to_adj_np(hyper_edges_idx) - - # adjacency matrix - n_vtx = max(max(v) for v in hyper_edges_idx.values()) + 1 - data = np.array([1] * len(adjacency_list_np)) - adjacency_matrix = coo_array( - (data, (adjacency_list_np[:, 1], adjacency_list_np[:, 0])), shape=(n_vtx, n_vtx) - ).tocsr() - - # neighbors - adjacency_matrix_powers_exclusive = { - 1: adjacency_matrix - csr_array(eye(n_vtx, dtype=int)), - 0: csr_array(eye(n_vtx, dtype=int)), - } - - all_neighbors = ( - adjacency_matrix_powers_exclusive[1] + adjacency_matrix_powers_exclusive[0] - ) - - for i in range(2, max_distance + 1): - new = adjacency_matrix_powers_exclusive[i - 1] @ adjacency_matrix - new.data = np.array([1] * new.nnz) # make all entries 1 - - exclusive = new > all_neighbors - all_neighbors = all_neighbors + new - - adjacency_matrix_powers_exclusive[i] = exclusive - - neighborhoods = {} - for dist in range(1, max_distance + 1): - neighborhoods[dist] = [ - adjacency_matrix_powers_exclusive[dist][[i], :].indices for i in range(n_vtx) - ] - return neighborhoods - - class MatMulAdjacency(AbstractAdjacencyStrategy): # pylint: disable=too-few-public-methods - """calc adjacencies using matrix multiplication with low memory footprint""" + """calc adjacencies using matrix multiplication""" def calc_adjacencies( self, hyper_edges_idx: OrderedDict[str, List[int]], max_distance: int ) -> Dict[int, List[List[int]]]: """calc adjacencies using matrix multiplication""" - # vtx_connectivity: holds for each vtx all connected vtx - adjacency_list_np = _hyper_edges_to_adj_np(hyper_edges_idx) + adjacency_pair_list_np = _hyper_edges_to_adj_pairs_np(hyper_edges_idx) # adjacency matrix n_vtx = max(max(v) for v in hyper_edges_idx.values()) + 1 - data = np.array([1] * len(adjacency_list_np)) + data = np.array([1] * len(adjacency_pair_list_np)) adjacency_matrix = coo_array( - (data, (adjacency_list_np[:, 1], adjacency_list_np[:, 0])), shape=(n_vtx, n_vtx) + (data, (adjacency_pair_list_np[:, 1], adjacency_pair_list_np[:, 0])), + shape=(n_vtx, n_vtx), ).tocsr() # neighbors @@ -133,131 +86,12 @@ def calc_adjacencies( neighborhoods = {} for dist in range(1, max_distance + 1): neighborhoods[dist] = [ - adjacency_matrix_powers_exclusive[dist][[i], :].indices for i in range(n_vtx) - ] - return neighborhoods - - -class BFSAdjacency(AbstractAdjacencyStrategy): - """calc adjacencies using BFS from igraph""" - - # pylint: disable=too-few-public-methods - def calc_adjacencies( - self, hyper_edges_idx: OrderedDict[str, List[int]], max_distance: int - ) -> Dict[int, List[List[int]]]: - """calc adjacencies using BFS from igraph""" - adjacency_list_np: np.ndarray = _hyper_edges_to_adj_np(hyper_edges_idx) - - graph = ip.Graph(adjacency_list_np) - - #################################################### - # General idea: BFS for each vertex for each depth # - #################################################### - neighborhoods = {} - n_vtx = max(max(v) for v in hyper_edges_idx.values()) + 1 - - for depth in range(1, max_distance + 1): - - exclusive_neighborhood = graph.neighborhood(vertices=None, mindist=depth, order=depth) - neighborhoods[depth] = [ - list(set(exclusive_neighborhood[i]) - {i}) for i in range(n_vtx) + adjacency_matrix_powers_exclusive[dist][[i], :].indices.tolist() + for i in range(n_vtx) ] - return neighborhoods -class BFSNumba(AbstractAdjacencyStrategy): - """calc adjacencies using BFS in numba""" - - # pylint: disable=too-few-public-methods - def calc_adjacencies( - self, hyper_edges_idx: OrderedDict[str, List[int]], max_distance: int - ) -> Dict[int, List[List[int]]]: - """calc adjacencies using BFS in numba""" - vtx_count = max(vtx_a for vtxs in hyper_edges_idx.values() for vtx_a in vtxs) + 1 - - adjacency_list = _hyper_edges_to_adj_list(vtx_count, hyper_edges_idx) - - print("building adjacency_list") - adjacency_list_numba = numba.typed.List() - for _ in range(vtx_count): - new = numba.typed.List() - new.append(1) - new.pop() - adjacency_list_numba.append(new) - - for i, neighbors in enumerate(adjacency_list): - for neighbor in neighbors: - adjacency_list_numba[i].append(neighbor) - - # neighbors_at_depth = List(List( List([-1]) for _ in range(max_distance + 1)) - # for vertex in range(vtx_count)) - print("building neighbors_at_depth") - neighbors_at_depth = numba.typed.List() - for i in range(vtx_count): - new = numba.typed.List() - for _ in range(max_distance + 1): - new_new = numba.typed.List() - new_new.append(1) - new_new.pop() - new.append(new_new) - neighbors_at_depth.append(new) - - print("start numba") - start = time.time() - - neighbors_at_depth = _numba_compute_bfs( - adjacency_list_numba, max_distance, vtx_count, neighbors_at_depth - ) - print(f"end numba after second {time.time() - start}") - - connectivity = {} - for d in range(1, max_distance + 1): - connectivity[d] = [list(neighbors_at_depth[i][d]) for i in range(vtx_count)] - return connectivity - - -@jit(nopython=True, parallel=False, fastmath=True) -def _numba_compute_bfs(adj_list, max_depth, vtx_count, neighbors_at_depth): - """ - compute bfs in numba - Args: - adj_list: adjacency list as numba typed list of lists - max_depth: maximum depth to search - vtx_count: number of vertices - neighbors_at_depth: empty list of lists of lists (vtx, depth, neighbors) - - """ - for start_vertex in range(vtx_count): - # Initialize the queue for BFS: (vertex, depth); numba does not support dequeue - queue = [(start_vertex, 0)] - - # Use a set to keep track of visited vertices - # visited = {start_vertex} - visited_array = np.array([False] * vtx_count) - - # queue not empty - while queue: - # depth is saved in queue -> no tracking in loop - vertex, depth = queue.pop(0) - - # do not track vertex identity in neighbors; new vertices will not be duplicates due - # to visited set tracking - if vertex != start_vertex: - # add found neighbor to start_vertex's neighbors - neighbors_at_depth[start_vertex][depth].append(vertex) - - # Check if we've reached the maximum depth; if not look for next level neighbors - # of found neighbor - if depth < max_depth: - neighbors = [x for x in adj_list[vertex] if not visited_array[x]] - for neighbor in neighbors: - queue.append((neighbor, depth + 1)) - visited_array[neighbor] = True - - return neighbors_at_depth - - class PurePythonBFS(AbstractAdjacencyStrategy): # pylint: disable=too-few-public-methods """calc adjacencies using BFS in pure python""" @@ -266,14 +100,10 @@ def calc_adjacencies( self, hyper_edges_idx: OrderedDict[str, List[int]], max_distance: int ) -> Dict[int, List[List[int]]]: """calc adjacencies using BFS in pure python""" - # values in .values(): indices list -> maximum index + 1 is length of unique vertices vtx_count = max(vtx_a for vtxs in hyper_edges_idx.values() for vtx_a in vtxs) + 1 adjacency_list = _hyper_edges_to_adj_list(vtx_count, hyper_edges_idx) - # each vertex has a list of lists where the index of the list corresponds - # to the depth of its contained neighbors - # e.g.: neighbor 300 at depth 2, neighbor 400 at depth 1 with - # max_distance 3 -> [[], [400], [300], []] + # neighbors_at_depth: dict of lists of lists (distance, vertex, neighbors) neighbors_at_depth = { dist: [[] for vertex in range(vtx_count)] for dist in range(max_distance + 1) } @@ -302,10 +132,8 @@ def calc_adjacencies( neighbors = set(adjacency_list[vertex]) - visited queue.extend((neighbor, depth + 1) for neighbor in neighbors) visited.update(neighbors) - connectivity = {} - for d in range(1, max_distance + 1): - connectivity[d] = [list(neighbors_at_depth[d][i]) for i in range(vtx_count)] - return connectivity + + return neighbors_at_depth class PurePythonDFS(AbstractAdjacencyStrategy): @@ -317,17 +145,21 @@ def calc_adjacencies( ) -> Dict[int, List[List[int]]]: """calc adjacencies using DFS in pure python""" # pylint: disable=too-many-locals - # values in .values(): indices list -> maximum index + 1 is length of unique vertices vtx_count = max(vtx_a for vtxs in hyper_edges_idx.values() for vtx_a in vtxs) + 1 adjacency_list = _hyper_edges_to_adj_list(vtx_count, hyper_edges_idx) + # neighbors_at_depth: dict of lists of lists (distance, vertex, neighbors) neighbors_at_depth = { dist: [[] for vertex in range(vtx_count)] for dist in range(max_distance + 1) } + + # neighbors_at_depth of dist-1 will be used to find neighbors of dist + # here its initialized with dist=1 and dist=0 for vertex in range(vtx_count): neighbors_at_depth[1][vertex] = adjacency_list[vertex].copy() neighbors_at_depth[0][vertex] = [vertex] + # neighbors: list of sets of neighbors for each vertex all distances up to current neighbors = [set(adjacency_list[vertex] + [vertex]) for vertex in range(vtx_count)] for dist in range(2, max_distance + 1): @@ -339,91 +171,3 @@ def calc_adjacencies( neighbors[v].update(new) neighbors_at_depth[dist][v] = list(new) return neighbors_at_depth - - -class DFSNumba(AbstractAdjacencyStrategy): - # pylint: disable=too-few-public-methods,too-many-locals - """calc adjacencies using DFS in numba""" - - def calc_adjacencies( - self, hyper_edges_idx: OrderedDict[str, List[int]], max_distance: int - ) -> Dict[int, List[List[int]]]: - """calc adjacencies using DFS in numba""" - # values in .values(): indices list -> maximum index + 1 is length of unique vertices - vtx_count = max(vtx_a for vtxs in hyper_edges_idx.values() for vtx_a in vtxs) + 1 - adjacency_list = _hyper_edges_to_adj_list(vtx_count, hyper_edges_idx) - - print("building adjacency_list") - adjacency_list_numba = numba.typed.List() - for _ in range(vtx_count): - new = numba.typed.List() - new.append(1) - new.pop() - adjacency_list_numba.append(new) - - for i, neighbors in enumerate(adjacency_list): - for neighbor in neighbors: - adjacency_list_numba[i].append(neighbor) - - print("building neighbors_at_depth") - neighbors_at_depth = numba.typed.List() - for i in range(vtx_count): - new = numba.typed.List() - - vtx_self = numba.typed.List() - vtx_self.append(i) - new.append(vtx_self) # 0 - - adjs = numba.typed.List() - for nn in adjacency_list_numba[i]: - adjs.append(nn) - - new.append(adjs) # 1 - - for _ in range(max_distance + 1): - new_new = numba.typed.List() - new_new.append(1) - new_new.pop() - new.append(new_new) - neighbors_at_depth.append(new) - - print("start numba") - start = time.time() - - neighbors_at_depth = _numba_compute_dfs( - adjacency_list_numba, max_distance, vtx_count, neighbors_at_depth - ) - print(f"end numba after second {time.time() - start}") - - connectivity = {} - for d in range(1, max_distance + 1): - connectivity[d] = [list(neighbors_at_depth[i][d]) for i in range(vtx_count)] - return connectivity - - -@jit(nopython=True, parallel=False, fastmath=True) -def _numba_compute_dfs(adj_list, max_distance, vtx_count, neighbors_at_depth): - """ - compute dfs in numba - Args: - adj_list: adjacency list as numba typed list of lists - max_distance: maximum depth to search - vtx_count: number of vertices - neighbors_at_depth: empty list of lists of lists (vtx, depth, neighbors) - - """ - neighbors = numba.typed.List() - for vertex in range(vtx_count): - neighbors_current = set(adj_list[vertex]) - neighbors_current.add(vertex) - neighbors.append(neighbors_current) - - for dist in range(2, max_distance + 1): - for v in range(vtx_count): - hood = set() - for nn in adj_list[v]: - hood.update(neighbors_at_depth[nn][dist - 1]) - hood = hood - neighbors[v] - neighbors[v].update(hood) - neighbors_at_depth[v][dist] = numba.typed.List(hood) - return neighbors_at_depth diff --git a/mesh2vec/mesh2vec_base.py b/mesh2vec/mesh2vec_base.py index 4bbda33..6b0eeb2 100644 --- a/mesh2vec/mesh2vec_base.py +++ b/mesh2vec/mesh2vec_base.py @@ -11,7 +11,7 @@ # noinspection PyProtectedMember from pandas.api.types import is_string_dtype -from mesh2vec.helpers import MatMulAdjacency, AbstractAdjacencyStrategy +from mesh2vec.helpers import MatMulAdjacency, PurePythonBFS, PurePythonDFS from mesh2vec.mesh2vec_exceptions import ( check_distance_init_arg, check_distance_arg, @@ -20,6 +20,7 @@ check_vtx_arg, check_feature_available, check_vtx_ids_column, + check_adjacency_calc_strategy, ) @@ -36,7 +37,7 @@ def __init__( distance: int, hyper_edges: Dict[str, List[str]], vtx_ids: Optional[List[str]] = None, - calc_strategy: AbstractAdjacencyStrategy = None, + calc_strategy: str = "dfs", ): r""" Create neighborhood sets on a hypergraph. @@ -52,6 +53,10 @@ def __init__( (features, aggregated feature) calc_strategy: choose the algorithm to calculate adjacencies + * "dfs": depth first search (defaultl fast) + * "bfs": breadth first search (low memory consumption) + * "matmul": matrix multiplication (deprecated, for compatibility only) + Example: >>> from mesh2vec.mesh2vec_base import Mesh2VecBase >>> edges = {"first": ["a", "b", "c"], "second": ["x", "y"]} @@ -62,6 +67,7 @@ def __init__( """ check_distance_init_arg(distance) check_hyper_edges(hyper_edges) + check_adjacency_calc_strategy(calc_strategy) if vtx_ids is None: vtx_ids = np.unique( @@ -87,18 +93,24 @@ def __init__( ) for h_edge_id, vtx_ids in self._hyper_edges.items() ) - if calc_strategy is None: + if calc_strategy == "dfs": + calc_strategy = PurePythonDFS() + elif calc_strategy == "bfs": + calc_strategy = PurePythonBFS() + elif calc_strategy == "matmul": calc_strategy = MatMulAdjacency() - elif isinstance(calc_strategy, type): - calc_strategy = calc_strategy() + else: + raise ValueError(f"Unknown adjacency_calc_strategy: {calc_strategy}") self._neighborhoods = calc_strategy.calc_adjacencies(hyper_edges_idx, distance) def save(self, path: Path): """ Save the Mesh2Vec object to a file with joblib + Args: path: path to the file + Example: >>> from pathlib import Path >>> from mesh2vec.mesh2vec_base import Mesh2VecBase @@ -111,8 +123,10 @@ def save(self, path: Path): def load(path: Path): """ Load the Mesh2Vec object from a file with joblib + Args: path: path to the file + Example: >>> from pathlib import Path >>> from mesh2vec.mesh2vec_base import Mesh2VecBase @@ -122,7 +136,7 @@ def load(path: Path): return joblib.load(path) @staticmethod - def from_file(hg_file: Path, distance: int) -> "Mesh2VecBase": + def from_file(hg_file: Path, distance: int, calc_strategy="dfs") -> "Mesh2VecBase": # pylint: disable=line-too-long r""" Read a hypergraph (hg) from a text file. @@ -133,6 +147,11 @@ def from_file(hg_file: Path, distance: int) -> "Mesh2VecBase": * a CSV files of pairs of alphanumerical vertex identifiers defining an undirected graph. Multiple edges are ignored. The initial hypergraph is given by the cliques of the graph. Since the CLIQUE problem is NP-complete, use this for small graphs only. * a hypergraph file (text). Each line of the file contains an alphanumerical edge identifier, followed by a list of vertex identifiers the edge is containing, in the form 'DGEID: VTXID1,VTXID2,...' distance: the maximum distance for neighborhood generation and feature aggregation + calc_strategy: choose the algorithm to calculate adjacencies + + * "dfs": depth first search (defaultl fast) + * "bfs": breadth first search (low memory consumption) + * "matmul": matrix multiplication (deprecated, for compatibility only) Example: >>> from pathlib import Path @@ -165,6 +184,7 @@ def from_file(hg_file: Path, distance: int) -> "Mesh2VecBase": f"clique_{i}": [vtx_idx_to_ids[v] for v in clique] for i, clique in enumerate(cliques) }, + calc_strategy=calc_strategy, ) # txt file @@ -335,72 +355,6 @@ def aggregate( return list(agg_values_to_add.keys())[0] return list(agg_values_to_add.keys()) - # # pylint: disable=too-many-arguments - # def aggregate_many( - # self, - # feature_list: List[str], - # dist_list: List[int], - # aggr_list: Callable, - # aggr_name_list: str = None, - # agg_add_ref: bool = False, - # default_value: float = 0.0, - # ) -> Union[str, List[str]]: - # # pylint: disable=line-too-long - - # for feature in feature_list: - # check_feature_available(feature, self) - - # for dist_to_check in dist_list: - # check_distance_arg(dist_to_check, self) - - # if aggr_name_list is None: - # aggr_name_list = [aggr.__name__ for aggr in aggr_list] - - # agg_values_to_add = OrderedDict() - # for distance in dist_list: - # values = self._collect_feature_list_values(feature_list, distance, default_value) - # if agg_add_ref: - # ref_values = self._collect_feature_list_values(feature_list, 0, default_value) - # agg_values = np.nan_to_num( - # [aggr(value, ref_value) for value, ref_value in zip(values, ref_values)], - # nan=default_value, - # ) - # else: - # agg_values = values - # # agg_values = np.nan_to_num([np.mean(value) for value in values], nan=default_value) - - # feature_name = f"{feature}-all-{distance}" - # agg_values_to_add["all"] = agg_values - - # # self._aggregated_features = self._aggregated_features.assign( - # # **agg_values_to_add, - # # ) - # if len(agg_values_to_add) == 1: - # return list(agg_values_to_add.keys())[0] - # return list(agg_values_to_add.keys()) - - # def _collect_feature_list_values( - # self, - # feature_list: str, - # dist: int, - # default_value: Optional[Union[float, int, str]], - # ) -> List[List[Union[float, int, str]]]: - # """helper method to collect data from all hyper nodes during aggregation""" - # check_distance_arg(dist, self) - - # if default_value is None: - # default_value = "NONE" if is_string_dtype(self._features[feature]) else np.nan - # features = self._features[feature_list].fillna(default_value).to_numpy() - - # if dist == 0: - # return features - - # values = [ - # [np.mean(feature[neighborhood]) for neighborhood in self._neighborhoods[dist]] - # for feature in features.T - # ] - # return values - def _collect_feature_values( self, feature_name: str, diff --git a/mesh2vec/mesh2vec_cae.py b/mesh2vec/mesh2vec_cae.py index e41424b..23ccc6a 100644 --- a/mesh2vec/mesh2vec_cae.py +++ b/mesh2vec/mesh2vec_cae.py @@ -14,7 +14,6 @@ import plotly.graph_objects as go from mesh2vec import mesh_features -from mesh2vec.helpers import AbstractAdjacencyStrategy from mesh2vec.mesh_features import CaeShellMesh, is_tri, num_border, midpoint from mesh2vec.mesh2vec_base import Mesh2VecBase from mesh2vec.mesh2vec_exceptions import ( @@ -39,7 +38,7 @@ def __init__( distance: int, mesh: CaeShellMesh, mesh_info: pd.DataFrame, - calc_strategy: AbstractAdjacencyStrategy = None, + calc_strategy: str = "dfs", ) -> None: # pylint: disable=line-too-long """ @@ -50,7 +49,11 @@ def __init__( mesh: points, point_ids/uids, connectivity and element_ids/uids mesh_info: additional info about the elements in mesh (same order is required) columns "part_name", "part_id", "file_path", "element_id" are required - calc_strategy: choose the algorithm to calculate adjacencies + calc_strategy: choose the algorithm to calculate adjacencies + + * "dfs": depth first search (defaultl fast) + * "bfs": breadth first search (low memory consumption) + * "matmul": matrix multiplication (deprecated, for compatibility only) Example: >>> import numpy as np @@ -137,7 +140,7 @@ def from_ansa_shell( ansa_executable: Optional[Path] = None, ansa_script: Optional[Path] = None, verbose: bool = False, - calc_strategy: AbstractAdjacencyStrategy = None, + calc_strategy: str = "dfs", ) -> "Mesh2VecCae": """ Read the given ANSA file and use the shell elements corresponding with ``partid`` to @@ -150,11 +153,24 @@ def from_ansa_shell( to work with shell meshes for LSDYNA. Each element gets a unique internal ID consisting of its element ID (which may be not unique), and a unique hash value. - Path to ANSA executable can also be provided in environment var: ANSA_EXECUTABLE - You can use a customized script to include more features ansa_script - (see :ref:`Customize Ansa script`) + Args: + distance: the maximum distance for neighborhood generation and feature aggregation + ansafile: path to ansa file + partid: part id to use for hypergraph generation + json_mesh_file: path to json mesh file, + if exists, it will be loaded instead of the ansafile else it will be generated. + ansa_executable: path to ansa executable + Path to ANSA executable can also be provided in environment var: ANSA_EXECUTABLE + ansa_script: path to ansa script + You can use a customized script to include more features ansa_script + (see :ref:`Customize Ansa script`) + verbose: print additional information + calc_strategy: choose the algorithm to calculate adjacencies + * "dfs": depth first search (defaultl fast) + * "bfs": breadth first search (low memory consumption) + * "matmul": matrix multiplication (deprecated, for compatibility only) Example: >>> from pathlib import Path @@ -184,12 +200,24 @@ def from_ansa_shell( return Mesh2VecCae(distance, mesh, element_info, calc_strategy) @staticmethod - def from_d3plot_shell(distance: int, d3plot: Path, partid: str = None) -> "Mesh2VecCae": + def from_d3plot_shell( + distance: int, d3plot: Path, partid: str = None, calc_strategy="dfs" + ) -> "Mesh2VecCae": """ Read the given d3plot file and use the shell elements corresponding with ``partid`` to generate a hypergraph, using CAE nodes as hyperedges, and adjacent elements as hypervertices. + Args: + distance: the maximum distance for neighborhood generation and feature aggregation + d3plot: path to d3plot file + partid: part id to use + calc_strategy: choose the algorithm to calculate adjacencies + + * "dfs": depth first search (defaultl fast) + * "bfs": breadth first search (low memory consumption) + * "matmul": matrix multiplication (deprecated, for compatibility only) + Example: >>> from pathlib import Path >>> from mesh2vec.mesh2vec_cae import Mesh2VecCae @@ -223,16 +251,23 @@ def from_d3plot_shell(distance: int, d3plot: Path, partid: str = None) -> "Mesh2 ] element_info["file_path"] = str(d3plot) - return Mesh2VecCae(distance, mesh, element_info) + return Mesh2VecCae(distance, mesh, element_info, calc_strategy=calc_strategy) @staticmethod - def from_keyfile_shell(distance: int, keyfile: Path) -> "Mesh2VecCae": + def from_keyfile_shell(distance: int, keyfile: Path, calc_strategy="bfs") -> "Mesh2VecCae": """ Read the given keyfile and use the shell elements to generate a hypergraph, using mesh nodes as hyperedges, and adjacent elements as hypervertices. + Args: distance: the maximum distance for neighborhood generation and feature aggregation keyfile: path to keyfile + calc_strategy: choose the algorithm to calculate adjacencies + + * "dfs": depth first search (defaultl fast) + * "bfs": breadth first search (low memory consumption) + * "matmul": matrix multiplication (deprecated, for compatibility only) + Example: >>> from pathlib import Path >>> from mesh2vec.mesh2vec_cae import Mesh2VecCae @@ -243,7 +278,7 @@ def from_keyfile_shell(distance: int, keyfile: Path) -> "Mesh2VecCae": mesh = CaeShellMesh.from_keyfile(keyfile) element_info = pd.DataFrame({"element_id": mesh.element_ids}) element_info["file_path"] = str(keyfile) - return Mesh2VecCae(distance, mesh, element_info) + return Mesh2VecCae(distance, mesh, element_info, calc_strategy=calc_strategy) def get_elements_info(self) -> pd.DataFrame: """ @@ -589,7 +624,6 @@ def aggregate_angle_diff( Aggregate a new feature calculated from the angle difference of each element's normal vector to the reference vector given by the center element (in radian). - Args: dist: either diff --git a/mesh2vec/mesh2vec_exceptions.py b/mesh2vec/mesh2vec_exceptions.py index 579ab8c..0e2cdbb 100644 --- a/mesh2vec/mesh2vec_exceptions.py +++ b/mesh2vec/mesh2vec_exceptions.py @@ -8,10 +8,18 @@ class InvalidDistanceArgumentException(Exception): """Exception raised when an invalid distance was provided""" +def check_adjacency_calc_strategy(adjacency_calc_strategy: str) -> None: + """check argument to rise exception or log warning if needed.""" + if adjacency_calc_strategy not in ["matmul", "dfs", "bfs"]: + raise InvalidDistanceArgumentException( + "adjacency_calc_strategy must be one of ['matmul', 'dfs', 'bfs']" + ) + + def check_distance_init_arg(distance: int) -> None: """check argument to rise exception or log warning if needed.""" - if distance < 0: - raise InvalidDistanceArgumentException("distance must be > 0") + if distance < 1: + raise InvalidDistanceArgumentException("distance must be >= 1") if distance > 30: logger.warning("Calculating neighborhoods for distances > 10 can take much time") diff --git a/mesh2vec/mesh_features.py b/mesh2vec/mesh_features.py index c83ebe0..ee0e301 100644 --- a/mesh2vec/mesh_features.py +++ b/mesh2vec/mesh_features.py @@ -238,7 +238,8 @@ def from_ansa_json(elements: List[Any], nodes: List[Any]) -> "CaeShellMesh": @staticmethod def from_keyfile(keyfile: str) -> "CaeShellMesh": - """create CaeShellMesh from keyfile + """ + create CaeShellMesh from keyfile Example: >>> from mesh2vec.mesh_features import CaeShellMesh >>> mesh = CaeShellMesh.from_keyfile("data/hat/Hatprofile.k") diff --git a/poetry.lock b/poetry.lock index b6b1856..b2a7054 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -15,6 +16,7 @@ files = [ name = "anyio" version = "4.1.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -36,6 +38,7 @@ trio = ["trio (>=0.23)"] name = "appnope" version = "0.1.3" description = "Disable App Nap on macOS >= 10.9" +category = "main" optional = false python-versions = "*" files = [ @@ -47,6 +50,7 @@ files = [ name = "argon2-cffi" version = "23.1.0" description = "Argon2 for Python" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -67,6 +71,7 @@ typing = ["mypy"] name = "argon2-cffi-bindings" version = "21.2.0" description = "Low-level CFFI bindings for Argon2" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -104,6 +109,7 @@ tests = ["pytest"] name = "arrow" version = "1.3.0" description = "Better dates & times for Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -117,12 +123,13 @@ types-python-dateutil = ">=2.8.10" [package.extras] doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] -test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] +test = ["dateparser (>=1.0.0,<2.0.0)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (>=3.0.0,<4.0.0)"] [[package]] name = "astroid" version = "2.15.8" description = "An abstract syntax tree for Python with inference support." +category = "dev" optional = false python-versions = ">=3.7.2" files = [ @@ -142,6 +149,7 @@ wrapt = [ name = "asttokens" version = "2.4.1" description = "Annotate AST trees with source code positions" +category = "main" optional = false python-versions = "*" files = [ @@ -160,6 +168,7 @@ test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -178,6 +187,7 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "babel" version = "2.13.1" description = "Internationalization utilities" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -195,6 +205,7 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] name = "backcall" version = "0.2.0" description = "Specifications for callback functions passed in to an API" +category = "main" optional = false python-versions = "*" files = [ @@ -206,6 +217,7 @@ files = [ name = "beautifulsoup4" version = "4.12.2" description = "Screen-scraping library" +category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -224,6 +236,7 @@ lxml = ["lxml"] name = "black" version = "22.12.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -259,6 +272,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "bleach" version = "6.1.0" description = "An easy safelist-based HTML-sanitizing tool." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -277,6 +291,7 @@ css = ["tinycss2 (>=1.1.0,<1.3)"] name = "blinker" version = "1.7.0" description = "Fast, simple object-to-object and broadcast signaling" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -288,6 +303,7 @@ files = [ name = "boolean-py" version = "4.0" description = "Define boolean algebras, create and parse boolean expressions and create custom boolean DSL." +category = "dev" optional = false python-versions = "*" files = [ @@ -299,6 +315,7 @@ files = [ name = "cachecontrol" version = "0.13.1" description = "httplib2 caching for requests" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -320,6 +337,7 @@ redis = ["redis (>=2.10.5)"] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -331,6 +349,7 @@ files = [ name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -395,6 +414,7 @@ pycparser = "*" name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -494,6 +514,7 @@ files = [ name = "check-wheel-contents" version = "0.3.4" description = "Check your wheels have the right contents" +category = "dev" optional = false python-versions = "~=3.6" files = [ @@ -513,6 +534,7 @@ wheel-filename = ">=1.1,<2.0" name = "cleo" version = "2.1.0" description = "Cleo allows you to create beautiful and testable command-line interfaces." +category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -528,6 +550,7 @@ rapidfuzz = ">=3.0.0,<4.0.0" name = "click" version = "8.1.7" description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -542,6 +565,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -553,6 +577,7 @@ files = [ name = "comm" version = "0.2.0" description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -570,6 +595,7 @@ test = ["pytest"] name = "contourpy" version = "1.1.1" description = "Python library for calculating contours of 2D quadrilateral grids" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -641,6 +667,7 @@ test-no-images = ["pytest", "pytest-cov", "wurlitzer"] name = "crashtest" version = "0.4.1" description = "Manage Python errors with ease" +category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -652,6 +679,7 @@ files = [ name = "cycler" version = "0.12.1" description = "Composable style cycles" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -667,6 +695,7 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] name = "cyclonedx-python-lib" version = "4.2.3" description = "A library for producing CycloneDX SBOM (Software Bill of Materials) files." +category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -684,6 +713,7 @@ sortedcontainers = ">=2.4.0,<3.0.0" name = "debugpy" version = "1.8.0" description = "An implementation of the Debug Adapter Protocol for Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -711,6 +741,7 @@ files = [ name = "decorator" version = "5.1.1" description = "Decorators for Humans" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -722,6 +753,7 @@ files = [ name = "defusedxml" version = "0.7.1" description = "XML bomb protection for Python stdlib modules" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -733,6 +765,7 @@ files = [ name = "dill" version = "0.3.7" description = "serialize all of Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -747,6 +780,7 @@ graph = ["objgraph (>=1.7.2)"] name = "docutils" version = "0.18.1" description = "Docutils -- Python Documentation Utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -758,6 +792,7 @@ files = [ name = "entrypoints" version = "0.4" description = "Discover and load entry points from installed packages." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -769,6 +804,7 @@ files = [ name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -783,6 +819,7 @@ test = ["pytest (>=6)"] name = "executing" version = "2.0.1" description = "Get the currently executing AST node of a frame, and other information" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -797,6 +834,7 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth name = "fastjsonschema" version = "2.19.0" description = "Fastest Python implementation of JSON schema" +category = "main" optional = false python-versions = "*" files = [ @@ -811,6 +849,7 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc name = "filelock" version = "3.13.1" description = "A platform independent file lock." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -827,6 +866,7 @@ typing = ["typing-extensions (>=4.8)"] name = "flask" version = "2.3.3" description = "A simple framework for building complex web applications." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -850,6 +890,7 @@ dotenv = ["python-dotenv"] name = "fonttools" version = "4.45.1" description = "Tools to manipulate font files" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -915,6 +956,7 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] name = "fqdn" version = "1.5.1" description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" +category = "main" optional = false python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" files = [ @@ -926,6 +968,7 @@ files = [ name = "grpcio" version = "1.59.3" description = "HTTP/2-based RPC framework" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -992,6 +1035,7 @@ protobuf = ["grpcio-tools (>=1.59.3)"] name = "h5py" version = "3.10.0" description = "Read and write HDF5 files from Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1029,6 +1073,7 @@ numpy = ">=1.17.3" name = "html5lib" version = "1.1" description = "HTML parser based on the WHATWG HTML specification" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1050,6 +1095,7 @@ lxml = ["lxml"] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1061,6 +1107,7 @@ files = [ name = "igraph" version = "0.11.3" description = "High performance graph data structures and algorithms" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1118,6 +1165,7 @@ test-musl = ["cairocffi (>=1.2.0)", "networkx (>=2.5)", "pytest (>=7.0.1)", "pyt name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1129,6 +1177,7 @@ files = [ name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1148,6 +1197,7 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "6.1.1" description = "Read resources from Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1166,6 +1216,7 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1177,6 +1228,7 @@ files = [ name = "ipykernel" version = "6.27.1" description = "IPython Kernel for Jupyter" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1190,7 +1242,7 @@ comm = ">=0.1.1" debugpy = ">=1.6.5" ipython = ">=7.23.1" jupyter-client = ">=6.1.12" -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" matplotlib-inline = ">=0.1" nest-asyncio = "*" packaging = "*" @@ -1210,6 +1262,7 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio" name = "ipython" version = "8.12.3" description = "IPython: Productive Interactive Computing" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1249,6 +1302,7 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pa name = "ipython-genutils" version = "0.2.0" description = "Vestigial utilities from IPython" +category = "main" optional = false python-versions = "*" files = [ @@ -1260,6 +1314,7 @@ files = [ name = "isoduration" version = "20.11.0" description = "Operations with ISO 8601 durations" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1274,6 +1329,7 @@ arrow = ">=0.15.0" name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." +category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -1291,6 +1347,7 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1302,6 +1359,7 @@ files = [ name = "jedi" version = "0.19.1" description = "An autocompletion tool for Python that can be used for text editors." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1321,6 +1379,7 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1338,6 +1397,7 @@ i18n = ["Babel (>=2.7)"] name = "joblib" version = "1.3.2" description = "Lightweight pipelining with Python functions" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1349,6 +1409,7 @@ files = [ name = "jsonpointer" version = "2.4" description = "Identify specific nodes in a JSON document (RFC 6901)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" files = [ @@ -1360,6 +1421,7 @@ files = [ name = "jsonschema" version = "4.20.0" description = "An implementation of JSON Schema validation for Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1391,6 +1453,7 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- name = "jsonschema-specifications" version = "2023.11.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1406,6 +1469,7 @@ referencing = ">=0.31.0" name = "jupyter-client" version = "7.4.9" description = "Jupyter protocol implementation and client libraries" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1430,6 +1494,7 @@ test = ["codecov", "coverage", "ipykernel (>=6.12)", "ipython", "mypy", "pre-com name = "jupyter-core" version = "5.5.0" description = "Jupyter core package. A base package on which Jupyter projects rely." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1450,6 +1515,7 @@ test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] name = "jupyter-events" version = "0.9.0" description = "Jupyter Event System library" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1473,13 +1539,14 @@ test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "p [[package]] name = "jupyter-server" -version = "2.11.1" +version = "2.12.1" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_server-2.11.1-py3-none-any.whl", hash = "sha256:4b3a16e3ed16fd202588890f10b8ca589bd3e29405d128beb95935f059441373"}, - {file = "jupyter_server-2.11.1.tar.gz", hash = "sha256:fe80bab96493acf5f7d6cd9a1575af8fbd253dc2591aa4d015131a1e03b5799a"}, + {file = "jupyter_server-2.12.1-py3-none-any.whl", hash = "sha256:fd030dd7be1ca572e4598203f718df6630c12bd28a599d7f1791c4d7938e1010"}, + {file = "jupyter_server-2.12.1.tar.gz", hash = "sha256:dc77b7dcc5fc0547acba2b2844f01798008667201eea27c6319ff9257d700a6d"}, ] [package.dependencies] @@ -1487,7 +1554,7 @@ anyio = ">=3.1.0" argon2-cffi = "*" jinja2 = "*" jupyter-client = ">=7.4.4" -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" jupyter-events = ">=0.9.0" jupyter-server-terminals = "*" nbconvert = ">=6.4.4" @@ -1511,6 +1578,7 @@ test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-sc name = "jupyter-server-terminals" version = "0.4.4" description = "A Jupyter Server Extension Providing Terminals." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1530,6 +1598,7 @@ test = ["coverage", "jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-cov", name = "jupyterlab-pygments" version = "0.3.0" description = "Pygments theme using JupyterLab CSS variables" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1541,6 +1610,7 @@ files = [ name = "kiwisolver" version = "1.4.5" description = "A fast implementation of the Cassowary constraint solver" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1654,6 +1724,7 @@ files = [ name = "lasso-python" version = "1.5.2.post1" description = "A next-generation CAE Python Library." +category = "main" optional = false python-versions = "*" files = [ @@ -1679,6 +1750,7 @@ sklearn = "*" name = "lazy-object-proxy" version = "1.9.0" description = "A fast and thorough lazy object proxy." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1724,6 +1796,7 @@ files = [ name = "license-expression" version = "30.1.1" description = "license-expression is a comprehensive utility library to parse, compare, simplify and normalize license expressions (such as SPDX license expressions) using boolean logic." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1742,6 +1815,7 @@ testing = ["black", "isort", "pytest (>=6,!=7.0.0)", "pytest-xdist (>=2)", "twin name = "livereload" version = "2.6.3" description = "Python LiveReload is an awesome tool for web developers" +category = "dev" optional = false python-versions = "*" files = [ @@ -1757,6 +1831,7 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} name = "llvmlite" version = "0.41.1" description = "lightweight wrapper around basic LLVM functionality" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1790,6 +1865,7 @@ files = [ name = "loguru" version = "0.6.0" description = "Python logging made (stupidly) simple" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1808,6 +1884,7 @@ dev = ["Sphinx (>=4.1.1)", "black (>=19.10b0)", "colorama (>=0.3.4)", "docutils name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1832,6 +1909,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1901,6 +1979,7 @@ files = [ name = "matplotlib" version = "3.7.4" description = "Python plotting package" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1969,6 +2048,7 @@ python-dateutil = ">=2.7" name = "matplotlib-inline" version = "0.1.6" description = "Inline Matplotlib backend for Jupyter" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1983,6 +2063,7 @@ traitlets = "*" name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1994,6 +2075,7 @@ files = [ name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2005,6 +2087,7 @@ files = [ name = "mistune" version = "3.0.2" description = "A sane and fast Markdown parser with useful plugins and renderers" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2016,6 +2099,7 @@ files = [ name = "msgpack" version = "1.0.7" description = "MessagePack serializer" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2081,6 +2165,7 @@ files = [ name = "mypy" version = "0.971" description = "Optional static typing for Python" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2123,6 +2208,7 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2134,6 +2220,7 @@ files = [ name = "nbclassic" version = "1.0.0" description = "Jupyter Notebook as a Jupyter Server extension." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2169,6 +2256,7 @@ test = ["coverage", "nbval", "pytest", "pytest-cov", "pytest-jupyter", "pytest-p name = "nbclient" version = "0.9.0" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." +category = "main" optional = false python-versions = ">=3.8.0" files = [ @@ -2178,7 +2266,7 @@ files = [ [package.dependencies] jupyter-client = ">=6.1.12" -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" nbformat = ">=5.1" traitlets = ">=5.4" @@ -2191,6 +2279,7 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>= name = "nbconvert" version = "7.11.0" description = "Converting Jupyter Notebooks" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2229,6 +2318,7 @@ webpdf = ["playwright"] name = "nbformat" version = "5.9.2" description = "The Jupyter Notebook format" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2250,6 +2340,7 @@ test = ["pep440", "pre-commit", "pytest", "testpath"] name = "nest-asyncio" version = "1.5.8" description = "Patch asyncio to allow nested event loops" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2261,6 +2352,7 @@ files = [ name = "networkx" version = "2.8.8" description = "Python package for creating and manipulating graphs and networks" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2279,6 +2371,7 @@ test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] name = "notebook" version = "6.5.6" description = "A web-based notebook environment for interactive computing" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2313,6 +2406,7 @@ test = ["coverage", "nbval", "pytest", "pytest-cov", "requests", "requests-unixs name = "notebook-shim" version = "0.2.3" description = "A shim layer for notebook traits and config" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2330,6 +2424,7 @@ test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync" name = "numba" version = "0.58.1" description = "compiling Python code using LLVM" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2358,13 +2453,14 @@ files = [ [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.9\""} -llvmlite = "==0.41.*" +llvmlite = ">=0.41.0dev0,<0.42" numpy = ">=1.22,<1.27" [[package]] name = "numpy" version = "1.24.4" description = "Fundamental package for array computing in Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2402,6 +2498,7 @@ files = [ name = "overrides" version = "7.4.0" description = "A decorator to automatically detect mismatch when overriding a method." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2413,6 +2510,7 @@ files = [ name = "packageurl-python" version = "0.11.2" description = "A purl aka. Package URL parser and builder" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2430,6 +2528,7 @@ test = ["pytest"] name = "packaging" version = "23.2" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2441,6 +2540,7 @@ files = [ name = "pandas" version = "1.5.3" description = "Powerful data structures for data analysis, time series, and statistics" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2489,6 +2589,7 @@ test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] name = "pandocfilters" version = "1.5.0" description = "Utilities for writing pandoc filters in python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2500,6 +2601,7 @@ files = [ name = "parso" version = "0.8.3" description = "A Python Parser" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2515,6 +2617,7 @@ testing = ["docopt", "pytest (<6.0.0)"] name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2526,6 +2629,7 @@ files = [ name = "pexpect" version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." +category = "main" optional = false python-versions = "*" files = [ @@ -2540,6 +2644,7 @@ ptyprocess = ">=0.5" name = "pickleshare" version = "0.7.5" description = "Tiny 'shelve'-like database with concurrency support" +category = "main" optional = false python-versions = "*" files = [ @@ -2551,6 +2656,7 @@ files = [ name = "pillow" version = "10.1.0" description = "Python Imaging Library (Fork)" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2618,6 +2724,7 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "pip" version = "23.3.1" description = "The PyPA recommended tool for installing Python packages." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2629,6 +2736,7 @@ files = [ name = "pip-api" version = "0.0.30" description = "An unofficial, importable pip API" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2643,6 +2751,7 @@ pip = "*" name = "pip-audit" version = "2.6.1" description = "A tool for scanning Python environments for known vulnerabilities" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2671,6 +2780,7 @@ test = ["coverage[toml]", "pretend", "pytest", "pytest-cov"] name = "pip-requirements-parser" version = "32.0.1" description = "pip requirements parser - a mostly correct pip requirements parsing library because it uses pip's own code." +category = "dev" optional = false python-versions = ">=3.6.0" files = [ @@ -2690,6 +2800,7 @@ testing = ["aboutcode-toolkit (>=6.0.0)", "black", "pytest (>=6,!=7.0.0)", "pyte name = "pkgutil-resolve-name" version = "1.3.10" description = "Resolve a name to an object." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2701,6 +2812,7 @@ files = [ name = "platformdirs" version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2716,6 +2828,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "plotly" version = "5.18.0" description = "An open-source, interactive data visualization library for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2731,6 +2844,7 @@ tenacity = ">=6.2.0" name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2746,6 +2860,7 @@ testing = ["pytest", "pytest-benchmark"] name = "prometheus-client" version = "0.19.0" description = "Python client for the Prometheus monitoring system." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2760,6 +2875,7 @@ twisted = ["twisted"] name = "prompt-toolkit" version = "3.0.41" description = "Library for building powerful interactive command lines in Python" +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -2774,6 +2890,7 @@ wcwidth = "*" name = "protobuf" version = "4.25.1" description = "" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2794,6 +2911,7 @@ files = [ name = "psutil" version = "5.9.6" description = "Cross-platform lib for process and system monitoring in Python." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -2822,6 +2940,7 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] name = "ptyprocess" version = "0.7.0" description = "Run a subprocess in a pseudo terminal" +category = "main" optional = false python-versions = "*" files = [ @@ -2833,6 +2952,7 @@ files = [ name = "pure-eval" version = "0.2.2" description = "Safely evaluate AST nodes without side effects" +category = "main" optional = false python-versions = "*" files = [ @@ -2847,6 +2967,7 @@ tests = ["pytest"] name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2858,6 +2979,7 @@ files = [ name = "py-serializable" version = "0.11.1" description = "Library for serializing and deserializing Python Objects to and from JSON and XML." +category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -2872,6 +2994,7 @@ defusedxml = ">=0.7.1,<0.8.0" name = "pycparser" version = "2.21" description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2883,6 +3006,7 @@ files = [ name = "pydantic" version = "1.10.13" description = "Data validation and settings management using python type hints" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2935,6 +3059,7 @@ email = ["email-validator (>=1.0.3)"] name = "pyglet" version = "1.5.28" description = "Cross-platform windowing and multimedia library" +category = "main" optional = false python-versions = "*" files = [ @@ -2946,6 +3071,7 @@ files = [ name = "pygments" version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2961,6 +3087,7 @@ windows-terminal = ["colorama (>=0.4.6)"] name = "pylint" version = "2.17.7" description = "python code static checker" +category = "dev" optional = false python-versions = ">=3.7.2" files = [ @@ -2990,6 +3117,7 @@ testutils = ["gitpython (>3)"] name = "pyparsing" version = "3.1.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" optional = false python-versions = ">=3.6.8" files = [ @@ -3004,6 +3132,7 @@ diagrams = ["jinja2", "railroad-diagrams"] name = "pytest" version = "7.4.3" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3026,6 +3155,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -3040,6 +3170,7 @@ six = ">=1.5" name = "python-json-logger" version = "2.0.7" description = "A python library adding a json log formatter" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3051,6 +3182,7 @@ files = [ name = "pytz" version = "2023.3.post1" description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" files = [ @@ -3062,6 +3194,7 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" +category = "main" optional = false python-versions = "*" files = [ @@ -3085,6 +3218,7 @@ files = [ name = "pywinpty" version = "2.0.12" description = "Pseudo terminal support for Windows from Python." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3100,6 +3234,7 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3159,6 +3294,7 @@ files = [ name = "pyzmq" version = "24.0.1" description = "Python bindings for 0MQ" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3246,6 +3382,7 @@ py = {version = "*", markers = "implementation_name == \"pypy\""} name = "rapidfuzz" version = "3.5.2" description = "rapid fuzzy string matching" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3348,6 +3485,7 @@ full = ["numpy"] name = "referencing" version = "0.31.1" description = "JSON Referencing + Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3363,6 +3501,7 @@ rpds-py = ">=0.7.0" name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3384,6 +3523,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rfc3339-validator" version = "0.1.4" description = "A pure python RFC3339 validator" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -3398,6 +3538,7 @@ six = "*" name = "rfc3986-validator" version = "0.1.1" description = "Pure python rfc3986 validator" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -3409,6 +3550,7 @@ files = [ name = "rich" version = "13.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -3428,6 +3570,7 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "rpds-py" version = "0.13.2" description = "Python bindings to Rust's persistent data structures (rpds)" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3536,6 +3679,7 @@ files = [ name = "scipy" version = "1.10.1" description = "Fundamental algorithms for scientific computing in Python" +category = "main" optional = false python-versions = "<3.12,>=3.8" files = [ @@ -3574,6 +3718,7 @@ test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeo name = "send2trash" version = "1.8.2" description = "Send file to trash natively under Mac OS X, Windows and Linux" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -3590,6 +3735,7 @@ win32 = ["pywin32"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3601,6 +3747,7 @@ files = [ name = "sklearn" version = "0.0.post11" description = "deprecated sklearn package, use scikit-learn instead" +category = "main" optional = false python-versions = "*" files = [ @@ -3611,6 +3758,7 @@ files = [ name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3622,6 +3770,7 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" optional = false python-versions = "*" files = [ @@ -3633,6 +3782,7 @@ files = [ name = "sortedcontainers" version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +category = "dev" optional = false python-versions = "*" files = [ @@ -3644,6 +3794,7 @@ files = [ name = "soupsieve" version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3655,6 +3806,7 @@ files = [ name = "sphinx" version = "5.3.0" description = "Python documentation generator" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3690,6 +3842,7 @@ test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] name = "sphinx-autobuild" version = "2021.3.14" description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3709,6 +3862,7 @@ test = ["pytest", "pytest-cov"] name = "sphinx-gallery" version = "0.11.1" description = "A Sphinx extension that builds an HTML version of any Python script and puts it into an examples gallery." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3723,6 +3877,7 @@ sphinx = ">=3" name = "sphinx-rtd-theme" version = "1.3.0" description = "Read the Docs theme for Sphinx" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -3742,6 +3897,7 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] name = "sphinxcontrib-applehelp" version = "1.0.4" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3757,6 +3913,7 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -3772,6 +3929,7 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3787,6 +3945,7 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jquery" version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" +category = "dev" optional = false python-versions = ">=2.7" files = [ @@ -3801,6 +3960,7 @@ Sphinx = ">=1.8" name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -3815,6 +3975,7 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -3830,6 +3991,7 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -3845,6 +4007,7 @@ test = ["pytest"] name = "stack-data" version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" +category = "main" optional = false python-versions = "*" files = [ @@ -3864,6 +4027,7 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] name = "tenacity" version = "8.2.3" description = "Retry code until it succeeds" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3878,6 +4042,7 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"] name = "terminado" version = "0.18.0" description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3899,6 +4064,7 @@ typing = ["mypy (>=1.6,<2.0)", "traitlets (>=5.11.1)"] name = "texttable" version = "1.7.0" description = "module to create simple ASCII tables" +category = "main" optional = false python-versions = "*" files = [ @@ -3910,6 +4076,7 @@ files = [ name = "tinycss2" version = "1.2.1" description = "A tiny CSS parser" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3928,6 +4095,7 @@ test = ["flake8", "isort", "pytest"] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3939,6 +4107,7 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3950,6 +4119,7 @@ files = [ name = "tomlkit" version = "0.12.3" description = "Style preserving TOML library" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3961,6 +4131,7 @@ files = [ name = "tornado" version = "6.4" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -3981,6 +4152,7 @@ files = [ name = "traitlets" version = "5.14.0" description = "Traitlets Python configuration system" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3996,6 +4168,7 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, name = "trimesh" version = "3.23.5" description = "Import, export, process, analyze and view triangular meshes." +category = "main" optional = false python-versions = "*" files = [ @@ -4016,6 +4189,7 @@ test = ["autopep8 (<2)", "coveralls", "ezdxf", "pyinstrument", "pymeshlab", "pyt name = "types-python-dateutil" version = "2.8.19.14" description = "Typing stubs for python-dateutil" +category = "main" optional = false python-versions = "*" files = [ @@ -4027,6 +4201,7 @@ files = [ name = "typing-extensions" version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4038,6 +4213,7 @@ files = [ name = "uri-template" version = "1.3.0" description = "RFC 6570 URI Template Processor" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4052,6 +4228,7 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake name = "urllib3" version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4068,6 +4245,7 @@ zstd = ["zstandard (>=0.18.0)"] name = "wcwidth" version = "0.2.12" description = "Measures the displayed width of unicode strings in a terminal" +category = "main" optional = false python-versions = "*" files = [ @@ -4079,6 +4257,7 @@ files = [ name = "webcolors" version = "1.13" description = "A library for working with the color formats defined by HTML and CSS." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4094,6 +4273,7 @@ tests = ["pytest", "pytest-cov"] name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" +category = "main" optional = false python-versions = "*" files = [ @@ -4105,6 +4285,7 @@ files = [ name = "websocket-client" version = "1.6.4" description = "WebSocket client for Python with low level API options" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4121,6 +4302,7 @@ test = ["websockets"] name = "werkzeug" version = "2.3.8" description = "The comprehensive WSGI web application library." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4138,6 +4320,7 @@ watchdog = ["watchdog (>=2.3)"] name = "wheel-filename" version = "1.4.1" description = "Parse wheel filenames" +category = "dev" optional = false python-versions = "~=3.6" files = [ @@ -4149,6 +4332,7 @@ files = [ name = "win32-setctime" version = "1.1.0" description = "A small Python utility to set file creation time on Windows" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -4163,6 +4347,7 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] name = "wrapt" version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -4242,6 +4427,7 @@ files = [ name = "zipp" version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4256,4 +4442,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8, <3.12" -content-hash = "616997f5becffd637b1e397f159135017fb3ff9eadbbef4685260f3dbbeb3c88" +content-hash = "7cad0ec3e575b31ad49890f649086e9973c0615ac864ebfeff968ad5a807da69" diff --git a/pyproject.toml b/pyproject.toml index 8924fbf..34100ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ werkzeug = "^2.2.3" ipython = "^8.10.0" numba = "^0.58.1" igraph = "^0.11.3" +jupyter-server = "^2.11.2" [tool.poetry.dev-dependencies] pytest = "^7.2" diff --git a/scripts/evaluation_1.0.3.py b/scripts/evaluation_1x0x3.py similarity index 94% rename from scripts/evaluation_1.0.3.py rename to scripts/evaluation_1x0x3.py index c303e25..30c0927 100644 --- a/scripts/evaluation_1.0.3.py +++ b/scripts/evaluation_1x0x3.py @@ -1,17 +1,20 @@ -from mesh2vec.mesh2vec_cae import Mesh2VecCae +""" +measured time and memory usage of mesh2vec 1.0.3 +""" + from pathlib import Path import time -import numpy as np import tracemalloc +import numpy as np +from mesh2vec.mesh2vec_cae import Mesh2VecCae + DIST = 30 PATH = "data/hat/cached_hat_key.json" -PATH = "/home/markus/Downloads/test_data_internal/p210.key_tmp.json" - -res = None PROFILE_MEM = True +# pylint: disable=f-string-without-interpolation ### create neighbors start = time.time() if PROFILE_MEM: @@ -83,7 +86,7 @@ print(f"Current memory usage is {current / 10**6}MB; Peak was {peak / 10**6}MB") tracemalloc.stop() - +## tested with p210.key_tmp and mesh2vec 1.0.3 # calc_adjacencies: 41.01193881034851 # add_features_from_ansa: 0.21686553955078125 # 1 aggregate: 27.74431562423706 diff --git a/tests/test_mesh2vec_base.py b/tests/test_mesh2vec_base.py index fbfdf79..e2bffb8 100644 --- a/tests/test_mesh2vec_base.py +++ b/tests/test_mesh2vec_base.py @@ -5,7 +5,6 @@ import numpy as np import pandas as pd import pytest -from scipy.sparse.csr import csr_matrix from mesh2vec.mesh2vec_base import Mesh2VecBase from mesh2vec.mesh2vec_exceptions import ( @@ -16,82 +15,62 @@ FeatureDoesNotExistException, ) -from mesh2vec.helpers import ( - MatMulAdjacency, - BFSAdjacency, - BFSNumba, - PurePythonBFS, - PurePythonDFS, - DFSNumba, - MatMulAdjacencySmart, -) - - -strategies = [ - MatMulAdjacency, - BFSAdjacency, - BFSNumba, - PurePythonBFS, - PurePythonDFS, - DFSNumba, - MatMulAdjacencySmart, -] +strategies = ["matmul", "bfs", "dfs"] # pylint: disable=protected-access -def _csr_equal(csr_a: csr_matrix, csr_b: csr_matrix) -> bool: - return all(x == y for x, y in zip(csr_a.todok().items(), csr_b.todok().items())) - -def _csr_a_gte_b(csr_a: csr_matrix, csr_b: csr_matrix) -> bool: - return all(y in csr_a.todok().items() for y in csr_b.todok().items()) - -def test_init() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_init(strategy) -> None: """test init""" edges = {"first": ["a", "b", "c"], "second": ["x", "y"]} - hg = Mesh2VecBase(3, edges) + hg = Mesh2VecBase(3, edges, calc_strategy=strategy) assert hg._distance == 3 assert "y" in hg._vtx_ids_to_idx.keys() assert set(hg._neighborhoods[1][0]) == {1, 2} # a->b,c -def test_invalid_hyperedges() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_invalid_hyperedges(strategy) -> None: """test that InvalidDistanceArgument is raised""" with pytest.raises(InvalidHyperEdgesArgument): edges_0 = {"first": ["a", 0.1, "c"], "second": ["x", "y"], "third": ["x", "a"]} - _ = Mesh2VecBase(3, edges_0) # type: ignore + _ = Mesh2VecBase(3, edges_0, calc_strategy=strategy) with pytest.raises(InvalidHyperEdgesArgument): edges_1 = {"first": ["a", "b", "c"], "second": ["x", "y"], "third": ["x", 123]} - _ = Mesh2VecBase(3, edges_1) # type: ignore + _ = Mesh2VecBase(3, edges_1, calc_strategy=strategy) with pytest.raises(InvalidHyperEdgesArgument): edges_2 = [[1, 2, 3], [2, 3, 4]] - _ = Mesh2VecBase(3, edges_2) # type: ignore + _ = Mesh2VecBase(3, edges_2, calc_strategy=strategy) -def test_invalid_vtx_ids_argument() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_invalid_vtx_ids_argument(strategy) -> None: """test that InvalidVtxIdsArgument is raised""" with pytest.raises(InvalidVtxIdsArgument): edges_0 = {"first": ["a", "b", "c"], "second": ["x", "y"], "third": ["x", "a"]} - _ = Mesh2VecBase(3, edges_0, vtx_ids=[1, "b", "x", "y"]) # type: ignore + _ = Mesh2VecBase(3, edges_0, vtx_ids=[1, "b", "x", "y"], calc_strategy=strategy) with pytest.raises(InvalidVtxIdsArgument): edges_1 = {"first": ["a", "b", "c"], "second": ["x", "y"], "third": ["x", "a"]} - _ = Mesh2VecBase(3, edges_1, vtx_ids=["a", "b", "x", "y"]) + _ = Mesh2VecBase(3, edges_1, vtx_ids=["a", "b", "x", "y"], calc_strategy=strategy) -def test_from_txt_file() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_from_txt_file(strategy) -> None: """test import of txt file creates a consistent hypergraph""" - hg = Mesh2VecBase.from_file(Path("data/hyper_02.txt"), 3) + hg = Mesh2VecBase.from_file(Path("data/hyper_02.txt"), 3, calc_strategy=strategy) assert hg._distance == 3 assert "vtx02" in hg._vtx_ids_to_idx.keys() -def test_from_csv_file() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_from_csv_file(strategy) -> None: """test import of csv file creates a consistent hypergraph""" - hg1 = Mesh2VecBase.from_file(Path("data/hyper_02.txt"), 3) + hg1 = Mesh2VecBase.from_file(Path("data/hyper_02.txt"), 3, calc_strategy=strategy) # write pair-wise connectivity to csv connections = [] @@ -102,13 +81,13 @@ def test_from_csv_file() -> None: df = pd.DataFrame(connections, columns=["vtx_a", "vtx_b"]) df.to_csv("data/tmp_hyper_02.csv", header=False, index=False) - hg2 = Mesh2VecBase.from_file(Path("data/tmp_hyper_02.csv"), 3) + hg2 = Mesh2VecBase.from_file(Path("data/tmp_hyper_02.csv"), 3, calc_strategy=strategy) assert hg2._distance == 3 # adj_mat must be equal for i in range(1, 3 + 1): for j in range(13): - assert all(hg2._neighborhoods[i][j] == hg1._neighborhoods[i][j]) + assert set(hg2._neighborhoods[i][j]) == set(hg1._neighborhoods[i][j]) # hyper edges of hg1 that are not fully contained in larger other hyper edge form a clique # and should be contained in hyper edges of hg2 @@ -143,34 +122,37 @@ def test_get_nbh(strategy) -> None: assert {"b", "c"} == set(hg.get_nbh("y", 3)) -def test_invalid_distance_exception() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_invalid_distance_exception(strategy) -> None: """test that InvalidDistanceArgument is raised""" edges = {"first": ["a", "b", "c"], "second": ["x", "y"], "third": ["x", "a"]} with pytest.raises(InvalidDistanceArgumentException): - _ = Mesh2VecBase(-3, edges) + _ = Mesh2VecBase(-3, edges, calc_strategy=strategy) with pytest.raises(InvalidDistanceArgumentException): - hg = Mesh2VecBase(3, edges) + hg = Mesh2VecBase(3, edges, calc_strategy=strategy) hg.get_nbh("b", 4) -def test_invalidvtx_id_exception() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_invalidvtx_id_exception(strategy) -> None: """test that InvalidDistanceArgument is raised""" edges = {"first": ["a", "b", "c"], "second": ["x", "y"], "third": ["x", "a"]} with pytest.raises(InvalidVtxIdArgument): - hg = Mesh2VecBase(3, edges) - hg.get_nbh(42, 3) # type: ignore + hg = Mesh2VecBase(3, edges, calc_strategy=strategy) + hg.get_nbh(42, 3) with pytest.raises(InvalidVtxIdArgument): - hg = Mesh2VecBase(3, edges) + hg = Mesh2VecBase(3, edges, calc_strategy=strategy) hg.get_nbh("42", 3) -def test_aggregate_categorical_disjunctive() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_aggregate_categorical_disjunctive(strategy) -> None: """test categorical aggregation with simple with disjunctive feature values""" edges = {"first": ["a", "b", "c"], "second": ["x", "y"]} - hg = Mesh2VecBase(3, edges) + hg = Mesh2VecBase(3, edges, calc_strategy=strategy) df1 = pd.DataFrame({"vtx_id": ["a", "b", "c", "x", "y"], "f1": [2, 4, 8, 16, 32]}) hg.add_features_from_dataframe(df1) _ = hg.aggregate_categorical("f1", [1, 3]) @@ -183,10 +165,11 @@ def test_aggregate_categorical_disjunctive() -> None: @pytest.mark.parametrize("feature_values", [[2, 4, 8, 16, 32], ["2", "4", "8", "16", "32"]]) -def test_aggregate_categorical_disjunctive_complex(feature_values: List[Any]) -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_aggregate_categorical_disjunctive_complex(feature_values: List[Any], strategy) -> None: """test categorical aggregation with complex graph but disjunctive values""" edges = {"first": ["a", "b", "c"], "second": ["x", "y"], "third": ["x", "a"]} - hg = Mesh2VecBase(3, edges) + hg = Mesh2VecBase(3, edges, calc_strategy=strategy) df1 = pd.DataFrame({"vtx_id": ["a", "b", "c", "x", "y"], "f1": feature_values}) hg.add_features_from_dataframe(df1) @@ -213,10 +196,11 @@ def test_aggregate_categorical_disjunctive_complex(feature_values: List[Any]) -> @pytest.mark.parametrize("feature_values", [[2, 4, 8, 2, 4], ["2", "4", "8", "2", "4"]]) -def test_aggregate_categorical_ones(feature_values: List[Any]) -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_aggregate_categorical_ones(feature_values: List[Any], strategy) -> None: """test categorical aggregation with complex graph but only ones in sums""" edges = {"first": ["a", "b", "c"], "second": ["x", "y"], "third": ["x", "a"]} - hg = Mesh2VecBase(3, edges) + hg = Mesh2VecBase(3, edges, calc_strategy=strategy) df1 = pd.DataFrame({"vtx_id": ["a", "b", "c", "x", "y"], "f1": feature_values}) hg.add_features_from_dataframe(df1) @@ -237,10 +221,11 @@ def test_aggregate_categorical_ones(feature_values: List[Any]) -> None: @pytest.mark.parametrize("feature_values", [[2, 4, 2, 2, 4], ["2", "4", "2", "2", "4"]]) -def test_aggregate_categorical_mores(feature_values: List[Any]) -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_aggregate_categorical_mores(feature_values: List[Any], strategy) -> None: """test categorical aggregation with complex graph and sums > 1""" edges = {"first": ["a", "b", "c"], "second": ["x", "y"], "third": ["x", "a"]} - hg = Mesh2VecBase(3, edges) + hg = Mesh2VecBase(3, edges, calc_strategy=strategy) df1 = pd.DataFrame({"vtx_id": ["a", "b", "c", "x", "y"], "f1": feature_values}) hg.add_features_from_dataframe(df1) @@ -261,10 +246,11 @@ def test_aggregate_categorical_mores(feature_values: List[Any]) -> None: assert hg._aggregated_features["f1-cat-4-3"].tolist() == [0, 1, 1, 0, 1] -def test_aggregates_raise_feature_not_available() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_aggregates_raise_feature_not_available(strategy) -> None: """test that FeatureDoesNotExistException is raised""" edges = {"first": ["a", "b", "c"], "second": ["x", "y"]} - hg = Mesh2VecBase(3, edges) + hg = Mesh2VecBase(3, edges, calc_strategy=strategy) df1 = pd.DataFrame({"vtx_id": ["a", "b", "c", "x", "y"], "f1": [2, 4, 8, 16, 32]}) hg.add_features_from_dataframe(df1) with pytest.raises(FeatureDoesNotExistException, match=r"\['f1'\]"): @@ -288,10 +274,11 @@ def test_aggregate_simple(strategy) -> None: assert hg._aggregated_features[name].iloc[4] == np.mean([16]) -def test_aggregate_simple_two_dists() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_aggregate_simple_two_dists(strategy) -> None: """test aggregation with simple graph""" edges = {"first": ["a", "b", "c"], "second": ["x", "y"]} - hg = Mesh2VecBase(3, edges) + hg = Mesh2VecBase(3, edges, calc_strategy=strategy) df1 = pd.DataFrame({"vtx_id": ["a", "b", "c", "x", "y"], "f1": [2, 4, 8, 16, 32]}) hg.add_features_from_dataframe(df1) names = hg.aggregate("f1", [1, 2], np.mean) @@ -303,10 +290,11 @@ def test_aggregate_simple_two_dists() -> None: assert hg._aggregated_features[names[1]].iloc[0] == 0 -def test_aggregate_complex() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_aggregate_complex(strategy) -> None: """test aggregation with complex graph""" edges = {"first": ["a", "b", "c"], "second": ["x", "y"], "third": ["x", "a"]} - hg = Mesh2VecBase(3, edges) + hg = Mesh2VecBase(3, edges, calc_strategy=strategy) df1 = pd.DataFrame({"vtx_id": ["a", "b", "c", "x", "y"], "f1": [2, 4, 8, 16, 32]}) hg.add_features_from_dataframe(df1) @@ -339,18 +327,19 @@ def test_aggregate_complex() -> None: assert hg._aggregated_features[name].iloc[4] == np.mean([4, 8]) -def test_add_features_from_csv() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_add_features_from_csv(strategy) -> None: """test add_features_from_csv with and without header""" - hg_01 = Mesh2VecBase.from_file(Path("data/hyper_02.txt"), 3) + hg_01 = Mesh2VecBase.from_file(Path("data/hyper_02.txt"), 3, calc_strategy=strategy) hg_01.add_features_from_csv(Path("data/hyper_02_features.csv"), with_header=True) - hg_02 = Mesh2VecBase.from_file(Path("data/hyper_02.txt"), 3) + hg_02 = Mesh2VecBase.from_file(Path("data/hyper_02.txt"), 3, calc_strategy=strategy) hg_02.add_features_from_csv( Path("data/hyper_02_features_noheader.csv"), columns=["vtx_id", "by_2", "by_3", "pow2", "sqrt"], ) - hg_03 = Mesh2VecBase.from_file(Path("data/hyper_02.txt"), 3) + hg_03 = Mesh2VecBase.from_file(Path("data/hyper_02.txt"), 3, calc_strategy=strategy) hg_03.add_features_from_csv( Path("data/hyper_02_features.csv"), columns=["vtx_id", "by_2", "by_3", "pow2", "sqrt!"], @@ -361,10 +350,11 @@ def test_add_features_from_csv() -> None: ) -def test_add_features_from_dataframe() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_add_features_from_dataframe(strategy) -> None: """test add_features_from_dataframe""" edges = {"first": ["a", "b", "c"], "second": ["x", "y"], "third": ["x", "a"]} - hg = Mesh2VecBase(3, edges) + hg = Mesh2VecBase(3, edges, calc_strategy=strategy) df1 = pd.DataFrame({"vtx_id": ["a", "b", "c", "x", "y"], "f1": ["a", "b", "c", "x", "y"]}) hg.add_features_from_dataframe(df1) assert hg._features["f1"].tolist() == ["a", "b", "c", "x", "y"] diff --git a/tests/test_mesh2vec_cae.py b/tests/test_mesh2vec_cae.py index 6fc27ce..3ede7db 100644 --- a/tests/test_mesh2vec_cae.py +++ b/tests/test_mesh2vec_cae.py @@ -5,7 +5,6 @@ from tempfile import TemporaryDirectory import numpy as np import pandas as pd -from scipy.sparse import csr_matrix from lasso.dyna import ArrayType, D3plot import pytest @@ -14,6 +13,7 @@ Mesh2VecCae, ) +strategies = ["matmul", "bfs", "dfs"] # pylint: disable=protected-access features_reduced = [ @@ -30,14 +30,6 @@ ] -def _csr_equal(csr_a: csr_matrix, csr_b: csr_matrix) -> bool: - return all(x == y for x, y in zip(csr_a.todok().items(), csr_b.todok().items())) - - -def _csr_a_gte_b(csr_a: csr_matrix, csr_b: csr_matrix) -> bool: - return all(y in csr_a.todok().items() for y in csr_b.todok().items()) - - def _make_mesh_info(elem_ids: np.ndarray) -> pd.DataFrame: element_info = pd.DataFrame({"element_id": elem_ids}) element_info["part_name"] = "part_name" @@ -46,7 +38,8 @@ def _make_mesh_info(elem_ids: np.ndarray) -> pd.DataFrame: return element_info -def test_init_tri_unique_ids() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_init_tri_unique_ids(strategy) -> None: """test init with triangles having unique ids""" point_coordinates = np.array([[v, v, v] for v in range(6)]) pnt_ids = np.array(["0", "1", "2", "3x", "4", "5"]) @@ -54,24 +47,25 @@ def test_init_tri_unique_ids() -> None: elem_node_idxs = np.array([[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5]]) mesh = CaeShellMesh(point_coordinates, pnt_ids, elem_ids, elem_node_idxs) - m2v = Mesh2VecCae(2, mesh, _make_mesh_info(elem_ids)) + m2v = Mesh2VecCae(2, mesh, _make_mesh_info(elem_ids), calc_strategy=strategy) assert ["1", "2"] == m2v.get_nbh("0", 1) -def test_init_tri_overlapping_ids() -> None: - """test init with triangles having overlapping ids - OPEN TODO""" +@pytest.mark.parametrize("strategy", strategies) +def test_init_tri_overlapping_ids(strategy) -> None: + """test init with triangles having overlapping ids""" point_coordinates = np.array([[v, v, v] for v in range(6)]) pnt_ids = np.array(["0", "1", "2x", "3x", "4x", "5"]) elem_ids = np.array(["0", "1", "0", "1"]) elem_node_idxs = np.array([[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5]]) mesh = CaeShellMesh(point_coordinates, pnt_ids, elem_ids, elem_node_idxs) - m2v = Mesh2VecCae(2, mesh, _make_mesh_info(elem_ids)) + m2v = Mesh2VecCae(2, mesh, _make_mesh_info(elem_ids), calc_strategy=strategy) assert ["1", "0_2x_3x_4x_4x"] == m2v.get_nbh("0", 1) - # 2DO: check mesh_point ids -def test_init_quad_overlapping_ids() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_init_quad_overlapping_ids(strategy) -> None: """test init with quads having overlapping ids""" point_coordinates = np.array([[v, v, v] for v in range(6)]) pnt_ids = np.array(["0", "1", "2", "3x", "4x", "5x"]) @@ -79,43 +73,50 @@ def test_init_quad_overlapping_ids() -> None: elem_node_idxs = np.array([[0, 1, 2, 2], [1, 2, 3, 3], [3, 4, 5, 5]]) mesh = CaeShellMesh(point_coordinates, pnt_ids, elem_ids, elem_node_idxs) - m2v = Mesh2VecCae(2, mesh, _make_mesh_info(elem_ids)) + m2v = Mesh2VecCae(2, mesh, _make_mesh_info(elem_ids), calc_strategy=strategy) assert ["1"] == m2v.get_nbh("0", 1) assert ["0", "0_3x_4x_5x_5x"] == m2v.get_nbh("1", 1) -def test_shell_from_ansa() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_shell_from_ansa(strategy) -> None: """test ansa shell is loaded""" m2v = Mesh2VecCae.from_ansa_shell( 4, Path("data/hat/Hatprofile.k"), json_mesh_file=Path("data/hat/cached_hat_key.json"), + calc_strategy=strategy, ) assert len(m2v.vtx_ids()) == 6400 -def test_from_keyfile_shell() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_from_keyfile_shell(strategy) -> None: """test ansa shell is loaded""" m2v = Mesh2VecCae.from_keyfile_shell( 4, Path("data/hat/Hatprofile.k"), + calc_strategy=strategy, ) m2v_ansa = Mesh2VecCae.from_ansa_shell( 4, Path("data/hat/Hatprofile.k"), json_mesh_file=Path("data/hat/cached_hat_key.json"), + calc_strategy=strategy, ) all(sorted(v) == sorted(m2v_ansa._hyper_edges[k]) for k, v in m2v._hyper_edges.items()) assert set(m2v.vtx_ids()) == set(m2v_ansa.vtx_ids()) -def test_shell_from_ansa_partid() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_shell_from_ansa_partid(strategy) -> None: """test ansa shell is loaded with and without partid""" m2v1 = Mesh2VecCae.from_ansa_shell( 4, Path("data/hat/Hatprofile.k"), json_mesh_file=Path("data/hat/cached_hat_key.json"), + calc_strategy=strategy, ) m2v2 = Mesh2VecCae.from_ansa_shell( @@ -123,23 +124,29 @@ def test_shell_from_ansa_partid() -> None: Path("data/hat/Hatprofile.k"), json_mesh_file=Path("data/hat/cached_hat_key_partid.json"), partid="1", + calc_strategy=strategy, ) assert all(x in m2v1.vtx_ids() for x in m2v2.vtx_ids()) -def test_shell_from_d3plot() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_shell_from_d3plot(strategy) -> None: """test d3plot is loaded""" - m2v = Mesh2VecCae.from_d3plot_shell(3, Path("data/hat/HAT.d3plot")) + m2v = Mesh2VecCae.from_d3plot_shell(3, Path("data/hat/HAT.d3plot"), calc_strategy=strategy) assert len(m2v.vtx_ids()) > 2000 -def test_shell_from_d3plot_partid() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_shell_from_d3plot_partid(strategy) -> None: """test d3plot is loaded with and without partid""" - m2v = Mesh2VecCae.from_d3plot_shell(3, Path("data/hat/HAT.d3plot"), partid="1") + m2v = Mesh2VecCae.from_d3plot_shell( + 3, Path("data/hat/HAT.d3plot"), partid="1", calc_strategy=strategy + ) assert len(m2v.vtx_ids()) < 35000 -def test_add_features_from_ansa() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_add_features_from_ansa(strategy) -> None: """test adding features from ansa works""" ansafile = Path("data/hat/Hatprofile.k") json_mesh_file = Path("data/hat/cached_hat_key.json") @@ -148,6 +155,7 @@ def test_add_features_from_ansa() -> None: 4, ansafile, json_mesh_file=json_mesh_file, + calc_strategy=strategy, ) elements, nodes = Mesh2VecCae._read_ansafile(ansafile, json_mesh_file, verbose=True) assert len(elements) == len(m2v._features) @@ -162,9 +170,10 @@ def test_add_features_from_ansa() -> None: assert all(feature in m2v._features.keys().values for feature in features) -def test_add_features_from_d3plot() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_add_features_from_d3plot(strategy) -> None: """test adding features from d3plot works""" - m2v = Mesh2VecCae.from_d3plot_shell(3, Path("data/hat/HAT.d3plot")) + m2v = Mesh2VecCae.from_d3plot_shell(3, Path("data/hat/HAT.d3plot"), calc_strategy=strategy) for feature in features_reduced: m2v.add_feature_from_d3plot( @@ -176,9 +185,10 @@ def test_add_features_from_d3plot() -> None: ) -def test_add_feature_from_d3plot_accumulated() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_add_feature_from_d3plot_accumulated(strategy) -> None: """test adding features from d3plot with shell_layer accumulation works""" - m2v = Mesh2VecCae.from_d3plot_shell(3, Path("data/hat/HAT.d3plot")) + m2v = Mesh2VecCae.from_d3plot_shell(3, Path("data/hat/HAT.d3plot"), calc_strategy=strategy) d3plot_data = D3plot(Path("data/hat/HAT.d3plot").as_posix()) axis_0_sum = partial(np.sum, axis=0) axis_0_sum.__name__ = "axis0sum" # type: ignore @@ -188,9 +198,10 @@ def test_add_feature_from_d3plot_accumulated() -> None: ) -def test_save_load() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_save_load(strategy) -> None: """test saving and loading works""" - m2v = Mesh2VecCae.from_d3plot_shell(3, Path("data/hat/HAT.d3plot")) + m2v = Mesh2VecCae.from_d3plot_shell(3, Path("data/hat/HAT.d3plot"), calc_strategy=strategy) d3plot_data = D3plot(Path("data/hat/HAT.d3plot").as_posix()) axis_0_sum = partial(np.sum, axis=0) axis_0_sum.__name__ = "axis0sum" # type: ignore @@ -204,7 +215,8 @@ def test_save_load() -> None: assert m2v_loaded._features.equals(m2v._features) -def test_add_feature_from_d3plot_to_ansa_shell() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_add_feature_from_d3plot_to_ansa_shell(strategy) -> None: """test adding features from ansa works""" ansafile = Path("data/hat/Hatprofile.k") json_mesh_file = Path("data/hat/cached_hat_key.json") @@ -213,6 +225,7 @@ def test_add_feature_from_d3plot_to_ansa_shell() -> None: 4, ansafile, json_mesh_file=json_mesh_file, + calc_strategy=strategy, ) m2v.add_features_from_ansa( @@ -239,10 +252,14 @@ def test_add_feature_from_d3plot_to_ansa_shell() -> None: print(m2v._aggregated_features[name].shape) -def test_aggregate_angle_diff() -> None: +@pytest.mark.parametrize("strategy", strategies) +def test_aggregate_angle_diff(strategy) -> None: """test aggregating angel difference works""" m2v = Mesh2VecCae.from_ansa_shell( - 4, Path("data/hat/Hatprofile.k"), json_mesh_file=Path("data/hat/cached_hat_key.json") + 4, + Path("data/hat/Hatprofile.k"), + json_mesh_file=Path("data/hat/cached_hat_key.json"), + calc_strategy=strategy, ) m2v.add_features_from_ansa( ["normal"], diff --git a/tests/test_mesh_features.py b/tests/test_mesh_features.py index 7365503..37f6ad3 100644 --- a/tests/test_mesh_features.py +++ b/tests/test_mesh_features.py @@ -21,7 +21,7 @@ def test_area() -> None: """test calculated face area is same as in ansa""" hg = Mesh2VecCae.from_ansa_shell( - 0, + 1, Path("data/hat/Hatprofile.k"), json_mesh_file=Path("data/hat/cached_hat_key.json"), )