diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8556ddcd2e8210e33701a17219ad72b5241b2886 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/__pycache__/base.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/__pycache__/base.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb152866c94e0913f31c33382559c4d174583852 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/__pycache__/base.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/__pycache__/version.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/__pycache__/version.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e7562f5f1b1ac70216167b1479b9c2c078f864b Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/__pycache__/version.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab1c0903b885481652573b0ae74aac83a52d74dc Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/__pycache__/base.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/__pycache__/base.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c214df9e7e9c9156e1787d8b08aaab1feb86caa5 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/__pycache__/base.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/__pycache__/helpers.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/__pycache__/helpers.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d306ac88d72fdee50514d4390714c9ad6b26b6d Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/__pycache__/helpers.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/model_compressors/__init__.py b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/model_compressors/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d9cfa8525dbaa915d7aa820de387b1dcbbf534ed --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/model_compressors/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + + +from .model_compressor import * diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/model_compressors/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/model_compressors/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2ae5366b35fcf43e0c83d42528664957e9ad6c6 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/model_compressors/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/model_compressors/__pycache__/model_compressor.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/model_compressors/__pycache__/model_compressor.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..384c0d77153ae5ceb70fa8fb251ff16aa02423a3 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/model_compressors/__pycache__/model_compressor.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/model_compressors/model_compressor.py b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/model_compressors/model_compressor.py new file mode 100644 index 0000000000000000000000000000000000000000..951eef1f87c08f7765cbe4814b22398805f5c11b --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/model_compressors/model_compressor.py @@ -0,0 +1,466 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import logging +import operator +import os +import re +from contextlib import contextmanager +from copy import deepcopy +from typing import TYPE_CHECKING, Any, Dict, Optional, Set, TypeVar, Union + +import compressed_tensors +import torch +import transformers +from compressed_tensors.base import ( + COMPRESSION_VERSION_NAME, + QUANTIZATION_CONFIG_NAME, + QUANTIZATION_METHOD_NAME, + SPARSITY_CONFIG_NAME, +) +from compressed_tensors.compressors.base import BaseCompressor +from compressed_tensors.config import CompressionFormat, SparsityCompressionConfig +from compressed_tensors.quantization import ( + DEFAULT_QUANTIZATION_METHOD, + QuantizationConfig, + QuantizationStatus, + apply_quantization_config, + load_pretrained_quantization, +) +from compressed_tensors.quantization.lifecycle import expand_sparse_target_names +from compressed_tensors.quantization.quant_args import QuantizationArgs +from compressed_tensors.quantization.utils import ( + is_module_quantized, + iter_named_leaf_modules, +) +from compressed_tensors.utils import get_safetensors_folder, update_parameter_data +from compressed_tensors.utils.helpers import ( + fix_fsdp_module_name, + is_compressed_tensors_config, +) +from torch import Tensor +from torch.nn import Module +from tqdm import tqdm +from transformers import AutoConfig +from transformers.file_utils import CONFIG_NAME + + +__all__ = ["ModelCompressor", "map_modules_to_quant_args"] + +_LOGGER: logging.Logger = logging.getLogger(__name__) + + +if TYPE_CHECKING: + # dummy type if not available from transformers + CompressedTensorsConfig = TypeVar("CompressedTensorsConfig") + + +class ModelCompressor: + """ + Handles compression and decompression of a model with a sparsity config and/or + quantization config. + + Compression LifeCycle + - compressor = ModelCompressor.from_pretrained_model(model) + - compressed_state_dict = compressor.compress(model, state_dict) + - compressor.quantization_compressor.compress(model, state_dict) + - compressor.sparsity_compressor.compress(model, state_dict) + - model.save_pretrained(output_dir, state_dict=compressed_state_dict) + - compressor.update_config(output_dir) + + Decompression LifeCycle + - compressor = ModelCompressor.from_pretrained(comp_model_path) + - model = AutoModel.from_pretrained(comp_model_path) + - compressor.decompress(comp_model_path, model) + - compressor.sparsity_compressor.decompress(comp_model_path, model) + - compressor.quantization_compressor.decompress(comp_model_path, model) + + :param sparsity_config: config specifying sparsity compression parameters + :param quantization_config: config specifying quantization compression parameters + """ + + @classmethod + def from_pretrained( + cls, + pretrained_model_name_or_path: str, + **kwargs, + ) -> Optional["ModelCompressor"]: + """ + Given a path to a model config, extract the sparsity and/or quantization + configs and load a ModelCompressor + + :param pretrained_model_name_or_path: path to model config on disk or HF hub + :return: compressor for the configs, or None if model is not compressed + """ + config = AutoConfig.from_pretrained(pretrained_model_name_or_path, **kwargs) + compression_config = getattr(config, QUANTIZATION_CONFIG_NAME, None) + return cls.from_compression_config(compression_config) + + @classmethod + def from_compression_config( + cls, + compression_config: Union[Dict[str, Any], "CompressedTensorsConfig"], + ): + """ + :param compression_config: + A compression or quantization config + + The type is one of the following: + 1. A Dict found under either "quantization_config" or "compression_config" + keys in the config.json + 2. A CompressedTensorsConfig found under key "quantization_config" in HF + model config + :return: compressor for the configs, or None if model is not compressed + """ + if compression_config is None: + return None + + sparsity_config = cls.parse_sparsity_config(compression_config) + quantization_config = cls.parse_quantization_config(compression_config) + if sparsity_config is None and quantization_config is None: + return None + + if sparsity_config is not None: + format = sparsity_config.get("format") + sparsity_config = SparsityCompressionConfig.load_from_registry( + format, **sparsity_config + ) + if quantization_config is not None: + quantization_config = QuantizationConfig.model_validate(quantization_config) + + return cls( + sparsity_config=sparsity_config, quantization_config=quantization_config + ) + + @classmethod + def from_pretrained_model( + cls, + model: Module, + sparsity_config: Union[SparsityCompressionConfig, str, None] = None, + quantization_format: Optional[str] = None, + ) -> Optional["ModelCompressor"]: + """ + Given a pytorch model and optional sparsity and/or quantization configs, + load the appropriate compressors + + :param model: pytorch model to target for compression + :param sparsity_config: a filled in sparsity config or string corresponding + to a sparsity compression algorithm + :param quantization_format: string corresponding to a quantization compression + algorithm + :return: compressor for the configs, or None if model is not compressed + """ + quantization_config = QuantizationConfig.from_pretrained( + model, format=quantization_format + ) + + if isinstance(sparsity_config, str): # we passed in a sparsity format + sparsity_config = SparsityCompressionConfig.load_from_registry( + sparsity_config + ) + + if sparsity_config is None and quantization_config is None: + return None + + return cls( + sparsity_config=sparsity_config, quantization_config=quantization_config + ) + + @staticmethod + def parse_sparsity_config( + compression_config: Union[Dict[str, Any], "CompressedTensorsConfig"] + ) -> Union[Dict[str, Any], None]: + """ + Parse sparsity config from quantization/compression config. Sparsity + config is nested inside q/c config + + :param compression_config: quantization/compression config + :return: sparsity config + """ + if compression_config is None: + return None + + if is_compressed_tensors_config(compression_config): + s_config = compression_config.sparsity_config + return s_config.model_dump() if s_config is not None else None + + return compression_config.get(SPARSITY_CONFIG_NAME, None) + + @staticmethod + def parse_quantization_config( + compression_config: Union[Dict[str, Any], "CompressedTensorsConfig"] + ) -> Union[Dict[str, Any], None]: + """ + Parse quantization config from quantization/compression config. The + quantization are all the fields that are not the sparsity config or + metadata fields + + :param compression_config: quantization/compression config + :return: quantization config without sparsity config or metadata fields + """ + if compression_config is None: + return None + + if is_compressed_tensors_config(compression_config): + q_config = compression_config.quantization_config + return q_config.model_dump() if q_config is not None else None + + quantization_config = deepcopy(compression_config) + quantization_config.pop(SPARSITY_CONFIG_NAME, None) + + # some fields are required, even if a qconfig is not present + # pop them off and if nothing remains, then there is no qconfig + quant_method = quantization_config.pop(QUANTIZATION_METHOD_NAME, None) + _ = quantization_config.pop(COMPRESSION_VERSION_NAME, None) + + if len(quantization_config) == 0: + return None + + # replace popped off values + # note that version is discarded for now + if quant_method is not None: + quantization_config[QUANTIZATION_METHOD_NAME] = quant_method + + return quantization_config + + def __init__( + self, + sparsity_config: Optional[SparsityCompressionConfig] = None, + quantization_config: Optional[QuantizationConfig] = None, + ): + self.sparsity_config = sparsity_config + self.quantization_config = quantization_config + self.sparsity_compressor = None + self.quantization_compressor = None + + if sparsity_config is not None: + self.sparsity_compressor = BaseCompressor.load_from_registry( + sparsity_config.format, config=sparsity_config + ) + if quantization_config is not None: + self.quantization_compressor = BaseCompressor.load_from_registry( + quantization_config.format, config=quantization_config + ) + + def compress( + self, model: Module, state_dict: Optional[Dict[str, Tensor]] = None + ) -> Dict[str, Tensor]: + """ + Compresses a dense state dict or model with sparsity and/or quantization + + :param model: uncompressed model to compress + :param state_dict: optional uncompressed state_dict to insert into model + :return: compressed state dict + """ + if state_dict is None: + state_dict = model.state_dict() + + compressed_state_dict = state_dict + + quantized_modules_to_args: Dict[ + str, QuantizationArgs + ] = map_modules_to_quant_args(model) + + if self.quantization_compressor is not None: + compressed_state_dict = self.quantization_compressor.compress( + state_dict, names_to_scheme=quantized_modules_to_args + ) + if self.quantization_config.format != CompressionFormat.dense.value: + self.quantization_config.quantization_status = ( + QuantizationStatus.COMPRESSED + ) + + if self.sparsity_compressor is not None: + sparse_compression_targets: Set[str] = expand_sparse_target_names( + model=model, + targets=self.sparsity_config.targets, + ignore=self.sparsity_config.ignore, + ) + compressed_state_dict = self.sparsity_compressor.compress( + compressed_state_dict, + compression_targets=sparse_compression_targets, + ) + + # HACK: Override the dtype_byte_size function in transformers to + # support float8 types. Fix is posted upstream + # https://github.com/huggingface/transformers/pull/30488 + transformers.modeling_utils.dtype_byte_size = new_dtype_byte_size + + return compressed_state_dict + + def decompress(self, model_path: str, model: Module): + """ + Overwrites the weights in model with weights decompressed from model_path + + :param model_path: path to compressed weights + :param model: pytorch model to load decompressed weights into + """ + model_path = get_safetensors_folder(model_path) + sparse_decompressed = False + + if ( + self.sparsity_compressor is not None + and self.sparsity_config.format != CompressionFormat.dense.value + ): + # Sparse decompression is applied on the model_path + dense_gen = self.sparsity_compressor.decompress(model_path) + self._replace_weights(dense_gen, model) + setattr(model, SPARSITY_CONFIG_NAME, self.sparsity_compressor.config) + sparse_decompressed = True + + if self.quantization_compressor is not None: + # Temporarily set quantization status to FROZEN to prevent + # quantization during apply_quantization_config. This ensures + # that the dtypes of the weights are not unintentionally updated. + # The status is restored after quantization params are loaded. + with override_quantization_status( + self.quantization_config, QuantizationStatus.FROZEN + ): + names_to_scheme = apply_quantization_config( + model, self.quantization_config + ) + load_pretrained_quantization(model, model_path) + + model_path_or_state_dict = ( + model.state_dict() if sparse_decompressed else model_path + ) + + dense_gen = self.quantization_compressor.decompress( + model_path_or_state_dict, names_to_scheme=names_to_scheme + ) + self._replace_weights(dense_gen, model) + + def freeze_quantization_status(module): + module.quantization_status = QuantizationStatus.FROZEN + + model.apply(freeze_quantization_status) + setattr(model, QUANTIZATION_CONFIG_NAME, self.quantization_config) + + def update_config(self, save_directory: str): + """ + Update the model config located at save_directory with compression configs + for sparsity and/or quantization + + :param save_directory: path to a folder containing a HF model config + """ + if self.quantization_config is None and self.sparsity_config is None: + return + + config_file_path = os.path.join(save_directory, CONFIG_NAME) + if not os.path.exists(config_file_path): + _LOGGER.warning( + f"Could not find a valid model config file in " + f"{save_directory}. Compression config will not be saved." + ) + return + + with open(config_file_path, "r") as config_file: + config_data = json.load(config_file) + + # required metadata whenever a quantization or sparsity config is present + # overwrite previous config and version if already existing + config_data[QUANTIZATION_CONFIG_NAME] = {} + config_data[QUANTIZATION_CONFIG_NAME][ + COMPRESSION_VERSION_NAME + ] = compressed_tensors.__version__ + if self.quantization_config is not None: + self.quantization_config.quant_method = DEFAULT_QUANTIZATION_METHOD + else: + config_data[QUANTIZATION_CONFIG_NAME][ + QUANTIZATION_METHOD_NAME + ] = DEFAULT_QUANTIZATION_METHOD + + # quantization and sparsity configs + if self.quantization_config is not None: + quant_config_data = self.quantization_config.model_dump() + config_data[QUANTIZATION_CONFIG_NAME] = quant_config_data + if self.sparsity_config is not None: + sparsity_config_data = self.sparsity_config.model_dump() + config_data[QUANTIZATION_CONFIG_NAME][ + SPARSITY_CONFIG_NAME + ] = sparsity_config_data + + with open(config_file_path, "w") as config_file: + json.dump(config_data, config_file, indent=2, sort_keys=True) + + def _replace_weights(self, dense_weight_generator, model: Module): + """ + Replace the weights of the model with the + provided dense weights. + + This method iterates over the dense_weight_generator and + updates the corresponding weights in the model. If a parameter + name does not exist in the model, it will be skipped. + + :param dense_weight_generator (generator): A generator that yields + tuples of (name, data), where 'name' is the parameter name and + 'data' is the updated param data + :param model: The model whose weights are to be updated. + """ + for name, data in tqdm(dense_weight_generator, desc="Decompressing model"): + split_name = name.split(".") + prefix, param_name = ".".join(split_name[:-1]), split_name[-1] + module = operator.attrgetter(prefix)(model) + if hasattr(module, param_name): + update_parameter_data(module, data, param_name) + + +def map_modules_to_quant_args(model: Module) -> Dict[str, QuantizationArgs]: + """ + Given a pytorch model, map out the submodule name (usually linear layers) + to the QuantizationArgs + + :param model: pytorch model + """ + quantized_modules_to_args = {} + for name, submodule in iter_named_leaf_modules(model): + if is_module_quantized(submodule): + if submodule.quantization_scheme.weights is not None: + name = fix_fsdp_module_name(name) + quantized_modules_to_args[name] = submodule.quantization_scheme.weights + + return quantized_modules_to_args + + +# HACK: Override the dtype_byte_size function in transformers to support float8 types +# Fix is posted upstream https://github.com/huggingface/transformers/pull/30488 +def new_dtype_byte_size(dtype): + if dtype == torch.bool: + return 1 / 8 + bit_search = re.search(r"[^\d](\d+)_?", str(dtype)) + if bit_search is None: + raise ValueError(f"`dtype` is not a valid dtype: {dtype}.") + bit_size = int(bit_search.groups()[0]) + return bit_size // 8 + + +@contextmanager +def override_quantization_status( + config: QuantizationConfig, status: QuantizationStatus +): + """ + Within this context, the quantization status will be set to the + supplied status. After the context exits, the original status + will be restored. + + :param config: the quantization config to override + :param status: the status to temporarily set + """ + original_status = config.quantization_status + config.quantization_status = status + try: + yield + finally: + config.quantization_status = original_status diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/sparse_quantized_compressors/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/sparse_quantized_compressors/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f574fe65ca1e9a4d233045f8a08beadbd8b13c00 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/sparse_quantized_compressors/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/sparse_quantized_compressors/__pycache__/marlin_24.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/sparse_quantized_compressors/__pycache__/marlin_24.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..927fdf888778dfd4c86efced52269534a826b08e Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/compressors/sparse_quantized_compressors/__pycache__/marlin_24.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/config/__init__.py b/.venv/lib/python3.11/site-packages/compressed_tensors/config/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..582b8a9e1a47b7d6a9e2feb780a7f955bfd03f20 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/config/__init__.py @@ -0,0 +1,19 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# flake8: noqa +from .base import * +from .dense import * +from .sparse_24_bitmask import * +from .sparse_bitmask import * diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2decca5b4a73f159e81c81c919d9acdbb02a3579 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/base.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/base.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3945eda358de6d7f301c095e1597fe148341b216 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/base.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/dense.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/dense.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ec62b7e92f40d2805e9234c91cc0e9dbb9705b5 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/dense.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/sparse_24_bitmask.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/sparse_24_bitmask.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a97a31e5a7c8aaede85641fd7f7a2c8fcb502071 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/sparse_24_bitmask.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/sparse_bitmask.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/sparse_bitmask.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae5ecd4b7698f3fe4a42d00fe61e0f3c15ddae3a Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/config/__pycache__/sparse_bitmask.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/config/base.py b/.venv/lib/python3.11/site-packages/compressed_tensors/config/base.py new file mode 100644 index 0000000000000000000000000000000000000000..9ca6f2cf2209cee3d97878f59f83349338c0c530 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/config/base.py @@ -0,0 +1,111 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from enum import Enum, unique +from typing import List, Optional + +from compressed_tensors.registry import RegistryMixin +from pydantic import BaseModel + + +__all__ = ["SparsityCompressionConfig", "CompressionFormat", "SparsityStructure"] + + +@unique +class CompressionFormat(Enum): + dense = "dense" + sparse_bitmask = "sparse-bitmask" + sparse_24_bitmask = "sparse-24-bitmask" + int_quantized = "int-quantized" + float_quantized = "float-quantized" + naive_quantized = "naive-quantized" + pack_quantized = "pack-quantized" + marlin_24 = "marlin-24" + + +@unique +class SparsityStructure(Enum): + """ + An enumeration to represent different sparsity structures. + + Attributes + ---------- + TWO_FOUR : str + Represents a 2:4 sparsity structure. + ZERO_ZERO : str + Represents a 0:0 sparsity structure. + UNSTRUCTURED : str + Represents an unstructured sparsity structure. + + Examples + -------- + >>> SparsityStructure('2:4') + + + >>> SparsityStructure('unstructured') + + + >>> SparsityStructure('2:4') == SparsityStructure.TWO_FOUR + True + + >>> SparsityStructure('UNSTRUCTURED') == SparsityStructure.UNSTRUCTURED + True + + >>> SparsityStructure(None) == SparsityStructure.UNSTRUCTURED + True + + >>> SparsityStructure('invalid') + Traceback (most recent call last): + ... + ValueError: invalid is not a valid SparsityStructure + """ + + TWO_FOUR = "2:4" + UNSTRUCTURED = "unstructured" + ZERO_ZERO = "0:0" + + def __new__(cls, value): + obj = object.__new__(cls) + obj._value_ = value.lower() if value is not None else value + return obj + + @classmethod + def _missing_(cls, value): + # Handle None and case-insensitive values + if value is None: + return cls.UNSTRUCTURED + for member in cls: + if member.value == value.lower(): + return member + raise ValueError(f"{value} is not a valid {cls.__name__}") + + +class SparsityCompressionConfig(RegistryMixin, BaseModel): + """ + Base data class for storing sparsity compression parameters + + :param format: name of compression format + :param targets: List of layer names or layer types that aren't sparse and should + be ignored during compression. By default, assume all layers are targeted + :param ignore: List of layer names (unique) to ignore from targets. Defaults to None + :param global_sparsity: average sparsity of the entire model + :param sparsity_structure: structure of the sparsity, such as + "unstructured", "2:4", "8:16" etc + """ + + format: str + targets: Optional[List[str]] = None + ignore: Optional[List[str]] = None + global_sparsity: Optional[float] = 0.0 + sparsity_structure: Optional[str] = "unstructured" diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/config/dense.py b/.venv/lib/python3.11/site-packages/compressed_tensors/config/dense.py new file mode 100644 index 0000000000000000000000000000000000000000..8e7e3b7a4b69c164f6497e4860ee18951e44189b --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/config/dense.py @@ -0,0 +1,36 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Optional + +from compressed_tensors.config import CompressionFormat, SparsityCompressionConfig + + +__all__ = ["DenseSparsityConfig"] + + +@SparsityCompressionConfig.register(name=CompressionFormat.dense.value) +class DenseSparsityConfig(SparsityCompressionConfig): + """ + Identity configuration for storing a sparse model in + an uncompressed dense format + + :param global_sparsity: average sparsity of the entire model + :param sparsity_structure: structure of the sparsity, such as + "unstructured", "2:4", "8:16" etc + """ + + format: str = CompressionFormat.dense.value + global_sparsity: Optional[float] = 0.0 + sparsity_structure: Optional[str] = "unstructured" diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/config/sparse_24_bitmask.py b/.venv/lib/python3.11/site-packages/compressed_tensors/config/sparse_24_bitmask.py new file mode 100644 index 0000000000000000000000000000000000000000..7aae2dbe595e7c365ae9996664209598af4adf0b --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/config/sparse_24_bitmask.py @@ -0,0 +1,40 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Optional + +from compressed_tensors.config import ( + CompressionFormat, + SparsityCompressionConfig, + SparsityStructure, +) + + +__all__ = ["Sparse24BitMaskConfig"] + + +@SparsityCompressionConfig.register(name=CompressionFormat.sparse_24_bitmask.value) +class Sparse24BitMaskConfig(SparsityCompressionConfig): + """ + Configuration for storing a 24 sparse model using + bytemask compression + + :param global_sparsity: average sparsity of the entire model + :param sparsity_structure: structure of the sparsity, should always be + "2:4" for this compression format + """ + + format: str = CompressionFormat.sparse_24_bitmask.value + global_sparsity: Optional[float] = 0.0 + sparsity_structure: Optional[str] = SparsityStructure.TWO_FOUR.value diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/config/sparse_bitmask.py b/.venv/lib/python3.11/site-packages/compressed_tensors/config/sparse_bitmask.py new file mode 100644 index 0000000000000000000000000000000000000000..c14d9f7cde01a4dc2ac2e8fe50fcd60931a1dc01 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/config/sparse_bitmask.py @@ -0,0 +1,36 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Optional + +from compressed_tensors.config import CompressionFormat, SparsityCompressionConfig + + +__all__ = ["BitmaskConfig"] + + +@SparsityCompressionConfig.register(name=CompressionFormat.sparse_bitmask.value) +class BitmaskConfig(SparsityCompressionConfig): + """ + Configuration for storing a sparse model using + bitmask compression + + :param global_sparsity: average sparsity of the entire model + :param sparsity_structure: structure of the sparsity, such as + "unstructured", "2:4", "8:16" etc + """ + + format: str = CompressionFormat.sparse_bitmask.value + global_sparsity: Optional[float] = 0.0 + sparsity_structure: Optional[str] = "unstructured" diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/linear/__init__.py b/.venv/lib/python3.11/site-packages/compressed_tensors/linear/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0c44f887a47844c08db4f0f3f2d3452ed7f5aedd --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/linear/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/linear/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/linear/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53cf34e9a0712862b01181a61fe355e9d69bfe5e Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/linear/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/linear/__pycache__/compressed_linear.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/linear/__pycache__/compressed_linear.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7165fe54d4d6f0c5db3be016e2e2129056dde4b5 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/linear/__pycache__/compressed_linear.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/linear/compressed_linear.py b/.venv/lib/python3.11/site-packages/compressed_tensors/linear/compressed_linear.py new file mode 100644 index 0000000000000000000000000000000000000000..3e2b2f5fd4d9b49da6b450b367c4a9575b9a0af0 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/linear/compressed_linear.py @@ -0,0 +1,89 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict, Tuple + +import torch +from compressed_tensors.compressors.base import BaseCompressor +from compressed_tensors.quantization import ( + QuantizationScheme, + QuantizationStatus, + initialize_module_for_quantization, +) +from torch import Tensor +from torch.nn import Parameter +from torch.nn.functional import linear +from torch.nn.modules import Linear + + +class CompressedLinear(Linear): + """ + Wrapper module for running a compressed forward pass of a quantized Linear module. + The wrapped layer will decompressed on each forward call. + + :param module: dense linear module to replace + :param quantization_scheme: quantization config for the module to wrap + :param quantization_format: compression format module is stored as + """ + + @classmethod + @torch.no_grad() + def from_linear( + cls, + module: Linear, + quantization_scheme: QuantizationScheme, + quantization_format: str, + ): + module.__class__ = CompressedLinear + module.compressor = BaseCompressor.load_from_registry(quantization_format) + device = next(module.parameters()).device + + # this will initialize all the scales and zero points + initialize_module_for_quantization( + module, quantization_scheme, force_zero_point=False + ) + + # get the shape and dtype of compressed parameters + compression_params: Dict[str, Tuple] = module.compressor.compression_param_info( + module.weight.shape, quantization_scheme.weights + ) + + # no need for this once quantization is initialized, will be replaced + # with the compressed parameter + delattr(module, "weight") + + # populate compressed weights and quantization parameters + for name, (shape, dtype) in compression_params.items(): + param = Parameter( + torch.empty(shape, device=device, dtype=dtype), requires_grad=False + ) + module.register_parameter(name, param) + + # mark module as compressed + module.quantization_status = QuantizationStatus.COMPRESSED + + # handles case where forward is wrapped in new_forward by accelerate hooks + if hasattr(module, "_old_forward"): + module._old_forward = CompressedLinear.forward.__get__( + module, CompressedLinear + ) + + return module + + def forward(self, input: Tensor) -> Tensor: + """ + Decompresses the weight, then runs the wrapped forward pass + """ + uncompressed_weight = self.compressor.decompress_module(self) + return linear(input, uncompressed_weight, self.bias) diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/registry/__init__.py b/.venv/lib/python3.11/site-packages/compressed_tensors/registry/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..241d9d55ebac91d271c09ec0c003f1abedb3fb00 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/registry/__init__.py @@ -0,0 +1,17 @@ +# flake8: noqa + +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .registry import * diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/registry/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/registry/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..170716c548f6f6baff5c8245781cb2ae81019ed0 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/registry/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/registry/__pycache__/registry.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/registry/__pycache__/registry.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55d80580ecfb995bab28621a4e95a814c693bf70 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/registry/__pycache__/registry.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/registry/registry.py b/.venv/lib/python3.11/site-packages/compressed_tensors/registry/registry.py new file mode 100644 index 0000000000000000000000000000000000000000..76026313aea054dbb32f3d9044aab190bc153053 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/registry/registry.py @@ -0,0 +1,360 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Universal registry to support registration and loading of child classes and plugins +of neuralmagic utilities +""" + +import importlib +from collections import defaultdict +from typing import Any, Dict, List, Optional, Type, Union + + +__all__ = [ + "RegistryMixin", + "register", + "get_from_registry", + "registered_names", + "registered_aliases", + "standardize_lookup_name", +] + + +_ALIAS_REGISTRY: Dict[Type, Dict[str, str]] = defaultdict(dict) +_REGISTRY: Dict[Type, Dict[str, Any]] = defaultdict(dict) + + +def standardize_lookup_name(name: str) -> str: + """ + Standardize the given name for lookup in the registry. + This will replace all underscores and spaces with hyphens and + convert the name to lowercase. + + example: + ``` + standardize_lookup_name("Foo_bar baz") == "foo-bar-baz" + ``` + + :param name: name to standardize + :return: standardized name + """ + return name.replace("_", "-").replace(" ", "-").lower() + + +def standardize_alias_name( + name: Union[None, str, List[str]] +) -> Union[None, str, List[str]]: + if name is None: + return None + elif isinstance(name, str): + return standardize_lookup_name(name) + else: # isinstance(name, list) + return [standardize_lookup_name(n) for n in name] + + +class RegistryMixin: + """ + Universal registry to support registration and loading of child classes and plugins + of neuralmagic utilities. + + Classes that require a registry or plugins may add the `RegistryMixin` and use + `register` and `load` as the main entrypoints for adding new implementations and + loading requested values from its registry. + + If a class should only have its child classes in its registry, the class should + set the static attribute `registry_requires_subclass` to True + + example + ```python + class Dataset(RegistryMixin): + pass + + + # register with default name + @Dataset.register() + class ImageNetDataset(Dataset): + pass + + # load as "ImageNetDataset" + imagenet = Dataset.load("ImageNetDataset") + + # register with custom name + @Dataset.register(name="cifar-dataset") + class Cifar(Dataset): + pass + + Note: the name will be standardized for lookup in the registry. + For example, if a class is registered as "cifar_dataset" or + "cifar dataset", it will be stored as "cifar-dataset". The user + will be able to load the class with any of the three name variants. + + # register with multiple aliases + @Dataset.register(alias=["cifar-10-dataset", "cifar_100_dataset"]) + class Cifar(Dataset): + pass + + # load as "cifar-dataset" + cifar = Dataset.load_from_registry("cifar-dataset") + + # load from custom file that implements a dataset + mnist = Dataset.load_from_registry("/path/to/mnnist_dataset.py:MnistDataset") + ``` + """ + + # set to True in child class to add check that registered/retrieved values + # implement the class it is registered to + registry_requires_subclass: bool = False + + @classmethod + def register( + cls, name: Optional[str] = None, alias: Union[List[str], str, None] = None + ): + """ + Decorator for registering a value (ie class or function) wrapped by this + decorator to the base class (class that .register is called from) + + :param name: name or list of names to register the wrapped value as, + defaults to value.__name__ + :param alias: alias or list of aliases to register the wrapped value as, + defaults to None + :return: register decorator + """ + + def decorator(value: Any): + cls.register_value(value, name=name, alias=alias) + return value + + return decorator + + @classmethod + def register_value( + cls, value: Any, name: str, alias: Union[str, List[str], None] = None + ): + """ + Registers the given value to the class `.register_value` is called from + :param value: value to register + :param name: name to register the wrapped value as, + defaults to value.__name__ + :param alias: alias or list of aliases to register the wrapped value as, + defaults to None + """ + register( + parent_class=cls, + value=value, + name=name, + alias=alias, + require_subclass=cls.registry_requires_subclass, + ) + + @classmethod + def load_from_registry(cls, name: str, **constructor_kwargs) -> object: + """ + :param name: name of registered class to load + :param constructor_kwargs: arguments to pass to the constructor retrieved + from the registry + :return: loaded object registered to this class under the given name, + constructed with the given kwargs. Raises error if the name is + not found in the registry + """ + constructor = cls.get_value_from_registry(name=name) + return constructor(**constructor_kwargs) + + @classmethod + def get_value_from_registry(cls, name: str): + """ + :param name: name to retrieve from the registry + :return: value from retrieved the registry for the given name, raises + error if not found + """ + return get_from_registry( + parent_class=cls, + name=name, + require_subclass=cls.registry_requires_subclass, + ) + + @classmethod + def registered_names(cls) -> List[str]: + """ + :return: list of all names registered to this class + """ + return registered_names(cls) + + @classmethod + def registered_aliases(cls) -> List[str]: + """ + :return: list of all aliases registered to this class + """ + return registered_aliases(cls) + + +def register( + parent_class: Type, + value: Any, + name: Optional[str] = None, + alias: Union[List[str], str, None] = None, + require_subclass: bool = False, +): + """ + :param parent_class: class to register the name under + :param value: the value to register + :param name: name to register the wrapped value as, defaults to value.__name__ + :param alias: alias or list of aliases to register the wrapped value as, + defaults to None + :param require_subclass: require that value is a subclass of the class this + method is called from + """ + if name is None: + # default name + name = value.__name__ + + name = standardize_lookup_name(name) + alias = standardize_alias_name(alias) + register_alias(name=name, alias=alias, parent_class=parent_class) + + if require_subclass: + _validate_subclass(parent_class, value) + + if name in _REGISTRY[parent_class]: + # name already exists - raise error if two different values are attempting + # to share the same name + registered_value = _REGISTRY[parent_class][name] + if registered_value is not value: + raise RuntimeError( + f"Attempting to register name {name} as {value} " + f"however {name} has already been registered as {registered_value}" + ) + else: + _REGISTRY[parent_class][name] = value + + +def get_from_registry( + parent_class: Type, name: str, require_subclass: bool = False +) -> Any: + """ + :param parent_class: class that the name is registered under + :param name: name to retrieve from the registry of the class + :param require_subclass: require that value is a subclass of the class this + method is called from + :return: value from retrieved the registry for the given name, raises + error if not found + """ + name = standardize_lookup_name(name) + + if ":" in name: + # user specifying specific module to load and value to import + module_path, value_name = name.split(":") + retrieved_value = _import_and_get_value_from_module(module_path, value_name) + else: + # look up name in alias registry + name = _ALIAS_REGISTRY[parent_class].get(name, name) + # look up name in registry + retrieved_value = _REGISTRY[parent_class].get(name) + if retrieved_value is None: + raise KeyError( + f"Unable to find {name} registered under type {parent_class}.\n" + f"Registered values for {parent_class}: " + f"{registered_names(parent_class)}\n" + f"Registered aliases for {parent_class}: " + f"{registered_aliases(parent_class)}" + ) + + if require_subclass: + _validate_subclass(parent_class, retrieved_value) + + return retrieved_value + + +def registered_names(parent_class: Type) -> List[str]: + """ + :param parent_class: class to look up the registry of + :return: all names registered to the given class + """ + return list(_REGISTRY[parent_class].keys()) + + +def registered_aliases(parent_class: Type) -> List[str]: + """ + :param parent_class: class to look up the registry of + :return: all aliases registered to the given class + """ + registered_aliases_plus_names = list(_ALIAS_REGISTRY[parent_class].keys()) + registered_aliases = list( + set(registered_aliases_plus_names) - set(registered_names(parent_class)) + ) + return registered_aliases + + +def register_alias( + name: str, parent_class: Type, alias: Union[str, List[str], None] = None +): + """ + Updates the mapping from the alias(es) to the given name. + If the alias is None, the name is used as the alias. + ``` + + :param name: name that the alias refers to + :param parent_class: class that the name is registered under + :param alias: single alias or list of aliases that + refer to the name, defaults to None + """ + if alias is not None: + alias = alias if isinstance(alias, list) else [alias] + else: + alias = [] + + if name in alias: + raise KeyError( + f"Attempting to register alias {name}, " + f"that is identical to the standardized name: {name}." + ) + alias.append(name) + + for alias_name in alias: + if alias_name in _ALIAS_REGISTRY[parent_class]: + raise KeyError( + f"Attempting to register alias {alias_name} as {name} " + f"however {alias_name} has already been registered as " + f"{_ALIAS_REGISTRY[alias_name]}" + ) + _ALIAS_REGISTRY[parent_class][alias_name] = name + + +def _import_and_get_value_from_module(module_path: str, value_name: str) -> Any: + # import the given module path and try to get the value_name if it is included + # in the module + + # load module + spec = importlib.util.spec_from_file_location( + f"plugin_module_for_{value_name}", module_path + ) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + # get value from module + value = getattr(module, value_name, None) + + if not value: + raise RuntimeError( + f"Unable to find attribute {value_name} in module {module_path}" + ) + return value + + +def _validate_subclass(parent_class: Type, child_class: Type): + if not issubclass(child_class, parent_class): + raise ValueError( + f"class {child_class} is not a subclass of the class it is " + f"registered for: {parent_class}." + ) diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__init__.py b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..976d55f789b453db0e60ead84acfcbf54c17ba54 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__init__.py @@ -0,0 +1,21 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# flake8: noqa + +from .helpers import * +from .offload import * +from .permutations_24 import * +from .permute import * +from .safetensors_load import * +from .semi_structured_conversions import * diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dbe3205f641e0e1833feb83647febaaffb1cf403 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/helpers.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/helpers.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d583dfaefdc5e224e9a44d5d9997f564d77f67a Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/helpers.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/offload.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/offload.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..239a42d5dae256f0d1576ce4b9328b2a71aaba34 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/offload.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/permutations_24.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/permutations_24.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..483bb508ef1aa7c91ade72f0fc76333a727747c7 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/permutations_24.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/permute.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/permute.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62744d4fbfeaded78c0eeeb03fb8676a1a9f7b65 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/permute.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/safetensors_load.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/safetensors_load.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ec20c9b203bc119b643d0b7be01e90ae2aad3d4 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/safetensors_load.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/semi_structured_conversions.cpython-311.pyc b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/semi_structured_conversions.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b0a7f831a09b5ac09d2ce3318242b3f076c2372 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/__pycache__/semi_structured_conversions.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/helpers.py b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..82ebf21a7326a85fd1c5d29365dc6ae1e6be9112 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/helpers.py @@ -0,0 +1,326 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import warnings +from functools import wraps +from typing import Any, Callable, Dict, List, Optional + +import numpy +import torch +from transformers import AutoConfig + + +__all__ = [ + "infer_compressor_from_model_config", + "fix_fsdp_module_name", + "tensor_follows_mask_structure", + "replace_module", + "is_compressed_tensors_config", + "getattr_chain", + "deprecated", + "Aliasable", + "combine_shards", + "shard_tensor", + "pack_bitmasks", + "unpack_bitmasks", +] + +FSDP_WRAPPER_NAME = "_fsdp_wrapped_module" + + +def infer_compressor_from_model_config( + pretrained_model_name_or_path: str, +) -> Optional["ModelCompressor"]: # noqa: F821 + """ + Given a path to a model config, extract a sparsity config if it exists and return + the associated ModelCompressor + + :param pretrained_model_name_or_path: path to model config on disk or HF hub + :return: matching compressor if config contains a sparsity config + """ + from compressed_tensors.compressors import ModelCompressor + from compressed_tensors.config import CompressionConfig + + config = AutoConfig.from_pretrained(pretrained_model_name_or_path) + sparsity_config = ModelCompressor.parse_sparsity_config(config) + if sparsity_config is None: + return None + + format = sparsity_config.get("format") + sparsity_config = CompressionConfig.load_from_registry(format, **sparsity_config) + compressor = ModelCompressor.load_from_registry(format, config=sparsity_config) + return compressor + + +# TODO: There is already the same function in +# SparseML, should be moved to a shared location +# in the future +def fix_fsdp_module_name(name: str) -> str: + """ + Remove FSDP wrapper prefixes from a module name + Accounts for scenario where FSDP_WRAPPER_NAME is + at the end of the name, as well as in the middle. + :param name: name to strip + :return: stripped name + """ + return name.replace(FSDP_WRAPPER_NAME + ".", "").replace( + "." + FSDP_WRAPPER_NAME, "" + ) + + +def tensor_follows_mask_structure(tensor, mask: str = "2:4") -> bool: + """ + :param tensor: tensor to check + :param mask: mask structure to check for, in the format "n:m" + :return: True if the tensor follows the mask structure, False otherwise. + Note, some weights can incidentally be zero, so we check for + atleast n zeros in each chunk of size m + """ + + n, m = tuple(map(int, mask.split(":"))) + # Reshape the tensor into chunks of size m + tensor = tensor.view(-1, m) + + # Count the number of zeros in each chunk + zero_counts = (tensor == 0).sum(dim=1) + + # Check if the number of zeros in each chunk atleast n + # Greater than sign is needed as some weights can incidentally + # be zero + if not torch.all(zero_counts >= n).item(): + raise ValueError() + + return True + + +def replace_module(model: torch.nn.Module, name: str, new_module: torch.nn.Module): + if "." in name: + parent_name = name.rsplit(".", 1)[0] + child_name = name[len(parent_name) + 1 :] + parent = model.get_submodule(parent_name) + else: + parent_name = "" + parent = model + child_name = name + setattr(parent, child_name, new_module) + + +def is_compressed_tensors_config(compression_config: Any) -> bool: + """ + Returns True if CompressedTensorsConfig is available from transformers and + compression_config is an instance of CompressedTensorsConfig + + See: https://github.com/huggingface/transformers/pull/31704 + """ + try: + from transformers.utils.quantization_config import CompressedTensorsConfig + + return isinstance(compression_config, CompressedTensorsConfig) + except ImportError: + return False + + +def getattr_chain(obj: Any, chain_str: str, *args, **kwargs) -> Any: + """ + Chain multiple getattr calls, separated by `.` + + :param obj: base object whose attributes are being retrieved + :param chain_str: attribute names separated by `.` + :param default: default value, throw error otherwise + """ + if len(args) >= 1: + has_default = True + default = args[0] + elif "default" in kwargs: + has_default = True + default = kwargs["default"] + else: + has_default = False + + attr_names = chain_str.split(".") + + res = obj + for attr_name in attr_names: + if not hasattr(res, attr_name): + if has_default: + return default + else: + raise AttributeError(f"{res} object has no attribute {attr_name}") + res = getattr(res, attr_name) + + return res + + +def deprecated(future_name: Optional[str] = None, message: Optional[str] = None): + """ + Decorator to mark functions as deprecated + + :param new_function: Function called in place of depreciated function + :param message: Depreciation message, replaces default depreciation message + """ + + def decorator(func: Callable[[Any], Any]): + nonlocal message + + if message is None: + message = ( + f"{func.__name__} is deprecated and will be removed in a future release" + ) + if future_name is not None: + message += f". Please use {future_name} instead." + + @wraps(func) + def wrapped(*args, **kwargs): + warnings.warn(message, DeprecationWarning, stacklevel=2) + return func(*args, **kwargs) + + return wrapped + + return decorator + + +class Aliasable: + """ + A mixin for enums to allow aliasing of enum members + + Example: + >>> class MyClass(Aliasable, int, Enum): + >>> ... + """ + + @staticmethod + def get_aliases() -> Dict[str, str]: + raise NotImplementedError() + + def __eq__(self, other): + if isinstance(other, self.__class__): + aliases = self.get_aliases() + return self.value == other.value or ( + aliases.get(self.value, self.value) + == aliases.get(other.value, other.value) + ) + else: + aliases = self.get_aliases() + self_value = aliases.get(self.value, self.value) + other_value = aliases.get(other, other) + return self_value == other_value + + def __hash__(self): + canonical_value = self.aliases.get(self.value, self.value) + return hash(canonical_value) + + +def shard_tensor( + tensor: torch.Tensor, shard_sizes: List[int], dim: int = 0 +) -> List[torch.Tensor]: + """ + Shards a tensor into a list of tensors along a given dimension. + + raises: ValueError: If the sum of shard_sizes does not match the + size of the tensor along the given dimension. + + :param tensor: The input tensor to shard. + :param shard_sizes : List of sizes for each shard along the specified dimension. + :param dim : The dimension along which to shard the tensor. + :returns: A list of tensors sharded along the specified dimension. + """ + if sum(shard_sizes) != tensor.size(dim): + raise ValueError( + "Sum of shard_sizes must equal the size of the tensor " + "along the specified dimension." + ) + + shards = [] + start_idx = 0 + + for size in shard_sizes: + end_idx = start_idx + size + shard = tensor.narrow(dim, start_idx, size) + shards.append(shard) + start_idx = end_idx + + return shards + + +def combine_shards(shards, dim=0): + """ + Combine decompressed shards along a given dimension using `narrow`. + + :param shards: List of decompressed shard tensors. + :param dim: Dimension to combine along (default: 0). + :return: Combined decompressed tensor. + """ + if not shards: + raise ValueError("The list of shards is empty.") + + # Assert that all shards have the same dtype + shard_dtypes = {shard.dtype for shard in shards} + if len(shard_dtypes) > 1: + raise ValueError("All shards must have the same dtype.") + + # Determine the total shape of the combined tensor + total_shape = list(shards[0].shape) + total_shape[dim] = sum(shard.shape[dim] for shard in shards) + + # Create the combined tensor + combined = torch.zeros(total_shape, dtype=shards[0].dtype, device=shards[0].device) + + # Fill the combined tensor using narrow + shard_offset = 0 + for shard in shards: + shard_size = shard.shape[dim] + combined.narrow(dim, shard_offset, shard_size).copy_(shard) + shard_offset += shard_size + + return combined + + +def pack_bitmasks(bytemasks: torch.Tensor) -> torch.Tensor: + """ + Converts a bytemask tensor to a bitmask tensor to reduce memory. Shape RxC will be + compressed to R x ceil(C/8) + + :param bytemasks: mask tensor where each byte corresponds to a weight + :return: mask tensor where each bit corresounds to a weight + """ + packed_bits_numpy = numpy.packbits(bytemasks.numpy(), axis=-1, bitorder="little") + packed_bits_torch = torch.from_numpy(packed_bits_numpy) + + return packed_bits_torch + + +def unpack_bitmasks( + packed_bitmasks: torch.Tensor, original_shape: List[int] +) -> torch.Tensor: + """ + Converts a bitmask tensor back to a bytemask tensor for use during decompression + + :param packed_bitmasks: mask tensor where each bit corresponds to a weight + :param original_shape: dense shape to decompress to + :return: boolean mask of weights in the original dense shape + """ + # Unpack the bits + unpacked_bits = numpy.unpackbits( + packed_bitmasks.cpu().numpy(), + axis=-1, + count=original_shape[-1], + bitorder="little", + ) + + # Reshape to match the original shape + unpacked_bitmasks_torch = torch.from_numpy( + unpacked_bits.reshape(original_shape).astype(bool) + ) + + return unpacked_bitmasks_torch diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/offload.py b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/offload.py new file mode 100644 index 0000000000000000000000000000000000000000..b3c77c580f61ffa669e532251866b86511cfe816 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/offload.py @@ -0,0 +1,404 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Utilities associated with offloading functionality provided by `accelerate`. + +| ----------------------------------------------------------------------------------------------------- | # noqa: E501 +| Operation | Without offloading support | With offloading support | # noqa: E501 +| --------- | -------------------------------------- | ------------------------------------------------ | # noqa: E501 +| Add | module.register_parameter(name, param) | register_offload_parameter(module, name, param) | # noqa: E501 +| Check | N/A | has_offloaded_params(module) | # noqa: E501 +| Onload | N/A | with align_module_device(module) | # noqa: E501 +| Update | module.name.data.copy_(new_data) | update_offload_parameter(module, name, new_data) | # noqa: E501 +| Delete | del module.name | delete_offload_parameter(module, name) | # noqa: E501 +| ----------------------------------------------------------------------------------------------------- | # noqa: E501 +""" + +import contextlib +from functools import wraps +from typing import Any, Callable, Dict, Literal, Optional, Union + +import torch + + +try: + from accelerate.hooks import ( + AlignDevicesHook, + add_hook_to_module, + remove_hook_from_module, + ) + from accelerate.utils import ( + OffloadedWeightsLoader, + PrefixedDataset, + set_module_tensor_to_device, + ) + + _has_accelerate = True +except ImportError: + _has_accelerate = False + AlignDevicesHook = None + add_hook_to_module = None + remove_hook_from_module = None + OffloadedWeightsLoader = None + PrefixedDataset = None + set_module_tensor_to_device = None + + +__all__ = [ + "is_module_offloaded", + "get_execution_device", + "get_offloaded_device", + "update_prefix_dict", + "update_parameter_data", + "register_offload_parameter", + "update_offload_parameter", + "delete_offload_parameter", + "has_offloaded_params", + "disable_hf_hook", + "align_module_device", +] + + +def check_accelerate(fallback: Any): + def decorator(func: Callable[[Any], Any]): + if not _has_accelerate: + + @wraps(func) + def fallback_fn(*args, **kwargs): + return fallback + + return fallback_fn + + return func + + return decorator + + +""" Candidates for Depreciation """ + + +@check_accelerate(fallback=False) +def is_module_offloaded(module: torch.nn.Module) -> bool: + return has_offloaded_params(module) + + +def get_execution_device(module: torch.nn.Module) -> torch.device: + """ + :param module: module to check + :return: device module is loaded onto during forward pass + """ + if has_offloaded_params(module): + return module._hf_hook.execution_device + device = next(module.parameters()).device + + # offload only gets set for leaf modules, fallback to checking for device type + if device.type == "meta": + return module._hf_hook.execution_device + + return device + + +def get_offloaded_device(module: torch.nn.Module) -> torch.device: + """ + :param module: module to check + :return: device module is offloaded to onto after forward pass + """ + if has_offloaded_params(module): + first_key = list(module._hf_hook.weights_map.keys())[0] + prefix_dataset = module._hf_hook.weights_map.dataset + return prefix_dataset[first_key].device + return next(module.parameters()).device + + +@check_accelerate(fallback=None) +def update_prefix_dict(module: torch.nn.Module, key: str, data: torch.Tensor): + """ + Updates the offloaded state dict for a given module. Parameter named key is replaced + by data. This is neccesary because parameter updates for offloaded modules do not + persist automatically between loads. This function only affects the offloaded + state dict and not the current state of the loaded module. + + :param module: module containing the parameter to update + :param key: name of parameter to update + :param data: tensor to update parameter with in the offloaded state dict + """ + if not has_offloaded_params(module): + raise ValueError("Prefix dict is only applicable to offloaded modules") + + weights_map = module._hf_hook.weights_map + offload_to_weights_map(weights_map, key, data) + + +def update_parameter_data( + module: torch.nn.Module, new_param_data: torch.Tensor, param_name: str +): + """ + Update the data of an existing parameter and its offload dict. Supports both + parameters of offloaded modules and non-offloaded modules + + :param module: module containing the parameter to update + :param new_param_data: tensor to update parameter with + :param param_name: name of module parameter to update + """ + update_offload_parameter(module, param_name, new_param_data) + + +""" Candidates for Upstreaming """ + + +def register_offload_parameter( + module: torch.nn.Module, + name: str, + parameter: torch.nn.Parameter, + offload_device: Optional[Union[torch.device, Literal["disk"]]] = None, +): + """ + Register a parameter to the given module which may be offloaded + + :param module: maybe offloaded module + :param name: name of newly registered parameter + :param parameter: parameter being registered + :param offload_device: device on which weight will be offloaded to. If None is + provided, then infer device from parameters on module + """ + has_onload = any(p.device != torch.device("meta") for p in module.parameters()) + module.register_parameter(name, parameter) + + if has_offloaded_params(module): + weights_map = module._hf_hook.weights_map + offload_to_weights_map(weights_map, name, parameter.data, offload_device) + if not has_onload: + set_module_tensor_to_device(module, name, "meta") + + +def update_offload_parameter( + module: torch.nn.Module, + name: str, + data: Optional[torch.Tensor], + offload_device: Optional[Union[torch.device, Literal["disk"]]] = None, +): + """ + Update the data of an existing parameter and its offload dict. Supports both + parameters of offloaded modules and non-offloaded modules + + :param module: module containing the parameter to update + :param name: name of module parameter to update + :param data: tensor to update parameter with + :param offload_device: device on which weight will be offloaded to. If None is + provided, then infer device from parameters on module + """ + param = getattr(module, name) + data = data.to(param.dtype) + + # copy data into onloaded parameter if applicable + if param.device != "meta": + param.data.copy_(data) + + # update offload dict + if has_offloaded_params(module): + weights_map = module._hf_hook.weights_map + offload_to_weights_map(weights_map, name, data, offload_device) + + +def delete_offload_parameter(module: torch.nn.Module, name: str): + """ + Delete a parameter from a module which may be offloaded + + :param module: maybe offloaded module + :param name: name of parameter being deleted + """ + delattr(module, name) + + if has_offloaded_params(module): + weights_map = module._hf_hook.weights_map + delete_from_weights_map(weights_map, name) + + +@check_accelerate(fallback=contextlib.nullcontext()) +@contextlib.contextmanager +def disable_hf_hook(module: torch.nn.Module): + hooks = {} + + def collect_hooks(module): + nonlocal hooks + if hasattr(module, "_hf_hook"): + hooks[module] = module._hf_hook + remove_hook_from_module(module) + + module.apply(collect_hooks) + + yield + + for submodule, hook in hooks.items(): + add_hook_to_module(submodule, hook) + + +@check_accelerate(fallback=None) +def offload_to_weights_map( + weights_map: Union[PrefixedDataset, Dict, OffloadedWeightsLoader], + key: str, + value: torch.Tensor, + offload_device: Optional[Union[torch.device, Literal["disk"]]] = None, +): + """ + Helper function which implements offloaded item assignment for PrefixedDataset, + OffloadedWeightsLoader, and Dict types. + + :param weights_map: weight map to be updated with offload information + :param key: key used to identify weight location + :param value: weight being offloaded + :param offload_device: device on which weight will be offloaded to. If None is + provided, then infer device from parameters in weights_map + """ + if isinstance(weights_map, PrefixedDataset): + if offload_device == "disk": + raise ValueError(f"Cannot offload to disk with type {type(weights_map)}") + + dataset = weights_map.dataset + key = f"{weights_map.prefix}{key}" + offload_to_weights_map(dataset, key, value, offload_device) + + elif isinstance(weights_map, OffloadedWeightsLoader): + if key not in weights_map.all_keys: + weights_map.all_keys.append(key) + + if len(weights_map.index) <= 0 and offload_device != "disk": + offload_to_weights_map(weights_map.state_dict, key, value, offload_device) + + else: + raise NotImplementedError( + "Updating weights_map with disk offloading is not implemented yet" + ) + + elif isinstance(weights_map, dict): + if offload_device == "disk": + raise ValueError(f"Cannot offload to disk with type {type(weights_map)}") + + # infer offload device + if offload_device is None: + if key in weights_map: + offload_device = weights_map[key].device + else: + tens = next(iter(weights_map.values()), None) + if tens is None: + raise ValueError( + "Cannot infer offload device from empty weights_map" + ) + offload_device = tens.device + + weights_map[key] = value.to(device=offload_device) + + else: + raise NotImplementedError( + "Updating offload data not implemented for weights_map of type " + f"{type(weights_map)}" + ) + + +@check_accelerate(fallback=None) +def delete_from_weights_map( + weights_map: Union[PrefixedDataset, Dict, OffloadedWeightsLoader], + key: str, +): + if isinstance(weights_map, PrefixedDataset): + dataset = weights_map.dataset + key = f"{weights_map.prefix}{key}" + delete_from_weights_map(dataset, key) + + elif isinstance(weights_map, OffloadedWeightsLoader): + if len(weights_map.index) <= 0: + delete_from_weights_map(weights_map.state_dict, key) + + else: + raise NotImplementedError( + "Delete from weights_map with disk offloading is not implemented yet" + ) + + elif isinstance(weights_map, dict): + del weights_map[key] + + else: + raise NotImplementedError( + "Updating offload data not implemented for weights_map of type " + f"{type(weights_map)}" + ) + + +""" Upstreamed Functions """ + + +# introduced in accelerate v1.1.0 +@check_accelerate(fallback=False) +def has_offloaded_params(module: torch.nn.Module) -> bool: + """ + Checks if a module has offloaded parameters by checking if the given module has a + AlignDevicesHook attached with offloading enabled + + Args: + module (`torch.nn.Module`): The module to check for an offload hook. + + Returns: + bool: `True` if the module has an offload hook and offloading is enabled, + `False` otherwise. + """ + return ( + hasattr(module, "_hf_hook") + and isinstance(module._hf_hook, AlignDevicesHook) + and module._hf_hook.offload + ) + + +# introduced in accelerate v1.1.0 +@check_accelerate(fallback=contextlib.nullcontext()) +@contextlib.contextmanager +def align_module_device( + module: torch.nn.Module, execution_device: Optional[torch.device] = None +): + """ + Context manager that moves a module's parameters to the specified execution device. + + Args: + module (`torch.nn.Module`): + Module with parameters to align. + execution_device (`torch.device`, *optional*): + If provided, overrides the module's execution device within the context. + Otherwise, use hook execution device or pass + """ + if has_offloaded_params(module): + if execution_device is not None: + original_device = module._hf_hook.execution_device + module._hf_hook.execution_device = execution_device + + try: + module._hf_hook.pre_forward(module) + yield + finally: + module._hf_hook.post_forward(module, None) + if execution_device is not None: + module._hf_hook.execution_device = original_device + + elif execution_device is not None: + devices = { + name: param.device for name, param in module.named_parameters(recurse=False) + } + try: + for name in devices: + set_module_tensor_to_device(module, name, execution_device) + yield + finally: + for name, device in devices.items(): + set_module_tensor_to_device(module, name, device) + + else: + yield diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/permutations_24.py b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/permutations_24.py new file mode 100644 index 0000000000000000000000000000000000000000..5b078e270e982f3a3448391eab1cf8ce1cbe4def --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/permutations_24.py @@ -0,0 +1,65 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import numpy +import torch + + +__all__ = ["get_permutations_24"] + + +# Precompute permutations for Marlin24 weight and scale shuffling +# Originally implemented in nm-vllm/vllm/model_executor/layers/quantization/utils/marlin_24_perms.py # noqa: E501 +# +# Marlin works on [16*2,64] tiles. The goal of the permutations is to reorder the weight +# data so that it is compatible with the tensor-core format that is described here: +# https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#matrix-fragments-for-mma-m16n8k16-with-floating-point-type # noqa: E501 +# +# As a result of this reordering, the vector loads inside the kernel will get the data +# as it is needed for tensor-core (without the need to use ldmatrix instructions) +def get_permutations_24(num_bits): + perm_list = [] + for i in range(32): + perm1 = [] + col = i // 4 + col_o = col // 2 + for block in [0, 1]: + for row in [ + 2 * (i % 4), + 2 * (i % 4) + 1, + 2 * (i % 4 + 4), + 2 * (i % 4 + 4) + 1, + ]: + perm1.append(16 * row + col_o * 256 + 8 * (col % 2) + 4 * block) + for j in range(4): + perm_list.extend([p + 1 * j for p in perm1]) + perm = numpy.array(perm_list) + + if num_bits == 4: + interleave = numpy.array([0, 2, 4, 6, 1, 3, 5, 7]) + elif num_bits == 8: + interleave = numpy.array([0, 2, 1, 3]) + else: + raise ValueError("num_bits must be 4 or 8, got {}".format(num_bits)) + + perm = perm.reshape((-1, len(interleave)))[:, interleave].ravel() + perm = torch.from_numpy(perm) + scale_perm = [] + for i in range(8): + scale_perm.extend([i * 8 + j for j in [0, 4, 1, 5, 2, 6, 3, 7]]) + scale_perm_single = [] + for i in range(8): + scale_perm_single.extend([8 * i + j for j in [0, 1, 2, 3, 4, 5, 6, 7]]) + return perm, scale_perm, scale_perm_single diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/permute.py b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/permute.py new file mode 100644 index 0000000000000000000000000000000000000000..e31d4862b69818a6de37cf93883101df2da0b1a9 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/permute.py @@ -0,0 +1,70 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Set, Tuple + +import torch + + +__all__ = ["safe_permute"] + + +# these datatypes are missing implementations required for standard permutation +_EXPERIMENTAL_DTYPES: Set[Tuple[torch.dtype, torch.device]] = set() + + +def safe_permute(value: torch.Tensor, perm: torch.Tensor, dim: int = 0) -> torch.Tensor: + """ + Perform out-of-place permutation without using torch.Tensor.index_put_, + whose implementation is missing for datatypes such as `torch.float8_e4m3fn` + + :param value: tensor to permute + :param perm: permutation map + :param dim: dimension along which to apply permutation + :return: permuted value + """ + dtype_tuple = (value.dtype, value.device) + + if dtype_tuple in _EXPERIMENTAL_DTYPES: + return _fallback_permute(value, perm, dim) + + try: + return value[tuple([slice(None)] * dim + [perm])] + except RuntimeError: + # Mark dtype as experimental if advanced indexing fails + _EXPERIMENTAL_DTYPES.add(dtype_tuple) + return _fallback_permute(value, perm, dim) + + +def _fallback_permute( + value: torch.Tensor, perm: torch.Tensor, dim: int +) -> torch.Tensor: + """ + Fallback permutation method for experimental dtypes. + + :param value: tensor to permute + :param perm: permutation map + :param dim: dimension along which to apply permutation + :return: permuted value + """ + value_ret = value.clone() # cannot use zeros_like b/c of missing impl. + orig_slices = [slice(None)] * (dim + 1) + perm_slices = [slice(None)] * (dim + 1) + + for index, perm_index in enumerate(perm): + orig_slices[dim] = index + perm_slices[dim] = perm_index + value_ret[tuple(orig_slices)] = value[tuple(perm_slices)] + + return value_ret diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/safetensors_load.py b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/safetensors_load.py new file mode 100644 index 0000000000000000000000000000000000000000..ab4d04bf75adcb002f0ec2cf24f362fe4faf8bd9 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/safetensors_load.py @@ -0,0 +1,306 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import re +import struct +from typing import Dict, List, Optional, Tuple, Union + +from safetensors import safe_open +from torch import Tensor +from transformers.utils import SAFE_WEIGHTS_INDEX_NAME, SAFE_WEIGHTS_NAME, cached_file + + +__all__ = [ + "get_safetensors_folder", + "get_safetensors_header", + "match_param_name", + "merge_names", + "get_weight_mappings", + "get_nested_weight_mappings", + "get_nested_mappings_from_state_dict", + "get_quantization_state_dict", + "is_quantization_param", +] + +WeightMappingType = Dict[str, str] +NestedWeightMappingType = Dict[str, WeightMappingType] + + +def get_safetensors_folder( + pretrained_model_name_or_path: str, cache_dir: Optional[str] = None +) -> str: + """ + Given a Hugging Face stub or a local path, return the folder containing the + safetensors weight files + + :param pretrained_model_name_or_path: local path to model or HF stub + :param cache_dir: optional cache dir to search through, if none is specified the + model will be searched for in the default TRANSFORMERS_CACHE + :return: local folder containing model data + """ + if os.path.exists(pretrained_model_name_or_path): + # argument is a path to a local folder + return os.path.abspath(pretrained_model_name_or_path) + + safetensors_path = cached_file( + pretrained_model_name_or_path, + SAFE_WEIGHTS_NAME, + cache_dir=cache_dir, + _raise_exceptions_for_missing_entries=False, + ) + index_path = cached_file( + pretrained_model_name_or_path, + SAFE_WEIGHTS_INDEX_NAME, + cache_dir=cache_dir, + _raise_exceptions_for_missing_entries=False, + ) + if safetensors_path is not None: + # found a single cached safetensors file + return os.path.split(safetensors_path)[0] + if index_path is not None: + # found a cached safetensors weight index file + return os.path.split(index_path)[0] + + # model weights could not be found locally or cached from HF Hub + raise ValueError( + "Could not locate safetensors weight or index file from " + f"{pretrained_model_name_or_path}." + ) + + +def get_safetensors_header(safetensors_path: str) -> Dict[str, str]: + """ + Extracts the metadata from a safetensors file as JSON + + :param safetensors_path: path to a safetensors file + :return: dictionary of metadata extracted from the safetensors file + """ + with open(safetensors_path, "rb") as f: + length_of_header = struct.unpack(" Optional[str]: + """ + Helper function extracting the uncompressed parameterized layer name from a + compressed name. Assumes the compressed name was merged using merge_names. + + :param full_name: full name of parameter in compressed model + :param param_name: compression paramater name + :return: uncompressed name of the uncompressed parameterized layer + """ + pattern = r"^(.*)\." + param_name + r"$" + regex = re.findall(pattern, full_name) + if len(regex) == 0: + return None + return regex[0] + + +def merge_names(parent_name: str, child_name: str) -> str: + """ + Helper function for merging an uncompressed parameterized layer name with a + compression parameter. Names merged with this function can then be parsed by + match_param_name. + + :param parent_name: uncompressed parameterized layer name + :param child_name: compression parameter name + :return: merged compressed name + """ + return parent_name + "." + child_name + + +def get_weight_mappings(path_to_model_or_tensors: str) -> Dict[str, str]: + """ + Takes a path to a state dict saved in safetensors format and returns a mapping + from parameterized layer name to file location. + + { + layer.weight.bitmask: file_location, + layer.weight.row_offsets: file_location, + layer.weight.shape: file_location, + layer.weight.compressed: file_location + } + + This generalizes to cases where the model is split into multiple safetensors files + + :param path_to_model_or_tensors: path to directory that contains + safetensors (must contain either a single file or multiple files with an index), + or a path to a single safetensors file + :return: mapping of parameterized layer name to file location + """ + + if os.path.isfile(path_to_model_or_tensors): + # we have a single safetensors file to read + header = get_safetensors_header(path_to_model_or_tensors) + for key in header.keys(): + header[key] = path_to_model_or_tensors + header.pop("__metadata__", None) + else: + # we have a directory with multiple safetensors files + safetensors_path = os.path.join(path_to_model_or_tensors, SAFE_WEIGHTS_NAME) + index_path = os.path.join(path_to_model_or_tensors, SAFE_WEIGHTS_INDEX_NAME) + if os.path.exists(safetensors_path): + # we have a single safetensors file to read + header = get_safetensors_header(safetensors_path) + for key in header.keys(): + header[key] = SAFE_WEIGHTS_NAME + header.pop("__metadata__", None) + elif os.path.exists(index_path): + # we have multiple safetensors file, read from index + with open(index_path, "r", encoding="utf-8") as f: + index = json.load(f) + header = index["weight_map"] + else: + raise ValueError( + "Could not find a safetensors weight " + f"or index file at {path_to_model_or_tensors}" + ) + + # convert weight locations to full paths + for key, value in header.items(): + header[key] = os.path.join(path_to_model_or_tensors, value) + + return header + + +def get_nested_weight_mappings( + model_path: str, params_to_nest: List[str], return_unmatched_params: bool = False +) -> Union[NestedWeightMappingType, Tuple[NestedWeightMappingType, WeightMappingType]]: + """ + Takes a path to a state dict saved in safetensors format and returns a nested + mapping from uncompressed parameterized layer names to the file locations of + each layer's compression parameters. + + Example of the nested mapping: + layer: { + bitmask: file_location, + row_offsets: file_location, + shape: file_location, + compressed: file_location + } + + If other parameters are found that do not match the nested parameters, they will + be returned in a separate dictionary only if return_unmatched_params is True. + This dictionary may be needed for cases where compressors are stacked (e.g., + quantization compression followed by sparse compression). + + Example of the unmatched params mapping: + { + layer.weight_scale: file_location, + layer.input_scale: file_location + } + + This generalizes to cases where the model is split into multiple safetensors + files. + + :param model_path: Path to the safetensors state dict, must contain either a + single safetensors file or multiple files with an index. + :param params_to_nest: List of parameter names to nest. + :param return_unmatched_params: If True, return a second dictionary containing + the remaining parameters that were not matched to the params_to_nest. + :return: + - If return_unmatched_params is False: + NestedWeightMappingType: A nested mapping of parameterized layer names to + file locations of each layer's compression parameters. + - If return_unmatched_params is True: + Tuple[NestedWeightMappingType, WeightMappingType]: A tuple containing: + - NestedWeightMappingType: A nested mapping of parameterized layer + names to file locations of each layer's compression parameters. + - WeightMappingType: A mapping of the remaining parameter names to + their file locations that were not matched to the params_to_nest. + """ + weight_mappings = get_weight_mappings(model_path) + nested_weight_mappings = {} + unmatched_params = {} + + for key, file_location in weight_mappings.items(): + matched = False + for param_name in params_to_nest: + dense_param = match_param_name(key, param_name) + if dense_param: + if dense_param not in nested_weight_mappings: + nested_weight_mappings[dense_param] = {} + nested_weight_mappings[dense_param][param_name] = file_location + matched = True + if return_unmatched_params and not matched: + unmatched_params[key] = file_location + + if return_unmatched_params: + return nested_weight_mappings, unmatched_params + return nested_weight_mappings + + +def get_nested_mappings_from_state_dict( + state_dict, params_to_nest +) -> NestedWeightMappingType: + """ + Takes a state dict and returns a nested mapping from uncompressed + parameterized layer names to the value of + each layer's compression parameters. + + Example of the nested mapping: + layer: { + weight_scale: ..., + weight: ..., + zero_point: ..., + } + + :param state_dict: state dict of the model + :param params_to_nest: List of parameter names to nest. + :return: Nested mapping of parameterized layer names to the value of + each layer's compression parameters. + """ + nested_weight_mappings = {} + for key in state_dict.keys(): + for param_name in params_to_nest: + dense_param = match_param_name(key, param_name) + if dense_param: + if dense_param not in nested_weight_mappings: + nested_weight_mappings[dense_param] = {} + nested_weight_mappings[dense_param][param_name] = state_dict[key] + return nested_weight_mappings + + +def get_quantization_state_dict(model_path: str) -> Dict[str, Tensor]: + weight_mappings = get_weight_mappings(model_path) + state_dict = {} + for weight_name, safe_path in weight_mappings.items(): + if not is_quantization_param(weight_name): + continue + with safe_open(safe_path, framework="pt", device="cpu") as f: + state_dict[weight_name] = f.get_tensor(weight_name) + + return state_dict + + +def is_quantization_param(name: str) -> bool: + """ + Checks is a parameter name is associated with a quantization parameter + + :param name: parameter name to check + :return: True if parameter name is a quantization parameter, else False + """ + if name.endswith("_scale"): + return True + if name.endswith("zero_point"): + return True + if name.endswith("g_idx"): + return True + + return False diff --git a/.venv/lib/python3.11/site-packages/compressed_tensors/utils/semi_structured_conversions.py b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/semi_structured_conversions.py new file mode 100644 index 0000000000000000000000000000000000000000..ef318a48869e7d945030694460e438a0a7ae50e7 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/compressed_tensors/utils/semi_structured_conversions.py @@ -0,0 +1,342 @@ +# +# Modified by Roberto Lopez Castro (roberto.lopez.castro@udc.es). +# Pulled from nm-vllm/vllm/model_executor/layers/quantization/utils/format_24.py +# +# flake8: noqa +# isort: skip_file + +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import torch + + +__all__ = [ + "sparse_semi_structured_from_dense_cutlass", + "sparse_semi_structured_to_dense_cutlass", + "mask_creator", +] + + +# This is PyTorch implementation of main part of reorder_meta() +# function, from tools/util/include/cutlass/util/host_reorder.h file +# of CUTLASS source tree. Furthermore, CUTLASS template for sparse +# GEMM decides upon layout of this matrix, and at the moment for the +# sparse GEMM executed on tensor cores, this is layout described by +# ColumnMajorInterleaved<2> data structure, in +# include/cutlass/layout/matrix.h of CUTLASS source tree. The +# reordering of meta matrix into meta_reordered matrix calculated +# according to these segments of CUTLASS code is re-implemented here. +# Note that this calculation produces offsets for scattering metadata +# matrix elements into reordered metadata matrix elements (or, +# equivalently, for gathering reordered metadata matrix element back +# into metadata matrix elements). +def _calculate_meta_reordering_scatter_offsets(m, meta_ncols, meta_dtype, device): + dst_rows = torch.arange(0, m, device=device)[:, None].repeat(1, meta_ncols) + dst_cols = torch.arange(0, meta_ncols, device=device).repeat(m, 1) + + # Reorder the rows, then swizzle the 2x2 blocks. + group_x = 64 + group_y = 32 if meta_dtype.itemsize == 2 else 16 + + dst_rows = ( + dst_rows // group_x * group_x + + (dst_rows % 2) * 2 + + (dst_rows % 8) // 4 + + ((dst_rows % group_y) % 4) // 2 * 32 + + ((dst_rows % group_x) // 8) * 4 + ) + + topright = ((dst_rows % 2 == 0) & (dst_cols % 2 == 1)).to(torch.int8) + bottomleft = ((dst_rows % 2 == 1) & (dst_cols % 2 == 0)).to(torch.int8) + dst_rows += topright - bottomleft + dst_cols -= topright - bottomleft + + # Assumed that meta tensor is to be stored in CUTLASS + # InterleavedColumnMajor layout, and reverse engineered + # corresponding code to store values into this tensor. + interleave = 2 + cols_maj = dst_cols // interleave + cols_min = dst_cols % interleave + return (cols_maj * m * interleave + dst_rows * interleave + cols_min).view(-1) + + +# This function converts dense matrix into sparse semi-structured +# representation, producing "compressed" matrix, in the layout used by +# CUTLASS backend, and corresponding metadata matrix. +def sparse_semi_structured_from_dense_cutlass(dense): + if dense.dim() != 2: + raise RuntimeError( + f"Expected 2-dimensional dense tensor, got {dense.dim()}-dimensional tensor" # noqa: E501 + ) + + m, k = dense.shape + device = dense.device + + meta_dtype = torch.int8 + if dense.dtype == torch.int8: + meta_dtype = torch.int32 + elif dense.dtype in [torch.half, torch.bfloat16, torch.float, torch.int32]: + meta_dtype = torch.int16 + else: + raise RuntimeError(f"Invalid datatype {dense.dtype} of dense matrix") + quadbits_per_meta_elem = meta_dtype.itemsize * 8 // 4 + if quadbits_per_meta_elem not in (4, 8): + raise RuntimeError("Invalid number of elements per meta element calculated") + + if meta_dtype == torch.int32: + if m % 16 != 0: + raise RuntimeError( + f"Number of rows of dense matrix {m} must be divisible by 16" + ) + else: + if m % 32 != 0: + raise RuntimeError( + f"Number of rows of dense matrix {m} must be divisible by 32" + ) + if k % (4 * quadbits_per_meta_elem) != 0: + raise RuntimeError( + f"Number of columns of dense matrix {k} must be divisible by {4 * quadbits_per_meta_elem}" # noqa: E501 + ) + + if dense.dtype != torch.float: + ksparse = 4 + dense_4 = dense.view(-1, k // ksparse, ksparse) + m0, m1, m2, m3 = (dense_4 != 0).unbind(-1) + else: + ksparse = 2 + dense_2 = dense.view(-1, k // ksparse, ksparse) + m0, m2 = m1, m3 = (dense_2 != 0).unbind(-1) + meta_ncols = k // (ksparse * quadbits_per_meta_elem) + + # Encoding quadruples of True/False values as follows: + # [True, True, False, False] -> 0b0100 + # [True, False, True, False] -> 0b1000 + # [False, True, True, False] -> 0b1001 + # [True, False, False, True ] -> 0b1100 + # [False, True, False, True ] -> 0b1101 + # [False, False, True, True ] -> 0b1110 + # Thus, lower two bits in the encoding are index of the True value + # at the lowest index in the quadruple, and the higher two bits in + # the encoding are index of the other True value in the quadruple. + # In case there are less than two True values, than False value or + # values at some index or indices are considered True for the + # encoding. In case there are more than two True values, then the + # excess True value(s) at some indices are considered False for + # the encoding. The exact encodings used for these cases are as + # follows: + # [False, False, False, False] -> 0b1110 + # [False, False, False, True ] -> 0b1110 + # [False, False, True, False] -> 0b1110 + # [False, True, False, False] -> 0b1001 + # [False, True, True, True ] -> 0b1101 + # [True, False, False, False] -> 0b1000 + # [True, False, True, True ] -> 0b1100 + # [True, True, False, True ] -> 0b0100 + # [True, True, True, False] -> 0b0100 + # [True, True, True, True ] -> 0b0100 + # These particular encodings are chosen, with the help of Espresso + # logic minimizer software, for the purpose of minimization of + # corresponding Boolean functions, that translate non-zero flags + # into encoding bits. Note also possible choices for the first + # and last of these encodings were limited only to (0b0100, + # 0b1110), in order to produce valid encodings for 1:2 sparsity + # case. + + expr0 = m0 & m1 + expr1 = ~m0 & m1 + expr2 = ~m0 & ~m1 + bit0 = expr1 + bit1 = expr2 + bit2 = expr0 | expr2 | m3 + bit3 = expr1 | ~m1 + idxs0 = bit0 | (bit1.to(torch.int64) << 1) + idxs1 = bit2 | (bit3.to(torch.int64) << 1) + + if dense.dtype != torch.float: + sparse0 = dense_4.gather( + -1, idxs0.unsqueeze(-1) + ) # type: ignore[possibly-undefined] + sparse1 = dense_4.gather(-1, idxs1.unsqueeze(-1)) + sparse = torch.stack((sparse0, sparse1), dim=-1).view(m, k // 2) + else: + sparse = dense_2.gather(-1, idxs0.unsqueeze(-1) // 2).view( + m, k // 2 + ) # type: ignore[possibly-undefined] + + meta_4 = idxs0 | (idxs1 << 2) + meta_n = meta_4.view((-1, meta_ncols, quadbits_per_meta_elem)).to(meta_dtype) + + if quadbits_per_meta_elem == 4: + meta = ( + meta_n[:, :, 0] + | (meta_n[:, :, 1] << 4) + | (meta_n[:, :, 2] << 8) + | (meta_n[:, :, 3] << 12) + ) + elif quadbits_per_meta_elem == 8: + meta = ( + meta_n[:, :, 0] + | (meta_n[:, :, 1] << 4) + | (meta_n[:, :, 2] << 8) + | (meta_n[:, :, 3] << 12) + | (meta_n[:, :, 4] << 16) + | (meta_n[:, :, 5] << 20) + | (meta_n[:, :, 6] << 24) + | (meta_n[:, :, 7] << 28) + ) + + # Reorder meta tensor elements. + meta_reordered = meta.new_empty( + (m * meta_ncols,) + ) # type: ignore[possibly-undefined] + meta_offsets = _calculate_meta_reordering_scatter_offsets( + m, meta_ncols, meta_dtype, device + ) + meta_reordered.scatter_(0, meta_offsets, meta.view(-1)) + + return (sparse, meta_reordered.view(m, meta_ncols)) + + +# This function performs reverse of the function above - it +# reconstructs dense matrix from a pair of "compressed" matrix, given +# in the layout used by CUTLASS backend, and accompanying metadata +# matrix. +def sparse_semi_structured_to_dense_cutlass(sparse, meta_reordered): + if sparse.dim() != 2: + raise RuntimeError( + f"Expected 2-dimensional sparse tensor, got {sparse.dim()}-dimensional tensor" # noqa: E501 + ) + + m, k = sparse.shape + device = sparse.device + + if meta_reordered.dim() != 2: + raise RuntimeError( + f"Expected 2-dimensional meta tensor, got {meta_reordered.dim()}-dimensional tensor" # noqa: E501 + ) + if meta_reordered.device != device: + raise RuntimeError( + f"Expected meta matrix to be on {device} device, got matrix on {meta_reordered.device} device" # noqa: E501 + ) + + meta_dtype = meta_reordered.dtype + if meta_dtype not in (torch.int16, torch.int32): + raise RuntimeError(f"Invalid datatype {meta_dtype} of meta matrix") + quadbits_per_meta_elem = meta_dtype.itemsize * 8 // 4 + + ksparse = 4 if sparse.dtype != torch.float else 2 + + meta_nrows, meta_ncols = meta_reordered.shape + if meta_nrows != m: + raise RuntimeError( + f"Number of rows of meta matrix {meta_nrows} must be equal to number of columns of spase matrix {m}" # noqa: E501 + ) + if meta_ncols * ksparse * quadbits_per_meta_elem != 2 * k: + raise RuntimeError( + f"Number of columns of sparse matrix {k} different from the {meta_ncols * ksparse * quadbits_per_meta_elem // 2}, " # noqa: E501 + "expected according to the number of columns of meta matrix" + ) + + # Undo meta tensor elements reordering. + meta_offsets = _calculate_meta_reordering_scatter_offsets( + m, meta_ncols, meta_dtype, device + ) + meta = torch.gather(meta_reordered.view(-1), 0, meta_offsets).view(m, meta_ncols) + + # Unpack sparse tensor back to original dense tensor, using + # information provided by meta tensor. Note that torch.float + # datatype is handled pretty much the same as + # torch.half/torch.bfloat16, as metadata for a pair of torch.float + # value is encoded as if underlying 8 bytes contain four + # torch.half/torch.bfloat16 values, where either first two or last + # two are zeros. + meta_2 = torch.empty( + (m, meta_ncols, 2 * quadbits_per_meta_elem), + dtype=meta_dtype, + device=device, + ) + if quadbits_per_meta_elem == 4: + meta_2[:, :, 0] = meta & 0b11 + meta_2[:, :, 1] = (meta >> 2) & 0b11 + meta_2[:, :, 2] = (meta >> 4) & 0b11 + meta_2[:, :, 3] = (meta >> 6) & 0b11 + meta_2[:, :, 4] = (meta >> 8) & 0b11 + meta_2[:, :, 5] = (meta >> 10) & 0b11 + meta_2[:, :, 6] = (meta >> 12) & 0b11 + meta_2[:, :, 7] = (meta >> 14) & 0b11 + elif quadbits_per_meta_elem == 8: + meta_2[:, :, 0] = meta & 0b11 + meta_2[:, :, 1] = (meta >> 2) & 0b11 + meta_2[:, :, 2] = (meta >> 4) & 0b11 + meta_2[:, :, 3] = (meta >> 6) & 0b11 + meta_2[:, :, 4] = (meta >> 8) & 0b11 + meta_2[:, :, 5] = (meta >> 10) & 0b11 + meta_2[:, :, 6] = (meta >> 12) & 0b11 + meta_2[:, :, 7] = (meta >> 14) & 0b11 + meta_2[:, :, 8] = (meta >> 16) & 0b11 + meta_2[:, :, 9] = (meta >> 18) & 0b11 + meta_2[:, :, 10] = (meta >> 20) & 0b11 + meta_2[:, :, 11] = (meta >> 22) & 0b11 + meta_2[:, :, 12] = (meta >> 24) & 0b11 + meta_2[:, :, 13] = (meta >> 26) & 0b11 + meta_2[:, :, 14] = (meta >> 28) & 0b11 + meta_2[:, :, 15] = (meta >> 30) & 0b11 + + dense_offsets = meta_2.view(-1) + ( + torch.arange(0, 2 * m * k // ksparse, device=device) * 4 + ).view(-1, 1).repeat(1, 2).view(-1) + + dense = torch.zeros((m * 2 * k,), dtype=sparse.dtype, device=device) + if sparse.dtype != torch.float: + # dense.scatter_(0, dense_offsets, sparse.view(-1)) + dense.scatter_(0, dense_offsets, sparse.reshape(-1)) + else: + dense.view(torch.half).scatter_( + 0, dense_offsets, sparse.view(torch.half).view(-1) + ) + + return dense.view(m, 2 * k) + + +def mask_creator(tensor): + """ + Class for creating N:M sparsity masks. + Masks will be created using the N:M ratio, where for every block of + M weights, N will be pruned based on ranked weight value. Each mask + will correspond to the given tensor. + + :param N: The number of weights in a group to keep + :param M: The size of a weight group + """ + N = 2 + M = 4 + + mask = None + # for i, tensor in enumerate(tensors): + if tensor.numel() % M != 0: + raise ValueError( + f"Tensor of size {tensor.shape} can't be evenly divided into " f"{M} groups" + ) + + num_groups = tensor.numel() // M + + # N:M sparsity for linear layers + tensor_temp = tensor.detach().abs().reshape(num_groups, M) + index = torch.argsort(tensor_temp, dim=1)[:, : int(M - N)] + + w_b = torch.ones(tensor_temp.shape, device=tensor_temp.device) + mask = w_b.scatter_(dim=1, index=index, value=0).reshape(tensor.shape) + + return mask diff --git a/.venv/lib/python3.11/site-packages/dotenv/__init__.py b/.venv/lib/python3.11/site-packages/dotenv/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7f4c631ba11786bceebd22591f91bd378d8b232c --- /dev/null +++ b/.venv/lib/python3.11/site-packages/dotenv/__init__.py @@ -0,0 +1,49 @@ +from typing import Any, Optional + +from .main import (dotenv_values, find_dotenv, get_key, load_dotenv, set_key, + unset_key) + + +def load_ipython_extension(ipython: Any) -> None: + from .ipython import load_ipython_extension + load_ipython_extension(ipython) + + +def get_cli_string( + path: Optional[str] = None, + action: Optional[str] = None, + key: Optional[str] = None, + value: Optional[str] = None, + quote: Optional[str] = None, +): + """Returns a string suitable for running as a shell script. + + Useful for converting a arguments passed to a fabric task + to be passed to a `local` or `run` command. + """ + command = ['dotenv'] + if quote: + command.append(f'-q {quote}') + if path: + command.append(f'-f {path}') + if action: + command.append(action) + if key: + command.append(key) + if value: + if ' ' in value: + command.append(f'"{value}"') + else: + command.append(value) + + return ' '.join(command).strip() + + +__all__ = ['get_cli_string', + 'load_dotenv', + 'dotenv_values', + 'get_key', + 'set_key', + 'unset_key', + 'find_dotenv', + 'load_ipython_extension'] diff --git a/.venv/lib/python3.11/site-packages/dotenv/__main__.py b/.venv/lib/python3.11/site-packages/dotenv/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..3977f55a8b1e94e67bab364885e502e6c89e3bc5 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/dotenv/__main__.py @@ -0,0 +1,6 @@ +"""Entry point for cli, enables execution with `python -m dotenv`""" + +from .cli import cli + +if __name__ == "__main__": + cli() diff --git a/.venv/lib/python3.11/site-packages/dotenv/__pycache__/__main__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/__main__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1444789a5823b865bee2507996139cc2d7b64588 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/__main__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/dotenv/__pycache__/cli.cpython-311.pyc b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/cli.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5335615933ab9a33baf436ded519607f8086219 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/cli.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/dotenv/__pycache__/ipython.cpython-311.pyc b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/ipython.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a72fd8e53580f18b290d791c9c30c4352bca3046 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/ipython.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/dotenv/__pycache__/main.cpython-311.pyc b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fada0e01863a89fe19481aaf6fc5e733835bd723 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/main.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/dotenv/__pycache__/parser.cpython-311.pyc b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/parser.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad16b30a6ae0a6538802283228841ae7f6128214 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/parser.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/dotenv/__pycache__/variables.cpython-311.pyc b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/variables.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d89b72fd24a26ef2b49950c1bcc8532889b6e0bf Binary files /dev/null and b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/variables.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/dotenv/__pycache__/version.cpython-311.pyc b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/version.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9dbfe7f643e5781306a434fd6f7774c96ef17245 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/dotenv/__pycache__/version.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/dotenv/cli.py b/.venv/lib/python3.11/site-packages/dotenv/cli.py new file mode 100644 index 0000000000000000000000000000000000000000..65ead46155f568a197a16b64c6335f1f28cda9a6 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/dotenv/cli.py @@ -0,0 +1,199 @@ +import json +import os +import shlex +import sys +from contextlib import contextmanager +from subprocess import Popen +from typing import Any, Dict, IO, Iterator, List + +try: + import click +except ImportError: + sys.stderr.write('It seems python-dotenv is not installed with cli option. \n' + 'Run pip install "python-dotenv[cli]" to fix this.') + sys.exit(1) + +from .main import dotenv_values, set_key, unset_key +from .version import __version__ + + +def enumerate_env(): + """ + Return a path for the ${pwd}/.env file. + + If pwd does not exist, return None. + """ + try: + cwd = os.getcwd() + except FileNotFoundError: + return None + path = os.path.join(cwd, '.env') + return path + + +@click.group() +@click.option('-f', '--file', default=enumerate_env(), + type=click.Path(file_okay=True), + help="Location of the .env file, defaults to .env file in current working directory.") +@click.option('-q', '--quote', default='always', + type=click.Choice(['always', 'never', 'auto']), + help="Whether to quote or not the variable values. Default mode is always. This does not affect parsing.") +@click.option('-e', '--export', default=False, + type=click.BOOL, + help="Whether to write the dot file as an executable bash script.") +@click.version_option(version=__version__) +@click.pass_context +def cli(ctx: click.Context, file: Any, quote: Any, export: Any) -> None: + """This script is used to set, get or unset values from a .env file.""" + ctx.obj = {'QUOTE': quote, 'EXPORT': export, 'FILE': file} + + +@contextmanager +def stream_file(path: os.PathLike) -> Iterator[IO[str]]: + """ + Open a file and yield the corresponding (decoded) stream. + + Exits with error code 2 if the file cannot be opened. + """ + + try: + with open(path) as stream: + yield stream + except OSError as exc: + print(f"Error opening env file: {exc}", file=sys.stderr) + exit(2) + + +@cli.command() +@click.pass_context +@click.option('--format', default='simple', + type=click.Choice(['simple', 'json', 'shell', 'export']), + help="The format in which to display the list. Default format is simple, " + "which displays name=value without quotes.") +def list(ctx: click.Context, format: bool) -> None: + """Display all the stored key/value.""" + file = ctx.obj['FILE'] + + with stream_file(file) as stream: + values = dotenv_values(stream=stream) + + if format == 'json': + click.echo(json.dumps(values, indent=2, sort_keys=True)) + else: + prefix = 'export ' if format == 'export' else '' + for k in sorted(values): + v = values[k] + if v is not None: + if format in ('export', 'shell'): + v = shlex.quote(v) + click.echo(f'{prefix}{k}={v}') + + +@cli.command() +@click.pass_context +@click.argument('key', required=True) +@click.argument('value', required=True) +def set(ctx: click.Context, key: Any, value: Any) -> None: + """Store the given key/value.""" + file = ctx.obj['FILE'] + quote = ctx.obj['QUOTE'] + export = ctx.obj['EXPORT'] + success, key, value = set_key(file, key, value, quote, export) + if success: + click.echo(f'{key}={value}') + else: + exit(1) + + +@cli.command() +@click.pass_context +@click.argument('key', required=True) +def get(ctx: click.Context, key: Any) -> None: + """Retrieve the value for the given key.""" + file = ctx.obj['FILE'] + + with stream_file(file) as stream: + values = dotenv_values(stream=stream) + + stored_value = values.get(key) + if stored_value: + click.echo(stored_value) + else: + exit(1) + + +@cli.command() +@click.pass_context +@click.argument('key', required=True) +def unset(ctx: click.Context, key: Any) -> None: + """Removes the given key.""" + file = ctx.obj['FILE'] + quote = ctx.obj['QUOTE'] + success, key = unset_key(file, key, quote) + if success: + click.echo(f"Successfully removed {key}") + else: + exit(1) + + +@cli.command(context_settings={'ignore_unknown_options': True}) +@click.pass_context +@click.option( + "--override/--no-override", + default=True, + help="Override variables from the environment file with those from the .env file.", +) +@click.argument('commandline', nargs=-1, type=click.UNPROCESSED) +def run(ctx: click.Context, override: bool, commandline: List[str]) -> None: + """Run command with environment variables present.""" + file = ctx.obj['FILE'] + if not os.path.isfile(file): + raise click.BadParameter( + f'Invalid value for \'-f\' "{file}" does not exist.', + ctx=ctx + ) + dotenv_as_dict = { + k: v + for (k, v) in dotenv_values(file).items() + if v is not None and (override or k not in os.environ) + } + + if not commandline: + click.echo('No command given.') + exit(1) + ret = run_command(commandline, dotenv_as_dict) + exit(ret) + + +def run_command(command: List[str], env: Dict[str, str]) -> int: + """Run command in sub process. + + Runs the command in a sub process with the variables from `env` + added in the current environment variables. + + Parameters + ---------- + command: List[str] + The command and it's parameters + env: Dict + The additional environment variables + + Returns + ------- + int + The return code of the command + + """ + # copy the current environment variables and add the vales from + # `env` + cmd_env = os.environ.copy() + cmd_env.update(env) + + p = Popen(command, + universal_newlines=True, + bufsize=0, + shell=False, + env=cmd_env) + _, _ = p.communicate() + + return p.returncode diff --git a/.venv/lib/python3.11/site-packages/dotenv/ipython.py b/.venv/lib/python3.11/site-packages/dotenv/ipython.py new file mode 100644 index 0000000000000000000000000000000000000000..7df727cd0ba5eb481a9e2568ffdd063bfce90314 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/dotenv/ipython.py @@ -0,0 +1,39 @@ +from IPython.core.magic import Magics, line_magic, magics_class # type: ignore +from IPython.core.magic_arguments import (argument, magic_arguments, # type: ignore + parse_argstring) # type: ignore + +from .main import find_dotenv, load_dotenv + + +@magics_class +class IPythonDotEnv(Magics): + + @magic_arguments() + @argument( + '-o', '--override', action='store_true', + help="Indicate to override existing variables" + ) + @argument( + '-v', '--verbose', action='store_true', + help="Indicate function calls to be verbose" + ) + @argument('dotenv_path', nargs='?', type=str, default='.env', + help='Search in increasingly higher folders for the `dotenv_path`') + @line_magic + def dotenv(self, line): + args = parse_argstring(self.dotenv, line) + # Locate the .env file + dotenv_path = args.dotenv_path + try: + dotenv_path = find_dotenv(dotenv_path, True, True) + except IOError: + print("cannot find .env file") + return + + # Load the .env file + load_dotenv(dotenv_path, verbose=args.verbose, override=args.override) + + +def load_ipython_extension(ipython): + """Register the %dotenv magic.""" + ipython.register_magics(IPythonDotEnv) diff --git a/.venv/lib/python3.11/site-packages/dotenv/main.py b/.venv/lib/python3.11/site-packages/dotenv/main.py new file mode 100644 index 0000000000000000000000000000000000000000..7bc5428572d9e0ca221146484b42cd321d930fe5 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/dotenv/main.py @@ -0,0 +1,392 @@ +import io +import logging +import os +import pathlib +import shutil +import sys +import tempfile +from collections import OrderedDict +from contextlib import contextmanager +from typing import (IO, Dict, Iterable, Iterator, Mapping, Optional, Tuple, + Union) + +from .parser import Binding, parse_stream +from .variables import parse_variables + +# A type alias for a string path to be used for the paths in this file. +# These paths may flow to `open()` and `shutil.move()`; `shutil.move()` +# only accepts string paths, not byte paths or file descriptors. See +# https://github.com/python/typeshed/pull/6832. +StrPath = Union[str, 'os.PathLike[str]'] + +logger = logging.getLogger(__name__) + + +def with_warn_for_invalid_lines(mappings: Iterator[Binding]) -> Iterator[Binding]: + for mapping in mappings: + if mapping.error: + logger.warning( + "Python-dotenv could not parse statement starting at line %s", + mapping.original.line, + ) + yield mapping + + +class DotEnv: + def __init__( + self, + dotenv_path: Optional[StrPath], + stream: Optional[IO[str]] = None, + verbose: bool = False, + encoding: Optional[str] = None, + interpolate: bool = True, + override: bool = True, + ) -> None: + self.dotenv_path: Optional[StrPath] = dotenv_path + self.stream: Optional[IO[str]] = stream + self._dict: Optional[Dict[str, Optional[str]]] = None + self.verbose: bool = verbose + self.encoding: Optional[str] = encoding + self.interpolate: bool = interpolate + self.override: bool = override + + @contextmanager + def _get_stream(self) -> Iterator[IO[str]]: + if self.dotenv_path and os.path.isfile(self.dotenv_path): + with open(self.dotenv_path, encoding=self.encoding) as stream: + yield stream + elif self.stream is not None: + yield self.stream + else: + if self.verbose: + logger.info( + "Python-dotenv could not find configuration file %s.", + self.dotenv_path or '.env', + ) + yield io.StringIO('') + + def dict(self) -> Dict[str, Optional[str]]: + """Return dotenv as dict""" + if self._dict: + return self._dict + + raw_values = self.parse() + + if self.interpolate: + self._dict = OrderedDict(resolve_variables(raw_values, override=self.override)) + else: + self._dict = OrderedDict(raw_values) + + return self._dict + + def parse(self) -> Iterator[Tuple[str, Optional[str]]]: + with self._get_stream() as stream: + for mapping in with_warn_for_invalid_lines(parse_stream(stream)): + if mapping.key is not None: + yield mapping.key, mapping.value + + def set_as_environment_variables(self) -> bool: + """ + Load the current dotenv as system environment variable. + """ + if not self.dict(): + return False + + for k, v in self.dict().items(): + if k in os.environ and not self.override: + continue + if v is not None: + os.environ[k] = v + + return True + + def get(self, key: str) -> Optional[str]: + """ + """ + data = self.dict() + + if key in data: + return data[key] + + if self.verbose: + logger.warning("Key %s not found in %s.", key, self.dotenv_path) + + return None + + +def get_key( + dotenv_path: StrPath, + key_to_get: str, + encoding: Optional[str] = "utf-8", +) -> Optional[str]: + """ + Get the value of a given key from the given .env. + + Returns `None` if the key isn't found or doesn't have a value. + """ + return DotEnv(dotenv_path, verbose=True, encoding=encoding).get(key_to_get) + + +@contextmanager +def rewrite( + path: StrPath, + encoding: Optional[str], +) -> Iterator[Tuple[IO[str], IO[str]]]: + pathlib.Path(path).touch() + + with tempfile.NamedTemporaryFile(mode="w", encoding=encoding, delete=False) as dest: + error = None + try: + with open(path, encoding=encoding) as source: + yield (source, dest) + except BaseException as err: + error = err + + if error is None: + shutil.move(dest.name, path) + else: + os.unlink(dest.name) + raise error from None + + +def set_key( + dotenv_path: StrPath, + key_to_set: str, + value_to_set: str, + quote_mode: str = "always", + export: bool = False, + encoding: Optional[str] = "utf-8", +) -> Tuple[Optional[bool], str, str]: + """ + Adds or Updates a key/value to the given .env + + If the .env path given doesn't exist, fails instead of risking creating + an orphan .env somewhere in the filesystem + """ + if quote_mode not in ("always", "auto", "never"): + raise ValueError(f"Unknown quote_mode: {quote_mode}") + + quote = ( + quote_mode == "always" + or (quote_mode == "auto" and not value_to_set.isalnum()) + ) + + if quote: + value_out = "'{}'".format(value_to_set.replace("'", "\\'")) + else: + value_out = value_to_set + if export: + line_out = f'export {key_to_set}={value_out}\n' + else: + line_out = f"{key_to_set}={value_out}\n" + + with rewrite(dotenv_path, encoding=encoding) as (source, dest): + replaced = False + missing_newline = False + for mapping in with_warn_for_invalid_lines(parse_stream(source)): + if mapping.key == key_to_set: + dest.write(line_out) + replaced = True + else: + dest.write(mapping.original.string) + missing_newline = not mapping.original.string.endswith("\n") + if not replaced: + if missing_newline: + dest.write("\n") + dest.write(line_out) + + return True, key_to_set, value_to_set + + +def unset_key( + dotenv_path: StrPath, + key_to_unset: str, + quote_mode: str = "always", + encoding: Optional[str] = "utf-8", +) -> Tuple[Optional[bool], str]: + """ + Removes a given key from the given `.env` file. + + If the .env path given doesn't exist, fails. + If the given key doesn't exist in the .env, fails. + """ + if not os.path.exists(dotenv_path): + logger.warning("Can't delete from %s - it doesn't exist.", dotenv_path) + return None, key_to_unset + + removed = False + with rewrite(dotenv_path, encoding=encoding) as (source, dest): + for mapping in with_warn_for_invalid_lines(parse_stream(source)): + if mapping.key == key_to_unset: + removed = True + else: + dest.write(mapping.original.string) + + if not removed: + logger.warning("Key %s not removed from %s - key doesn't exist.", key_to_unset, dotenv_path) + return None, key_to_unset + + return removed, key_to_unset + + +def resolve_variables( + values: Iterable[Tuple[str, Optional[str]]], + override: bool, +) -> Mapping[str, Optional[str]]: + new_values: Dict[str, Optional[str]] = {} + + for (name, value) in values: + if value is None: + result = None + else: + atoms = parse_variables(value) + env: Dict[str, Optional[str]] = {} + if override: + env.update(os.environ) # type: ignore + env.update(new_values) + else: + env.update(new_values) + env.update(os.environ) # type: ignore + result = "".join(atom.resolve(env) for atom in atoms) + + new_values[name] = result + + return new_values + + +def _walk_to_root(path: str) -> Iterator[str]: + """ + Yield directories starting from the given directory up to the root + """ + if not os.path.exists(path): + raise IOError('Starting path not found') + + if os.path.isfile(path): + path = os.path.dirname(path) + + last_dir = None + current_dir = os.path.abspath(path) + while last_dir != current_dir: + yield current_dir + parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir)) + last_dir, current_dir = current_dir, parent_dir + + +def find_dotenv( + filename: str = '.env', + raise_error_if_not_found: bool = False, + usecwd: bool = False, +) -> str: + """ + Search in increasingly higher folders for the given file + + Returns path to the file if found, or an empty string otherwise + """ + + def _is_interactive(): + """ Decide whether this is running in a REPL or IPython notebook """ + try: + main = __import__('__main__', None, None, fromlist=['__file__']) + except ModuleNotFoundError: + return False + return not hasattr(main, '__file__') + + if usecwd or _is_interactive() or getattr(sys, 'frozen', False): + # Should work without __file__, e.g. in REPL or IPython notebook. + path = os.getcwd() + else: + # will work for .py files + frame = sys._getframe() + current_file = __file__ + + while frame.f_code.co_filename == current_file or not os.path.exists( + frame.f_code.co_filename + ): + assert frame.f_back is not None + frame = frame.f_back + frame_filename = frame.f_code.co_filename + path = os.path.dirname(os.path.abspath(frame_filename)) + + for dirname in _walk_to_root(path): + check_path = os.path.join(dirname, filename) + if os.path.isfile(check_path): + return check_path + + if raise_error_if_not_found: + raise IOError('File not found') + + return '' + + +def load_dotenv( + dotenv_path: Optional[StrPath] = None, + stream: Optional[IO[str]] = None, + verbose: bool = False, + override: bool = False, + interpolate: bool = True, + encoding: Optional[str] = "utf-8", +) -> bool: + """Parse a .env file and then load all the variables found as environment variables. + + Parameters: + dotenv_path: Absolute or relative path to .env file. + stream: Text stream (such as `io.StringIO`) with .env content, used if + `dotenv_path` is `None`. + verbose: Whether to output a warning the .env file is missing. + override: Whether to override the system environment variables with the variables + from the `.env` file. + encoding: Encoding to be used to read the file. + Returns: + Bool: True if at least one environment variable is set else False + + If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the + .env file. + """ + if dotenv_path is None and stream is None: + dotenv_path = find_dotenv() + + dotenv = DotEnv( + dotenv_path=dotenv_path, + stream=stream, + verbose=verbose, + interpolate=interpolate, + override=override, + encoding=encoding, + ) + return dotenv.set_as_environment_variables() + + +def dotenv_values( + dotenv_path: Optional[StrPath] = None, + stream: Optional[IO[str]] = None, + verbose: bool = False, + interpolate: bool = True, + encoding: Optional[str] = "utf-8", +) -> Dict[str, Optional[str]]: + """ + Parse a .env file and return its content as a dict. + + The returned dict will have `None` values for keys without values in the .env file. + For example, `foo=bar` results in `{"foo": "bar"}` whereas `foo` alone results in + `{"foo": None}` + + Parameters: + dotenv_path: Absolute or relative path to the .env file. + stream: `StringIO` object with .env content, used if `dotenv_path` is `None`. + verbose: Whether to output a warning if the .env file is missing. + encoding: Encoding to be used to read the file. + + If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the + .env file. + """ + if dotenv_path is None and stream is None: + dotenv_path = find_dotenv() + + return DotEnv( + dotenv_path=dotenv_path, + stream=stream, + verbose=verbose, + interpolate=interpolate, + override=True, + encoding=encoding, + ).dict() diff --git a/.venv/lib/python3.11/site-packages/dotenv/parser.py b/.venv/lib/python3.11/site-packages/dotenv/parser.py new file mode 100644 index 0000000000000000000000000000000000000000..735f14a3b425a3ee7d81e6e4aa5a2b04202925c4 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/dotenv/parser.py @@ -0,0 +1,175 @@ +import codecs +import re +from typing import (IO, Iterator, Match, NamedTuple, Optional, # noqa:F401 + Pattern, Sequence, Tuple) + + +def make_regex(string: str, extra_flags: int = 0) -> Pattern[str]: + return re.compile(string, re.UNICODE | extra_flags) + + +_newline = make_regex(r"(\r\n|\n|\r)") +_multiline_whitespace = make_regex(r"\s*", extra_flags=re.MULTILINE) +_whitespace = make_regex(r"[^\S\r\n]*") +_export = make_regex(r"(?:export[^\S\r\n]+)?") +_single_quoted_key = make_regex(r"'([^']+)'") +_unquoted_key = make_regex(r"([^=\#\s]+)") +_equal_sign = make_regex(r"(=[^\S\r\n]*)") +_single_quoted_value = make_regex(r"'((?:\\'|[^'])*)'") +_double_quoted_value = make_regex(r'"((?:\\"|[^"])*)"') +_unquoted_value = make_regex(r"([^\r\n]*)") +_comment = make_regex(r"(?:[^\S\r\n]*#[^\r\n]*)?") +_end_of_line = make_regex(r"[^\S\r\n]*(?:\r\n|\n|\r|$)") +_rest_of_line = make_regex(r"[^\r\n]*(?:\r|\n|\r\n)?") +_double_quote_escapes = make_regex(r"\\[\\'\"abfnrtv]") +_single_quote_escapes = make_regex(r"\\[\\']") + + +class Original(NamedTuple): + string: str + line: int + + +class Binding(NamedTuple): + key: Optional[str] + value: Optional[str] + original: Original + error: bool + + +class Position: + def __init__(self, chars: int, line: int) -> None: + self.chars = chars + self.line = line + + @classmethod + def start(cls) -> "Position": + return cls(chars=0, line=1) + + def set(self, other: "Position") -> None: + self.chars = other.chars + self.line = other.line + + def advance(self, string: str) -> None: + self.chars += len(string) + self.line += len(re.findall(_newline, string)) + + +class Error(Exception): + pass + + +class Reader: + def __init__(self, stream: IO[str]) -> None: + self.string = stream.read() + self.position = Position.start() + self.mark = Position.start() + + def has_next(self) -> bool: + return self.position.chars < len(self.string) + + def set_mark(self) -> None: + self.mark.set(self.position) + + def get_marked(self) -> Original: + return Original( + string=self.string[self.mark.chars:self.position.chars], + line=self.mark.line, + ) + + def peek(self, count: int) -> str: + return self.string[self.position.chars:self.position.chars + count] + + def read(self, count: int) -> str: + result = self.string[self.position.chars:self.position.chars + count] + if len(result) < count: + raise Error("read: End of string") + self.position.advance(result) + return result + + def read_regex(self, regex: Pattern[str]) -> Sequence[str]: + match = regex.match(self.string, self.position.chars) + if match is None: + raise Error("read_regex: Pattern not found") + self.position.advance(self.string[match.start():match.end()]) + return match.groups() + + +def decode_escapes(regex: Pattern[str], string: str) -> str: + def decode_match(match: Match[str]) -> str: + return codecs.decode(match.group(0), 'unicode-escape') # type: ignore + + return regex.sub(decode_match, string) + + +def parse_key(reader: Reader) -> Optional[str]: + char = reader.peek(1) + if char == "#": + return None + elif char == "'": + (key,) = reader.read_regex(_single_quoted_key) + else: + (key,) = reader.read_regex(_unquoted_key) + return key + + +def parse_unquoted_value(reader: Reader) -> str: + (part,) = reader.read_regex(_unquoted_value) + return re.sub(r"\s+#.*", "", part).rstrip() + + +def parse_value(reader: Reader) -> str: + char = reader.peek(1) + if char == u"'": + (value,) = reader.read_regex(_single_quoted_value) + return decode_escapes(_single_quote_escapes, value) + elif char == u'"': + (value,) = reader.read_regex(_double_quoted_value) + return decode_escapes(_double_quote_escapes, value) + elif char in (u"", u"\n", u"\r"): + return u"" + else: + return parse_unquoted_value(reader) + + +def parse_binding(reader: Reader) -> Binding: + reader.set_mark() + try: + reader.read_regex(_multiline_whitespace) + if not reader.has_next(): + return Binding( + key=None, + value=None, + original=reader.get_marked(), + error=False, + ) + reader.read_regex(_export) + key = parse_key(reader) + reader.read_regex(_whitespace) + if reader.peek(1) == "=": + reader.read_regex(_equal_sign) + value: Optional[str] = parse_value(reader) + else: + value = None + reader.read_regex(_comment) + reader.read_regex(_end_of_line) + return Binding( + key=key, + value=value, + original=reader.get_marked(), + error=False, + ) + except Error: + reader.read_regex(_rest_of_line) + return Binding( + key=None, + value=None, + original=reader.get_marked(), + error=True, + ) + + +def parse_stream(stream: IO[str]) -> Iterator[Binding]: + reader = Reader(stream) + while reader.has_next(): + yield parse_binding(reader) diff --git a/.venv/lib/python3.11/site-packages/dotenv/py.typed b/.venv/lib/python3.11/site-packages/dotenv/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..7632ecf77545c5e5501cb3fc5719df0761104ca2 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/dotenv/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561 diff --git a/.venv/lib/python3.11/site-packages/dotenv/variables.py b/.venv/lib/python3.11/site-packages/dotenv/variables.py new file mode 100644 index 0000000000000000000000000000000000000000..667f2f26ff2182ecdfc5b809ba97a6cf1d1be13a --- /dev/null +++ b/.venv/lib/python3.11/site-packages/dotenv/variables.py @@ -0,0 +1,86 @@ +import re +from abc import ABCMeta, abstractmethod +from typing import Iterator, Mapping, Optional, Pattern + +_posix_variable: Pattern[str] = re.compile( + r""" + \$\{ + (?P[^\}:]*) + (?::- + (?P[^\}]*) + )? + \} + """, + re.VERBOSE, +) + + +class Atom(metaclass=ABCMeta): + def __ne__(self, other: object) -> bool: + result = self.__eq__(other) + if result is NotImplemented: + return NotImplemented + return not result + + @abstractmethod + def resolve(self, env: Mapping[str, Optional[str]]) -> str: ... + + +class Literal(Atom): + def __init__(self, value: str) -> None: + self.value = value + + def __repr__(self) -> str: + return f"Literal(value={self.value})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return self.value == other.value + + def __hash__(self) -> int: + return hash((self.__class__, self.value)) + + def resolve(self, env: Mapping[str, Optional[str]]) -> str: + return self.value + + +class Variable(Atom): + def __init__(self, name: str, default: Optional[str]) -> None: + self.name = name + self.default = default + + def __repr__(self) -> str: + return f"Variable(name={self.name}, default={self.default})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return (self.name, self.default) == (other.name, other.default) + + def __hash__(self) -> int: + return hash((self.__class__, self.name, self.default)) + + def resolve(self, env: Mapping[str, Optional[str]]) -> str: + default = self.default if self.default is not None else "" + result = env.get(self.name, default) + return result if result is not None else "" + + +def parse_variables(value: str) -> Iterator[Atom]: + cursor = 0 + + for match in _posix_variable.finditer(value): + (start, end) = match.span() + name = match["name"] + default = match["default"] + + if start > cursor: + yield Literal(value=value[cursor:start]) + + yield Variable(name=name, default=default) + cursor = end + + length = len(value) + if cursor < length: + yield Literal(value=value[cursor:length]) diff --git a/.venv/lib/python3.11/site-packages/dotenv/version.py b/.venv/lib/python3.11/site-packages/dotenv/version.py new file mode 100644 index 0000000000000000000000000000000000000000..5c4105cd39cc4181c773f21fc007b4d120968c8b --- /dev/null +++ b/.venv/lib/python3.11/site-packages/dotenv/version.py @@ -0,0 +1 @@ +__version__ = "1.0.1" diff --git a/.venv/lib/python3.11/site-packages/networkx/conftest.py b/.venv/lib/python3.11/site-packages/networkx/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..4a261c6d0d6f1a31c55349c2cee6776f9b2ba6c4 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/networkx/conftest.py @@ -0,0 +1,284 @@ +""" +Testing +======= + +General guidelines for writing good tests: + +- doctests always assume ``import networkx as nx`` so don't add that +- prefer pytest fixtures over classes with setup methods. +- use the ``@pytest.mark.parametrize`` decorator +- use ``pytest.importorskip`` for numpy, scipy, pandas, and matplotlib b/c of PyPy. + and add the module to the relevant entries below. + +""" + +import os +import sys +import warnings +from importlib.metadata import entry_points + +import pytest + +import networkx + + +def pytest_addoption(parser): + parser.addoption( + "--runslow", action="store_true", default=False, help="run slow tests" + ) + parser.addoption( + "--backend", + action="store", + default=None, + help="Run tests with a backend by auto-converting nx graphs to backend graphs", + ) + parser.addoption( + "--fallback-to-nx", + action="store_true", + default=False, + help="Run nx function if a backend doesn't implement a dispatchable function" + " (use with --backend)", + ) + + +def pytest_configure(config): + config.addinivalue_line("markers", "slow: mark test as slow to run") + backend = config.getoption("--backend") + if backend is None: + backend = os.environ.get("NETWORKX_TEST_BACKEND") + # nx_loopback backend is only available when testing with a backend + loopback_ep = entry_points(name="nx_loopback", group="networkx.backends") + if not loopback_ep: + warnings.warn( + "\n\n WARNING: Mixed NetworkX configuration! \n\n" + " This environment has mixed configuration for networkx.\n" + " The test object nx_loopback is not configured correctly.\n" + " You should not be seeing this message.\n" + " Try `pip install -e .`, or change your PYTHONPATH\n" + " Make sure python finds the networkx repo you are testing\n\n" + ) + config.backend = backend + if backend: + # We will update `networkx.config.backend_priority` below in `*_modify_items` + # to allow tests to get set up with normal networkx graphs. + networkx.utils.backends.backends["nx_loopback"] = loopback_ep["nx_loopback"] + networkx.utils.backends.backend_info["nx_loopback"] = {} + networkx.config.backends = networkx.utils.Config( + nx_loopback=networkx.utils.Config(), + **networkx.config.backends, + ) + fallback_to_nx = config.getoption("--fallback-to-nx") + if not fallback_to_nx: + fallback_to_nx = os.environ.get("NETWORKX_FALLBACK_TO_NX") + networkx.config.fallback_to_nx = bool(fallback_to_nx) + + +def pytest_collection_modifyitems(config, items): + # Setting this to True here allows tests to be set up before dispatching + # any function call to a backend. + if config.backend: + # Allow pluggable backends to add markers to tests (such as skip or xfail) + # when running in auto-conversion test mode + backend_name = config.backend + if backend_name != "networkx": + networkx.utils.backends._dispatchable._is_testing = True + networkx.config.backend_priority.algos = [backend_name] + networkx.config.backend_priority.generators = [backend_name] + backend = networkx.utils.backends.backends[backend_name].load() + if hasattr(backend, "on_start_tests"): + getattr(backend, "on_start_tests")(items) + + if config.getoption("--runslow"): + # --runslow given in cli: do not skip slow tests + return + skip_slow = pytest.mark.skip(reason="need --runslow option to run") + for item in items: + if "slow" in item.keywords: + item.add_marker(skip_slow) + + +# TODO: The warnings below need to be dealt with, but for now we silence them. +@pytest.fixture(autouse=True) +def set_warnings(): + warnings.filterwarnings( + "ignore", + category=FutureWarning, + message="\n\nsingle_target_shortest_path_length", + ) + warnings.filterwarnings( + "ignore", + category=FutureWarning, + message="\n\nshortest_path", + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\nThe `normalized`" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\nall_triplets" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\nrandom_triad" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="minimal_d_separator" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="d_separated" + ) + warnings.filterwarnings("ignore", category=DeprecationWarning, message="\n\nk_core") + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\nk_shell" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\nk_crust" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\nk_corona" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\ntotal_spanning_tree_weight" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message=r"\n\nThe 'create=matrix'" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="\n\n`compute_v_structures" + ) + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="Keyword argument 'link'" + ) + + +@pytest.fixture(autouse=True) +def add_nx(doctest_namespace): + doctest_namespace["nx"] = networkx + + +# What dependencies are installed? + +try: + import numpy + + has_numpy = True +except ImportError: + has_numpy = False + +try: + import scipy + + has_scipy = True +except ImportError: + has_scipy = False + +try: + import matplotlib + + has_matplotlib = True +except ImportError: + has_matplotlib = False + +try: + import pandas + + has_pandas = True +except ImportError: + has_pandas = False + +try: + import pygraphviz + + has_pygraphviz = True +except ImportError: + has_pygraphviz = False + +try: + import pydot + + has_pydot = True +except ImportError: + has_pydot = False + +try: + import sympy + + has_sympy = True +except ImportError: + has_sympy = False + + +# List of files that pytest should ignore + +collect_ignore = [] + +needs_numpy = [ + "algorithms/approximation/traveling_salesman.py", + "algorithms/centrality/current_flow_closeness.py", + "algorithms/centrality/laplacian.py", + "algorithms/node_classification.py", + "algorithms/non_randomness.py", + "algorithms/polynomials.py", + "algorithms/shortest_paths/dense.py", + "algorithms/tree/mst.py", + "drawing/nx_latex.py", + "generators/expanders.py", + "linalg/bethehessianmatrix.py", + "linalg/laplacianmatrix.py", + "utils/misc.py", +] +needs_scipy = [ + "algorithms/approximation/traveling_salesman.py", + "algorithms/assortativity/correlation.py", + "algorithms/assortativity/mixing.py", + "algorithms/assortativity/pairs.py", + "algorithms/bipartite/matrix.py", + "algorithms/bipartite/spectral.py", + "algorithms/centrality/current_flow_betweenness.py", + "algorithms/centrality/current_flow_betweenness_subset.py", + "algorithms/centrality/eigenvector.py", + "algorithms/centrality/katz.py", + "algorithms/centrality/laplacian.py", + "algorithms/centrality/second_order.py", + "algorithms/centrality/subgraph_alg.py", + "algorithms/communicability_alg.py", + "algorithms/community/divisive.py", + "algorithms/distance_measures.py", + "algorithms/link_analysis/hits_alg.py", + "algorithms/link_analysis/pagerank_alg.py", + "algorithms/node_classification.py", + "algorithms/similarity.py", + "algorithms/tree/mst.py", + "algorithms/walks.py", + "convert_matrix.py", + "drawing/layout.py", + "drawing/nx_pylab.py", + "generators/spectral_graph_forge.py", + "generators/expanders.py", + "linalg/algebraicconnectivity.py", + "linalg/attrmatrix.py", + "linalg/bethehessianmatrix.py", + "linalg/graphmatrix.py", + "linalg/laplacianmatrix.py", + "linalg/modularitymatrix.py", + "linalg/spectrum.py", + "utils/rcm.py", +] +needs_matplotlib = ["drawing/nx_pylab.py", "generators/classic.py"] +needs_pandas = ["convert_matrix.py"] +needs_pygraphviz = ["drawing/nx_agraph.py"] +needs_pydot = ["drawing/nx_pydot.py"] +needs_sympy = ["algorithms/polynomials.py"] + +if not has_numpy: + collect_ignore += needs_numpy +if not has_scipy: + collect_ignore += needs_scipy +if not has_matplotlib: + collect_ignore += needs_matplotlib +if not has_pandas: + collect_ignore += needs_pandas +if not has_pygraphviz: + collect_ignore += needs_pygraphviz +if not has_pydot: + collect_ignore += needs_pydot +if not has_sympy: + collect_ignore += needs_sympy diff --git a/.venv/lib/python3.11/site-packages/networkx/convert_matrix.py b/.venv/lib/python3.11/site-packages/networkx/convert_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..8992627cbac970e46ca7dce0557611a51cea2c26 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/networkx/convert_matrix.py @@ -0,0 +1,1317 @@ +"""Functions to convert NetworkX graphs to and from common data containers +like numpy arrays, scipy sparse arrays, and pandas DataFrames. + +The preferred way of converting data to a NetworkX graph is through the +graph constructor. The constructor calls the `~networkx.convert.to_networkx_graph` +function which attempts to guess the input type and convert it automatically. + +Examples +-------- +Create a 10 node random graph from a numpy array + +>>> import numpy as np +>>> rng = np.random.default_rng() +>>> a = rng.integers(low=0, high=2, size=(10, 10)) +>>> DG = nx.from_numpy_array(a, create_using=nx.DiGraph) + +or equivalently: + +>>> DG = nx.DiGraph(a) + +which calls `from_numpy_array` internally based on the type of ``a``. + +See Also +-------- +nx_agraph, nx_pydot +""" + +import itertools +from collections import defaultdict + +import networkx as nx +from networkx.utils import not_implemented_for + +__all__ = [ + "from_pandas_adjacency", + "to_pandas_adjacency", + "from_pandas_edgelist", + "to_pandas_edgelist", + "from_scipy_sparse_array", + "to_scipy_sparse_array", + "from_numpy_array", + "to_numpy_array", +] + + +@nx._dispatchable(edge_attrs="weight") +def to_pandas_adjacency( + G, + nodelist=None, + dtype=None, + order=None, + multigraph_weight=sum, + weight="weight", + nonedge=0.0, +): + """Returns the graph adjacency matrix as a Pandas DataFrame. + + Parameters + ---------- + G : graph + The NetworkX graph used to construct the Pandas DataFrame. + + nodelist : list, optional + The rows and columns are ordered according to the nodes in `nodelist`. + If `nodelist` is None, then the ordering is produced by G.nodes(). + + multigraph_weight : {sum, min, max}, optional + An operator that determines how weights in multigraphs are handled. + The default is to sum the weights of the multiple edges. + + weight : string or None, optional + The edge attribute that holds the numerical value used for + the edge weight. If an edge does not have that attribute, then the + value 1 is used instead. + + nonedge : float, optional + The matrix values corresponding to nonedges are typically set to zero. + However, this could be undesirable if there are matrix values + corresponding to actual edges that also have the value zero. If so, + one might prefer nonedges to have some other value, such as nan. + + Returns + ------- + df : Pandas DataFrame + Graph adjacency matrix + + Notes + ----- + For directed graphs, entry i,j corresponds to an edge from i to j. + + The DataFrame entries are assigned to the weight edge attribute. When + an edge does not have a weight attribute, the value of the entry is set to + the number 1. For multiple (parallel) edges, the values of the entries + are determined by the 'multigraph_weight' parameter. The default is to + sum the weight attributes for each of the parallel edges. + + When `nodelist` does not contain every node in `G`, the matrix is built + from the subgraph of `G` that is induced by the nodes in `nodelist`. + + The convention used for self-loop edges in graphs is to assign the + diagonal matrix entry value to the weight attribute of the edge + (or the number 1 if the edge has no weight attribute). If the + alternate convention of doubling the edge weight is desired the + resulting Pandas DataFrame can be modified as follows:: + + >>> import pandas as pd + >>> G = nx.Graph([(1, 1), (2, 2)]) + >>> df = nx.to_pandas_adjacency(G) + >>> df + 1 2 + 1 1.0 0.0 + 2 0.0 1.0 + >>> diag_idx = list(range(len(df))) + >>> df.iloc[diag_idx, diag_idx] *= 2 + >>> df + 1 2 + 1 2.0 0.0 + 2 0.0 2.0 + + Examples + -------- + >>> G = nx.MultiDiGraph() + >>> G.add_edge(0, 1, weight=2) + 0 + >>> G.add_edge(1, 0) + 0 + >>> G.add_edge(2, 2, weight=3) + 0 + >>> G.add_edge(2, 2) + 1 + >>> nx.to_pandas_adjacency(G, nodelist=[0, 1, 2], dtype=int) + 0 1 2 + 0 0 2 0 + 1 1 0 0 + 2 0 0 4 + + """ + import pandas as pd + + M = to_numpy_array( + G, + nodelist=nodelist, + dtype=dtype, + order=order, + multigraph_weight=multigraph_weight, + weight=weight, + nonedge=nonedge, + ) + if nodelist is None: + nodelist = list(G) + return pd.DataFrame(data=M, index=nodelist, columns=nodelist) + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_pandas_adjacency(df, create_using=None): + r"""Returns a graph from Pandas DataFrame. + + The Pandas DataFrame is interpreted as an adjacency matrix for the graph. + + Parameters + ---------- + df : Pandas DataFrame + An adjacency matrix representation of a graph + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Notes + ----- + For directed graphs, explicitly mention create_using=nx.DiGraph, + and entry i,j of df corresponds to an edge from i to j. + + If `df` has a single data type for each entry it will be converted to an + appropriate Python data type. + + If you have node attributes stored in a separate dataframe `df_nodes`, + you can load those attributes to the graph `G` using the following code: + + ``` + df_nodes = pd.DataFrame({"node_id": [1, 2, 3], "attribute1": ["A", "B", "C"]}) + G.add_nodes_from((n, dict(d)) for n, d in df_nodes.iterrows()) + ``` + + If `df` has a user-specified compound data type the names + of the data fields will be used as attribute keys in the resulting + NetworkX graph. + + See Also + -------- + to_pandas_adjacency + + Examples + -------- + Simple integer weights on edges: + + >>> import pandas as pd + >>> pd.options.display.max_columns = 20 + >>> df = pd.DataFrame([[1, 1], [2, 1]]) + >>> df + 0 1 + 0 1 1 + 1 2 1 + >>> G = nx.from_pandas_adjacency(df) + >>> G.name = "Graph from pandas adjacency matrix" + >>> print(G) + Graph named 'Graph from pandas adjacency matrix' with 2 nodes and 3 edges + """ + + try: + df = df[df.index] + except Exception as err: + missing = list(set(df.index).difference(set(df.columns))) + msg = f"{missing} not in columns" + raise nx.NetworkXError("Columns must match Indices.", msg) from err + + A = df.values + G = from_numpy_array(A, create_using=create_using, nodelist=df.columns) + + return G + + +@nx._dispatchable(preserve_edge_attrs=True) +def to_pandas_edgelist( + G, + source="source", + target="target", + nodelist=None, + dtype=None, + edge_key=None, +): + """Returns the graph edge list as a Pandas DataFrame. + + Parameters + ---------- + G : graph + The NetworkX graph used to construct the Pandas DataFrame. + + source : str or int, optional + A valid column name (string or integer) for the source nodes (for the + directed case). + + target : str or int, optional + A valid column name (string or integer) for the target nodes (for the + directed case). + + nodelist : list, optional + Use only nodes specified in nodelist + + dtype : dtype, default None + Use to create the DataFrame. Data type to force. + Only a single dtype is allowed. If None, infer. + + edge_key : str or int or None, optional (default=None) + A valid column name (string or integer) for the edge keys (for the + multigraph case). If None, edge keys are not stored in the DataFrame. + + Returns + ------- + df : Pandas DataFrame + Graph edge list + + Examples + -------- + >>> G = nx.Graph( + ... [ + ... ("A", "B", {"cost": 1, "weight": 7}), + ... ("C", "E", {"cost": 9, "weight": 10}), + ... ] + ... ) + >>> df = nx.to_pandas_edgelist(G, nodelist=["A", "C"]) + >>> df[["source", "target", "cost", "weight"]] + source target cost weight + 0 A B 1 7 + 1 C E 9 10 + + >>> G = nx.MultiGraph([("A", "B", {"cost": 1}), ("A", "B", {"cost": 9})]) + >>> df = nx.to_pandas_edgelist(G, nodelist=["A", "C"], edge_key="ekey") + >>> df[["source", "target", "cost", "ekey"]] + source target cost ekey + 0 A B 1 0 + 1 A B 9 1 + + """ + import pandas as pd + + if nodelist is None: + edgelist = G.edges(data=True) + else: + edgelist = G.edges(nodelist, data=True) + source_nodes = [s for s, _, _ in edgelist] + target_nodes = [t for _, t, _ in edgelist] + + all_attrs = set().union(*(d.keys() for _, _, d in edgelist)) + if source in all_attrs: + raise nx.NetworkXError(f"Source name {source!r} is an edge attr name") + if target in all_attrs: + raise nx.NetworkXError(f"Target name {target!r} is an edge attr name") + + nan = float("nan") + edge_attr = {k: [d.get(k, nan) for _, _, d in edgelist] for k in all_attrs} + + if G.is_multigraph() and edge_key is not None: + if edge_key in all_attrs: + raise nx.NetworkXError(f"Edge key name {edge_key!r} is an edge attr name") + edge_keys = [k for _, _, k in G.edges(keys=True)] + edgelistdict = {source: source_nodes, target: target_nodes, edge_key: edge_keys} + else: + edgelistdict = {source: source_nodes, target: target_nodes} + + edgelistdict.update(edge_attr) + return pd.DataFrame(edgelistdict, dtype=dtype) + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_pandas_edgelist( + df, + source="source", + target="target", + edge_attr=None, + create_using=None, + edge_key=None, +): + """Returns a graph from Pandas DataFrame containing an edge list. + + The Pandas DataFrame should contain at least two columns of node names and + zero or more columns of edge attributes. Each row will be processed as one + edge instance. + + Note: This function iterates over DataFrame.values, which is not + guaranteed to retain the data type across columns in the row. This is only + a problem if your row is entirely numeric and a mix of ints and floats. In + that case, all values will be returned as floats. See the + DataFrame.iterrows documentation for an example. + + Parameters + ---------- + df : Pandas DataFrame + An edge list representation of a graph + + source : str or int + A valid column name (string or integer) for the source nodes (for the + directed case). + + target : str or int + A valid column name (string or integer) for the target nodes (for the + directed case). + + edge_attr : str or int, iterable, True, or None + A valid column name (str or int) or iterable of column names that are + used to retrieve items and add them to the graph as edge attributes. + If `True`, all columns will be added except `source`, `target` and `edge_key`. + If `None`, no edge attributes are added to the graph. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + edge_key : str or None, optional (default=None) + A valid column name for the edge keys (for a MultiGraph). The values in + this column are used for the edge keys when adding edges if create_using + is a multigraph. + + If you have node attributes stored in a separate dataframe `df_nodes`, + you can load those attributes to the graph `G` using the following code: + + ``` + df_nodes = pd.DataFrame({"node_id": [1, 2, 3], "attribute1": ["A", "B", "C"]}) + G.add_nodes_from((n, dict(d)) for n, d in df_nodes.iterrows()) + ``` + + See Also + -------- + to_pandas_edgelist + + Examples + -------- + Simple integer weights on edges: + + >>> import pandas as pd + >>> pd.options.display.max_columns = 20 + >>> import numpy as np + >>> rng = np.random.RandomState(seed=5) + >>> ints = rng.randint(1, 11, size=(3, 2)) + >>> a = ["A", "B", "C"] + >>> b = ["D", "A", "E"] + >>> df = pd.DataFrame(ints, columns=["weight", "cost"]) + >>> df[0] = a + >>> df["b"] = b + >>> df[["weight", "cost", 0, "b"]] + weight cost 0 b + 0 4 7 A D + 1 7 1 B A + 2 10 9 C E + >>> G = nx.from_pandas_edgelist(df, 0, "b", ["weight", "cost"]) + >>> G["E"]["C"]["weight"] + 10 + >>> G["E"]["C"]["cost"] + 9 + >>> edges = pd.DataFrame( + ... { + ... "source": [0, 1, 2], + ... "target": [2, 2, 3], + ... "weight": [3, 4, 5], + ... "color": ["red", "blue", "blue"], + ... } + ... ) + >>> G = nx.from_pandas_edgelist(edges, edge_attr=True) + >>> G[0][2]["color"] + 'red' + + Build multigraph with custom keys: + + >>> edges = pd.DataFrame( + ... { + ... "source": [0, 1, 2, 0], + ... "target": [2, 2, 3, 2], + ... "my_edge_key": ["A", "B", "C", "D"], + ... "weight": [3, 4, 5, 6], + ... "color": ["red", "blue", "blue", "blue"], + ... } + ... ) + >>> G = nx.from_pandas_edgelist( + ... edges, + ... edge_key="my_edge_key", + ... edge_attr=["weight", "color"], + ... create_using=nx.MultiGraph(), + ... ) + >>> G[0][2] + AtlasView({'A': {'weight': 3, 'color': 'red'}, 'D': {'weight': 6, 'color': 'blue'}}) + + + """ + g = nx.empty_graph(0, create_using) + + if edge_attr is None: + if g.is_multigraph() and edge_key is not None: + for u, v, k in zip(df[source], df[target], df[edge_key]): + g.add_edge(u, v, k) + else: + g.add_edges_from(zip(df[source], df[target])) + return g + + reserved_columns = [source, target] + if g.is_multigraph() and edge_key is not None: + reserved_columns.append(edge_key) + + # Additional columns requested + attr_col_headings = [] + attribute_data = [] + if edge_attr is True: + attr_col_headings = [c for c in df.columns if c not in reserved_columns] + elif isinstance(edge_attr, list | tuple): + attr_col_headings = edge_attr + else: + attr_col_headings = [edge_attr] + if len(attr_col_headings) == 0: + raise nx.NetworkXError( + f"Invalid edge_attr argument: No columns found with name: {attr_col_headings}" + ) + + try: + attribute_data = zip(*[df[col] for col in attr_col_headings]) + except (KeyError, TypeError) as err: + msg = f"Invalid edge_attr argument: {edge_attr}" + raise nx.NetworkXError(msg) from err + + if g.is_multigraph(): + # => append the edge keys from the df to the bundled data + if edge_key is not None: + try: + multigraph_edge_keys = df[edge_key] + attribute_data = zip(attribute_data, multigraph_edge_keys) + except (KeyError, TypeError) as err: + msg = f"Invalid edge_key argument: {edge_key}" + raise nx.NetworkXError(msg) from err + + for s, t, attrs in zip(df[source], df[target], attribute_data): + if edge_key is not None: + attrs, multigraph_edge_key = attrs + key = g.add_edge(s, t, key=multigraph_edge_key) + else: + key = g.add_edge(s, t) + + g[s][t][key].update(zip(attr_col_headings, attrs)) + else: + for s, t, attrs in zip(df[source], df[target], attribute_data): + g.add_edge(s, t) + g[s][t].update(zip(attr_col_headings, attrs)) + + return g + + +@nx._dispatchable(edge_attrs="weight") +def to_scipy_sparse_array(G, nodelist=None, dtype=None, weight="weight", format="csr"): + """Returns the graph adjacency matrix as a SciPy sparse array. + + Parameters + ---------- + G : graph + The NetworkX graph used to construct the sparse array. + + nodelist : list, optional + The rows and columns are ordered according to the nodes in `nodelist`. + If `nodelist` is None, then the ordering is produced by ``G.nodes()``. + + dtype : NumPy data-type, optional + A valid NumPy dtype used to initialize the array. If None, then the + NumPy default is used. + + weight : string or None, optional (default='weight') + The edge attribute that holds the numerical value used for + the edge weight. If None then all edge weights are 1. + + format : str in {'bsr', 'csr', 'csc', 'coo', 'lil', 'dia', 'dok'} + The format of the sparse array to be returned (default 'csr'). For + some algorithms different implementations of sparse arrays + can perform better. See [1]_ for details. + + Returns + ------- + A : SciPy sparse array + Graph adjacency matrix. + + Notes + ----- + For directed graphs, matrix entry ``i, j`` corresponds to an edge from + ``i`` to ``j``. + + The values of the adjacency matrix are populated using the edge attribute held in + parameter `weight`. When an edge does not have that attribute, the + value of the entry is 1. + + For multiple edges the matrix values are the sums of the edge weights. + + When `nodelist` does not contain every node in `G`, the adjacency matrix + is built from the subgraph of `G` that is induced by the nodes in + `nodelist`. + + The convention used for self-loop edges in graphs is to assign the + diagonal matrix entry value to the weight attribute of the edge + (or the number 1 if the edge has no weight attribute). If the + alternate convention of doubling the edge weight is desired the + resulting array can be modified as follows:: + + >>> G = nx.Graph([(1, 1)]) + >>> A = nx.to_scipy_sparse_array(G) + >>> A.toarray() + array([[1]]) + >>> A.setdiag(A.diagonal() * 2) + >>> A.toarray() + array([[2]]) + + Examples + -------- + + Basic usage: + + >>> G = nx.path_graph(4) + >>> A = nx.to_scipy_sparse_array(G) + >>> A # doctest: +SKIP + + + >>> A.toarray() + array([[0, 1, 0, 0], + [1, 0, 1, 0], + [0, 1, 0, 1], + [0, 0, 1, 0]]) + + .. note:: The `toarray` method is used in these examples to better visualize + the adjacancy matrix. For a dense representation of the adjaceny matrix, + use `to_numpy_array` instead. + + Directed graphs: + + >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 3)]) + >>> nx.to_scipy_sparse_array(G).toarray() + array([[0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + [0, 0, 0, 0]]) + + >>> H = G.reverse() + >>> H.edges + OutEdgeView([(1, 0), (2, 1), (3, 2)]) + >>> nx.to_scipy_sparse_array(H).toarray() + array([[0, 0, 0, 0], + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0]]) + + By default, the order of the rows/columns of the adjacency matrix is determined + by the ordering of the nodes in `G`: + + >>> G = nx.Graph() + >>> G.add_nodes_from([3, 5, 0, 1]) + >>> G.add_edges_from([(1, 3), (1, 5)]) + >>> nx.to_scipy_sparse_array(G).toarray() + array([[0, 0, 0, 1], + [0, 0, 0, 1], + [0, 0, 0, 0], + [1, 1, 0, 0]]) + + The ordering of the rows can be changed with `nodelist`: + + >>> ordered = [0, 1, 3, 5] + >>> nx.to_scipy_sparse_array(G, nodelist=ordered).toarray() + array([[0, 0, 0, 0], + [0, 0, 1, 1], + [0, 1, 0, 0], + [0, 1, 0, 0]]) + + If `nodelist` contains a subset of the nodes in `G`, the adjacency matrix + for the node-induced subgraph is produced: + + >>> nx.to_scipy_sparse_array(G, nodelist=[1, 3, 5]).toarray() + array([[0, 1, 1], + [1, 0, 0], + [1, 0, 0]]) + + The values of the adjacency matrix are drawn from the edge attribute + specified by the `weight` parameter: + + >>> G = nx.path_graph(4) + >>> nx.set_edge_attributes( + ... G, values={(0, 1): 1, (1, 2): 10, (2, 3): 2}, name="weight" + ... ) + >>> nx.set_edge_attributes( + ... G, values={(0, 1): 50, (1, 2): 35, (2, 3): 10}, name="capacity" + ... ) + >>> nx.to_scipy_sparse_array(G).toarray() # Default weight="weight" + array([[ 0, 1, 0, 0], + [ 1, 0, 10, 0], + [ 0, 10, 0, 2], + [ 0, 0, 2, 0]]) + >>> nx.to_scipy_sparse_array(G, weight="capacity").toarray() + array([[ 0, 50, 0, 0], + [50, 0, 35, 0], + [ 0, 35, 0, 10], + [ 0, 0, 10, 0]]) + + Any edges that don't have a `weight` attribute default to 1: + + >>> G[1][2].pop("capacity") + 35 + >>> nx.to_scipy_sparse_array(G, weight="capacity").toarray() + array([[ 0, 50, 0, 0], + [50, 0, 1, 0], + [ 0, 1, 0, 10], + [ 0, 0, 10, 0]]) + + When `G` is a multigraph, the values in the adjacency matrix are given by + the sum of the `weight` edge attribute over each edge key: + + >>> G = nx.MultiDiGraph([(0, 1), (0, 1), (0, 1), (2, 0)]) + >>> nx.to_scipy_sparse_array(G).toarray() + array([[0, 3, 0], + [0, 0, 0], + [1, 0, 0]]) + + References + ---------- + .. [1] Scipy Dev. References, "Sparse Arrays", + https://docs.scipy.org/doc/scipy/reference/sparse.html + """ + import scipy as sp + + if len(G) == 0: + raise nx.NetworkXError("Graph has no nodes or edges") + + if nodelist is None: + nodelist = list(G) + nlen = len(G) + else: + nlen = len(nodelist) + if nlen == 0: + raise nx.NetworkXError("nodelist has no nodes") + nodeset = set(G.nbunch_iter(nodelist)) + if nlen != len(nodeset): + for n in nodelist: + if n not in G: + raise nx.NetworkXError(f"Node {n} in nodelist is not in G") + raise nx.NetworkXError("nodelist contains duplicates.") + if nlen < len(G): + G = G.subgraph(nodelist) + + index = dict(zip(nodelist, range(nlen))) + coefficients = zip( + *((index[u], index[v], wt) for u, v, wt in G.edges(data=weight, default=1)) + ) + try: + row, col, data = coefficients + except ValueError: + # there is no edge in the subgraph + row, col, data = [], [], [] + + if G.is_directed(): + A = sp.sparse.coo_array((data, (row, col)), shape=(nlen, nlen), dtype=dtype) + else: + # symmetrize matrix + d = data + data + r = row + col + c = col + row + # selfloop entries get double counted when symmetrizing + # so we subtract the data on the diagonal + selfloops = list(nx.selfloop_edges(G, data=weight, default=1)) + if selfloops: + diag_index, diag_data = zip(*((index[u], -wt) for u, v, wt in selfloops)) + d += diag_data + r += diag_index + c += diag_index + A = sp.sparse.coo_array((d, (r, c)), shape=(nlen, nlen), dtype=dtype) + try: + return A.asformat(format) + except ValueError as err: + raise nx.NetworkXError(f"Unknown sparse matrix format: {format}") from err + + +def _csr_gen_triples(A): + """Converts a SciPy sparse array in **Compressed Sparse Row** format to + an iterable of weighted edge triples. + + """ + nrows = A.shape[0] + indptr, dst_indices, data = A.indptr, A.indices, A.data + import numpy as np + + src_indices = np.repeat(np.arange(nrows), np.diff(indptr)) + return zip(src_indices.tolist(), dst_indices.tolist(), A.data.tolist()) + + +def _csc_gen_triples(A): + """Converts a SciPy sparse array in **Compressed Sparse Column** format to + an iterable of weighted edge triples. + + """ + ncols = A.shape[1] + indptr, src_indices, data = A.indptr, A.indices, A.data + import numpy as np + + dst_indices = np.repeat(np.arange(ncols), np.diff(indptr)) + return zip(src_indices.tolist(), dst_indices.tolist(), A.data.tolist()) + + +def _coo_gen_triples(A): + """Converts a SciPy sparse array in **Coordinate** format to an iterable + of weighted edge triples. + + """ + return zip(A.row.tolist(), A.col.tolist(), A.data.tolist()) + + +def _dok_gen_triples(A): + """Converts a SciPy sparse array in **Dictionary of Keys** format to an + iterable of weighted edge triples. + + """ + for (r, c), v in A.items(): + # Use `v.item()` to convert a NumPy scalar to the appropriate Python scalar + yield int(r), int(c), v.item() + + +def _generate_weighted_edges(A): + """Returns an iterable over (u, v, w) triples, where u and v are adjacent + vertices and w is the weight of the edge joining u and v. + + `A` is a SciPy sparse array (in any format). + + """ + if A.format == "csr": + return _csr_gen_triples(A) + if A.format == "csc": + return _csc_gen_triples(A) + if A.format == "dok": + return _dok_gen_triples(A) + # If A is in any other format (including COO), convert it to COO format. + return _coo_gen_triples(A.tocoo()) + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_scipy_sparse_array( + A, parallel_edges=False, create_using=None, edge_attribute="weight" +): + """Creates a new graph from an adjacency matrix given as a SciPy sparse + array. + + Parameters + ---------- + A: scipy.sparse array + An adjacency matrix representation of a graph + + parallel_edges : Boolean + If this is True, `create_using` is a multigraph, and `A` is an + integer matrix, then entry *(i, j)* in the matrix is interpreted as the + number of parallel edges joining vertices *i* and *j* in the graph. + If it is False, then the entries in the matrix are interpreted as + the weight of a single edge joining the vertices. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + edge_attribute: string + Name of edge attribute to store matrix numeric value. The data will + have the same type as the matrix entry (int, float, (real,imag)). + + Notes + ----- + For directed graphs, explicitly mention create_using=nx.DiGraph, + and entry i,j of A corresponds to an edge from i to j. + + If `create_using` is :class:`networkx.MultiGraph` or + :class:`networkx.MultiDiGraph`, `parallel_edges` is True, and the + entries of `A` are of type :class:`int`, then this function returns a + multigraph (constructed from `create_using`) with parallel edges. + In this case, `edge_attribute` will be ignored. + + If `create_using` indicates an undirected multigraph, then only the edges + indicated by the upper triangle of the matrix `A` will be added to the + graph. + + Examples + -------- + >>> import scipy as sp + >>> A = sp.sparse.eye(2, 2, 1) + >>> G = nx.from_scipy_sparse_array(A) + + If `create_using` indicates a multigraph and the matrix has only integer + entries and `parallel_edges` is False, then the entries will be treated + as weights for edges joining the nodes (without creating parallel edges): + + >>> A = sp.sparse.csr_array([[1, 1], [1, 2]]) + >>> G = nx.from_scipy_sparse_array(A, create_using=nx.MultiGraph) + >>> G[1][1] + AtlasView({0: {'weight': 2}}) + + If `create_using` indicates a multigraph and the matrix has only integer + entries and `parallel_edges` is True, then the entries will be treated + as the number of parallel edges joining those two vertices: + + >>> A = sp.sparse.csr_array([[1, 1], [1, 2]]) + >>> G = nx.from_scipy_sparse_array( + ... A, parallel_edges=True, create_using=nx.MultiGraph + ... ) + >>> G[1][1] + AtlasView({0: {'weight': 1}, 1: {'weight': 1}}) + + """ + G = nx.empty_graph(0, create_using) + n, m = A.shape + if n != m: + raise nx.NetworkXError(f"Adjacency matrix not square: nx,ny={A.shape}") + # Make sure we get even the isolated nodes of the graph. + G.add_nodes_from(range(n)) + # Create an iterable over (u, v, w) triples and for each triple, add an + # edge from u to v with weight w. + triples = _generate_weighted_edges(A) + # If the entries in the adjacency matrix are integers, the graph is a + # multigraph, and parallel_edges is True, then create parallel edges, each + # with weight 1, for each entry in the adjacency matrix. Otherwise, create + # one edge for each positive entry in the adjacency matrix and set the + # weight of that edge to be the entry in the matrix. + if A.dtype.kind in ("i", "u") and G.is_multigraph() and parallel_edges: + chain = itertools.chain.from_iterable + # The following line is equivalent to: + # + # for (u, v) in edges: + # for d in range(A[u, v]): + # G.add_edge(u, v, weight=1) + # + triples = chain(((u, v, 1) for d in range(w)) for (u, v, w) in triples) + # If we are creating an undirected multigraph, only add the edges from the + # upper triangle of the matrix. Otherwise, add all the edges. This relies + # on the fact that the vertices created in the + # `_generated_weighted_edges()` function are actually the row/column + # indices for the matrix `A`. + # + # Without this check, we run into a problem where each edge is added twice + # when `G.add_weighted_edges_from()` is invoked below. + if G.is_multigraph() and not G.is_directed(): + triples = ((u, v, d) for u, v, d in triples if u <= v) + G.add_weighted_edges_from(triples, weight=edge_attribute) + return G + + +@nx._dispatchable(edge_attrs="weight") # edge attrs may also be obtained from `dtype` +def to_numpy_array( + G, + nodelist=None, + dtype=None, + order=None, + multigraph_weight=sum, + weight="weight", + nonedge=0.0, +): + """Returns the graph adjacency matrix as a NumPy array. + + Parameters + ---------- + G : graph + The NetworkX graph used to construct the NumPy array. + + nodelist : list, optional + The rows and columns are ordered according to the nodes in `nodelist`. + If `nodelist` is ``None``, then the ordering is produced by ``G.nodes()``. + + dtype : NumPy data type, optional + A NumPy data type used to initialize the array. If None, then the NumPy + default is used. The dtype can be structured if `weight=None`, in which + case the dtype field names are used to look up edge attributes. The + result is a structured array where each named field in the dtype + corresponds to the adjacency for that edge attribute. See examples for + details. + + order : {'C', 'F'}, optional + Whether to store multidimensional data in C- or Fortran-contiguous + (row- or column-wise) order in memory. If None, then the NumPy default + is used. + + multigraph_weight : callable, optional + An function that determines how weights in multigraphs are handled. + The function should accept a sequence of weights and return a single + value. The default is to sum the weights of the multiple edges. + + weight : string or None optional (default = 'weight') + The edge attribute that holds the numerical value used for + the edge weight. If an edge does not have that attribute, then the + value 1 is used instead. `weight` must be ``None`` if a structured + dtype is used. + + nonedge : array_like (default = 0.0) + The value used to represent non-edges in the adjacency matrix. + The array values corresponding to nonedges are typically set to zero. + However, this could be undesirable if there are array values + corresponding to actual edges that also have the value zero. If so, + one might prefer nonedges to have some other value, such as ``nan``. + + Returns + ------- + A : NumPy ndarray + Graph adjacency matrix + + Raises + ------ + NetworkXError + If `dtype` is a structured dtype and `G` is a multigraph + ValueError + If `dtype` is a structured dtype and `weight` is not `None` + + See Also + -------- + from_numpy_array + + Notes + ----- + For directed graphs, entry ``i, j`` corresponds to an edge from ``i`` to ``j``. + + Entries in the adjacency matrix are given by the `weight` edge attribute. + When an edge does not have a weight attribute, the value of the entry is + set to the number 1. For multiple (parallel) edges, the values of the + entries are determined by the `multigraph_weight` parameter. The default is + to sum the weight attributes for each of the parallel edges. + + When `nodelist` does not contain every node in `G`, the adjacency matrix is + built from the subgraph of `G` that is induced by the nodes in `nodelist`. + + The convention used for self-loop edges in graphs is to assign the + diagonal array entry value to the weight attribute of the edge + (or the number 1 if the edge has no weight attribute). If the + alternate convention of doubling the edge weight is desired the + resulting NumPy array can be modified as follows: + + >>> import numpy as np + >>> G = nx.Graph([(1, 1)]) + >>> A = nx.to_numpy_array(G) + >>> A + array([[1.]]) + >>> A[np.diag_indices_from(A)] *= 2 + >>> A + array([[2.]]) + + Examples + -------- + >>> G = nx.MultiDiGraph() + >>> G.add_edge(0, 1, weight=2) + 0 + >>> G.add_edge(1, 0) + 0 + >>> G.add_edge(2, 2, weight=3) + 0 + >>> G.add_edge(2, 2) + 1 + >>> nx.to_numpy_array(G, nodelist=[0, 1, 2]) + array([[0., 2., 0.], + [1., 0., 0.], + [0., 0., 4.]]) + + When `nodelist` argument is used, nodes of `G` which do not appear in the `nodelist` + and their edges are not included in the adjacency matrix. Here is an example: + + >>> G = nx.Graph() + >>> G.add_edge(3, 1) + >>> G.add_edge(2, 0) + >>> G.add_edge(2, 1) + >>> G.add_edge(3, 0) + >>> nx.to_numpy_array(G, nodelist=[1, 2, 3]) + array([[0., 1., 1.], + [1., 0., 0.], + [1., 0., 0.]]) + + This function can also be used to create adjacency matrices for multiple + edge attributes with structured dtypes: + + >>> G = nx.Graph() + >>> G.add_edge(0, 1, weight=10) + >>> G.add_edge(1, 2, cost=5) + >>> G.add_edge(2, 3, weight=3, cost=-4.0) + >>> dtype = np.dtype([("weight", int), ("cost", float)]) + >>> A = nx.to_numpy_array(G, dtype=dtype, weight=None) + >>> A["weight"] + array([[ 0, 10, 0, 0], + [10, 0, 1, 0], + [ 0, 1, 0, 3], + [ 0, 0, 3, 0]]) + >>> A["cost"] + array([[ 0., 1., 0., 0.], + [ 1., 0., 5., 0.], + [ 0., 5., 0., -4.], + [ 0., 0., -4., 0.]]) + + As stated above, the argument "nonedge" is useful especially when there are + actually edges with weight 0 in the graph. Setting a nonedge value different than 0, + makes it much clearer to differentiate such 0-weighted edges and actual nonedge values. + + >>> G = nx.Graph() + >>> G.add_edge(3, 1, weight=2) + >>> G.add_edge(2, 0, weight=0) + >>> G.add_edge(2, 1, weight=0) + >>> G.add_edge(3, 0, weight=1) + >>> nx.to_numpy_array(G, nonedge=-1.0) + array([[-1., 2., -1., 1.], + [ 2., -1., 0., -1.], + [-1., 0., -1., 0.], + [ 1., -1., 0., -1.]]) + """ + import numpy as np + + if nodelist is None: + nodelist = list(G) + nlen = len(nodelist) + + # Input validation + nodeset = set(nodelist) + if nodeset - set(G): + raise nx.NetworkXError(f"Nodes {nodeset - set(G)} in nodelist is not in G") + if len(nodeset) < nlen: + raise nx.NetworkXError("nodelist contains duplicates.") + + A = np.full((nlen, nlen), fill_value=nonedge, dtype=dtype, order=order) + + # Corner cases: empty nodelist or graph without any edges + if nlen == 0 or G.number_of_edges() == 0: + return A + + # If dtype is structured and weight is None, use dtype field names as + # edge attributes + edge_attrs = None # Only single edge attribute by default + if A.dtype.names: + if weight is None: + edge_attrs = dtype.names + else: + raise ValueError( + "Specifying `weight` not supported for structured dtypes\n." + "To create adjacency matrices from structured dtypes, use `weight=None`." + ) + + # Map nodes to row/col in matrix + idx = dict(zip(nodelist, range(nlen))) + if len(nodelist) < len(G): + G = G.subgraph(nodelist).copy() + + # Collect all edge weights and reduce with `multigraph_weights` + if G.is_multigraph(): + if edge_attrs: + raise nx.NetworkXError( + "Structured arrays are not supported for MultiGraphs" + ) + d = defaultdict(list) + for u, v, wt in G.edges(data=weight, default=1.0): + d[(idx[u], idx[v])].append(wt) + i, j = np.array(list(d.keys())).T # indices + wts = [multigraph_weight(ws) for ws in d.values()] # reduced weights + else: + i, j, wts = [], [], [] + + # Special branch: multi-attr adjacency from structured dtypes + if edge_attrs: + # Extract edges with all data + for u, v, data in G.edges(data=True): + i.append(idx[u]) + j.append(idx[v]) + wts.append(data) + # Map each attribute to the appropriate named field in the + # structured dtype + for attr in edge_attrs: + attr_data = [wt.get(attr, 1.0) for wt in wts] + A[attr][i, j] = attr_data + if not G.is_directed(): + A[attr][j, i] = attr_data + return A + + for u, v, wt in G.edges(data=weight, default=1.0): + i.append(idx[u]) + j.append(idx[v]) + wts.append(wt) + + # Set array values with advanced indexing + A[i, j] = wts + if not G.is_directed(): + A[j, i] = wts + + return A + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_numpy_array( + A, parallel_edges=False, create_using=None, edge_attr="weight", *, nodelist=None +): + """Returns a graph from a 2D NumPy array. + + The 2D NumPy array is interpreted as an adjacency matrix for the graph. + + Parameters + ---------- + A : a 2D numpy.ndarray + An adjacency matrix representation of a graph + + parallel_edges : Boolean + If this is True, `create_using` is a multigraph, and `A` is an + integer array, then entry *(i, j)* in the array is interpreted as the + number of parallel edges joining vertices *i* and *j* in the graph. + If it is False, then the entries in the array are interpreted as + the weight of a single edge joining the vertices. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + edge_attr : String, optional (default="weight") + The attribute to which the array values are assigned on each edge. If + it is None, edge attributes will not be assigned. + + nodelist : sequence of nodes, optional + A sequence of objects to use as the nodes in the graph. If provided, the + list of nodes must be the same length as the dimensions of `A`. The + default is `None`, in which case the nodes are drawn from ``range(n)``. + + Notes + ----- + For directed graphs, explicitly mention create_using=nx.DiGraph, + and entry i,j of A corresponds to an edge from i to j. + + If `create_using` is :class:`networkx.MultiGraph` or + :class:`networkx.MultiDiGraph`, `parallel_edges` is True, and the + entries of `A` are of type :class:`int`, then this function returns a + multigraph (of the same type as `create_using`) with parallel edges. + + If `create_using` indicates an undirected multigraph, then only the edges + indicated by the upper triangle of the array `A` will be added to the + graph. + + If `edge_attr` is Falsy (False or None), edge attributes will not be + assigned, and the array data will be treated like a binary mask of + edge presence or absence. Otherwise, the attributes will be assigned + as follows: + + If the NumPy array has a single data type for each array entry it + will be converted to an appropriate Python data type. + + If the NumPy array has a user-specified compound data type the names + of the data fields will be used as attribute keys in the resulting + NetworkX graph. + + See Also + -------- + to_numpy_array + + Examples + -------- + Simple integer weights on edges: + + >>> import numpy as np + >>> A = np.array([[1, 1], [2, 1]]) + >>> G = nx.from_numpy_array(A) + >>> G.edges(data=True) + EdgeDataView([(0, 0, {'weight': 1}), (0, 1, {'weight': 2}), (1, 1, {'weight': 1})]) + + If `create_using` indicates a multigraph and the array has only integer + entries and `parallel_edges` is False, then the entries will be treated + as weights for edges joining the nodes (without creating parallel edges): + + >>> A = np.array([[1, 1], [1, 2]]) + >>> G = nx.from_numpy_array(A, create_using=nx.MultiGraph) + >>> G[1][1] + AtlasView({0: {'weight': 2}}) + + If `create_using` indicates a multigraph and the array has only integer + entries and `parallel_edges` is True, then the entries will be treated + as the number of parallel edges joining those two vertices: + + >>> A = np.array([[1, 1], [1, 2]]) + >>> temp = nx.MultiGraph() + >>> G = nx.from_numpy_array(A, parallel_edges=True, create_using=temp) + >>> G[1][1] + AtlasView({0: {'weight': 1}, 1: {'weight': 1}}) + + User defined compound data type on edges: + + >>> dt = [("weight", float), ("cost", int)] + >>> A = np.array([[(1.0, 2)]], dtype=dt) + >>> G = nx.from_numpy_array(A) + >>> G.edges() + EdgeView([(0, 0)]) + >>> G[0][0]["cost"] + 2 + >>> G[0][0]["weight"] + 1.0 + + """ + kind_to_python_type = { + "f": float, + "i": int, + "u": int, + "b": bool, + "c": complex, + "S": str, + "U": str, + "V": "void", + } + G = nx.empty_graph(0, create_using) + if A.ndim != 2: + raise nx.NetworkXError(f"Input array must be 2D, not {A.ndim}") + n, m = A.shape + if n != m: + raise nx.NetworkXError(f"Adjacency matrix not square: nx,ny={A.shape}") + dt = A.dtype + try: + python_type = kind_to_python_type[dt.kind] + except Exception as err: + raise TypeError(f"Unknown numpy data type: {dt}") from err + if _default_nodes := (nodelist is None): + nodelist = range(n) + else: + if len(nodelist) != n: + raise ValueError("nodelist must have the same length as A.shape[0]") + + # Make sure we get even the isolated nodes of the graph. + G.add_nodes_from(nodelist) + # Get a list of all the entries in the array with nonzero entries. These + # coordinates become edges in the graph. (convert to int from np.int64) + edges = ((int(e[0]), int(e[1])) for e in zip(*A.nonzero())) + # handle numpy constructed data type + if python_type == "void": + # Sort the fields by their offset, then by dtype, then by name. + fields = sorted( + (offset, dtype, name) for name, (dtype, offset) in A.dtype.fields.items() + ) + triples = ( + ( + u, + v, + {} + if edge_attr in [False, None] + else { + name: kind_to_python_type[dtype.kind](val) + for (_, dtype, name), val in zip(fields, A[u, v]) + }, + ) + for u, v in edges + ) + # If the entries in the adjacency matrix are integers, the graph is a + # multigraph, and parallel_edges is True, then create parallel edges, each + # with weight 1, for each entry in the adjacency matrix. Otherwise, create + # one edge for each positive entry in the adjacency matrix and set the + # weight of that edge to be the entry in the matrix. + elif python_type is int and G.is_multigraph() and parallel_edges: + chain = itertools.chain.from_iterable + # The following line is equivalent to: + # + # for (u, v) in edges: + # for d in range(A[u, v]): + # G.add_edge(u, v, weight=1) + # + if edge_attr in [False, None]: + triples = chain(((u, v, {}) for d in range(A[u, v])) for (u, v) in edges) + else: + triples = chain( + ((u, v, {edge_attr: 1}) for d in range(A[u, v])) for (u, v) in edges + ) + else: # basic data type + if edge_attr in [False, None]: + triples = ((u, v, {}) for u, v in edges) + else: + triples = ((u, v, {edge_attr: python_type(A[u, v])}) for u, v in edges) + # If we are creating an undirected multigraph, only add the edges from the + # upper triangle of the matrix. Otherwise, add all the edges. This relies + # on the fact that the vertices created in the + # `_generated_weighted_edges()` function are actually the row/column + # indices for the matrix `A`. + # + # Without this check, we run into a problem where each edge is added twice + # when `G.add_edges_from()` is invoked below. + if G.is_multigraph() and not G.is_directed(): + triples = ((u, v, d) for u, v, d in triples if u <= v) + # Remap nodes if user provided custom `nodelist` + if not _default_nodes: + idx_to_node = dict(enumerate(nodelist)) + triples = ((idx_to_node[u], idx_to_node[v], d) for u, v, d in triples) + G.add_edges_from(triples) + return G diff --git a/.venv/lib/python3.11/site-packages/networkx/lazy_imports.py b/.venv/lib/python3.11/site-packages/networkx/lazy_imports.py new file mode 100644 index 0000000000000000000000000000000000000000..396404ba38f5885bfcc65af36d7b4655e94ccc27 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/networkx/lazy_imports.py @@ -0,0 +1,188 @@ +import importlib +import importlib.util +import inspect +import os +import sys +import types + +__all__ = ["attach", "_lazy_import"] + + +def attach(module_name, submodules=None, submod_attrs=None): + """Attach lazily loaded submodules, and functions or other attributes. + + Typically, modules import submodules and attributes as follows:: + + import mysubmodule + import anothersubmodule + + from .foo import someattr + + The idea of this function is to replace the `__init__.py` + module's `__getattr__`, `__dir__`, and `__all__` attributes such that + all imports work exactly the way they normally would, except that the + actual import is delayed until the resulting module object is first used. + + The typical way to call this function, replacing the above imports, is:: + + __getattr__, __lazy_dir__, __all__ = lazy.attach( + __name__, ["mysubmodule", "anothersubmodule"], {"foo": "someattr"} + ) + + This functionality requires Python 3.7 or higher. + + Parameters + ---------- + module_name : str + Typically use __name__. + submodules : set + List of submodules to lazily import. + submod_attrs : dict + Dictionary of submodule -> list of attributes / functions. + These attributes are imported as they are used. + + Returns + ------- + __getattr__, __dir__, __all__ + + """ + if submod_attrs is None: + submod_attrs = {} + + if submodules is None: + submodules = set() + else: + submodules = set(submodules) + + attr_to_modules = { + attr: mod for mod, attrs in submod_attrs.items() for attr in attrs + } + + __all__ = list(submodules | attr_to_modules.keys()) + + def __getattr__(name): + if name in submodules: + return importlib.import_module(f"{module_name}.{name}") + elif name in attr_to_modules: + submod = importlib.import_module(f"{module_name}.{attr_to_modules[name]}") + return getattr(submod, name) + else: + raise AttributeError(f"No {module_name} attribute {name}") + + def __dir__(): + return __all__ + + if os.environ.get("EAGER_IMPORT", ""): + for attr in set(attr_to_modules.keys()) | submodules: + __getattr__(attr) + + return __getattr__, __dir__, list(__all__) + + +class DelayedImportErrorModule(types.ModuleType): + def __init__(self, frame_data, *args, **kwargs): + self.__frame_data = frame_data + super().__init__(*args, **kwargs) + + def __getattr__(self, x): + if x in ("__class__", "__file__", "__frame_data"): + super().__getattr__(x) + else: + fd = self.__frame_data + raise ModuleNotFoundError( + f"No module named '{fd['spec']}'\n\n" + "This error is lazily reported, having originally occurred in\n" + f' File {fd["filename"]}, line {fd["lineno"]}, in {fd["function"]}\n\n' + f'----> {"".join(fd["code_context"] or "").strip()}' + ) + + +def _lazy_import(fullname): + """Return a lazily imported proxy for a module or library. + + Warning + ------- + Importing using this function can currently cause trouble + when the user tries to import from a subpackage of a module before + the package is fully imported. In particular, this idiom may not work: + + np = lazy_import("numpy") + from numpy.lib import recfunctions + + This is due to a difference in the way Python's LazyLoader handles + subpackage imports compared to the normal import process. Hopefully + we will get Python's LazyLoader to fix this, or find a workaround. + In the meantime, this is a potential problem. + + The workaround is to import numpy before importing from the subpackage. + + Notes + ----- + We often see the following pattern:: + + def myfunc(): + import scipy as sp + sp.argmin(...) + .... + + This is to prevent a library, in this case `scipy`, from being + imported at function definition time, since that can be slow. + + This function provides a proxy module that, upon access, imports + the actual module. So the idiom equivalent to the above example is:: + + sp = lazy.load("scipy") + + def myfunc(): + sp.argmin(...) + .... + + The initial import time is fast because the actual import is delayed + until the first attribute is requested. The overall import time may + decrease as well for users that don't make use of large portions + of the library. + + Parameters + ---------- + fullname : str + The full name of the package or subpackage to import. For example:: + + sp = lazy.load("scipy") # import scipy as sp + spla = lazy.load("scipy.linalg") # import scipy.linalg as spla + + Returns + ------- + pm : importlib.util._LazyModule + Proxy module. Can be used like any regularly imported module. + Actual loading of the module occurs upon first attribute request. + + """ + try: + return sys.modules[fullname] + except: + pass + + # Not previously loaded -- look it up + spec = importlib.util.find_spec(fullname) + + if spec is None: + try: + parent = inspect.stack()[1] + frame_data = { + "spec": fullname, + "filename": parent.filename, + "lineno": parent.lineno, + "function": parent.function, + "code_context": parent.code_context, + } + return DelayedImportErrorModule(frame_data, "DelayedImportErrorModule") + finally: + del parent + + module = importlib.util.module_from_spec(spec) + sys.modules[fullname] = module + + loader = importlib.util.LazyLoader(spec.loader) + loader.exec_module(module) + + return module diff --git a/.venv/lib/python3.11/site-packages/networkx/relabel.py b/.venv/lib/python3.11/site-packages/networkx/relabel.py new file mode 100644 index 0000000000000000000000000000000000000000..4b870f726ef42e0bcaa7bf724e2ae6ab4145f288 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/networkx/relabel.py @@ -0,0 +1,285 @@ +import networkx as nx + +__all__ = ["convert_node_labels_to_integers", "relabel_nodes"] + + +@nx._dispatchable( + preserve_all_attrs=True, mutates_input={"not copy": 2}, returns_graph=True +) +def relabel_nodes(G, mapping, copy=True): + """Relabel the nodes of the graph G according to a given mapping. + + The original node ordering may not be preserved if `copy` is `False` and the + mapping includes overlap between old and new labels. + + Parameters + ---------- + G : graph + A NetworkX graph + + mapping : dictionary + A dictionary with the old labels as keys and new labels as values. + A partial mapping is allowed. Mapping 2 nodes to a single node is allowed. + Any non-node keys in the mapping are ignored. + + copy : bool (optional, default=True) + If True return a copy, or if False relabel the nodes in place. + + Examples + -------- + To create a new graph with nodes relabeled according to a given + dictionary: + + >>> G = nx.path_graph(3) + >>> sorted(G) + [0, 1, 2] + >>> mapping = {0: "a", 1: "b", 2: "c"} + >>> H = nx.relabel_nodes(G, mapping) + >>> sorted(H) + ['a', 'b', 'c'] + + Nodes can be relabeled with any hashable object, including numbers + and strings: + + >>> import string + >>> G = nx.path_graph(26) # nodes are integers 0 through 25 + >>> sorted(G)[:3] + [0, 1, 2] + >>> mapping = dict(zip(G, string.ascii_lowercase)) + >>> G = nx.relabel_nodes(G, mapping) # nodes are characters a through z + >>> sorted(G)[:3] + ['a', 'b', 'c'] + >>> mapping = dict(zip(G, range(1, 27))) + >>> G = nx.relabel_nodes(G, mapping) # nodes are integers 1 through 26 + >>> sorted(G)[:3] + [1, 2, 3] + + To perform a partial in-place relabeling, provide a dictionary + mapping only a subset of the nodes, and set the `copy` keyword + argument to False: + + >>> G = nx.path_graph(3) # nodes 0-1-2 + >>> mapping = {0: "a", 1: "b"} # 0->'a' and 1->'b' + >>> G = nx.relabel_nodes(G, mapping, copy=False) + >>> sorted(G, key=str) + [2, 'a', 'b'] + + A mapping can also be given as a function: + + >>> G = nx.path_graph(3) + >>> H = nx.relabel_nodes(G, lambda x: x**2) + >>> list(H) + [0, 1, 4] + + In a multigraph, relabeling two or more nodes to the same new node + will retain all edges, but may change the edge keys in the process: + + >>> G = nx.MultiGraph() + >>> G.add_edge(0, 1, value="a") # returns the key for this edge + 0 + >>> G.add_edge(0, 2, value="b") + 0 + >>> G.add_edge(0, 3, value="c") + 0 + >>> mapping = {1: 4, 2: 4, 3: 4} + >>> H = nx.relabel_nodes(G, mapping, copy=True) + >>> print(H[0]) + {4: {0: {'value': 'a'}, 1: {'value': 'b'}, 2: {'value': 'c'}}} + + This works for in-place relabeling too: + + >>> G = nx.relabel_nodes(G, mapping, copy=False) + >>> print(G[0]) + {4: {0: {'value': 'a'}, 1: {'value': 'b'}, 2: {'value': 'c'}}} + + Notes + ----- + Only the nodes specified in the mapping will be relabeled. + Any non-node keys in the mapping are ignored. + + The keyword setting copy=False modifies the graph in place. + Relabel_nodes avoids naming collisions by building a + directed graph from ``mapping`` which specifies the order of + relabelings. Naming collisions, such as a->b, b->c, are ordered + such that "b" gets renamed to "c" before "a" gets renamed "b". + In cases of circular mappings (e.g. a->b, b->a), modifying the + graph is not possible in-place and an exception is raised. + In that case, use copy=True. + + If a relabel operation on a multigraph would cause two or more + edges to have the same source, target and key, the second edge must + be assigned a new key to retain all edges. The new key is set + to the lowest non-negative integer not already used as a key + for edges between these two nodes. Note that this means non-numeric + keys may be replaced by numeric keys. + + See Also + -------- + convert_node_labels_to_integers + """ + # you can pass any callable e.g. f(old_label) -> new_label or + # e.g. str(old_label) -> new_label, but we'll just make a dictionary here regardless + m = {n: mapping(n) for n in G} if callable(mapping) else mapping + + if copy: + return _relabel_copy(G, m) + else: + return _relabel_inplace(G, m) + + +def _relabel_inplace(G, mapping): + if len(mapping.keys() & mapping.values()) > 0: + # labels sets overlap + # can we topological sort and still do the relabeling? + D = nx.DiGraph(list(mapping.items())) + D.remove_edges_from(nx.selfloop_edges(D)) + try: + nodes = reversed(list(nx.topological_sort(D))) + except nx.NetworkXUnfeasible as err: + raise nx.NetworkXUnfeasible( + "The node label sets are overlapping and no ordering can " + "resolve the mapping. Use copy=True." + ) from err + else: + # non-overlapping label sets, sort them in the order of G nodes + nodes = [n for n in G if n in mapping] + + multigraph = G.is_multigraph() + directed = G.is_directed() + + for old in nodes: + # Test that old is in both mapping and G, otherwise ignore. + try: + new = mapping[old] + G.add_node(new, **G.nodes[old]) + except KeyError: + continue + if new == old: + continue + if multigraph: + new_edges = [ + (new, new if old == target else target, key, data) + for (_, target, key, data) in G.edges(old, data=True, keys=True) + ] + if directed: + new_edges += [ + (new if old == source else source, new, key, data) + for (source, _, key, data) in G.in_edges(old, data=True, keys=True) + ] + # Ensure new edges won't overwrite existing ones + seen = set() + for i, (source, target, key, data) in enumerate(new_edges): + if target in G[source] and key in G[source][target]: + new_key = 0 if not isinstance(key, int | float) else key + while new_key in G[source][target] or (target, new_key) in seen: + new_key += 1 + new_edges[i] = (source, target, new_key, data) + seen.add((target, new_key)) + else: + new_edges = [ + (new, new if old == target else target, data) + for (_, target, data) in G.edges(old, data=True) + ] + if directed: + new_edges += [ + (new if old == source else source, new, data) + for (source, _, data) in G.in_edges(old, data=True) + ] + G.remove_node(old) + G.add_edges_from(new_edges) + return G + + +def _relabel_copy(G, mapping): + H = G.__class__() + H.add_nodes_from(mapping.get(n, n) for n in G) + H._node.update((mapping.get(n, n), d.copy()) for n, d in G.nodes.items()) + if G.is_multigraph(): + new_edges = [ + (mapping.get(n1, n1), mapping.get(n2, n2), k, d.copy()) + for (n1, n2, k, d) in G.edges(keys=True, data=True) + ] + + # check for conflicting edge-keys + undirected = not G.is_directed() + seen_edges = set() + for i, (source, target, key, data) in enumerate(new_edges): + while (source, target, key) in seen_edges: + if not isinstance(key, int | float): + key = 0 + key += 1 + seen_edges.add((source, target, key)) + if undirected: + seen_edges.add((target, source, key)) + new_edges[i] = (source, target, key, data) + + H.add_edges_from(new_edges) + else: + H.add_edges_from( + (mapping.get(n1, n1), mapping.get(n2, n2), d.copy()) + for (n1, n2, d) in G.edges(data=True) + ) + H.graph.update(G.graph) + return H + + +@nx._dispatchable(preserve_all_attrs=True, returns_graph=True) +def convert_node_labels_to_integers( + G, first_label=0, ordering="default", label_attribute=None +): + """Returns a copy of the graph G with the nodes relabeled using + consecutive integers. + + Parameters + ---------- + G : graph + A NetworkX graph + + first_label : int, optional (default=0) + An integer specifying the starting offset in numbering nodes. + The new integer labels are numbered first_label, ..., n-1+first_label. + + ordering : string + "default" : inherit node ordering from G.nodes() + "sorted" : inherit node ordering from sorted(G.nodes()) + "increasing degree" : nodes are sorted by increasing degree + "decreasing degree" : nodes are sorted by decreasing degree + + label_attribute : string, optional (default=None) + Name of node attribute to store old label. If None no attribute + is created. + + Notes + ----- + Node and edge attribute data are copied to the new (relabeled) graph. + + There is no guarantee that the relabeling of nodes to integers will + give the same two integers for two (even identical graphs). + Use the `ordering` argument to try to preserve the order. + + See Also + -------- + relabel_nodes + """ + N = G.number_of_nodes() + first_label + if ordering == "default": + mapping = dict(zip(G.nodes(), range(first_label, N))) + elif ordering == "sorted": + nlist = sorted(G.nodes()) + mapping = dict(zip(nlist, range(first_label, N))) + elif ordering == "increasing degree": + dv_pairs = [(d, n) for (n, d) in G.degree()] + dv_pairs.sort() # in-place sort from lowest to highest degree + mapping = dict(zip([n for d, n in dv_pairs], range(first_label, N))) + elif ordering == "decreasing degree": + dv_pairs = [(d, n) for (n, d) in G.degree()] + dv_pairs.sort() # in-place sort from lowest to highest degree + dv_pairs.reverse() + mapping = dict(zip([n for d, n in dv_pairs], range(first_label, N))) + else: + raise nx.NetworkXError(f"Unknown node ordering: {ordering}") + H = relabel_nodes(G, mapping) + # create node attribute with the old label + if label_attribute is not None: + nx.set_node_attributes(H, {v: k for k, v in mapping.items()}, label_attribute) + return H diff --git a/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/LICENSE b/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..51f3442917839f8e0f0cccb52b3c10968ad0779e --- /dev/null +++ b/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/LICENSE @@ -0,0 +1,3 @@ +This software is made available under the terms of *either* of the +licenses found in LICENSE.APACHE2 or LICENSE.MIT. Contributions to are +made under the terms of *both* these licenses. diff --git a/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 b/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/METADATA b/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..75e0057bbad50ca4c117b9130b92f1bed2720669 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/METADATA @@ -0,0 +1,104 @@ +Metadata-Version: 2.1 +Name: sniffio +Version: 1.3.1 +Summary: Sniff out which async library your code is running under +Author-email: "Nathaniel J. Smith" +License: MIT OR Apache-2.0 +Project-URL: Homepage, https://github.com/python-trio/sniffio +Project-URL: Documentation, https://sniffio.readthedocs.io/ +Project-URL: Changelog, https://sniffio.readthedocs.io/en/latest/history.html +Keywords: async,trio,asyncio +Classifier: License :: OSI Approved :: MIT License +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Framework :: Trio +Classifier: Framework :: AsyncIO +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Intended Audience :: Developers +Classifier: Development Status :: 5 - Production/Stable +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE +License-File: LICENSE.APACHE2 +License-File: LICENSE.MIT + +.. image:: https://img.shields.io/badge/chat-join%20now-blue.svg + :target: https://gitter.im/python-trio/general + :alt: Join chatroom + +.. image:: https://img.shields.io/badge/docs-read%20now-blue.svg + :target: https://sniffio.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + +.. image:: https://img.shields.io/pypi/v/sniffio.svg + :target: https://pypi.org/project/sniffio + :alt: Latest PyPi version + +.. image:: https://img.shields.io/conda/vn/conda-forge/sniffio.svg + :target: https://anaconda.org/conda-forge/sniffio + :alt: Latest conda-forge version + +.. image:: https://travis-ci.org/python-trio/sniffio.svg?branch=master + :target: https://travis-ci.org/python-trio/sniffio + :alt: Automated test status + +.. image:: https://codecov.io/gh/python-trio/sniffio/branch/master/graph/badge.svg + :target: https://codecov.io/gh/python-trio/sniffio + :alt: Test coverage + +================================================================= +sniffio: Sniff out which async library your code is running under +================================================================= + +You're writing a library. You've decided to be ambitious, and support +multiple async I/O packages, like `Trio +`__, and `asyncio +`__, and ... You've +written a bunch of clever code to handle all the differences. But... +how do you know *which* piece of clever code to run? + +This is a tiny package whose only purpose is to let you detect which +async library your code is running under. + +* Documentation: https://sniffio.readthedocs.io + +* Bug tracker and source code: https://github.com/python-trio/sniffio + +* License: MIT or Apache License 2.0, your choice + +* Contributor guide: https://trio.readthedocs.io/en/latest/contributing.html + +* Code of conduct: Contributors are requested to follow our `code of + conduct + `_ + in all project spaces. + +This library is maintained by the Trio project, as a service to the +async Python community as a whole. + + +Quickstart +---------- + +.. code-block:: python3 + + from sniffio import current_async_library + import trio + import asyncio + + async def print_library(): + library = current_async_library() + print("This is:", library) + + # Prints "This is trio" + trio.run(print_library) + + # Prints "This is asyncio" + asyncio.run(print_library()) + +For more details, including how to add support to new async libraries, +`please peruse our fine manual `__. diff --git a/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/RECORD b/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..cb001d82ccac5b66b01ca7dac0c4383fa686bab2 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/RECORD @@ -0,0 +1,19 @@ +sniffio-1.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +sniffio-1.3.1.dist-info/LICENSE,sha256=ZSyHhIjRRWNh4Iw_hgf9e6WYkqFBA9Fczk_5PIW1zIs,185 +sniffio-1.3.1.dist-info/LICENSE.APACHE2,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +sniffio-1.3.1.dist-info/LICENSE.MIT,sha256=Pm2uVV65J4f8gtHUg1Vnf0VMf2Wus40_nnK_mj2vA0s,1046 +sniffio-1.3.1.dist-info/METADATA,sha256=CzGLVwmO3sz1heYKiJprantcQIbzqapi7_dqHTzuEtk,3875 +sniffio-1.3.1.dist-info/RECORD,, +sniffio-1.3.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92 +sniffio-1.3.1.dist-info/top_level.txt,sha256=v9UJXGs5CyddCVeAqXkQiWOrpp6Wtx6GeRrPt9-jjHg,8 +sniffio/__init__.py,sha256=9WJEJlXu7yluP0YtI5SQ9M9OTQfbNHkadarK1vXGDPM,335 +sniffio/__pycache__/__init__.cpython-311.pyc,, +sniffio/__pycache__/_impl.cpython-311.pyc,, +sniffio/__pycache__/_version.cpython-311.pyc,, +sniffio/_impl.py,sha256=UmUFMZpiuOrcjnuHhuYiYMxeCNWfqu9kBlaPf0xk6X8,2843 +sniffio/_tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +sniffio/_tests/__pycache__/__init__.cpython-311.pyc,, +sniffio/_tests/__pycache__/test_sniffio.cpython-311.pyc,, +sniffio/_tests/test_sniffio.py,sha256=MMJZZJjQrUi95RANNM-a_55BZquA_gv4rHU1pevcTCM,2058 +sniffio/_version.py,sha256=iVes5xwsHeRzQDexBaAhyx_taNt2ucfA7CWAo4QDt6Q,89 +sniffio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/WHEEL b/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..98c0d20b7a64f4f998d7913e1d38a05dba20916c --- /dev/null +++ b/.venv/lib/python3.11/site-packages/sniffio-1.3.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.42.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/INSTALLER b/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/LICENSE b/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..1bec23eaf1dd562ae3d3216420b1b1bbfbd39cbc --- /dev/null +++ b/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2017 Facebook Inc. (Soumith Chintala), +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/METADATA b/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..71563c653cb0d8fbf2cdf7b1bdcc21bdfe1af99e --- /dev/null +++ b/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/METADATA @@ -0,0 +1,113 @@ +Metadata-Version: 2.1 +Name: torchaudio +Version: 2.5.1 +Summary: An audio package for PyTorch +Home-page: https://github.com/pytorch/audio +Author: Soumith Chintala, David Pollack, Sean Naren, Peter Goldsborough, Moto Hira, Caroline Chen, Jeff Hwang, Zhaoheng Ni, Xiaohui Zhang +Author-email: soumith@pytorch.org +Maintainer: Moto Hira, Caroline Chen, Jeff Hwang, Zhaoheng Ni, Xiaohui Zhang +Maintainer-email: moto@meta.com +Classifier: Environment :: Plugins +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Programming Language :: C++ +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Topic :: Multimedia :: Sound/Audio +Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: torch (==2.5.1) + +torchaudio: an audio library for PyTorch +======================================== + +[![Documentation](https://img.shields.io/badge/dynamic/json.svg?label=docs&url=https%3A%2F%2Fpypi.org%2Fpypi%2Ftorchaudio%2Fjson&query=%24.info.version&colorB=brightgreen&prefix=v)](https://pytorch.org/audio/main/) +[![Anaconda Badge](https://anaconda.org/pytorch/torchaudio/badges/downloads.svg)](https://anaconda.org/pytorch/torchaudio) +[![Anaconda-Server Badge](https://anaconda.org/pytorch/torchaudio/badges/platforms.svg)](https://anaconda.org/pytorch/torchaudio) + +![TorchAudio Logo](docs/source/_static/img/logo.png) + +The aim of torchaudio is to apply [PyTorch](https://github.com/pytorch/pytorch) to +the audio domain. By supporting PyTorch, torchaudio follows the same philosophy +of providing strong GPU acceleration, having a focus on trainable features through +the autograd system, and having consistent style (tensor names and dimension names). +Therefore, it is primarily a machine learning library and not a general signal +processing library. The benefits of PyTorch can be seen in torchaudio through +having all the computations be through PyTorch operations which makes it easy +to use and feel like a natural extension. + +- [Support audio I/O (Load files, Save files)](http://pytorch.org/audio/main/) + - Load a variety of audio formats, such as `wav`, `mp3`, `ogg`, `flac`, `opus`, `sphere`, into a torch Tensor using SoX + - [Kaldi (ark/scp)](http://pytorch.org/audio/main/kaldi_io.html) +- [Dataloaders for common audio datasets](http://pytorch.org/audio/main/datasets.html) +- Audio and speech processing functions + - [forced_align](https://pytorch.org/audio/main/generated/torchaudio.functional.forced_align.html) +- Common audio transforms + - [Spectrogram, AmplitudeToDB, MelScale, MelSpectrogram, MFCC, MuLawEncoding, MuLawDecoding, Resample](http://pytorch.org/audio/main/transforms.html) +- Compliance interfaces: Run code using PyTorch that align with other libraries + - [Kaldi: spectrogram, fbank, mfcc](https://pytorch.org/audio/main/compliance.kaldi.html) + +Installation +------------ + +Please refer to https://pytorch.org/audio/main/installation.html for installation and build process of TorchAudio. + + +API Reference +------------- + +API Reference is located here: http://pytorch.org/audio/main/ + +Contributing Guidelines +----------------------- + +Please refer to [CONTRIBUTING.md](./CONTRIBUTING.md) + +Citation +-------- + +If you find this package useful, please cite as: + +```bibtex +@article{yang2021torchaudio, + title={TorchAudio: Building Blocks for Audio and Speech Processing}, + author={Yao-Yuan Yang and Moto Hira and Zhaoheng Ni and Anjali Chourdia and Artyom Astafurov and Caroline Chen and Ching-Feng Yeh and Christian Puhrsch and David Pollack and Dmitriy Genzel and Donny Greenberg and Edward Z. Yang and Jason Lian and Jay Mahadeokar and Jeff Hwang and Ji Chen and Peter Goldsborough and Prabhat Roy and Sean Narenthiran and Shinji Watanabe and Soumith Chintala and Vincent Quenneville-B��lair and Yangyang Shi}, + journal={arXiv preprint arXiv:2110.15018}, + year={2021} +} +``` + +```bibtex +@misc{hwang2023torchaudio, + title={TorchAudio 2.1: Advancing speech recognition, self-supervised learning, and audio processing components for PyTorch}, + author={Jeff Hwang and Moto Hira and Caroline Chen and Xiaohui Zhang and Zhaoheng Ni and Guangzhi Sun and Pingchuan Ma and Ruizhe Huang and Vineel Pratap and Yuekai Zhang and Anurag Kumar and Chin-Yun Yu and Chuang Zhu and Chunxi Liu and Jacob Kahn and Mirco Ravanelli and Peng Sun and Shinji Watanabe and Yangyang Shi and Yumeng Tao and Robin Scheibler and Samuele Cornell and Sean Kim and Stavros Petridis}, + year={2023}, + eprint={2310.17864}, + archivePrefix={arXiv}, + primaryClass={eess.AS} +} +``` + +Disclaimer on Datasets +---------------------- + +This is a utility library that downloads and prepares public datasets. We do not host or distribute these datasets, vouch for their quality or fairness, or claim that you have license to use the dataset. It is your responsibility to determine whether you have permission to use the dataset under the dataset's license. + +If you're a dataset owner and wish to update any part of it (description, citation, etc.), or do not want your dataset to be included in this library, please get in touch through a GitHub issue. Thanks for your contribution to the ML community! + +Pre-trained Model License +------------------------- + +The pre-trained models provided in this library may have their own licenses or terms and conditions derived from the dataset used for training. It is your responsibility to determine whether you have permission to use the models for your use case. + +For instance, SquimSubjective model is released under the Creative Commons Attribution Non Commercial 4.0 International (CC-BY-NC 4.0) license. See [the link](https://zenodo.org/record/4660670#.ZBtWPOxuerN) for additional details. + +Other pre-trained models that have different license are noted in documentation. Please checkout the [documentation page](https://pytorch.org/audio/main/). diff --git a/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/RECORD b/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..1f2cfb4942d5c2433b4b71e4a3281339ac08c29e --- /dev/null +++ b/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/RECORD @@ -0,0 +1,280 @@ +torchaudio-2.5.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +torchaudio-2.5.1.dist-info/LICENSE,sha256=k6WIYahYzBCOa2uDPgjnbosqZjOeSoAHyKWowf-cQNY,1338 +torchaudio-2.5.1.dist-info/METADATA,sha256=RcyC-WrPDB8jGnGByVu2pMbcYh2Yu7gjeBIl9JoibDA,6355 +torchaudio-2.5.1.dist-info/RECORD,, +torchaudio-2.5.1.dist-info/WHEEL,sha256=a1J03bVK0PJLD4YVMpqIHMRBpCcE44lknNyAO_ItTFE,104 +torchaudio-2.5.1.dist-info/top_level.txt,sha256=GT0MktEbHKoLnvd-6ii7_dhJVvshupOujk840BcHU4U,17 +torchaudio/__init__.py,sha256=VSnZ6s4e5clAj7f7aCXBZt9amskeXg1j19txAQBQ2Iw,892 +torchaudio/__pycache__/__init__.cpython-311.pyc,, +torchaudio/__pycache__/kaldi_io.cpython-311.pyc,, +torchaudio/__pycache__/version.cpython-311.pyc,, +torchaudio/_backend/__init__.py,sha256=6zMYGajHaaCXUE_U7HuGLp0fqcviYAjBZdFDI4E7C-0,1631 +torchaudio/_backend/__pycache__/__init__.cpython-311.pyc,, +torchaudio/_backend/__pycache__/backend.cpython-311.pyc,, +torchaudio/_backend/__pycache__/common.cpython-311.pyc,, +torchaudio/_backend/__pycache__/ffmpeg.cpython-311.pyc,, +torchaudio/_backend/__pycache__/soundfile.cpython-311.pyc,, +torchaudio/_backend/__pycache__/soundfile_backend.cpython-311.pyc,, +torchaudio/_backend/__pycache__/sox.cpython-311.pyc,, +torchaudio/_backend/__pycache__/utils.cpython-311.pyc,, +torchaudio/_backend/backend.py,sha256=hSrfZcj5FMzx5ZpwubN-LLMvBFb7ENyw7HvT_6pVYVU,1565 +torchaudio/_backend/common.py,sha256=55Y0r0MsdW6gvTOT_Zy60UGFXc60DfdJ7uvycJKK3is,1783 +torchaudio/_backend/ffmpeg.py,sha256=oL_whDjkPtHzo6HJLiEPlGHdrOqzjlu81g-vlaNkRBA,11294 +torchaudio/_backend/soundfile.py,sha256=n0Epw0J9rBb89xVJWTXaDfb96YFz0-i2xarXIdDd-Cw,1703 +torchaudio/_backend/soundfile_backend.py,sha256=qJHEEXU1egCkPJ2Y9uJWFvVhW3AqDZ7z7P7mkJjJJWM,17376 +torchaudio/_backend/sox.py,sha256=p_y9bXKz_6Hto5LORGrHXVbXNS7nsvWUc9iucbN-tCA,3360 +torchaudio/_backend/utils.py,sha256=Q_RgMaeKFvwOoVdWfdwnL0CmpQli_tmi4wPQ2RRHyRA,13299 +torchaudio/_extension/__init__.py,sha256=lQPB8K7VSxWmtTEiMmF-u7WVq1O10_t5nEghkjCf4Ks,2202 +torchaudio/_extension/__pycache__/__init__.cpython-311.pyc,, +torchaudio/_extension/__pycache__/utils.cpython-311.pyc,, +torchaudio/_extension/utils.py,sha256=4FTD6xwcSLqVJ3Kmpx5cvJp1oAUKmWwRjwuxpcbrmzw,6258 +torchaudio/_internal/__init__.py,sha256=gjU8g9HhVd9hHrHXJM0xOlZL6cT8ktO60MN8RHI6ZbA,241 +torchaudio/_internal/__pycache__/__init__.cpython-311.pyc,, +torchaudio/_internal/__pycache__/module_utils.cpython-311.pyc,, +torchaudio/_internal/module_utils.py,sha256=SJr-RS6hs6uJkIVx_WZwsFPKUjtuG6lsfw3uI0UItDE,3562 +torchaudio/backend/__init__.py,sha256=AL8njOL5hDhIGq5tjRxfFzZXxQdTGlz5gs9g4RToblY,281 +torchaudio/backend/__pycache__/__init__.cpython-311.pyc,, +torchaudio/backend/__pycache__/_no_backend.cpython-311.pyc,, +torchaudio/backend/__pycache__/_sox_io_backend.cpython-311.pyc,, +torchaudio/backend/__pycache__/common.cpython-311.pyc,, +torchaudio/backend/__pycache__/no_backend.cpython-311.pyc,, +torchaudio/backend/__pycache__/soundfile_backend.cpython-311.pyc,, +torchaudio/backend/__pycache__/sox_io_backend.cpython-311.pyc,, +torchaudio/backend/_no_backend.py,sha256=9Ss3b4BMFao5Kfdqh6S8JSLUoYCodbPgNQCiDHbNhDQ,757 +torchaudio/backend/_sox_io_backend.py,sha256=PnH-ClsiOy0ekOTY1RKB-cL6xrTPtrzmuXGX3ugATps,11456 +torchaudio/backend/common.py,sha256=T_iYc4u_EzfIh7zbG_xW052fyJMXUXEpPfDOaAQ6sAY,443 +torchaudio/backend/no_backend.py,sha256=PBEQ9vFG5uVurktjxRAiEqSuVJxImnMyPQlt0reRpP0,469 +torchaudio/backend/soundfile_backend.py,sha256=2Tyh5yAn7LQqKzeqW-rx4o2QbmmUrocmh3iYPnuAds0,499 +torchaudio/backend/sox_io_backend.py,sha256=XsAB5HkRbI9-W2nXx-yUMUPJP2Ca5sd09TLywrQ2N-E,477 +torchaudio/compliance/__init__.py,sha256=hhNObUS0c-fS-VMudM7zl3-CvupvCDmESlikntSMn5g,48 +torchaudio/compliance/__pycache__/__init__.cpython-311.pyc,, +torchaudio/compliance/__pycache__/kaldi.cpython-311.pyc,, +torchaudio/compliance/kaldi.py,sha256=XL6hpYTd6nSPb2imIdeU4TM06I2fqh1AmG968y8ZbSk,36666 +torchaudio/datasets/__init__.py,sha256=taRr3duDaEK1Pfzj9N1dFuZpXfy8e4uFItcJiRLAQwQ,1171 +torchaudio/datasets/__pycache__/__init__.cpython-311.pyc,, +torchaudio/datasets/__pycache__/cmuarctic.cpython-311.pyc,, +torchaudio/datasets/__pycache__/cmudict.cpython-311.pyc,, +torchaudio/datasets/__pycache__/commonvoice.cpython-311.pyc,, +torchaudio/datasets/__pycache__/dr_vctk.cpython-311.pyc,, +torchaudio/datasets/__pycache__/fluentcommands.cpython-311.pyc,, +torchaudio/datasets/__pycache__/gtzan.cpython-311.pyc,, +torchaudio/datasets/__pycache__/iemocap.cpython-311.pyc,, +torchaudio/datasets/__pycache__/librilight_limited.cpython-311.pyc,, +torchaudio/datasets/__pycache__/librimix.cpython-311.pyc,, +torchaudio/datasets/__pycache__/librispeech.cpython-311.pyc,, +torchaudio/datasets/__pycache__/librispeech_biasing.cpython-311.pyc,, +torchaudio/datasets/__pycache__/libritts.cpython-311.pyc,, +torchaudio/datasets/__pycache__/ljspeech.cpython-311.pyc,, +torchaudio/datasets/__pycache__/musdb_hq.cpython-311.pyc,, +torchaudio/datasets/__pycache__/quesst14.cpython-311.pyc,, +torchaudio/datasets/__pycache__/snips.cpython-311.pyc,, +torchaudio/datasets/__pycache__/speechcommands.cpython-311.pyc,, +torchaudio/datasets/__pycache__/tedlium.cpython-311.pyc,, +torchaudio/datasets/__pycache__/utils.cpython-311.pyc,, +torchaudio/datasets/__pycache__/vctk.cpython-311.pyc,, +torchaudio/datasets/__pycache__/voxceleb1.cpython-311.pyc,, +torchaudio/datasets/__pycache__/yesno.cpython-311.pyc,, +torchaudio/datasets/cmuarctic.py,sha256=KAhTHUJ3g5RSlmsU5mCTcvutOCm3Oqcd3643u3HNqIg,7097 +torchaudio/datasets/cmudict.py,sha256=9OEpNDYpyqeEyinAnyGIU8FampDj7ziSOHRwJLIlq2M,5990 +torchaudio/datasets/commonvoice.py,sha256=9khedUCmdEkCKPU6_r8VWz6I2VdJokatuziZ6BxJMZs,2763 +torchaudio/datasets/dr_vctk.py,sha256=Km4-tKllAgnOKCuq66YRWhTlNWmC7D0Xz3dAttRRGSo,4377 +torchaudio/datasets/fluentcommands.py,sha256=u3tkO4-AAaTWdbRQi6lIvad4x2plZgXM39KljGtmRsw,3245 +torchaudio/datasets/gtzan.py,sha256=I5dRP_QGuQ1joXWRwZwtvpwi22uZTb8QZm9Mr2W55Mg,24357 +torchaudio/datasets/iemocap.py,sha256=X_WCoXOzRqcWRRRoUtY0AlD9SJcUUOACIcgbV0irt48,4930 +torchaudio/datasets/librilight_limited.py,sha256=fAwpX0hEMze5aV57BP7rjBLwRiZa3Aje_NXi_3o16wA,4179 +torchaudio/datasets/librimix.py,sha256=VtKOhf6VJc1ysWCvUvh0SbtjOkXJChmBM_BhoSkg_2A,5116 +torchaudio/datasets/librispeech.py,sha256=zkzJFWchWs4AktYAI-ghmWH4ZeJ84C0uDo9E1_pTgSI,6308 +torchaudio/datasets/librispeech_biasing.py,sha256=d-02tyrXI-CSGbXBFYFcnM_yT8WSGABHfpNiFxyadL0,6958 +torchaudio/datasets/libritts.py,sha256=EtWOoCDz7_qGLZF5YcZfnHaLxH4Y8QJCnopafLiqFno,5870 +torchaudio/datasets/ljspeech.py,sha256=92NeLQsC1iKpqfiMkKKbcJDpaYdZKVdVEBQJze1wmxY,3494 +torchaudio/datasets/musdb_hq.py,sha256=TYKjpat6JKr9bkFqUecu7_hRdshRfQP2UbknaYR3Q0U,5075 +torchaudio/datasets/quesst14.py,sha256=QyGd4fMS820ATbP8YgBtu7bSSK09pw5RZklsPJ8Jf0Y,4455 +torchaudio/datasets/snips.py,sha256=WaYUknGFM3rnLklOj5ZYHSX5mhlf_Ce4p3LBZdA9yJc,5008 +torchaudio/datasets/speechcommands.py,sha256=cLSgiVYlQjEOuYPpFeAtcXSGirraH4IMoP8p9WIvUoY,7481 +torchaudio/datasets/tedlium.py,sha256=a8Hf2QvOki7_chgXcMAFMk-piTjodktfnc3HRbUVJkU,8698 +torchaudio/datasets/utils.py,sha256=QaI02lOcesy6Dnvlof4BeTDIbiOqUcoVEPxL5_T8vwU,1689 +torchaudio/datasets/vctk.py,sha256=twR_n8LyQcT8A_HrJoMx3RkaVrRXXZAnIVU1d0E0npQ,5699 +torchaudio/datasets/voxceleb1.py,sha256=9vU0ftB4-2usO8ZiEUKR_IQTEdHhA0M8l9scXCNehnw,11725 +torchaudio/datasets/yesno.py,sha256=4sgfMeSxz8HaRDk6A2UIFP-20q29MwEO_r8DoEtfbvE,3026 +torchaudio/functional/__init__.py,sha256=l-gC2WyY5COabU0lhkUS8EnwOYdEYR_6234OyoAIgnU,2357 +torchaudio/functional/__pycache__/__init__.cpython-311.pyc,, +torchaudio/functional/__pycache__/_alignment.cpython-311.pyc,, +torchaudio/functional/__pycache__/filtering.cpython-311.pyc,, +torchaudio/functional/__pycache__/functional.cpython-311.pyc,, +torchaudio/functional/_alignment.py,sha256=wmDeohWvuoYORYDeIRxnYUhUqv1uCUkaCZYLEK_ryUg,4695 +torchaudio/functional/filtering.py,sha256=5SAPz-0sioU5EYX4VqOnbPAys8jtPSpFnpzbVnTlZyM,61445 +torchaudio/functional/functional.py,sha256=c8qr3mmPXLi40N4NCLcpHvQeUIuoNtbszksjtruC15g,96006 +torchaudio/io/__init__.py,sha256=8nd6s_xuBh5iVzIvQ-qNlforukZzuCx36DyvCmHK748,297 +torchaudio/io/__pycache__/__init__.cpython-311.pyc,, +torchaudio/io/__pycache__/_effector.cpython-311.pyc,, +torchaudio/io/__pycache__/_playback.cpython-311.pyc,, +torchaudio/io/_effector.py,sha256=APDrIU2biwFsSVmhrXjelmc4ndcmb0JL-H189Zp689g,11870 +torchaudio/io/_playback.py,sha256=70IxGrGPkI1h4rz8_04SFCGsbbGZkTiUdRhbPOMLLgQ,2321 +torchaudio/kaldi_io.py,sha256=TwS2YgSLlJwOXjNNsHBuXyxhKeKKpptVHLBV7QYZCas,5073 +torchaudio/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +torchaudio/lib/__pycache__/__init__.cpython-311.pyc,, +torchaudio/lib/_torchaudio.so,sha256=wgoyj8EQgFemJj0FVUaIW8aYOeFGrBoQUjZjARhoxZk,133816 +torchaudio/lib/_torchaudio_sox.so,sha256=KRRss-mGGIMJ7vaq-Ftr603yS9iCUHxy79wvMb6kOdM,265672 +torchaudio/lib/libctc_prefix_decoder.so,sha256=ZjrVrO90jUenBkuCXVEgfKH-eMTqigvG_gZuQ2GBiW4,4943448 +torchaudio/lib/libtorchaudio.so,sha256=0QGxucHiD2B7LonCMjYC8Ja8Tdlrjt4eP2DmZILU3B8,2452168 +torchaudio/lib/libtorchaudio_sox.so,sha256=rrpDUnlJ6VS4Vjo0x3O7J4p-_3OGz56CDyBIFn3Wlb0,151800 +torchaudio/lib/pybind11_prefixctc.so,sha256=5_OKYH8FGY7Ovf9RWQV55sWS-IxSs_Jkmj-eow0sgOM,213888 +torchaudio/models/__init__.py,sha256=BNMNGuwpJAFRsdtwHYQ6slGClkrUTu31_7mXh7FjeV4,1995 +torchaudio/models/__pycache__/__init__.cpython-311.pyc,, +torchaudio/models/__pycache__/_hdemucs.cpython-311.pyc,, +torchaudio/models/__pycache__/conformer.cpython-311.pyc,, +torchaudio/models/__pycache__/conv_tasnet.cpython-311.pyc,, +torchaudio/models/__pycache__/deepspeech.cpython-311.pyc,, +torchaudio/models/__pycache__/emformer.cpython-311.pyc,, +torchaudio/models/__pycache__/rnnt.cpython-311.pyc,, +torchaudio/models/__pycache__/rnnt_decoder.cpython-311.pyc,, +torchaudio/models/__pycache__/tacotron2.cpython-311.pyc,, +torchaudio/models/__pycache__/wav2letter.cpython-311.pyc,, +torchaudio/models/__pycache__/wavernn.cpython-311.pyc,, +torchaudio/models/_hdemucs.py,sha256=VPnQ73lA9lfAxRjZ85NCGJYP36mPNwTjS-TU4qelu_k,38242 +torchaudio/models/conformer.py,sha256=5IceU-jcZKofkHTTqRKoytubQ75MzZPrPlfkLsIlxeA,10068 +torchaudio/models/conv_tasnet.py,sha256=v-DI_Ej9FCBBbSH-Spkh3tzq8rkBhbQNA-Wp52Uf32E,12540 +torchaudio/models/decoder/__init__.py,sha256=4IS_DyQageh2_uY3YE1aBCYEE3HArCFd8ZUfbgww-Tc,1206 +torchaudio/models/decoder/__pycache__/__init__.cpython-311.pyc,, +torchaudio/models/decoder/__pycache__/_ctc_decoder.cpython-311.pyc,, +torchaudio/models/decoder/__pycache__/_cuda_ctc_decoder.cpython-311.pyc,, +torchaudio/models/decoder/_ctc_decoder.py,sha256=K3gSsG9htU08fe7tKSuIJPDIs7ruY50pJ3eNdNhXSVY,20082 +torchaudio/models/decoder/_cuda_ctc_decoder.py,sha256=rtpN1Z_Xni1LlHgHx6jJ1Jap4TnQ0rRRMvwGWa-xnvA,7186 +torchaudio/models/deepspeech.py,sha256=kQW3B6YcjYuq7xRzWjRJFGr7ZNraY9gMYDTxII7Cgtg,2746 +torchaudio/models/emformer.py,sha256=ncDeEcYegUmIKQoDBoufUhVWj4dYpZAXxLX0qmEqt1A,37766 +torchaudio/models/rnnt.py,sha256=jz66nwDd1qGT6KQR1lbA_urPktygewhm0FH66T7P3Ek,35541 +torchaudio/models/rnnt_decoder.py,sha256=IwlDsuw1SA-uCRrXGMBqm05auGFSha2bZ-8BOImnK0c,12839 +torchaudio/models/squim/__init__.py,sha256=b98nAaL28Q4w3lrqd_6wUd0An-xNhhJn4Tj8oZlzQnc,346 +torchaudio/models/squim/__pycache__/__init__.cpython-311.pyc,, +torchaudio/models/squim/__pycache__/objective.cpython-311.pyc,, +torchaudio/models/squim/__pycache__/subjective.cpython-311.pyc,, +torchaudio/models/squim/objective.py,sha256=YPkEWdDMyeP7GcR0OzUPHr2wKhIKFMjy4peYsABmZQk,12289 +torchaudio/models/squim/subjective.py,sha256=N00kILSPm0akWyNsrNYKmHgZmooo8gbyUm5IVLf7bx8,5797 +torchaudio/models/tacotron2.py,sha256=FimYhGSI8FKwWb87CLk4h3yKWatCU2HvFmU1t5WUn4E,45914 +torchaudio/models/wav2letter.py,sha256=KNcq4p0qZG2Bwfdakv7YwLCvi_yGT-qB4fJwGMuFQhg,3278 +torchaudio/models/wav2vec2/__init__.py,sha256=WlafukV6GwuSNh0CZifrYUt4V5l59kjvGX7AZNonjfk,927 +torchaudio/models/wav2vec2/__pycache__/__init__.cpython-311.pyc,, +torchaudio/models/wav2vec2/__pycache__/components.cpython-311.pyc,, +torchaudio/models/wav2vec2/__pycache__/model.cpython-311.pyc,, +torchaudio/models/wav2vec2/__pycache__/wavlm_attention.cpython-311.pyc,, +torchaudio/models/wav2vec2/components.py,sha256=DRmW-GHYf-JReCg_0l1ovNWJBnAavePO3S2vPY-1ze4,47077 +torchaudio/models/wav2vec2/model.py,sha256=Z2VN6KbDOOdq5JtP7lxPQebwYqsxKms1Eu4IjDJtZaQ,60092 +torchaudio/models/wav2vec2/utils/__init__.py,sha256=qmMbz4HAN5kEEyl4cSGm_JQZI47beyh4witydPC_qns,181 +torchaudio/models/wav2vec2/utils/__pycache__/__init__.cpython-311.pyc,, +torchaudio/models/wav2vec2/utils/__pycache__/import_fairseq.cpython-311.pyc,, +torchaudio/models/wav2vec2/utils/__pycache__/import_huggingface.cpython-311.pyc,, +torchaudio/models/wav2vec2/utils/import_fairseq.py,sha256=oCwG6qpG0bCXue2V56fjDcC8cA2rgy4b3O_nu_FI9ZY,9198 +torchaudio/models/wav2vec2/utils/import_huggingface.py,sha256=1nVCipp-lOUAyl_-P103DWLUeTOZi9X_ffX93bOXxEk,5946 +torchaudio/models/wav2vec2/wavlm_attention.py,sha256=1DU_pkoLCeHQwSF4lJ06cez0PsMVoXNxiYKP0Yv0qFQ,10844 +torchaudio/models/wavernn.py,sha256=5xUyao5g69jRXX4ReNi4mP_aTSIonJPP6XcPrqKybEk,15446 +torchaudio/pipelines/__init__.py,sha256=Xy8NmInKwTcNBHwLTTjHjrfczRLuQq8a67ENt1OTVXM,2745 +torchaudio/pipelines/__pycache__/__init__.cpython-311.pyc,, +torchaudio/pipelines/__pycache__/_source_separation_pipeline.cpython-311.pyc,, +torchaudio/pipelines/__pycache__/_squim_pipeline.cpython-311.pyc,, +torchaudio/pipelines/__pycache__/rnnt_pipeline.cpython-311.pyc,, +torchaudio/pipelines/_source_separation_pipeline.py,sha256=WxngB1d13H5IVqs4RPqSL53ZGYsJ3tnfCpxgc5FNSOM,4224 +torchaudio/pipelines/_squim_pipeline.py,sha256=eCimTeoqNX8LilIQNGmb8UaRtnLIXa4LNShXFjodcZM,6280 +torchaudio/pipelines/_tts/__init__.py,sha256=PP7l8XzVURqelwuMJFgfOCv4fvzZunDiy90ZQlRkv7g,426 +torchaudio/pipelines/_tts/__pycache__/__init__.cpython-311.pyc,, +torchaudio/pipelines/_tts/__pycache__/impl.cpython-311.pyc,, +torchaudio/pipelines/_tts/__pycache__/interface.cpython-311.pyc,, +torchaudio/pipelines/_tts/__pycache__/utils.cpython-311.pyc,, +torchaudio/pipelines/_tts/impl.py,sha256=Tig4_5sITJADwxN5eZGek7Ath_-e3sV8CTM5t6UpeUU,15374 +torchaudio/pipelines/_tts/interface.py,sha256=yUaS0UK3PTRruYXRWFil7lAhr-1iYiyBaDBLmEnJPUQ,10224 +torchaudio/pipelines/_tts/utils.py,sha256=0rLyoFWS78n5jn9AC99pgIwAjaXSw-MVbj_pjSaOHiM,4616 +torchaudio/pipelines/_wav2vec2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +torchaudio/pipelines/_wav2vec2/__pycache__/__init__.cpython-311.pyc,, +torchaudio/pipelines/_wav2vec2/__pycache__/aligner.cpython-311.pyc,, +torchaudio/pipelines/_wav2vec2/__pycache__/impl.cpython-311.pyc,, +torchaudio/pipelines/_wav2vec2/__pycache__/utils.cpython-311.pyc,, +torchaudio/pipelines/_wav2vec2/aligner.py,sha256=pIWRgQ-kdYUxtL8bdc0qk9wBjwRrHY1uSWL3L4e2vxs,2709 +torchaudio/pipelines/_wav2vec2/impl.py,sha256=zdXFjytJO5MvnB-3aygzUUFKxCTkQGU_OX_rhUh9c0k,65561 +torchaudio/pipelines/_wav2vec2/utils.py,sha256=Q8_fWOR2JDnHu0TTRmHzRjI3BOJa0hGIAl0cjtALgsQ,6971 +torchaudio/pipelines/rnnt_pipeline.py,sha256=Qy37z7v6d1jLOHd67zbRu21dgL6Fml1rTd7j4Jl1NsM,13749 +torchaudio/prototype/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +torchaudio/prototype/__pycache__/__init__.cpython-311.pyc,, +torchaudio/prototype/datasets/__init__.py,sha256=GSMcp2CykcBc-krhlHTrPm5DCvDFwnA7_6GFNCGwsaQ,47 +torchaudio/prototype/datasets/__pycache__/__init__.cpython-311.pyc,, +torchaudio/prototype/datasets/__pycache__/musan.cpython-311.pyc,, +torchaudio/prototype/datasets/musan.py,sha256=g68PIJCtJM_mXK8vngJ4PRzMqvp-YShPLN9qTMgeiKw,2096 +torchaudio/prototype/functional/__init__.py,sha256=GlbhnDHcNyUWdRd3R-ATzRkG2FXsbqjL56OptyTXpec,562 +torchaudio/prototype/functional/__pycache__/__init__.cpython-311.pyc,, +torchaudio/prototype/functional/__pycache__/_dsp.cpython-311.pyc,, +torchaudio/prototype/functional/__pycache__/_rir.cpython-311.pyc,, +torchaudio/prototype/functional/__pycache__/functional.cpython-311.pyc,, +torchaudio/prototype/functional/_dsp.py,sha256=H4IZgQYjrmV6ITb7iex3F4qwBSFDyPbdrb0e4ZXbkMY,16638 +torchaudio/prototype/functional/_rir.py,sha256=k-svDQK56U1WNpj4dNUxWArBiVM7sZ_BZ98oOop4NNg,17255 +torchaudio/prototype/functional/functional.py,sha256=xd8ZQe69Utl7HzC-VDyhniS0K-dment-Z7FrEwTrfYk,6464 +torchaudio/prototype/models/__init__.py,sha256=zrav5cgVlM51xvocGlL10GoR2r5UuQrenPDlYRUzv40,1254 +torchaudio/prototype/models/__pycache__/__init__.cpython-311.pyc,, +torchaudio/prototype/models/__pycache__/_conformer_wav2vec2.cpython-311.pyc,, +torchaudio/prototype/models/__pycache__/_emformer_hubert.cpython-311.pyc,, +torchaudio/prototype/models/__pycache__/conv_emformer.cpython-311.pyc,, +torchaudio/prototype/models/__pycache__/hifi_gan.cpython-311.pyc,, +torchaudio/prototype/models/__pycache__/rnnt.cpython-311.pyc,, +torchaudio/prototype/models/__pycache__/rnnt_decoder.cpython-311.pyc,, +torchaudio/prototype/models/_conformer_wav2vec2.py,sha256=J7ZJ0dPIFLj9RyPsnuSQC9Y5OVJ9xL6F4JS44zua9zA,29522 +torchaudio/prototype/models/_emformer_hubert.py,sha256=D1WlL1S5xNrN5zOWYnzGyRUtPWnFZOkJprhbkYln0fM,13498 +torchaudio/prototype/models/conv_emformer.py,sha256=tdUz8WwhNlmGXpmki4voZg5nrg749xi23rmfrq2XRCk,23076 +torchaudio/prototype/models/hifi_gan.py,sha256=-ZMA722hoYabIbJl3OGqlxyhhqAHEL7UsTEkOyy8w5I,12480 +torchaudio/prototype/models/rnnt.py,sha256=MTsXxGGv8xOIlH_zhOeSUdedI29CHBIsJ0Pcr8D6yK0,30859 +torchaudio/prototype/models/rnnt_decoder.py,sha256=lIacC7qCjMxjAuBHpgrPXlNI3eERo11fYgaEwPDT7ms,15735 +torchaudio/prototype/pipelines/__init__.py,sha256=yo19xKvIW3XDdDo19thGSMkPRuR8xTwSe0qMWEPS9bE,382 +torchaudio/prototype/pipelines/__pycache__/__init__.cpython-311.pyc,, +torchaudio/prototype/pipelines/__pycache__/hifigan_pipeline.cpython-311.pyc,, +torchaudio/prototype/pipelines/__pycache__/rnnt_pipeline.cpython-311.pyc,, +torchaudio/prototype/pipelines/_vggish/__init__.py,sha256=yi9HO_14_YWFOEvQOhTXb9eqF3JGJ9FtM5-J-a3nEnA,89 +torchaudio/prototype/pipelines/_vggish/__pycache__/__init__.cpython-311.pyc,, +torchaudio/prototype/pipelines/_vggish/__pycache__/_vggish_impl.cpython-311.pyc,, +torchaudio/prototype/pipelines/_vggish/__pycache__/_vggish_pipeline.cpython-311.pyc,, +torchaudio/prototype/pipelines/_vggish/_vggish_impl.py,sha256=EvpbNx4oHk7Zr7LQo-c191XRDLuklj3dZLi2Lgrpe_0,8497 +torchaudio/prototype/pipelines/_vggish/_vggish_pipeline.py,sha256=M47A-KFargc9PxZUeu0zqaZsIapAV4XhJqj_P2IHs9g,2713 +torchaudio/prototype/pipelines/hifigan_pipeline.py,sha256=mEmTdVBn50Zyyy0hcjp-fGv9oIbvJFBYBjShXgjFvhU,9654 +torchaudio/prototype/pipelines/rnnt_pipeline.py,sha256=3wwlCg3refzmsAA69XulFRy4GIixA2EH5ZqFbNFKXhk,2184 +torchaudio/prototype/transforms/__init__.py,sha256=1PcKxc8AWlSRkmchT6IDYDseO7itWY7ueZrlcAUZlkY,225 +torchaudio/prototype/transforms/__pycache__/__init__.cpython-311.pyc,, +torchaudio/prototype/transforms/__pycache__/_transforms.cpython-311.pyc,, +torchaudio/prototype/transforms/_transforms.py,sha256=K2MjxUrTxlk0nO0NcJA07bDCoFolsQh-TabE84eKC6k,19144 +torchaudio/sox_effects/__init__.py,sha256=gCxdiwHK3ldlGCeYc9VatJW5HyzjWIgw_Sz_krp_rOw,262 +torchaudio/sox_effects/__pycache__/__init__.cpython-311.pyc,, +torchaudio/sox_effects/__pycache__/sox_effects.cpython-311.pyc,, +torchaudio/sox_effects/sox_effects.py,sha256=7cHpPFRJ_pZuohHMnX9JIhiVmIJGYntSmgT6QH5GNMA,10981 +torchaudio/transforms/__init__.py,sha256=Tp1o4haiJAV3MRJenmvGXFbmt-RE4qM_pd6U3Ghohqw,1270 +torchaudio/transforms/__pycache__/__init__.cpython-311.pyc,, +torchaudio/transforms/__pycache__/_multi_channel.cpython-311.pyc,, +torchaudio/transforms/__pycache__/_transforms.cpython-311.pyc,, +torchaudio/transforms/_multi_channel.py,sha256=GZ2rrwFt2KtSG7At7kS9Bqh1KmYYw0HwcUnEjc-AWr8,22221 +torchaudio/transforms/_transforms.py,sha256=QHrEsxxxm1bPd5dltPeTcNOsMBu0Ecxa2oe6GIX-nvk,86872 +torchaudio/utils/__init__.py,sha256=NCtfdIUxDi1u0zaamscSbiWzbxn2TOI-MHHWOKU0RnQ,174 +torchaudio/utils/__pycache__/__init__.cpython-311.pyc,, +torchaudio/utils/__pycache__/download.cpython-311.pyc,, +torchaudio/utils/__pycache__/ffmpeg_utils.cpython-311.pyc,, +torchaudio/utils/__pycache__/sox_utils.cpython-311.pyc,, +torchaudio/utils/download.py,sha256=2IFKD1rsWBFE31HTiyUgpE5y7AJh8_AUPdc-btNQuKw,2882 +torchaudio/utils/ffmpeg_utils.py,sha256=3I6YM95eNyOAg2K-ebEy9kjBzEDq3_OBqggXztPIDcU,319 +torchaudio/utils/sox_utils.py,sha256=WGSj_RfELpol8U2XPABGsAjO7yrPHS3_MgHkx7oHYQU,2421 +torchaudio/version.py,sha256=Pz3LXTV6k3pr9rVL0ttY6CGXK1gGt0EnSjg3H0GpIcw,85 +torio/__init__.py,sha256=aX9s0XAHxHhEXE1akQt74BZ0cMUDgBPhaYHQH1lCbXQ,111 +torio/__pycache__/__init__.cpython-311.pyc,, +torio/_extension/__init__.py,sha256=q5jjeOhSrzqn0WTEwrx61Fr13aCjb7IQCDGsBqAdGEU,313 +torio/_extension/__pycache__/__init__.cpython-311.pyc,, +torio/_extension/__pycache__/utils.cpython-311.pyc,, +torio/_extension/utils.py,sha256=ktE0L_z-RF1qkpLVGgdG4DEGHa2Zn6uokOAmwC7Evvo,4904 +torio/io/__init__.py,sha256=xz7REkkyfRhAASzVCAfoNruFtAGIx1I--usPAa2tMww,226 +torio/io/__pycache__/__init__.cpython-311.pyc,, +torio/io/__pycache__/_streaming_media_decoder.cpython-311.pyc,, +torio/io/__pycache__/_streaming_media_encoder.cpython-311.pyc,, +torio/io/_streaming_media_decoder.py,sha256=vSylEWAB_JXOW-0E1n0zDM3Q3Vf1jc1-CNpdUSs13XU,34376 +torio/io/_streaming_media_encoder.py,sha256=rSTYmHdi7RPJ6YPgAyGJhbQvn4mcxLem3nlnr_ophTs,19722 +torio/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +torio/lib/__pycache__/__init__.cpython-311.pyc,, +torio/lib/_torio_ffmpeg4.so,sha256=khUi7zb7tCiZk6V0j0H6hqggK3g9aMDbcl6zqoO9Goc,484112 +torio/lib/_torio_ffmpeg5.so,sha256=l3HdLRjOrSytRUjZ7teUbIAbH0DIRohcJdTJvIurjPg,484112 +torio/lib/_torio_ffmpeg6.so,sha256=tcKNPLt4W4q5OOs54WAHFFmvs55yLTKPRdimvWVdFp0,484112 +torio/lib/libtorio_ffmpeg4.so,sha256=0o89BEcvPDnr1G15l3840e3UW7Qlj8DWcKkZFzNrU_g,652112 +torio/lib/libtorio_ffmpeg5.so,sha256=KOQXaVhf9ZWvGVwHTueh-wEG-dma4OvoymudXk2tUzQ,652112 +torio/lib/libtorio_ffmpeg6.so,sha256=lcS5Nki6kJ9HhRzKlBI90PplEvXAV2160Cm2Hdhd3XI,652112 +torio/utils/__init__.py,sha256=ScHtnontymRDNn9qEIC0neue5mfG82yhB8bwETOb0Z4,56 +torio/utils/__pycache__/__init__.cpython-311.pyc,, +torio/utils/__pycache__/ffmpeg_utils.cpython-311.pyc,, +torio/utils/ffmpeg_utils.py,sha256=JsP2ptjQAE4U7Z_CSauQKH_k72wdu6nrBMfNHl9pIXQ,8026 diff --git a/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/WHEEL b/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..140b013f2f224d0e565ca498d77ee0499b4ab76d --- /dev/null +++ b/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (75.1.0) +Root-Is-Purelib: false +Tag: cp311-cp311-linux_x86_64 + diff --git a/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/top_level.txt b/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..9f448fc64e7113394edf208556101c579616cc18 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/torchaudio-2.5.1.dist-info/top_level.txt @@ -0,0 +1,2 @@ +torchaudio +torio