From 441719abcaa6de1905ce1ecb93e02e5f0f5ed6a1 Mon Sep 17 00:00:00 2001 From: Command M Date: Fri, 22 Nov 2024 01:53:17 +0800 Subject: [PATCH 1/2] start using gt and embed datasets --- README.md | 87 +-- network_dismantling/__init__.py | 37 +- network_dismantling/dismantler.py | 6 +- .../edge_selectors/__init__.py | 2 +- .../edge_selectors/edge_selector.py | 5 +- network_dismantling/evaluators/__init__.py | 8 +- .../evaluators/avg_clustering.py | 7 +- .../evaluators/avg_path_length.py | 8 +- network_dismantling/evaluators/evaluator.py | 21 +- .../evaluators/global_effiency.py | 9 +- network_dismantling/evaluators/lcc_size.py | 8 +- .../evaluators/num_components.py | 10 +- .../node_selectors/__init__.py | 13 +- network_dismantling/node_selectors/basic.py | 10 +- .../node_selectors/centrality.py | 9 +- network_dismantling/node_selectors/gdm.py | 50 +- network_dismantling/node_selectors/gnd.py | 16 +- .../node_selectors/influence.py | 15 +- .../node_selectors/node_selector.py | 4 +- network_dismantling/operators/__init__.py | 4 +- network_dismantling/operators/basic.py | 15 +- network_dismantling/operators/operator.py | 5 +- network_dismantling/selector.py | 5 +- network_dismantling/utils/__init__.py | 0 network_dismantling/utils/data.py | 105 ++++ poetry.lock | 505 ++++++++---------- pyproject.toml | 2 - 27 files changed, 478 insertions(+), 488 deletions(-) create mode 100644 network_dismantling/utils/__init__.py create mode 100644 network_dismantling/utils/data.py diff --git a/README.md b/README.md index c843156..d05b476 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # network-dismantling -`network-dismantling` is a library for network dismantling, which refers to the process of strategically removing nodes from a network to disrupt its functionality. This repository provides implementations of various network dismantling algorithms, leveraging the power of PyTorch for efficient computation and flexibility. +`network-dismantling` is a framework for network dismantling, which refers to the process of strategically removing nodes from a network to disrupt its functionality. This repository provides implementations of various network dismantling algorithms, leveraging the power of PyTorch for efficient computation and flexibility. ## Installation @@ -10,14 +10,11 @@ To install the dependencies, easily install from PyPI: pip install network-dismantling ``` -Make sure you have PyTorch installed if you want to run a neural-network model. +Make sure you have PyTorch and graph-tool installed if you want to run a neural-network model. + You can install PyTorch from [here](https://pytorch.org/get-started/locally/). -Or directly using conda: - -```bash -conda install network-dismantling -``` +You can install graph-tool from [here](https://graph-tool.skewed.de/installation.html). Alternatively, you can clone the repository and install it locally: @@ -29,83 +26,7 @@ pip install -e . ## Usage -```python -# Create a test graph -G = nx.karate_club_graph() - -# Initialize CoreHD strategies -corehd_strategy = CoreHDDismantling() - -# Create NetworkDismantlers with different strategies -corehd_dismantler = NetworkDismantler(corehd_strategy) - -# Dismantle the graph using both strategies -num_nodes_to_remove = int(G.number_of_nodes() * 0.1) # Remove 10% of nodes - -dismantled_G_corehd, removed_nodes_corehd = corehd_dismantler.dismantle(G, num_nodes_to_remove) - -print(f"Original graph: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges") -print(f"CoreHD dismantled graph: {dismantled_G_corehd.number_of_nodes()} nodes, {dismantled_G_corehd.number_of_edges()} edges") -print(f"CoreHD removed nodes: {removed_nodes_corehd}") -``` - -```python -G = nx.karate_club_graph() - -# Initialize CoreGDM strategy -coregdm_strategy = CoreGDMDismantling() - -# Create NetworkDismantler with CoreGDM strategy -coregdm_dismantler = NetworkDismantler(coregdm_strategy) - -# Dismantle the graph using CoreGDM -num_nodes_to_remove = int(G.number_of_nodes() * 0.1) # Remove 10% of nodes -dismantled_G_coregdm, removed_nodes_coregdm = coregdm_dismantler.dismantle(G, num_nodes_to_remove) - -print(f"Original graph: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges") -print(f"CoreGDM dismantled graph: {dismantled_G_coregdm.number_of_nodes()} nodes, {dismantled_G_coregdm.number_of_edges()} edges") -print(f"CoreGDM removed nodes: {removed_nodes_coregdm}") -``` - -```python - # Create a test graph - G = nx.karate_club_graph() - - # Initialize different dismantling strategies - dismantling_strategies = [ - CollectiveInfluenceDismantling(l=2), - ExplosiveImmunizationDismantling(q=0.1, num_iterations=10), - CoreGDMDismantling(), - GNDDismantling(), - CoreHDDismantling() - ] - - # Initialize evaluation metrics - metrics = [ - LCCSizeMetric(), - NumComponentsMetric(), - AvgPathLengthMetric(), - GlobalEfficiencyMetric(), - AvgClusteringMetric() - ] - - # Choose evaluation strategy - evaluation_strategy = RelativeChangeStrategy() - - # Create evaluator - evaluator = DismantlingEvaluator(metrics, evaluation_strategy) - - # Compare strategies - num_nodes_to_remove = int(G.number_of_nodes() * 0.1) # Remove 10% of nodes - comparison_results = DismantlingEvaluator.compare_strategies(G, dismantling_strategies, num_nodes_to_remove, evaluator) - - # Print detailed results - for strategy_name, metrics in comparison_results.items(): - print(f"\n{strategy_name}:") - for metric, value in metrics.items(): - print(f" {metric}: {value:.4f}") -``` ## Documentation diff --git a/network_dismantling/__init__.py b/network_dismantling/__init__.py index 8169881..35ed1ac 100644 --- a/network_dismantling/__init__.py +++ b/network_dismantling/__init__.py @@ -1,6 +1,37 @@ -from .dismantler import DismantlingStrategy, NetworkDismantler -from .operators.operator import DismantlingOperator -from .selector import ElementSelector +def require_gt(): + ''' + Check graph-tool is installed + ''' + try: + import graph_tool.all as gt + except ImportError: + raise ImportError("Please install graph-tool to use network_dismantling. You can install it as the instructions in https://graph-tool.skewed.de/installation.html") + +def require_pytorch(): + ''' + Check PyTorch is installed + ''' + try: + import torch + except ImportError: + raise ImportError("Please install PyTorch to use network_dismantling. You can install it as the instructions in https://pytorch.org/get-started/locally/") + +def require_pyg(): + ''' + Check PyTorch Geometric is installed + ''' + try: + import torch_geometric + except ImportError: + raise ImportError("Please install PyTorch Geometric to use network_dismantling. You can install it as the instructions in https://pytorch-geometric.readthedocs.io/en/latest/notes/installation.html") + +require_gt() +require_pytorch() +require_pyg() + +from network_dismantling.dismantler import DismantlingStrategy, NetworkDismantler +from network_dismantling.operators.operator import DismantlingOperator +from network_dismantling.selector import ElementSelector __all__ = [ "DismantlingStrategy", diff --git a/network_dismantling/dismantler.py b/network_dismantling/dismantler.py index fac5676..50cdc6e 100644 --- a/network_dismantling/dismantler.py +++ b/network_dismantling/dismantler.py @@ -1,4 +1,4 @@ -import networkx as nx +import graph_tool.all as gt from abc import abstractmethod from typing import Any, List, Tuple import torch.nn as nn @@ -15,7 +15,7 @@ class DismantlingStrategy(nn.Module): """ @abstractmethod - def dismantle(self, G: nx.Graph, num_nodes: int) -> List[int]: + def dismantle(self, G: gt.Graph, num_nodes: int) -> List[int]: """ Dismantle the graph by removing nodes. @@ -44,7 +44,7 @@ def __init__(self, selector: ElementSelector, operator: DismantlingOperator): self.selector = selector self.operator = operator - def dismantle(self, G: nx.Graph, num_elements: int) -> Tuple[nx.Graph, List[Any]]: + def dismantle(self, G: gt.Graph, num_elements: int) -> Tuple[gt.Graph, List[Any]]: """ Dismantle the graph by removing elements. diff --git a/network_dismantling/edge_selectors/__init__.py b/network_dismantling/edge_selectors/__init__.py index 5c88c1e..6d5affd 100644 --- a/network_dismantling/edge_selectors/__init__.py +++ b/network_dismantling/edge_selectors/__init__.py @@ -1,3 +1,3 @@ -from .edge_selector import EdgeSelector +from network_dismantling.edge_selectors.edge_selector import EdgeSelector __all__ = ["EdgeSelector"] diff --git a/network_dismantling/edge_selectors/edge_selector.py b/network_dismantling/edge_selectors/edge_selector.py index 5cb9362..f24466e 100644 --- a/network_dismantling/edge_selectors/edge_selector.py +++ b/network_dismantling/edge_selectors/edge_selector.py @@ -1,5 +1,4 @@ -import networkx as nx - +import graph_tool.all as gt from abc import abstractmethod from typing import Any, List, Tuple from network_dismantling.selector import ElementSelector @@ -13,7 +12,7 @@ class EdgeSelector(ElementSelector): """ @abstractmethod - def select(self, G: nx.Graph, num_edges: int) -> List[Tuple[Any, Any]]: + def select(self, G: gt.Graph, num_edges: int) -> List[Tuple[Any, Any]]: """ Select edges from the graph. diff --git a/network_dismantling/evaluators/__init__.py b/network_dismantling/evaluators/__init__.py index 7591322..7cfbf7a 100644 --- a/network_dismantling/evaluators/__init__.py +++ b/network_dismantling/evaluators/__init__.py @@ -1,7 +1,7 @@ -from .avg_clustering import AvgClusteringMetric -from .avg_path_length import AvgPathLengthMetric -from .global_effiency import GlobalEfficiencyMetric -from .evaluator import ( +from network_dismantling.evaluators.avg_clustering import AvgClusteringMetric +from network_dismantling.evaluators.avg_path_length import AvgPathLengthMetric +from network_dismantling.evaluators.global_effiency import GlobalEfficiencyMetric +from network_dismantling.evaluators.evaluator import ( EvaluationMetric, EvaluationStrategy, RelativeChangeStrategy, diff --git a/network_dismantling/evaluators/avg_clustering.py b/network_dismantling/evaluators/avg_clustering.py index cf3d5e7..4adad7e 100644 --- a/network_dismantling/evaluators/avg_clustering.py +++ b/network_dismantling/evaluators/avg_clustering.py @@ -1,5 +1,4 @@ -import networkx as nx - +import graph_tool.all as gt from network_dismantling.evaluators.evaluator import EvaluationMetric @@ -12,14 +11,14 @@ class AvgClusteringMetric(EvaluationMetric): The average clustering coefficient is the average of the clustering coefficients of all nodes in the graph. """ - def compute(self, G: nx.Graph) -> float: + def compute(self, G: gt.Graph) -> float: """ Compute the average clustering coefficient of the graph. :param G: The graph. :return: The average clustering coefficient of the graph. """ - return nx.average_clustering(G) + return gt.average_clustering(G) @property def name(self) -> str: diff --git a/network_dismantling/evaluators/avg_path_length.py b/network_dismantling/evaluators/avg_path_length.py index ce54100..b4d9c53 100644 --- a/network_dismantling/evaluators/avg_path_length.py +++ b/network_dismantling/evaluators/avg_path_length.py @@ -1,4 +1,4 @@ -import networkx as nx +import graph_tool.all as gt from network_dismantling.evaluators.evaluator import EvaluationMetric @@ -12,15 +12,15 @@ class AvgPathLengthMetric(EvaluationMetric): length is the average of the shortest path lengths between all pairs of nodes in the graph. """ - def compute(self, G: nx.Graph) -> float: + def compute(self, G: gt.Graph) -> float: """ Compute the average path length of the graph. :param G: The graph. :return: The average path length of the graph. """ - lcc = max(nx.connected_components(G), key=len) - return nx.average_shortest_path_length(G.subgraph(lcc)) + lcc = max(gt.connected_components(G), key=len) + return gt.average_shortest_path_length(G.subgraph(lcc)) @property def name(self) -> str: diff --git a/network_dismantling/evaluators/evaluator.py b/network_dismantling/evaluators/evaluator.py index e8c7755..7ec3d25 100644 --- a/network_dismantling/evaluators/evaluator.py +++ b/network_dismantling/evaluators/evaluator.py @@ -1,4 +1,5 @@ -import networkx as nx +import graph_tool.all as gt + from typing import List, Dict from abc import ABC, abstractmethod @@ -14,7 +15,7 @@ class EvaluationMetric(ABC): """ @abstractmethod - def compute(self, G: nx.Graph) -> float: + def compute(self, G: gt.Graph) -> float: """ Compute the evaluation metric on the graph. @@ -45,8 +46,8 @@ class EvaluationStrategy(ABC): @abstractmethod def evaluate( self, - original_graph: nx.Graph, - dismantled_graph: nx.Graph, + original_graph: gt.Graph, + dismantled_graph: gt.Graph, metrics: List[EvaluationMetric], ) -> Dict[str, float]: """ @@ -67,8 +68,8 @@ class RelativeChangeStrategy(EvaluationStrategy): def evaluate( self, - original_graph: nx.Graph, - dismantled_graph: nx.Graph, + original_graph: gt.Graph, + dismantled_graph: gt.Graph, metrics: List[EvaluationMetric], ) -> Dict[str, float]: """ @@ -99,8 +100,8 @@ class AbsoluteValueStrategy(EvaluationStrategy): def evaluate( self, - original_graph: nx.Graph, - dismantled_graph: nx.Graph, + original_graph: gt.Graph, + dismantled_graph: gt.Graph, metrics: List[EvaluationMetric], ) -> Dict[str, float]: """ @@ -140,7 +141,7 @@ def __init__(self, metrics: List[EvaluationMetric], strategy: EvaluationStrategy self.strategy = strategy def evaluate( - self, original_graph: nx.Graph, dismantled_graph: nx.Graph + self, original_graph: gt.Graph, dismantled_graph: gt.Graph ) -> Dict[str, float]: """ Evaluate the performance of a dismantling strategy. @@ -153,7 +154,7 @@ def evaluate( @staticmethod def compare_strategies( - original_graph: nx.Graph, + original_graph: gt.Graph, dismantlers: List[NetworkDismantler], num_nodes_to_remove: int, evaluator: "DismantlingEvaluator", diff --git a/network_dismantling/evaluators/global_effiency.py b/network_dismantling/evaluators/global_effiency.py index 4da4b75..38ac10a 100644 --- a/network_dismantling/evaluators/global_effiency.py +++ b/network_dismantling/evaluators/global_effiency.py @@ -1,7 +1,6 @@ -import networkx as nx +import graph_tool.all as gt import numpy as np -from typing import List, Dict, Callable -from abc import ABC, abstractmethod +from typing import List, Dict from network_dismantling.evaluators.evaluator import EvaluationMetric @@ -14,14 +13,14 @@ class GlobalEfficiencyMetric(EvaluationMetric): average of the inverse shortest path lengths between all pairs of nodes in the graph. """ - def compute(self, G: nx.Graph) -> float: + def compute(self, G: gt.Graph) -> float: """ Compute the global efficiency of the graph. :param G: The graph. :return: The global efficiency of the graph. """ - return nx.global_efficiency(G) + return gt.global_efficiency(G) @property def name(self) -> str: diff --git a/network_dismantling/evaluators/lcc_size.py b/network_dismantling/evaluators/lcc_size.py index d1adcda..c107366 100644 --- a/network_dismantling/evaluators/lcc_size.py +++ b/network_dismantling/evaluators/lcc_size.py @@ -1,5 +1,5 @@ -import networkx as nx -import numpy as np +import graph_tool.all as gt + from typing import List, Dict, Callable from abc import ABC, abstractmethod @@ -13,14 +13,14 @@ class LCCSizeMetric(EvaluationMetric): The size of the largest connected component is a measure of the size of the largest connected subgraph of the graph. """ - def compute(self, G: nx.Graph) -> float: + def compute(self, G: gt.Graph) -> float: """ Compute the size of the largest connected component of the graph. :param G: The graph. :return: The size of the largest connected component of the graph. """ - return len(max(nx.connected_components(G), key=len)) + return len(max(gt.connected_components(G), key=len)) @property def name(self) -> str: diff --git a/network_dismantling/evaluators/num_components.py b/network_dismantling/evaluators/num_components.py index 419ec19..50d153a 100644 --- a/network_dismantling/evaluators/num_components.py +++ b/network_dismantling/evaluators/num_components.py @@ -1,8 +1,6 @@ -import networkx as nx -import numpy as np -from typing import List, Dict, Callable -from abc import ABC, abstractmethod +import graph_tool.all as gt +from typing import List, Dict from network_dismantling.evaluators.evaluator import EvaluationMetric @@ -13,14 +11,14 @@ class NumComponentsMetric(EvaluationMetric): The number of connected components is a measure of the number of connected subgraphs in the graph. """ - def compute(self, G: nx.Graph) -> float: + def compute(self, G: gt.Graph) -> float: """ Compute the number of connected components of the graph. :param G: The graph. :return: The number of connected components of the graph. """ - return nx.number_connected_components(G) + return gt.number_connected_components(G) @property def name(self) -> str: diff --git a/network_dismantling/node_selectors/__init__.py b/network_dismantling/node_selectors/__init__.py index 3d81310..366b7c5 100644 --- a/network_dismantling/node_selectors/__init__.py +++ b/network_dismantling/node_selectors/__init__.py @@ -1,8 +1,11 @@ -from .basic import BruteForce -from .centrality import Betweenness, Degree -from .gdm import GDM, CoreGDM -from .gnd import GND, CoreHD -from .influence import CollectiveInfluence, ExplosiveImmunization +from network_dismantling.node_selectors.basic import BruteForce +from network_dismantling.node_selectors.centrality import Betweenness, Degree +from network_dismantling.node_selectors.gdm import GDM, CoreGDM +from network_dismantling.node_selectors.gnd import GND, CoreHD +from network_dismantling.node_selectors.influence import ( + CollectiveInfluence, + ExplosiveImmunization, +) __all__ = [ "BruteForce", diff --git a/network_dismantling/node_selectors/basic.py b/network_dismantling/node_selectors/basic.py index 03dc269..387f008 100644 --- a/network_dismantling/node_selectors/basic.py +++ b/network_dismantling/node_selectors/basic.py @@ -1,5 +1,5 @@ -import networkx as nx import itertools +import graph_tool.all as gt from typing import List from .node_selector import NodeSelector @@ -24,7 +24,7 @@ def __init__(self, target_size_ratio=0.1, max_depth=None): self.target_size_ratio = target_size_ratio self.max_depth = max_depth - def select(self, G: nx.Graph, num_nodes: int) -> List[int]: + def select(self, G: gt.Graph, num_nodes: int) -> List[int]: """ Select nodes to remove from the graph. @@ -48,7 +48,7 @@ def select(self, G: nx.Graph, num_nodes: int) -> List[int]: # If no solution found within max_depth, return the best found so far return self.greedy_fallback(G, num_nodes) - def is_dismantled(self, G: nx.Graph, target_size: int) -> bool: + def is_dismantled(self, G: gt.Graph, target_size: int) -> bool: """ Check if the graph is dismantled. @@ -56,10 +56,10 @@ def is_dismantled(self, G: nx.Graph, target_size: int) -> bool: :param target_size: The target size of the largest connected component. :return: True if the largest connected component is smaller than the target size, False otherwise. """ - largest_cc = max(nx.connected_components(G), key=len) + largest_cc = max(gt.connected_components(G), key=len) return len(largest_cc) <= target_size - def greedy_fallback(self, G: nx.Graph, num_nodes: int) -> List[int]: + def greedy_fallback(self, G: gt.Graph, num_nodes: int) -> List[int]: """ Greedy fallback dismantling strategy. diff --git a/network_dismantling/node_selectors/centrality.py b/network_dismantling/node_selectors/centrality.py index 1805774..3ea409a 100644 --- a/network_dismantling/node_selectors/centrality.py +++ b/network_dismantling/node_selectors/centrality.py @@ -1,15 +1,16 @@ +import graph_tool.all as gt from typing import Any, List -import networkx as nx + from .node_selector import NodeSelector class Betweenness(NodeSelector): - def select(self, G: nx.Graph, num_nodes: int) -> List[Any]: - betweenness = nx.betweenness_centrality(G) + def select(self, G: gt.Graph, num_nodes: int) -> List[Any]: + betweenness = gt.betweenness_centrality(G) return sorted(betweenness, key=betweenness.get, reverse=True)[:num_nodes] class Degree(NodeSelector): - def select(self, G: nx.Graph, num_nodes: int) -> List[Any]: + def select(self, G: gt.Graph, num_nodes: int) -> List[Any]: return sorted(G.degree, key=lambda x: x[1], reverse=True)[:num_nodes] diff --git a/network_dismantling/node_selectors/gdm.py b/network_dismantling/node_selectors/gdm.py index fc82122..cba1b25 100644 --- a/network_dismantling/node_selectors/gdm.py +++ b/network_dismantling/node_selectors/gdm.py @@ -1,4 +1,4 @@ -import networkx as nx +import graph_tool.all as gt import torch import torch.nn as nn import torch.nn.functional as F @@ -119,7 +119,7 @@ def train_model(self): if self.early_stopper is not None and self.early_stopper.should_stop(loss): break - def select(self, G: nx.Graph, num_nodes: int) -> List[int]: + def select(self, G: gt.Graph, num_nodes: int) -> List[int]: """ Dismantle the graph by removing nodes based on their GDM score. @@ -139,7 +139,7 @@ def select(self, G: nx.Graph, num_nodes: int) -> List[int]: )[:num_nodes] return [node for node, _ in nodes_to_remove] - def compute_omega(self, G: nx.Graph, removed_nodes: List[int]) -> float: + def compute_omega(self, G: gt.Graph, removed_nodes: List[int]) -> float: """ Compute the GDM dismantling efficiency of the removed nodes. @@ -163,10 +163,12 @@ def extract_features(G): :return: A tensor of node features. """ features = [] + local_clustering = gt.local_clustering(G) + kcore_decomposition = gt.kcore_decomposition(G) for node in G.nodes(): degree = G.degree(node) - clustering = nx.clustering(G, node) - k_core = nx.core_number(G)[node] + clustering = local_clustering[node] + k_core = kcore_decomposition[node] chi_square = ( sum((G.degree(n) - degree) ** 2 for n in G.neighbors(node)) / degree if degree > 0 @@ -175,26 +177,6 @@ def extract_features(G): features.append([degree, clustering, k_core, chi_square]) return torch.tensor(features, dtype=torch.float) - @staticmethod - def generate_synthetic_networks(num_networks, num_nodes): - """ - Generate synthetic networks for training the GDM model. - - :param num_networks: The number of synthetic networks to generate. - :param num_nodes: The number of nodes in each synthetic network. - :return: A list of synthetic networks. - """ - networks = [] - for _ in range(num_networks): - if np.random.random() < 0.33: - G = nx.barabasi_albert_graph(num_nodes, 3) - elif np.random.random() < 0.66: - G = nx.erdos_renyi_graph(num_nodes, 0.1) - else: - G = nx.watts_strogatz_graph(num_nodes, 4, 0.1) - networks.append(G) - return networks - class GCNScoreModel(nn.Module): """ @@ -250,13 +232,13 @@ def __init__( self.early_stopper = early_stopper self.train_model() - def select(self, G: nx.Graph, num_nodes: int) -> List[int]: + def select(self, G: gt.Graph, num_nodes: int) -> List[int]: nodes_to_remove = [] G_copy = G.copy() while len(nodes_to_remove) < num_nodes: # Get 2-core of the network - two_core = nx.k_core(G_copy, k=2) + two_core = gt.k_core(G_copy, k=2) if len(two_core) == 0: # If 2-core is empty, remove nodes from the remaining graph remaining_nodes = list(G_copy.nodes()) @@ -289,7 +271,7 @@ def train_model(self): for epoch in tqdm(range(100), desc="Training CoreGDM model"): total_loss = 0 for G in train_networks: - two_core = nx.k_core(G, k=2) + two_core = gt.k_core(G, k=2) if len(two_core) == 0: continue @@ -318,9 +300,9 @@ def train_model(self): def prepare_data(self, G): features = [] - clustering_mapping = nx.clustering(G) - eigenvector_centrality_mapping = nx.eigenvector_centrality_numpy(G) - core_number_mapping = nx.core_number(G) + clustering_mapping = gt.clustering(G) + eigenvector_centrality_mapping = gt.eigenvector_centrality_numpy(G) + core_number_mapping = gt.core_number(G) for node in G.nodes(): degree = G.degree(node) clustering = clustering_mapping[node] @@ -338,10 +320,10 @@ def generate_synthetic_networks(num_networks, num_nodes): networks = [] for _ in range(num_networks): if np.random.random() < 0.33: - G = nx.barabasi_albert_graph(num_nodes, 3) + G = gt.barabasi_albert_graph(num_nodes, 3) elif np.random.random() < 0.66: - G = nx.erdos_renyi_graph(num_nodes, 0.1) + G = gt.erdos_renyi_graph(num_nodes, 0.1) else: - G = nx.watts_strogatz_graph(num_nodes, 4, 0.1) + G = gt.watts_strogatz_graph(num_nodes, 4, 0.1) networks.append(G) return networks diff --git a/network_dismantling/node_selectors/gnd.py b/network_dismantling/node_selectors/gnd.py index d14db36..0e88672 100644 --- a/network_dismantling/node_selectors/gnd.py +++ b/network_dismantling/node_selectors/gnd.py @@ -1,8 +1,8 @@ -import networkx as nx +import graph_tool.all as gt import numpy as np from typing import List -from scipy.sparse import csr_matrix # type: ignore -from tqdm import tqdm # type: ignore +from scipy.sparse import csr_matrix +from tqdm import tqdm from .node_selector import NodeSelector @@ -27,7 +27,7 @@ def __init__(self, c=1, epsilon=0.01, max_iterations=100): self.epsilon = epsilon self.max_iterations = max_iterations - def select(self, G: nx.Graph, num_nodes: int) -> List[int]: + def select(self, G: gt.Graph, num_nodes: int) -> List[int]: """ Dismantle the graph by removing nodes based on their GND score. @@ -35,7 +35,7 @@ def select(self, G: nx.Graph, num_nodes: int) -> List[int]: :param num_nodes: The number of nodes to remove. :return: A list of node indices to remove. """ - A = nx.adjacency_matrix(G) + A = gt.adjacency_matrix(G) N = A.shape[0] D = np.array(A.sum(axis=1)).flatten() L = csr_matrix(np.diag(D) - A) @@ -63,7 +63,7 @@ class CoreHD(NodeSelector): to. The nodes with the highest core number are removed first. """ - def select(self, G: nx.Graph, num_nodes: int) -> List[int]: + def select(self, G: gt.Graph, num_nodes: int) -> List[int]: """ Dismantle the graph by removing nodes based on their core number. @@ -71,7 +71,7 @@ def select(self, G: nx.Graph, num_nodes: int) -> List[int]: :param num_nodes: The number of nodes to remove. :return: A list of node indices to remove. """ - core_numbers = nx.core_number(G) + core_numbers = gt.core_number(G) degrees = dict(G.degree()) nodes_to_remove = [] @@ -86,7 +86,7 @@ def select(self, G: nx.Graph, num_nodes: int) -> List[int]: G.remove_node(node_to_remove) # Update core numbers and degrees - core_numbers = nx.core_number(G) + core_numbers = gt.core_number(G) degrees = dict(G.degree()) return nodes_to_remove diff --git a/network_dismantling/node_selectors/influence.py b/network_dismantling/node_selectors/influence.py index 37ef0e9..1dabcef 100644 --- a/network_dismantling/node_selectors/influence.py +++ b/network_dismantling/node_selectors/influence.py @@ -1,8 +1,9 @@ +import graph_tool.all as gt + from collections import defaultdict from typing import List -import networkx as nx -from .node_selector import NodeSelector +from network_dismantling.node_selectors.node_selector import NodeSelector class CollectiveInfluence(NodeSelector): @@ -21,7 +22,7 @@ def __init__(self, l=2): # noqa: E741 """ self.l = l # Ball radius - def select(self, G: nx.Graph, num_nodes: int) -> List[int]: + def select(self, G: gt.Graph, num_nodes: int) -> List[int]: """ Dismantle the graph by removing nodes based on their collective influence. @@ -52,7 +53,7 @@ def compute_collective_influence(self, G): ci_scores = {} for node in G.nodes(): ki = G.degree(node) - ball = nx.single_source_shortest_path_length(G, node, cutoff=self.l) + ball = gt.single_source_shortest_path_length(G, node, cutoff=self.l) ball_boundary = [n for n in ball if ball[n] == self.l] ci = (ki - 1) * sum(G.degree(v) - 1 for v in ball_boundary) ci_scores[node] = ci @@ -78,7 +79,7 @@ def __init__(self, q=0.1, num_iterations=10): self.q = q # Fraction of nodes to remove in each iteration self.num_iterations = num_iterations - def select(self, G: nx.Graph, num_nodes: int) -> List[int]: + def select(self, G: gt.Graph, num_nodes: int) -> List[int]: """ Dismantle the graph by removing nodes based on their explosive immunization score. @@ -112,7 +113,7 @@ def compute_ei_scores(self, G): """ scores = defaultdict(float) for _ in range(self.num_iterations): - components = list(nx.connected_components(G)) + components = list(gt.connected_components(G)) for component in components: if len(component) > 1: subgraph = G.subgraph(component) @@ -131,5 +132,5 @@ def compute_s(self, G, node): """ G_copy = G.copy() G_copy.remove_node(node) - new_components = list(nx.connected_components(G_copy)) + new_components = list(gt.connected_components(G_copy)) return sum(len(c) * (len(c) - 1) for c in new_components) diff --git a/network_dismantling/node_selectors/node_selector.py b/network_dismantling/node_selectors/node_selector.py index ed97388..b922106 100644 --- a/network_dismantling/node_selectors/node_selector.py +++ b/network_dismantling/node_selectors/node_selector.py @@ -1,4 +1,4 @@ -import networkx as nx +import graph_tool.all as gt from abc import abstractmethod from typing import Any, List @@ -13,7 +13,7 @@ class NodeSelector(ElementSelector): """ @abstractmethod - def select(self, G: nx.Graph, num_nodes: int) -> List[Any]: + def select(self, G: gt.Graph, num_nodes: int) -> List[Any]: """ Select nodes from the graph. diff --git a/network_dismantling/operators/__init__.py b/network_dismantling/operators/__init__.py index 86831e9..05e2fff 100644 --- a/network_dismantling/operators/__init__.py +++ b/network_dismantling/operators/__init__.py @@ -1,4 +1,4 @@ -from .operator import DismantlingOperator -from .basic import NodeRemovalOperator, EdgeRemovalOperator +from network_dismantling.operators.operator import DismantlingOperator +from network_dismantling.operators.basic import NodeRemovalOperator, EdgeRemovalOperator __all__ = ["DismantlingOperator", "NodeRemovalOperator", "EdgeRemovalOperator"] diff --git a/network_dismantling/operators/basic.py b/network_dismantling/operators/basic.py index 01a320f..eccda02 100644 --- a/network_dismantling/operators/basic.py +++ b/network_dismantling/operators/basic.py @@ -1,7 +1,6 @@ -from collections import deque -import networkx as nx - from typing import Any, List +import graph_tool.all as gt +from collections import deque from network_dismantling.operators.operator import DismantlingOperator @@ -12,7 +11,7 @@ class NodeRemovalOperator(DismantlingOperator): This dismantling operator removes nodes from the graph. """ - def operate(self, G: nx.Graph, elements: List[Any]) -> nx.Graph: + def operate(self, G: gt.Graph, elements: List[Any]) -> gt.Graph: """ Remove nodes from the graph. @@ -21,11 +20,11 @@ def operate(self, G: nx.Graph, elements: List[Any]) -> nx.Graph: :return: The modified graph """ H = G.copy() - H.remove_nodes_from(elements) + H.remove_vertex(elements) return H class TemporalNodeNeighborEgdesRemovalOperator(DismantlingOperator): - def operate(self, G: nx.Graph, elements: List[Any]) -> nx.Graph: + def operate(self, G: gt.Graph, elements: List[Any]) -> gt.Graph: """ Remove nodes from the graph. @@ -119,7 +118,7 @@ class EdgeRemovalOperator(DismantlingOperator): This dismantling operator removes edges from the graph. """ - def operate(self, G: nx.Graph, elements: List[Any]) -> nx.Graph: + def operate(self, G: gt.Graph, elements: List[Any]) -> gt.Graph: """ Remove edges from the graph. @@ -138,7 +137,7 @@ class TemporalEdgeRemovalOperator(DismantlingOperator): This dismantling operator removes edges from the graph. """ - def operate(self, G: nx.Graph, elements: List[Any]) -> nx.Graph: + def operate(self, G: gt.Graph, elements: List[Any]) -> gt.Graph: """ Remove edges from the graph. diff --git a/network_dismantling/operators/operator.py b/network_dismantling/operators/operator.py index cf3c237..f7f5577 100644 --- a/network_dismantling/operators/operator.py +++ b/network_dismantling/operators/operator.py @@ -1,11 +1,12 @@ +import graph_tool.all as gt + from abc import ABC, abstractmethod from typing import Any, List -import networkx as nx class DismantlingOperator(ABC): @abstractmethod - def operate(self, G: nx.Graph, elements: List[Any]) -> nx.Graph: + def operate(self, G: gt.Graph, elements: List[Any]) -> gt.Graph: """ Perform the dismantling operation on the graph. diff --git a/network_dismantling/selector.py b/network_dismantling/selector.py index fd49d05..8b65b7e 100644 --- a/network_dismantling/selector.py +++ b/network_dismantling/selector.py @@ -1,11 +1,12 @@ -import networkx as nx +import graph_tool.all as gt + from abc import ABC, abstractmethod from typing import Any, List class ElementSelector(ABC): @abstractmethod - def select(self, G: nx.Graph, num_elements: int) -> List[Any]: + def select(self, G: gt.Graph, num_elements: int) -> List[Any]: """ Select elements (nodes or edges) from the graph. diff --git a/network_dismantling/utils/__init__.py b/network_dismantling/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/network_dismantling/utils/data.py b/network_dismantling/utils/data.py new file mode 100644 index 0000000..22fa316 --- /dev/null +++ b/network_dismantling/utils/data.py @@ -0,0 +1,105 @@ +from typing import List +import graph_tool.all as gt +import os +import numpy as np +import requests +from tqdm import tqdm + + +def _download_with_tqdm(url: str, fname: str): + resp = requests.get(url, stream=True) + total = int(resp.headers.get("content-length", 0)) + + with open(fname, "wb") as file, tqdm( + desc=fname, + total=total, + unit="iB", + unit_scale=True, + unit_divisor=1024, + ) as bar: + for data in resp.iter_content(chunk_size=1024): + size = file.write(data) + bar.update(size) + + +class RealWorldDatasetFetcher: + def __init__( + self, + dataset_name: str, + dataset_path_prefix: str = "./network_dismantling_data", + ): + self.dataset_name = dataset_name + ".gt.xz" + self.dataset_path = os.path.abspath( + os.path.join(dataset_path_prefix, "realworld", dataset_name) + ) + + def fetch(self): + url = ( + "https://github.com/NetworkDismantling/review/raw/refs/heads/main/dataset/test_review/" + + self.dataset_name + + ".gt.xz" + ) + # download with progress bar + if not os.path.exists(self.dataset_path): + print( + f"Downloading {self.dataset_name} dataset and saving it to {self.dataset_path}" + ) + _download_with_tqdm(url, self.dataset_path) + print("Download completed") + + def load(self): + return gt.load_graph(self.dataset_path) + + +class SyntheticDatasetGenerator: + def __init__( + self, + dataset_name: str, + dataset_path_prefix: str = "./network_dismantling_data", + ): + self.dataset_name = dataset_name + ".gt.xz" + self.dataset_path = os.path.abspath( + os.path.join(dataset_path_prefix, "synthetic", dataset_name) + ) + + @staticmethod + def generate_synthetic_networks(num_networks: int, num_nodes: int) -> List[gt.Graph]: + """ + Generate synthetic networks for training the GDM model using graph-tool. + + Args: + num_networks: The number of synthetic networks to generate + num_nodes: The number of nodes in each synthetic network + + Returns: + List of synthetic networks as graph-tool Graph objects + """ + networks = [] + for _ in range(num_networks): + rand = np.random.random() + if rand < 0.33: + # Barabási-Albert model using Price network + # m=3 new edges per vertex + G = gt.price_network(num_nodes, m=3, directed=False) + + elif rand < 0.66: + # Erdős-Rényi model + # p=0.1 edge probability + G = gt.random_graph(num_nodes, + lambda: np.random.binomial(1, 0.1), + directed=False) + + else: + # Watts-Strogatz model + # First create regular ring lattice + G = gt.lattice([num_nodes], dim=1, k=4, periodic=True) + # Then rewire with probability 0.1 + gt.random_rewire(G, model="erdos", + n_iter=G.num_edges()*0.1, + edge_sweep=True) + + networks.append(G) + return networks + + def load(self): + return gt.load_graph(self.dataset_path) \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 1147574..4fb530e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "alabaster" @@ -13,13 +13,13 @@ files = [ [[package]] name = "astroid" -version = "3.3.2" +version = "3.3.5" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.9.0" files = [ - {file = "astroid-3.3.2-py3-none-any.whl", hash = "sha256:9f8136ce9770e0f912401b25a0f15d5c2ec20b50e99b1b413ac0778fe53ff6f1"}, - {file = "astroid-3.3.2.tar.gz", hash = "sha256:99e9b5b602cbb005434084309213d6af32bf7a9b743c836749168b8e2b330cbd"}, + {file = "astroid-3.3.5-py3-none-any.whl", hash = "sha256:a9d1c946ada25098d790e079ba2a1b112157278f3fb7e718ae6a9252f5835dc8"}, + {file = "astroid-3.3.5.tar.gz", hash = "sha256:5cfc40ae9f68311075d27ef68a4841bdc5cc7f6cf86671b49f00607d30188e2d"}, ] [package.dependencies] @@ -41,33 +41,33 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "black" -version = "24.8.0" +version = "24.10.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, ] [package.dependencies] @@ -81,118 +81,133 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2024.7.4" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] [[package]] @@ -233,15 +248,18 @@ files = [ [[package]] name = "idna" -version = "3.7" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "imagesize" version = "1.4.1" @@ -272,120 +290,74 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "markupsafe" -version = "2.1.5" +version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "mypy" -version = "1.11.1" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"}, - {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"}, - {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"}, - {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"}, - {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"}, - {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"}, - {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"}, - {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"}, - {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"}, - {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"}, - {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"}, - {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"}, - {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"}, - {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"}, - {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"}, - {file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"}, - {file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"}, - {file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"}, - {file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"}, - {file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"}, - {file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"}, - {file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"}, - {file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"}, - {file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"}, - {file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"}, - {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"}, - {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - [[package]] name = "mypy-extensions" version = "1.0.0" @@ -397,33 +369,15 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -[[package]] -name = "networkx" -version = "3.3" -description = "Python package for creating and manipulating graphs and networks" -optional = false -python-versions = ">=3.10" -files = [ - {file = "networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"}, - {file = "networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9"}, -] - -[package.extras] -default = ["matplotlib (>=3.6)", "numpy (>=1.23)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] -developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] -doc = ["myst-nb (>=1.0)", "numpydoc (>=1.7)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=2.0)", "pygraphviz (>=1.12)", "sympy (>=1.10)"] -test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] - [[package]] name = "packaging" -version = "24.1" +version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] @@ -439,19 +393,19 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.2" +version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] [[package]] name = "pygments" @@ -598,13 +552,13 @@ test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools [[package]] name = "sphinx-autoapi" -version = "3.2.1" +version = "3.3.3" description = "Sphinx API documentation generator" optional = false python-versions = ">=3.8" files = [ - {file = "sphinx_autoapi-3.2.1-py2.py3-none-any.whl", hash = "sha256:72fe556abc579528a46494f4fcbeaeaaf3e0b031f6514f7b496f6c36754c5430"}, - {file = "sphinx_autoapi-3.2.1.tar.gz", hash = "sha256:1f9d56b3a98d5653d1fad5644abeed2c042cec304a126ef72c236dae4af16b90"}, + {file = "sphinx_autoapi-3.3.3-py3-none-any.whl", hash = "sha256:5c7349b42d45a492a611cb81fb48583d5148e9eab7fc6b1f326dc9273b9191e3"}, + {file = "sphinx_autoapi-3.3.3.tar.gz", hash = "sha256:c44fd719580e9a3684ff82019f4f7f39fc970e3030ffd325936654a6f4d31f22"}, ] [package.dependencies] @@ -616,9 +570,6 @@ Jinja2 = "*" PyYAML = "*" sphinx = ">=6.1.0" -[package.extras] -docs = ["furo", "sphinx", "sphinx-design"] - [[package]] name = "sphinx-rtd-theme" version = "2.0.0" @@ -748,13 +699,13 @@ test = ["pytest"] [[package]] name = "tomli" -version = "2.0.1" +version = "2.1.0" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, + {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, ] [[package]] @@ -770,13 +721,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.2" +version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] @@ -788,4 +739,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "981a9c4a9a879e9a99093f46c9b4cc58ca62175544981540b9b506f3290fc8e1" +content-hash = "4212fd2bc713f8294c353dc5343e50eed2d40f32671ca93c8317070522302543" diff --git a/pyproject.toml b/pyproject.toml index 2befb0f..504f6a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,11 +8,9 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.10" -networkx = "^3.3" [tool.poetry.group.dev.dependencies] black = "*" -mypy = "*" [tool.poetry.group.docs.dependencies] sphinx = "^7.3.7" From 4f8a86186f69190de03dd0ae54ac0a74a5048cb9 Mon Sep 17 00:00:00 2001 From: Command M Date: Fri, 22 Nov 2024 22:09:13 +0800 Subject: [PATCH 2/2] bugfix --- examples/heuristic.py | 7 +- network_dismantling/__init__.py | 50 ++++++---- network_dismantling/dismantler.py | 1 - .../edge_selectors/edge_selector.py | 5 +- .../evaluators/avg_clustering.py | 3 +- .../evaluators/avg_path_length.py | 5 +- network_dismantling/evaluators/evaluator.py | 12 +-- .../evaluators/global_effiency.py | 36 ++++++- network_dismantling/evaluators/lcc_size.py | 6 +- .../evaluators/num_components.py | 3 +- network_dismantling/node_selectors/basic.py | 4 +- .../node_selectors/centrality.py | 4 +- network_dismantling/node_selectors/gnd.py | 13 +-- .../node_selectors/influence.py | 98 ++++++++++++------- .../node_selectors/node_selector.py | 6 +- network_dismantling/operators/basic.py | 6 +- network_dismantling/operators/operator.py | 1 - network_dismantling/selector.py | 1 - network_dismantling/utils/data.py | 30 +++--- 19 files changed, 181 insertions(+), 110 deletions(-) diff --git a/examples/heuristic.py b/examples/heuristic.py index 601eed7..422ad9e 100644 --- a/examples/heuristic.py +++ b/examples/heuristic.py @@ -1,4 +1,5 @@ -import networkx as nx +import graph_tool.all as gt + from network_dismantling.node_selectors import ( CollectiveInfluence, ExplosiveImmunization, @@ -19,7 +20,7 @@ # Example usage if __name__ == "__main__": # Create a test graph - G = nx.karate_club_graph() + G = gt.collection.data["karate"] # Initialize different dismantling strategies dismantlers = [ @@ -48,7 +49,7 @@ evaluator = DismantlingEvaluator(metrics, evaluation_strategy) # Compare strategies - num_nodes_to_remove = int(G.number_of_nodes() * 0.1) # Remove 10% of nodes + num_nodes_to_remove = int(G.num_vertices() * 0.1) # Remove 10% of nodes comparison_results = DismantlingEvaluator.compare_strategies( G, dismantlers, num_nodes_to_remove, evaluator ) diff --git a/network_dismantling/__init__.py b/network_dismantling/__init__.py index 35ed1ac..7d68daf 100644 --- a/network_dismantling/__init__.py +++ b/network_dismantling/__init__.py @@ -1,43 +1,57 @@ +from network_dismantling.dismantler import DismantlingStrategy, NetworkDismantler +from network_dismantling.operators.operator import DismantlingOperator +from network_dismantling.selector import ElementSelector + + def require_gt(): - ''' + """ Check graph-tool is installed - ''' + """ try: import graph_tool.all as gt - except ImportError: - raise ImportError("Please install graph-tool to use network_dismantling. You can install it as the instructions in https://graph-tool.skewed.de/installation.html") + + _ = gt + except ImportError as exc: + raise ImportError( + "Please install graph-tool to use network_dismantling. You can install it as the instructions in https://graph-tool.skewed.de/installation.html" + ) from exc + def require_pytorch(): - ''' + """ Check PyTorch is installed - ''' + """ try: import torch - except ImportError: - raise ImportError("Please install PyTorch to use network_dismantling. You can install it as the instructions in https://pytorch.org/get-started/locally/") + + _ = torch + except ImportError as exc: + raise ImportError( + "Please install PyTorch to use network_dismantling. You can install it as the instructions in https://pytorch.org/get-started/locally/" + ) from exc + def require_pyg(): - ''' + """ Check PyTorch Geometric is installed - ''' + """ try: import torch_geometric - except ImportError: - raise ImportError("Please install PyTorch Geometric to use network_dismantling. You can install it as the instructions in https://pytorch-geometric.readthedocs.io/en/latest/notes/installation.html") + + _ = torch_geometric + except ImportError as exc: + raise ImportError( + "Please install PyTorch Geometric to use network_dismantling. You can install it as the instructions in https://pytorch-geometric.readthedocs.io/en/latest/notes/installation.html" + ) from exc + require_gt() require_pytorch() require_pyg() -from network_dismantling.dismantler import DismantlingStrategy, NetworkDismantler -from network_dismantling.operators.operator import DismantlingOperator -from network_dismantling.selector import ElementSelector - __all__ = [ "DismantlingStrategy", "NetworkDismantler", - "EvaluationMetric", - "EvaluationStrategy", "DismantlingOperator", "ElementSelector", ] diff --git a/network_dismantling/dismantler.py b/network_dismantling/dismantler.py index 50cdc6e..d5c401b 100644 --- a/network_dismantling/dismantler.py +++ b/network_dismantling/dismantler.py @@ -23,7 +23,6 @@ def dismantle(self, G: gt.Graph, num_nodes: int) -> List[int]: :param num_nodes: Number of nodes to remove :return: List of node IDs to remove """ - pass class NetworkDismantler: diff --git a/network_dismantling/edge_selectors/edge_selector.py b/network_dismantling/edge_selectors/edge_selector.py index f24466e..3c4a757 100644 --- a/network_dismantling/edge_selectors/edge_selector.py +++ b/network_dismantling/edge_selectors/edge_selector.py @@ -12,12 +12,11 @@ class EdgeSelector(ElementSelector): """ @abstractmethod - def select(self, G: gt.Graph, num_edges: int) -> List[Tuple[Any, Any]]: + def select(self, G: gt.Graph, num_elements: int) -> List[Tuple[Any, Any]]: """ Select edges from the graph. :param G: The input graph - :param num_edges: Number of edges to select + :param num_elements: Number of edges to select :return: List of selected edges """ - pass diff --git a/network_dismantling/evaluators/avg_clustering.py b/network_dismantling/evaluators/avg_clustering.py index 4adad7e..bb7e5a8 100644 --- a/network_dismantling/evaluators/avg_clustering.py +++ b/network_dismantling/evaluators/avg_clustering.py @@ -18,7 +18,8 @@ def compute(self, G: gt.Graph) -> float: :param G: The graph. :return: The average clustering coefficient of the graph. """ - return gt.average_clustering(G) + clustering = gt.local_clustering(G) + return clustering.a.mean() @property def name(self) -> str: diff --git a/network_dismantling/evaluators/avg_path_length.py b/network_dismantling/evaluators/avg_path_length.py index b4d9c53..f9ad705 100644 --- a/network_dismantling/evaluators/avg_path_length.py +++ b/network_dismantling/evaluators/avg_path_length.py @@ -19,8 +19,9 @@ def compute(self, G: gt.Graph) -> float: :param G: The graph. :return: The average path length of the graph. """ - lcc = max(gt.connected_components(G), key=len) - return gt.average_shortest_path_length(G.subgraph(lcc)) + lcc = gt.extract_largest_component(G) + distances = gt.shortest_distance(lcc) + return distances[distances != float("inf")].a.mean() @property def name(self) -> str: diff --git a/network_dismantling/evaluators/evaluator.py b/network_dismantling/evaluators/evaluator.py index 7ec3d25..04f9569 100644 --- a/network_dismantling/evaluators/evaluator.py +++ b/network_dismantling/evaluators/evaluator.py @@ -22,7 +22,6 @@ def compute(self, G: gt.Graph) -> float: :param G: The graph. :return: The value of the evaluation metric. """ - pass @property @abstractmethod @@ -32,7 +31,6 @@ def name(self) -> str: :return: The name of the evaluation metric. """ - pass class EvaluationStrategy(ABC): @@ -58,7 +56,6 @@ def evaluate( :param metrics: The evaluation metrics to compute. :return: A dictionary mapping evaluation metric names to their values. """ - pass class RelativeChangeStrategy(EvaluationStrategy): @@ -87,9 +84,11 @@ def evaluate( dismantled_value = metric.compute(dismantled_graph) relative_change = (dismantled_value - original_value) / original_value results[metric.name] = relative_change + results["Removed Nodes"] = ( - 1 - dismantled_graph.number_of_nodes() / original_graph.number_of_nodes() - ) + original_graph.num_vertices() - dismantled_graph.num_vertices() + ) / original_graph.num_vertices() + return results @@ -165,6 +164,7 @@ def compare_strategies( original_graph, num_nodes_to_remove ) evaluation = evaluator.evaluate(original_graph, dismantled_graph) - results[dismantler.__class__.__name__] = evaluation + strategy_name = dismantler.selector.__class__.__name__ + " with " + dismantler.operator.__class__.__name__ + results[strategy_name] = evaluation return results diff --git a/network_dismantling/evaluators/global_effiency.py b/network_dismantling/evaluators/global_effiency.py index 38ac10a..12341c2 100644 --- a/network_dismantling/evaluators/global_effiency.py +++ b/network_dismantling/evaluators/global_effiency.py @@ -1,16 +1,15 @@ import graph_tool.all as gt import numpy as np -from typing import List, Dict from network_dismantling.evaluators.evaluator import EvaluationMetric - class GlobalEfficiencyMetric(EvaluationMetric): """ Evaluation metric that computes the global efficiency of a graph. - The global efficiency is a measure of the efficiency of information transfer in a graph. The global efficiency is the - average of the inverse shortest path lengths between all pairs of nodes in the graph. + The global efficiency is a measure of the efficiency of information transfer in a graph. + It is defined as the average of the inverse shortest path lengths between all pairs of nodes + in the graph. """ def compute(self, G: gt.Graph) -> float: @@ -20,7 +19,33 @@ def compute(self, G: gt.Graph) -> float: :param G: The graph. :return: The global efficiency of the graph. """ - return gt.global_efficiency(G) + N = G.num_vertices() + if N <= 1: + return 0.0 # Efficiency is zero for graphs with one or no nodes. + + # Compute the shortest distances between all pairs of nodes + dist_matrix = gt.shortest_distance(G, weights=None, directed=False) + + # Initialize efficiency sum + efficiency_sum = 0.0 + + # Iterate over all pairs of vertices + for v in G.vertices(): + # Extract distances as a NumPy array and convert to float type + distances = dist_matrix[v].get_array().astype(float) + # Exclude self-distance by setting it to infinity + distances[int(v)] = np.inf + # Compute inverse distances, handling infinities + with np.errstate(divide='ignore', invalid='ignore'): + inv_distances = 1.0 / distances + inv_distances[~np.isfinite(inv_distances)] = 0.0 # Set infinities and NaNs to zero + # Sum up the efficiencies for this vertex + efficiency_sum += np.sum(inv_distances) + + # Calculate global efficiency + global_efficiency = efficiency_sum / (N * (N - 1)) + + return global_efficiency @property def name(self) -> str: @@ -30,3 +55,4 @@ def name(self) -> str: :return: The name of the evaluation metric. """ return "Global Efficiency" + \ No newline at end of file diff --git a/network_dismantling/evaluators/lcc_size.py b/network_dismantling/evaluators/lcc_size.py index c107366..da24c52 100644 --- a/network_dismantling/evaluators/lcc_size.py +++ b/network_dismantling/evaluators/lcc_size.py @@ -1,8 +1,5 @@ import graph_tool.all as gt -from typing import List, Dict, Callable -from abc import ABC, abstractmethod - from network_dismantling.evaluators.evaluator import EvaluationMetric @@ -20,7 +17,8 @@ def compute(self, G: gt.Graph) -> float: :param G: The graph. :return: The size of the largest connected component of the graph. """ - return len(max(gt.connected_components(G), key=len)) + lcc = gt.extract_largest_component(G) + return lcc.num_vertices() @property def name(self) -> str: diff --git a/network_dismantling/evaluators/num_components.py b/network_dismantling/evaluators/num_components.py index 50d153a..d799d9c 100644 --- a/network_dismantling/evaluators/num_components.py +++ b/network_dismantling/evaluators/num_components.py @@ -18,7 +18,8 @@ def compute(self, G: gt.Graph) -> float: :param G: The graph. :return: The number of connected components of the graph. """ - return gt.number_connected_components(G) + _, hist = gt.label_components(G) + return len(hist) @property def name(self) -> str: diff --git a/network_dismantling/node_selectors/basic.py b/network_dismantling/node_selectors/basic.py index 387f008..b75d1a1 100644 --- a/network_dismantling/node_selectors/basic.py +++ b/network_dismantling/node_selectors/basic.py @@ -56,8 +56,8 @@ def is_dismantled(self, G: gt.Graph, target_size: int) -> bool: :param target_size: The target size of the largest connected component. :return: True if the largest connected component is smaller than the target size, False otherwise. """ - largest_cc = max(gt.connected_components(G), key=len) - return len(largest_cc) <= target_size + lcc = gt.extract_largest_component(G) + return lcc.num_vertices() <= target_size def greedy_fallback(self, G: gt.Graph, num_nodes: int) -> List[int]: """ diff --git a/network_dismantling/node_selectors/centrality.py b/network_dismantling/node_selectors/centrality.py index 3ea409a..c4d4a06 100644 --- a/network_dismantling/node_selectors/centrality.py +++ b/network_dismantling/node_selectors/centrality.py @@ -7,8 +7,8 @@ class Betweenness(NodeSelector): def select(self, G: gt.Graph, num_nodes: int) -> List[Any]: - betweenness = gt.betweenness_centrality(G) - return sorted(betweenness, key=betweenness.get, reverse=True)[:num_nodes] + betweenness = gt.betweenness(G)[0] + return sorted(enumerate(betweenness), key=lambda x: x[1], reverse=True)[:num_nodes] class Degree(NodeSelector): diff --git a/network_dismantling/node_selectors/gnd.py b/network_dismantling/node_selectors/gnd.py index 0e88672..846f2ae 100644 --- a/network_dismantling/node_selectors/gnd.py +++ b/network_dismantling/node_selectors/gnd.py @@ -35,7 +35,7 @@ def select(self, G: gt.Graph, num_nodes: int) -> List[int]: :param num_nodes: The number of nodes to remove. :return: A list of node indices to remove. """ - A = gt.adjacency_matrix(G) + A = gt.adjacency(G) N = A.shape[0] D = np.array(A.sum(axis=1)).flatten() L = csr_matrix(np.diag(D) - A) @@ -71,11 +71,12 @@ def select(self, G: gt.Graph, num_nodes: int) -> List[int]: :param num_nodes: The number of nodes to remove. :return: A list of node indices to remove. """ - core_numbers = gt.core_number(G) - degrees = dict(G.degree()) - nodes_to_remove = [] + for _ in tqdm(range(num_nodes), desc="CoreHD Dismantling"): + core_numbers = dict(enumerate(gt.kcore_decomposition(G).get_array())) + degrees = G.degree_property_map() + max_core = max(core_numbers.values()) candidates = [ node for node, core in core_numbers.items() if core == max_core @@ -85,8 +86,4 @@ def select(self, G: gt.Graph, num_nodes: int) -> List[int]: nodes_to_remove.append(node_to_remove) G.remove_node(node_to_remove) - # Update core numbers and degrees - core_numbers = gt.core_number(G) - degrees = dict(G.degree()) - return nodes_to_remove diff --git a/network_dismantling/node_selectors/influence.py b/network_dismantling/node_selectors/influence.py index 1dabcef..1486544 100644 --- a/network_dismantling/node_selectors/influence.py +++ b/network_dismantling/node_selectors/influence.py @@ -5,8 +5,7 @@ from network_dismantling.node_selectors.node_selector import NodeSelector - -class CollectiveInfluence(NodeSelector): +class CollectiveInfluence: """ Dismantling strategy that removes nodes based on their collective influence. @@ -22,14 +21,15 @@ def __init__(self, l=2): # noqa: E741 """ self.l = l # Ball radius - def select(self, G: gt.Graph, num_nodes: int) -> List[int]: + def select(self, G: gt.Graph, num_elements: int) -> List[int]: """ Dismantle the graph by removing nodes based on their collective influence. :param G: The graph to dismantle. - :param num_nodes: The number of nodes to remove. + :param num_elements: The number of nodes to remove. :return: A list of node indices to remove. """ + num_nodes = num_elements nodes_to_remove: List[int] = [] G_copy = G.copy() @@ -38,12 +38,12 @@ def select(self, G: gt.Graph, num_nodes: int) -> List[int]: if not ci_scores: break node_to_remove = max(ci_scores, key=ci_scores.get) - nodes_to_remove.append(node_to_remove) - G_copy.remove_node(node_to_remove) + nodes_to_remove.append(int(node_to_remove)) + G_copy.remove_vertex(node_to_remove, fast=True) return nodes_to_remove - def compute_collective_influence(self, G): + def compute_collective_influence(self, G: gt.Graph): """ Compute the collective influence of each node in the graph. @@ -51,11 +51,19 @@ def compute_collective_influence(self, G): :return: A dictionary with the collective influence scores of each node. """ ci_scores = {} - for node in G.nodes(): - ki = G.degree(node) - ball = gt.single_source_shortest_path_length(G, node, cutoff=self.l) - ball_boundary = [n for n in ball if ball[n] == self.l] - ci = (ki - 1) * sum(G.degree(v) - 1 for v in ball_boundary) + degrees = G.degree_property_map("total") + for node in G.vertices(): + ki = degrees[node] + if ki <= 1: + ci_scores[node] = 0 + continue + # Compute distances from node to all other nodes + dist_map = gt.shortest_distance(G, source=node, max_dist=self.l).a + # Get vertices at distance exactly l + ball_boundary = [v for v in G.vertices() if dist_map[int(v)] == self.l] + # Compute the sum over the degrees of the boundary nodes + sum_kj_minus1 = sum(degrees[v] - 1 for v in ball_boundary) + ci = (ki - 1) * sum_kj_minus1 ci_scores[node] = ci return ci_scores @@ -79,58 +87,80 @@ def __init__(self, q=0.1, num_iterations=10): self.q = q # Fraction of nodes to remove in each iteration self.num_iterations = num_iterations - def select(self, G: gt.Graph, num_nodes: int) -> List[int]: + def select(self, G: gt.Graph, num_elements: int) -> List[int]: """ Dismantle the graph by removing nodes based on their explosive immunization score. :param G: The graph to dismantle. - :param num_nodes: The number of nodes to remove. + :param num_elements: The number of nodes to remove. :return: A list of node indices to remove. """ + num_nodes = num_elements nodes_to_remove: List[int] = [] G_copy = G.copy() + mask = G_copy.new_vertex_property("bool", val=True) while len(nodes_to_remove) < num_nodes: - scores = self.compute_ei_scores(G_copy) + G_view = gt.GraphView(G_copy, vfilt=mask) + scores = self.compute_ei_scores(G_view) if not scores: break candidates = sorted(scores.items(), key=lambda x: x[1], reverse=True) num_to_remove = min( - int(self.q * len(G_copy)), num_nodes - len(nodes_to_remove) + max(1, int(self.q * G_view.num_vertices())), num_nodes - len(nodes_to_remove) ) - for node, _ in candidates[:num_to_remove]: - nodes_to_remove.append(node) - G_copy.remove_node(node) + for node_index, _ in candidates[:num_to_remove]: + nodes_to_remove.append(node_index) + mask[node_index] = False # Mark node as removed return nodes_to_remove - def compute_ei_scores(self, G): + def compute_ei_scores(self, G: gt.GraphView): """ Compute the explosive immunization score of each node in the graph. - :param G: The graph. + :param G: The graph view. :return: A dictionary with the explosive immunization scores of each node. """ scores = defaultdict(float) for _ in range(self.num_iterations): - components = list(gt.connected_components(G)) - for component in components: + comp, _ = gt.label_components(G) + # 使用 defaultdict 来存储组件 + components = defaultdict(list) + for v in G.vertices(): + comp_label = comp[v] + components[comp_label].append(int(v)) + for component in components.values(): if len(component) > 1: - subgraph = G.subgraph(component) - for node in component: - s = self.compute_s(subgraph, node) - scores[node] += s / self.num_iterations + subgraph_mask = G.new_vertex_property("bool", val=False) + for idx in component: + subgraph_mask[idx] = True + subgraph = gt.GraphView(G, vfilt=subgraph_mask) + for node_index in component: + s = self.compute_s(subgraph, node_index) + scores[node_index] += s / self.num_iterations return scores - def compute_s(self, G, node): + def compute_s(self, G: gt.GraphView, node_index): """ Compute the explosive immunization score of a node. - :param G: The graph. - :param node: The node. + :param G: The graph view. + :param node_index: The index of the node. :return: The explosive immunization score of the node. """ - G_copy = G.copy() - G_copy.remove_node(node) - new_components = list(gt.connected_components(G_copy)) - return sum(len(c) * (len(c) - 1) for c in new_components) + mask = G.new_vertex_property("bool") + mask.a = G.get_vertex_filter()[0].a.copy() + mask[node_index] = False # Simulate removal of the node + G_copy = gt.GraphView(G, vfilt=mask) + + if G_copy.num_vertices() == 0: + return 0 # return 0 when graph is empty + + comp, _ = gt.label_components(G_copy) + component_sizes = defaultdict(int) + for v in G_copy.vertices(): + comp_label = comp[v] + component_sizes[comp_label] += 1 + return sum(size * (size - 1) for size in component_sizes.values()) + \ No newline at end of file diff --git a/network_dismantling/node_selectors/node_selector.py b/network_dismantling/node_selectors/node_selector.py index b922106..46e736b 100644 --- a/network_dismantling/node_selectors/node_selector.py +++ b/network_dismantling/node_selectors/node_selector.py @@ -13,12 +13,12 @@ class NodeSelector(ElementSelector): """ @abstractmethod - def select(self, G: gt.Graph, num_nodes: int) -> List[Any]: + def select(self, G: gt.Graph, num_elements: int) -> List[Any]: """ Select nodes from the graph. :param G: The input graph - :param num_nodes: Number of nodes to select + :param num_elements: Number of nodes to select :return: List of selected nodes """ - pass + diff --git a/network_dismantling/operators/basic.py b/network_dismantling/operators/basic.py index eccda02..22ecbdb 100644 --- a/network_dismantling/operators/basic.py +++ b/network_dismantling/operators/basic.py @@ -23,6 +23,7 @@ def operate(self, G: gt.Graph, elements: List[Any]) -> gt.Graph: H.remove_vertex(elements) return H + class TemporalNodeNeighborEgdesRemovalOperator(DismantlingOperator): def operate(self, G: gt.Graph, elements: List[Any]) -> gt.Graph: """ @@ -38,6 +39,7 @@ def operate(self, G: gt.Graph, elements: List[Any]) -> gt.Graph: # cannot use the original data, because the original data does not have the message, time, and message_type attributes def dismantling_from_nodes( + self, G, nodes, message_type_label="message_type", @@ -130,6 +132,7 @@ def operate(self, G: gt.Graph, elements: List[Any]) -> gt.Graph: H.remove_edges_from(elements) return H + class TemporalEdgeRemovalOperator(DismantlingOperator): """ Dismantling operator that removes edges from the graph. @@ -150,6 +153,7 @@ def operate(self, G: gt.Graph, elements: List[Any]) -> gt.Graph: return H def dismantling_from_edges( + self, G, edges, message_type_label="message_type", @@ -215,4 +219,4 @@ def dismantling_from_edges( queue.append(v) processed_edges.add(v) - return G_dismantled \ No newline at end of file + return G_dismantled diff --git a/network_dismantling/operators/operator.py b/network_dismantling/operators/operator.py index f7f5577..d61df8c 100644 --- a/network_dismantling/operators/operator.py +++ b/network_dismantling/operators/operator.py @@ -14,4 +14,3 @@ def operate(self, G: gt.Graph, elements: List[Any]) -> gt.Graph: :param elements: Elements to operate on :return: The modified graph """ - pass diff --git a/network_dismantling/selector.py b/network_dismantling/selector.py index 8b65b7e..af00993 100644 --- a/network_dismantling/selector.py +++ b/network_dismantling/selector.py @@ -14,4 +14,3 @@ def select(self, G: gt.Graph, num_elements: int) -> List[Any]: :param num_elements: Number of elements to select :return: List of selected elements """ - pass diff --git a/network_dismantling/utils/data.py b/network_dismantling/utils/data.py index 22fa316..e526737 100644 --- a/network_dismantling/utils/data.py +++ b/network_dismantling/utils/data.py @@ -63,14 +63,16 @@ def __init__( ) @staticmethod - def generate_synthetic_networks(num_networks: int, num_nodes: int) -> List[gt.Graph]: + def generate_synthetic_networks( + num_networks: int, num_nodes: int + ) -> List[gt.Graph]: """ Generate synthetic networks for training the GDM model using graph-tool. Args: num_networks: The number of synthetic networks to generate num_nodes: The number of nodes in each synthetic network - + Returns: List of synthetic networks as graph-tool Graph objects """ @@ -81,25 +83,25 @@ def generate_synthetic_networks(num_networks: int, num_nodes: int) -> List[gt.Gr # Barabási-Albert model using Price network # m=3 new edges per vertex G = gt.price_network(num_nodes, m=3, directed=False) - + elif rand < 0.66: - # Erdős-Rényi model + # Erdős-Rényi model # p=0.1 edge probability - G = gt.random_graph(num_nodes, - lambda: np.random.binomial(1, 0.1), - directed=False) - + G = gt.random_graph( + num_nodes, lambda: np.random.binomial(1, 0.1), directed=False + ) + else: # Watts-Strogatz model # First create regular ring lattice - G = gt.lattice([num_nodes], dim=1, k=4, periodic=True) + G = gt.lattice([num_nodes], periodic=True) # Then rewire with probability 0.1 - gt.random_rewire(G, model="erdos", - n_iter=G.num_edges()*0.1, - edge_sweep=True) - + gt.random_rewire( + G, model="erdos", n_iter=G.num_edges() * 0.1, edge_sweep=True + ) + networks.append(G) return networks def load(self): - return gt.load_graph(self.dataset_path) \ No newline at end of file + return gt.load_graph(self.dataset_path)