diff --git a/.gitignore b/.gitignore index 9eb9e68..f3e87b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea .venv -tests/test-results/ \ No newline at end of file +tests/test-results/ +docs/_build/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..a0d9fb0 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,28 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub +formats: + - pdf + - epub + +# Optionally declare the Python requirements required to build your docs +python: + install: + - method: pip + path: . + - requirements: docs/requirements.txt diff --git a/README.md b/README.md index 803b65d..35339bc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ChemGED -ChemGED is a Python package for enabling the appoximate graph edit distance (GED) computation between -chemicals. Normally, GED is a NP-hard problem, but ChemGED uses heuristics to approximate the GED in +ChemGED is a Python package for enabling the appoximate graph edit distance (GED) computation between +chemicals. Normally, GED is a NP-hard problem, but ChemGED uses heuristics to approximate the GED in a reasonable time. ## Installation @@ -12,8 +12,8 @@ pip install chemged ``` ## Usage -To use ChemGED, you just create an ``ApproximateChemicalGED`` object, and then call the -``compute_ged`` method with two chemical structures. They can be SMILES *or* +To use ChemGED, you just create an ``ApproximateChemicalGED`` object, and then call the +``compute_ged`` method with two chemical structures. They can be SMILES *or* [RDKit](https://www.rdkit.org/docs/index.html) Mol objects. ```python @@ -33,25 +33,36 @@ approx_ged = ged_calc.compute_ged(MolFromSmiles(chemical1), MolFromSmiles(chemic ChemGED also implements ``pdist`` and ``cdist`` functions to compute pairwise distances between sets of chemicals. These will return as Numpy arrays. -> [!NOTE] +> [!NOTE] > ``pdist`` will return the vector-form distance vector, while ``cdist`` will return a > square-form distance matrix. ``scipy.spatial.distance.squareform`` can be used to convert > the vector-form distance vector to a square-form distance matrix. ```python +from chemged import pdist, cdist +# Create a list of chemicals +chemicals = ["CCO", "CCN", "CC", "C"] + +# Compute pairwise distances +distances_vector = pdist(chemicals) +print(distances_vector) # Vector form + +# Compute all-vs-all distances +distances_matrix = cdist(chemicals, chemicals) +print(distances_matrix) # Square form ``` +## Documentation +For more detailed documentation, please visit [ReadTheDocs](https://chemged.readthedocs.io/). ## Implementation - -The approach used here uses bipartite graph matching[[1]](#1), and most of its implementation in python +The approach used here uses bipartite graph matching[[1]](#1), and most of its implementation in python is based off scripts from https://github.com/priba/aproximated_ged/tree/master. ChemGED uses [RDKit](https://www.rdkit.org/docs/index.html) to handle chemicals inside python. - ## References -[1] -Riesen, Kaspar, and Horst Bunke. -"Approximate graph edit distance computation by means of bipartite graph matching." -Image and Vision computing 27.7 (2009): 950-959 \ No newline at end of file +[1] +Riesen, Kaspar, and Horst Bunke. +"Approximate graph edit distance computation by means of bipartite graph matching." +Image and Vision computing 27.7 (2009): 950-959 diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..8402288 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,45 @@ +============= +API Reference +============= + +This page provides detailed documentation for all the classes and functions in the ChemGED package. + +ApproximateChemicalGED +---------------------- + +.. autoclass:: src.chemged.ApproximateChemicalGED + :members: + :undoc-members: + :show-inheritance: + +ChemicalGEDCostMatrix +--------------------- + +.. autoclass:: src.chemged.ChemicalGEDCostMatrix + :members: + :undoc-members: + :show-inheritance: + +UniformElementCostMatrix +------------------------ + +.. autoclass:: src.chemged.UniformElementCostMatrix + :members: + :undoc-members: + :show-inheritance: + +Chemical Utilities +================== + +.. automodule:: src.chemged.chem_utils + :members: + :undoc-members: + :show-inheritance: + +Plotting Utilities +================== + +.. automodule:: src.chemged.plotting + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/chemical_graph_attributes.rst b/docs/chemical_graph_attributes.rst new file mode 100644 index 0000000..e1b41ba --- /dev/null +++ b/docs/chemical_graph_attributes.rst @@ -0,0 +1,26 @@ +.. _chem-graph-attributes: + +========================= +Chemical Graph Attributes +========================= + +ChemGED uses the NetworkX library to represent chemical graphs. Each node in the graph represents an atom, +and each edge represents a bond between atoms. The attributes of these nodes and edges are crucial for +calculating the graph edit distance (GED) accurately. + +By default, the ``mol_to_nx`` function in ChemGED will convert a chemical into a NetworkX graph with the following +atom attributes stored in the node: + +- **atomic_number**: The atomic number of the atom (e.g., 6 for Carbon, 8 for Oxygen). +- **degree**: The degree of the atom in the graph (number of bonds). +- **formal_charge**: The formal charge of the atom. +- **hybridization**: The hybridization state of the atom (e.g., 'sp3', 'sp2'). +- **is_aromatic**: A boolean indicating if the atom is part of an aromatic ring. +- **is_in_ring**: A boolean indicating if the atom is part of a ring structure. +- **hydrogen**: The number of hydrogen atoms attached to the atom. + +Chemical bond attributes are stored in the graph. The edge attributes include: +- **bond_type**: The type of bond (e.g., 'single', 'double', 'triple', 'aromatic'). + +While these are the current attributes stored in the graph, you can extend or modify these attributes as +needed for your specific use case. diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..0c23a00 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,63 @@ +"""Configuration file for the Sphinx documentation builder.""" + +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +import os +import sys + + +sys.path.insert(0, os.path.abspath("..")) +sys.path.insert(0, os.path.abspath("../src")) + +from chemged import __version__ as version + + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +source_suffix = ".rst" +master_doc = "index" + +project = "ChemGED" +copyright = "2025, James Wellnitz" +author = "James Wellnitz" +release = version # Added for ReadTheDocs compatibility + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx.ext.intersphinx", + "sphinx.ext.mathjax", +] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] + +# -- Extension configuration ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#configuration + +autodoc_member_order = "bysource" +autodoc_typehints = "description" + +# -- Intersphinx configuration ----------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "numpy": ("https://numpy.org/doc/stable/", None), + "networkx": ("https://networkx.org/documentation/stable/", None), +} + +issues_uri = "https://github.com/molecularmodelinglab/ChemGED/{issue}" +issues_pr_uri = "https://github.com/molecularmodelinglab/ChemGED/pull/{pr}" diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..5094065 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,213 @@ +======================= +Contributing Guidelines +======================= + +ChemGED was a side project that came about to support separate research on semantically aware +representation learning for chemical graphs. As a result, we only ever implemented a single cost matrix +for the chemicals, and only a single approximate GED algorithm. While it is unlikely that we will +ever add new matrices or algorithms, we are open to contributions that would do so. If interested +make sure to read through the documentation to understand how to +:ref:`construct new cost matrices `. + +Setting Up Development Environment +================================== + +1. **Fork the repository** + + Start by forking the ChemGED repository on GitHub. + +2. **Clone your fork** + + .. code-block:: bash + + git clone https://github.com/YOUR_USERNAME/ChemGED.git + cd ChemGED + +3. **Set up a virtual environment** + + We recommend using Poetry for dependency management: + + .. code-block:: bash + + # Install Poetry if you don't have it + pip install poetry + + # Create a virtual environment and install dependencies + poetry install --with dev,test,docs + + Alternatively, you can use pip: + + .. code-block:: bash + + # Create a virtual environment + python -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + + # Install dependencies + pip install -e ".[dev,test,docs]" + +Development Workflow +==================== + +1. **Create a new branch** + + Always create a new branch for your changes: + + .. code-block:: bash + + git checkout -b feature/your-feature-name + +2. **Make your changes** + + Implement your changes, following the coding standards described below. + +3. **Run tests** + + Make sure all tests pass: + + .. code-block:: bash + + pytest + + To run tests with coverage: + + .. code-block:: bash + + pytest --cov=chemged + +4. **Update documentation** + + If you've added or modified functionality, update the documentation accordingly. + +5. **Commit your changes** + + Follow the commit message guidelines below. + +6. **Push your changes** + + .. code-block:: bash + + git push origin feature/your-feature-name + +7. **Create a pull request** + + Go to the GitHub repository and create a pull request from your branch to the main branch. + +Coding Standards +================ + +We follow standard Python coding conventions: + +1. **PEP 8** + + Follow the PEP 8 style guide for Python code. You can use tools like flake8 or black to help with this. + +2. **Type Hints** + + Use type hints for function and method signatures: + + .. code-block:: python + + def example_function(param1: str, param2: int) -> bool: + # ... + +3. **Docstrings** + + Use NumPy-style docstrings for all functions, classes, and methods: + + .. code-block:: python + + def example_function(param1, param2): + """ + Brief description of the function. + + Parameters + ---------- + param1 : type + Description of param1 + param2 : type + Description of param2 + + Returns + ------- + type + Description of return value + """ + # ... + +4. **Tests** + + Write tests for all new functionality using pytest. + It is best to make a test case for each new function to maintain high coverage. + +ChemGED provides a ``pre-commit`` configuration to help with coding standard checks. +These checks will be enforced prior to merging a pull request, so it is best to check them +before you make a pull results. + +To set up pre-commit hooks, run: + +.. code-block:: bash + + pre-commit install + +This will install the pre-commit hooks defined in the `.pre-commit-config.yaml` file. + + +Commit Message Guidelines +========================= + +We follow the conventional commits specification: + +- **feat**: A new feature +- **fix**: A bug fix +- **docs**: Documentation only changes +- **style**: Changes that do not affect the meaning of the code (white-space, formatting, etc) +- **refactor**: A code change that neither fixes a bug nor adds a feature +- **perf**: A code change that improves performance +- **test**: Adding missing tests or correcting existing tests +- **chore**: Changes to the build process or auxiliary tools + +Example: + +.. code-block:: text + + feat(cost): add new atomic property cost matrix + + This commit adds a new cost matrix implementation that uses atomic properties + to determine costs for GED calculation. + +Pull Request Process +==================== + +1. Ensure all tests pass. +2. Update the documentation if necessary. +3. The PR should be reviewed by at least one maintainer. +4. Once approved, a maintainer will merge the PR. + +Reporting Bugs +============== + +If you find a bug, please report it by creating an issue on GitHub. Include the following information: + +1. A clear description of the bug +2. Steps to reproduce the bug +3. Expected behavior +4. Actual behavior +5. Environment information (Python version, OS, etc.) +6. If possible, a minimal code example that reproduces the bug + +Feature Requests +================ + +If you have an idea for a new feature, please create an issue on GitHub with the following information: + +1. A clear description of the feature +2. The motivation for the feature +3. If possible, a sketch of how the feature might be implemented + + +Questions? +========== + +If you have any questions about contributing, feel free to open an issue on GitHub or reach +out to the maintainers directly. diff --git a/docs/custom_cost_matrix.rst b/docs/custom_cost_matrix.rst new file mode 100644 index 0000000..7873df5 --- /dev/null +++ b/docs/custom_cost_matrix.rst @@ -0,0 +1,170 @@ +.. _custom_cost_matrix: + +==================== +Custom Cost Matrices +==================== + +The cost matrix is a crucial component of the Approximate Graph Edit Distance (GED) calculation used in ChemGED. + +This matrix takes form: + +.. math:: + + \\mathbf{C} = + \\begin{bmatrix} + c_{1,1} & c_{1,2} & \\cdots & c_{1,m} & c_{1,\\epsilon} & \\infty & \\cdots & \\infty \\\\ + c_{2,1} & c_{2,2} & \\cdots & c_{2,m} & \\infty & c_{2,\\epsilon} & \\ddots & \\vdots \\\\ + \\vdots & \\vdots & \\ddots & \\vdots & \\vdots & \\ddots & \\ddots & \\infty \\\\ + c_{n,1} & c_{n,2} & \\cdots & c_{n,m} & \\infty & \\cdots & \\infty & c_{n,\\epsilon}\\\\ + c_{\\epsilon, 1} & \\infty & \\cdots & \\infty & 0 & 0 & \\cdots & 0 \\\\ + \\infty & c_{\\epsilon, 2} & \\ddots & \\vdots & 0 & 0 & \\ddots & \\vdots \\\\ + \\vdots & \\ddots & \\ddots & \\infty & \\vdots & \\ddots & \\ddots & 0 \\\\ + \\infty & \\cdots & \\infty & c_{\\epsilon, m} & 0 & \\cdots & 0 & 0 \\\\ + \\end{bmatrix} + +where :math:`c_{n,m}` is the cost of substituting node :math:`n` in graph :math:`n1` +with node :math:`m` in graph :math:`n2`, :math:`c_{n,\\epsilon}` is the cost of deleting +a node from :math:`n1`, and :math:`c_{\\epsilon,m}` is the cost of inserting node +:math:`m` from :math:`n2` into :math:`n1`. Note that in this cost matrix form, the +cost of substituting a node takes into account the cost of the edge operations as well. +The edge cost is also defined using the same matrix structure, except for the edge +between two nodes. A more detailed explanation of the cost matrix can be found in the +paper by [1]_. + +This is alot to handle, so ChemGED provides a abstract class ``ChemicalGEDCostMatrix`` +that defines the interface for cost matrices definition. Rather than defining how to make +the full cost matrix in one go. Instead you need to define the follow cost functions: + +- Node substitution: Generates a cost matrix for replacing a node in one graph with a node in another graph + (shape: Num Nodes in Graph 1 x Num Nodes in Graph 2) +- Node insertion: Generates a cost vector for inserting a node from from the graph (shape: Num Nodes in Graph) +- Node deletion: Generates a cost vector for deleting a node from from the graph (shape: Num Nodes in Graph) +- Edge substitution: Generates a cost matrix for replacing all the edges in one node with edges in another node + (shape: Num Edges in node 1 x Num Edges in node 2) +- Edge insertion: Generates a cost vector for inserting a edge from from the node (shape: Num edges in Graph) +- Edge deletion: Generates a cost vector for deleting a edge from from the graph (shape: Num edge in Graph) + +With these functions defined, ChemGED will take care of the rest to construct the full cost matrix and run the +approximate GED algorithm. + +By default, ChemGED will use the ``UniformElementCostMatrix`` which defines a uniform cost for all operations. +This is a good starting point, but you allows limited control over atomic properties and bond types. Swapping +a Cl with a Br will have a bigger impact of the chemical than swapping a C with a N, for example. If this is +something you want to take into account, you can create your own cost matrix by subclassing +``ChemicalGEDCostMatrix`` and implementing the required methods taking into account the chemical properties of +the nodes and edges. + +Creating a Custom Cost Matrix +============================= + +To create a custom cost matrix, you need to subclass ``ChemicalGEDCostMatrix`` and implement the required methods: + +.. code-block:: python + + from chemged import ChemicalGEDCostMatrix + import networkx as nx + import numpy as np + + class MyCustomCostMatrix(ChemicalGEDCostMatrix): + def __init__(self, custom_param1, custom_param2): + # Initialize your custom parameters if needed + self.custom_param1 = custom_param1 + self.custom_param2 = custom_param2 + + def get_node_substitution_costs(self, g1: nx.Graph, g2: nx.Graph) -> np.ndarray: + # Implement your custom node substitution cost logic + # Return a matrix of shape (len(g1.nodes), len(g2.nodes)) + # where each element (i, j) is the cost of substituting node i in g1 with node j in g2 + pass + + def get_node_insertion_costs(self, g: nx.Graph) -> np.ndarray: + # Implement your custom node insertion cost logic + # Return a vector of shape (len(g.nodes),) + # where each element i is the cost of inserting node i + pass + + def get_node_deletion_costs(self, g: nx.Graph) -> np.ndarray: + # Implement your custom node deletion cost logic + # Return a vector of shape (len(g.nodes),) + # where each element i is the cost of deleting node i + pass + + def get_edge_substitution_costs(self, n1: nx.classes.coreviews.AtlasView, n2: nx.classes.coreviews.AtlasView) -> np.ndarray: + # Implement your custom edge substitution cost logic + # Return a matrix of shape (len(n1), len(n2)) + # where each element (i, j) is the cost of substituting edge i in n1 with edge j in n2 + pass + + def get_edge_insertion_costs(self, n: nx.classes.coreviews.AtlasView) -> np.ndarray: + # Implement your custom edge insertion cost logic + # Return a vector of shape (len(n),) + # where each element i is the cost of inserting edge i + pass + + def get_edge_deletion_costs(self, n: nx.classes.coreviews.AtlasView) -> np.ndarray: + # Implement your custom edge deletion cost logic + # Return a vector of shape (len(n),) + # where each element i is the cost of deleting edge i + pass + +Example: Custom Cost Matrix to Ignore Halogen Swaps +=================================================== + +Here's an example of how to define the a custom cost matrix that modifies the +UniformElementCostMatrix to ignore swaps between halogens (F, Cl, Br, I): + +.. code-block:: python + + from chemged import UniformElementCostMatrix + import networkx as nx + + class HalogenSwapCostMatrix(UniformElementCostMatrix): + def get_node_substitution_costs(self, g1: nx.Graph, g2: nx.Graph) -> np.ndarray: + v1 = list(nx.get_node_attributes(g1, "atomic_num").values()) + v2 = list(nx.get_node_attributes(g2, "atomic_num").values()) + + v1[np.where(np.isin(v1, [9, 17, 35, 53]))] = -1 # alias for F, Cl, Br, I + v2[np.where(np.isin(v2, [9, 17, 35, 53]))] = -1 # alias for F, Cl, Br, I + + # generate the cost matrix + return ~np.equal.outer(v1, v2) * self.node_sub_cost + +Now the chemicals "CCCCCCl" and "CCCCCBr" will have a GED of 0. +This can be a very helpful tool if trying to build more chemically aware +similarity calculations. + +.. note:: + atoms in the networkx graph have a handful of attributes other than "atomic_num", see the + :ref:`chemical graph attributes ` for more information + on what you can use (or how to add new ones). + +Using Your Custom Cost Matrix +============================= + +Once you've implemented your custom cost matrix, you can use it with the ApproximateChemicalGED class: + +.. code-block:: python + + from chemged import ApproximateChemicalGED + from my_module import HalogenSwapCostMatrix + + # Create an instance of your custom cost matrix + custom_cost_matrix = HalogenSwapCostMatrix() + + # Create an instance of ApproximateChemicalGED with your custom cost matrix + ged_calc = ApproximateChemicalGED(cost_matrix=custom_cost_matrix) + + # Compute the approximate GED + ged = ged_calc.compute_ged("CCCCCCl", "CCCCCBr") + print(f"Approximate GED: {ged}") + >>> Approximate GED: 0.0 + +Tips for Implementing Custom Cost Matrices +========================================== +While the approximate GED algorithm is far faster than a brute for GED calculation, it still can be computationally expensive, +especially if generating the cost matrix is not efficient. Try to limit the number of operations in the cost matrix generation +functions to only what is necessary for you case, and keep it as effiecent as you can. + +References +========== +.. [1] Riesen, Kaspar, and Horst Bunke. "Approximate graph edit distance computation by means of bipartite graph matching." Image and Vision computing 27.7 (2009): 950-959 diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..1b55c12 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,36 @@ +======= +ChemGED +======= + +ChemGED is a Python package for enabling the appoximate graph edit distance (GED) computation between +chemicals. Normally, GED is a NP-hard problem, but ChemGED uses heuristics to approximate the GED in +a reasonable time. + +.. code-block:: python + + from chemged import ApproximateChemicalGED, UniformElementCostMatrix + + ged_calc = ApproximateChemicalGED(cost_matrix=UniformElementCostMatrix()) + + # Compute the approximate GED + ged = ged_calc.compute_ged("CC(C)Cc1ccc(cc1)[C@@H](C)C(=O)O", "CC(=O)Nc1ccc(O)cc1") + print(f"Approximate GED: {ged}") + >>> Approximate GED: 12.0 + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + installation + chemical_graph_attributes + custom_cost_matrix + api + contributing + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..1784cb9 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,20 @@ +============ +Installation +============ + +ChemGED can be installed using pip. + +.. code-block:: bash + + pip install chemged + +From source +----------- + +You can also install ChemGED from source: + +.. code-block:: bash + + git clone https://github.com/molecularmodelinglab/ChemGED.git + cd ChemGED + pip install -e . diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..3c934c0 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,6 @@ +sphinx>=7.1.2 +sphinx_rtd_theme>=1.3.0 +networkx>=3.5 +numpy>=2.0.0 +scipy>=1.15.3 +rdkit>=2024.9.5 diff --git a/poetry.lock b/poetry.lock index 60987e5..61a69f6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -640,14 +640,14 @@ files = [ [[package]] name = "docutils" -version = "0.21.2" +version = "0.18.1" description = "Docutils -- Python Documentation Utilities" optional = false -python-versions = ">=3.9" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" groups = ["docs"] files = [ - {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, - {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, + {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, + {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, ] [[package]] @@ -1507,63 +1507,63 @@ files = [ [[package]] name = "numpy" -version = "2.3.0" +version = "2.3.1" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.11" groups = ["main", "test"] files = [ - {file = "numpy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3c9fdde0fa18afa1099d6257eb82890ea4f3102847e692193b54e00312a9ae9"}, - {file = "numpy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46d16f72c2192da7b83984aa5455baee640e33a9f1e61e656f29adf55e406c2b"}, - {file = "numpy-2.3.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a0be278be9307c4ab06b788f2a077f05e180aea817b3e41cebbd5aaf7bd85ed3"}, - {file = "numpy-2.3.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:99224862d1412d2562248d4710126355d3a8db7672170a39d6909ac47687a8a4"}, - {file = "numpy-2.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2393a914db64b0ead0ab80c962e42d09d5f385802006a6c87835acb1f58adb96"}, - {file = "numpy-2.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7729c8008d55e80784bd113787ce876ca117185c579c0d626f59b87d433ea779"}, - {file = "numpy-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:06d4fb37a8d383b769281714897420c5cc3545c79dc427df57fc9b852ee0bf58"}, - {file = "numpy-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c39ec392b5db5088259c68250e342612db82dc80ce044cf16496cf14cf6bc6f8"}, - {file = "numpy-2.3.0-cp311-cp311-win32.whl", hash = "sha256:ee9d3ee70d62827bc91f3ea5eee33153212c41f639918550ac0475e3588da59f"}, - {file = "numpy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:43c55b6a860b0eb44d42341438b03513cf3879cb3617afb749ad49307e164edd"}, - {file = "numpy-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:2e6a1409eee0cb0316cb64640a49a49ca44deb1a537e6b1121dc7c458a1299a8"}, - {file = "numpy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:389b85335838155a9076e9ad7f8fdba0827496ec2d2dc32ce69ce7898bde03ba"}, - {file = "numpy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9498f60cd6bb8238d8eaf468a3d5bb031d34cd12556af53510f05fcf581c1b7e"}, - {file = "numpy-2.3.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:622a65d40d8eb427d8e722fd410ac3ad4958002f109230bc714fa551044ebae2"}, - {file = "numpy-2.3.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b9446d9d8505aadadb686d51d838f2b6688c9e85636a0c3abaeb55ed54756459"}, - {file = "numpy-2.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:50080245365d75137a2bf46151e975de63146ae6d79f7e6bd5c0e85c9931d06a"}, - {file = "numpy-2.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c24bb4113c66936eeaa0dc1e47c74770453d34f46ee07ae4efd853a2ed1ad10a"}, - {file = "numpy-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4d8d294287fdf685281e671886c6dcdf0291a7c19db3e5cb4178d07ccf6ecc67"}, - {file = "numpy-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6295f81f093b7f5769d1728a6bd8bf7466de2adfa771ede944ce6711382b89dc"}, - {file = "numpy-2.3.0-cp312-cp312-win32.whl", hash = "sha256:e6648078bdd974ef5d15cecc31b0c410e2e24178a6e10bf511e0557eed0f2570"}, - {file = "numpy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:0898c67a58cdaaf29994bc0e2c65230fd4de0ac40afaf1584ed0b02cd74c6fdd"}, - {file = "numpy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:bd8df082b6c4695753ad6193018c05aac465d634834dca47a3ae06d4bb22d9ea"}, - {file = "numpy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5754ab5595bfa2c2387d241296e0381c21f44a4b90a776c3c1d39eede13a746a"}, - {file = "numpy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d11fa02f77752d8099573d64e5fe33de3229b6632036ec08f7080f46b6649959"}, - {file = "numpy-2.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:aba48d17e87688a765ab1cd557882052f238e2f36545dfa8e29e6a91aef77afe"}, - {file = "numpy-2.3.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4dc58865623023b63b10d52f18abaac3729346a7a46a778381e0e3af4b7f3beb"}, - {file = "numpy-2.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:df470d376f54e052c76517393fa443758fefcdd634645bc9c1f84eafc67087f0"}, - {file = "numpy-2.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:87717eb24d4a8a64683b7a4e91ace04e2f5c7c77872f823f02a94feee186168f"}, - {file = "numpy-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fa264d56882b59dcb5ea4d6ab6f31d0c58a57b41aec605848b6eb2ef4a43e8"}, - {file = "numpy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e651756066a0eaf900916497e20e02fe1ae544187cb0fe88de981671ee7f6270"}, - {file = "numpy-2.3.0-cp313-cp313-win32.whl", hash = "sha256:e43c3cce3b6ae5f94696669ff2a6eafd9a6b9332008bafa4117af70f4b88be6f"}, - {file = "numpy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:81ae0bf2564cf475f94be4a27ef7bcf8af0c3e28da46770fc904da9abd5279b5"}, - {file = "numpy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:c8738baa52505fa6e82778580b23f945e3578412554d937093eac9205e845e6e"}, - {file = "numpy-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39b27d8b38942a647f048b675f134dd5a567f95bfff481f9109ec308515c51d8"}, - {file = "numpy-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0eba4a1ea88f9a6f30f56fdafdeb8da3774349eacddab9581a21234b8535d3d3"}, - {file = "numpy-2.3.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0f1f11d0a1da54927436505a5a7670b154eac27f5672afc389661013dfe3d4f"}, - {file = "numpy-2.3.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:690d0a5b60a47e1f9dcec7b77750a4854c0d690e9058b7bef3106e3ae9117808"}, - {file = "numpy-2.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8b51ead2b258284458e570942137155978583e407babc22e3d0ed7af33ce06f8"}, - {file = "numpy-2.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:aaf81c7b82c73bd9b45e79cfb9476cb9c29e937494bfe9092c26aece812818ad"}, - {file = "numpy-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f420033a20b4f6a2a11f585f93c843ac40686a7c3fa514060a97d9de93e5e72b"}, - {file = "numpy-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d344ca32ab482bcf8735d8f95091ad081f97120546f3d250240868430ce52555"}, - {file = "numpy-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:48a2e8eaf76364c32a1feaa60d6925eaf32ed7a040183b807e02674305beef61"}, - {file = "numpy-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ba17f93a94e503551f154de210e4d50c5e3ee20f7e7a1b5f6ce3f22d419b93bb"}, - {file = "numpy-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f14e016d9409680959691c109be98c436c6249eaf7f118b424679793607b5944"}, - {file = "numpy-2.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80b46117c7359de8167cc00a2c7d823bdd505e8c7727ae0871025a86d668283b"}, - {file = "numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:5814a0f43e70c061f47abd5857d120179609ddc32a613138cbb6c4e9e2dbdda5"}, - {file = "numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ef6c1e88fd6b81ac6d215ed71dc8cd027e54d4bf1d2682d362449097156267a2"}, - {file = "numpy-2.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33a5a12a45bb82d9997e2c0b12adae97507ad7c347546190a18ff14c28bbca12"}, - {file = "numpy-2.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:54dfc8681c1906d239e95ab1508d0a533c4a9505e52ee2d71a5472b04437ef97"}, - {file = "numpy-2.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e017a8a251ff4d18d71f139e28bdc7c31edba7a507f72b1414ed902cbe48c74d"}, - {file = "numpy-2.3.0.tar.gz", hash = "sha256:581f87f9e9e9db2cba2141400e160e9dd644ee248788d6f90636eeb8fd9260a6"}, + {file = "numpy-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ea9e48336a402551f52cd8f593343699003d2353daa4b72ce8d34f66b722070"}, + {file = "numpy-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccb7336eaf0e77c1635b232c141846493a588ec9ea777a7c24d7166bb8533ae"}, + {file = "numpy-2.3.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bb3a4a61e1d327e035275d2a993c96fa786e4913aa089843e6a2d9dd205c66a"}, + {file = "numpy-2.3.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e344eb79dab01f1e838ebb67aab09965fb271d6da6b00adda26328ac27d4a66e"}, + {file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:467db865b392168ceb1ef1ffa6f5a86e62468c43e0cfb4ab6da667ede10e58db"}, + {file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:afed2ce4a84f6b0fc6c1ce734ff368cbf5a5e24e8954a338f3bdffa0718adffb"}, + {file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0025048b3c1557a20bc80d06fdeb8cc7fc193721484cca82b2cfa072fec71a93"}, + {file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5ee121b60aa509679b682819c602579e1df14a5b07fe95671c8849aad8f2115"}, + {file = "numpy-2.3.1-cp311-cp311-win32.whl", hash = "sha256:a8b740f5579ae4585831b3cf0e3b0425c667274f82a484866d2adf9570539369"}, + {file = "numpy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4580adadc53311b163444f877e0789f1c8861e2698f6b2a4ca852fda154f3ff"}, + {file = "numpy-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:ec0bdafa906f95adc9a0c6f26a4871fa753f25caaa0e032578a30457bff0af6a"}, + {file = "numpy-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2959d8f268f3d8ee402b04a9ec4bb7604555aeacf78b360dc4ec27f1d508177d"}, + {file = "numpy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:762e0c0c6b56bdedfef9a8e1d4538556438288c4276901ea008ae44091954e29"}, + {file = "numpy-2.3.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:867ef172a0976aaa1f1d1b63cf2090de8b636a7674607d514505fb7276ab08fc"}, + {file = "numpy-2.3.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4e602e1b8682c2b833af89ba641ad4176053aaa50f5cacda1a27004352dde943"}, + {file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8e333040d069eba1652fb08962ec5b76af7f2c7bce1df7e1418c8055cf776f25"}, + {file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e7cbf5a5eafd8d230a3ce356d892512185230e4781a361229bd902ff403bc660"}, + {file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1b8f26d1086835f442286c1d9b64bb3974b0b1e41bb105358fd07d20872952"}, + {file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee8340cb48c9b7a5899d1149eece41ca535513a9698098edbade2a8e7a84da77"}, + {file = "numpy-2.3.1-cp312-cp312-win32.whl", hash = "sha256:e772dda20a6002ef7061713dc1e2585bc1b534e7909b2030b5a46dae8ff077ab"}, + {file = "numpy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfecc7822543abdea6de08758091da655ea2210b8ffa1faf116b940693d3df76"}, + {file = "numpy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:7be91b2239af2658653c5bb6f1b8bccafaf08226a258caf78ce44710a0160d30"}, + {file = "numpy-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25a1992b0a3fdcdaec9f552ef10d8103186f5397ab45e2d25f8ac51b1a6b97e8"}, + {file = "numpy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dea630156d39b02a63c18f508f85010230409db5b2927ba59c8ba4ab3e8272e"}, + {file = "numpy-2.3.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bada6058dd886061f10ea15f230ccf7dfff40572e99fef440a4a857c8728c9c0"}, + {file = "numpy-2.3.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:a894f3816eb17b29e4783e5873f92faf55b710c2519e5c351767c51f79d8526d"}, + {file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18703df6c4a4fee55fd3d6e5a253d01c5d33a295409b03fda0c86b3ca2ff41a1"}, + {file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5902660491bd7a48b2ec16c23ccb9124b8abfd9583c5fdfa123fe6b421e03de1"}, + {file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:36890eb9e9d2081137bd78d29050ba63b8dab95dff7912eadf1185e80074b2a0"}, + {file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a780033466159c2270531e2b8ac063704592a0bc62ec4a1b991c7c40705eb0e8"}, + {file = "numpy-2.3.1-cp313-cp313-win32.whl", hash = "sha256:39bff12c076812595c3a306f22bfe49919c5513aa1e0e70fac756a0be7c2a2b8"}, + {file = "numpy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d5ee6eec45f08ce507a6570e06f2f879b374a552087a4179ea7838edbcbfa42"}, + {file = "numpy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c4d9e0a8368db90f93bd192bfa771ace63137c3488d198ee21dfb8e7771916e"}, + {file = "numpy-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b0b5397374f32ec0649dd98c652a1798192042e715df918c20672c62fb52d4b8"}, + {file = "numpy-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c5bdf2015ccfcee8253fb8be695516ac4457c743473a43290fd36eba6a1777eb"}, + {file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d70f20df7f08b90a2062c1f07737dd340adccf2068d0f1b9b3d56e2038979fee"}, + {file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:2fb86b7e58f9ac50e1e9dd1290154107e47d1eef23a0ae9145ded06ea606f992"}, + {file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:23ab05b2d241f76cb883ce8b9a93a680752fbfcbd51c50eff0b88b979e471d8c"}, + {file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ce2ce9e5de4703a673e705183f64fd5da5bf36e7beddcb63a25ee2286e71ca48"}, + {file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c4913079974eeb5c16ccfd2b1f09354b8fed7e0d6f2cab933104a09a6419b1ee"}, + {file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:010ce9b4f00d5c036053ca684c77441f2f2c934fd23bee058b4d6f196efd8280"}, + {file = "numpy-2.3.1-cp313-cp313t-win32.whl", hash = "sha256:6269b9edfe32912584ec496d91b00b6d34282ca1d07eb10e82dfc780907d6c2e"}, + {file = "numpy-2.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2a809637460e88a113e186e87f228d74ae2852a2e0c44de275263376f17b5bdc"}, + {file = "numpy-2.3.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eccb9a159db9aed60800187bc47a6d3451553f0e1b08b068d8b277ddfbb9b244"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad506d4b09e684394c42c966ec1527f6ebc25da7f4da4b1b056606ffe446b8a3"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ebb8603d45bc86bbd5edb0d63e52c5fd9e7945d3a503b77e486bd88dde67a19b"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:15aa4c392ac396e2ad3d0a2680c0f0dee420f9fed14eef09bdb9450ee6dcb7b7"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c6e0bf9d1a2f50d2b65a7cf56db37c095af17b59f6c132396f7c6d5dd76484df"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eabd7e8740d494ce2b4ea0ff05afa1b7b291e978c0ae075487c51e8bd93c0c68"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e610832418a2bc09d974cc9fecebfa51e9532d6190223bc5ef6a7402ebf3b5cb"}, + {file = "numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b"}, ] [[package]] @@ -2003,14 +2003,14 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["docs"] files = [ - {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, - {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] [package.extras] @@ -2552,27 +2552,27 @@ files = [ [[package]] name = "sphinx" -version = "7.4.7" +version = "7.3.7" description = "Python documentation generator" optional = false python-versions = ">=3.9" groups = ["docs"] files = [ - {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, - {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, + {file = "sphinx-7.3.7-py3-none-any.whl", hash = "sha256:413f75440be4cacf328f580b4274ada4565fb2187d696a84970c23f77b64d8c3"}, + {file = "sphinx-7.3.7.tar.gz", hash = "sha256:a4a7db75ed37531c05002d56ed6948d4c42f473a36f46e1382b0bd76ca9627bc"}, ] [package.dependencies] alabaster = ">=0.7.14,<0.8.0" -babel = ">=2.13" -colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} -docutils = ">=0.20,<0.22" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.18.1,<0.22" imagesize = ">=1.3" -Jinja2 = ">=3.1" -packaging = ">=23.0" -Pygments = ">=2.17" -requests = ">=2.30.0" -snowballstemmer = ">=2.2" +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.14" +requests = ">=2.25.0" +snowballstemmer = ">=2.0" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" @@ -2582,8 +2582,28 @@ sphinxcontrib-serializinghtml = ">=1.1.9" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] -test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] +lint = ["flake8 (>=3.5.0)", "importlib_metadata", "mypy (==1.9.0)", "pytest (>=6.0)", "ruff (==0.3.7)", "sphinx-lint", "tomli", "types-docutils", "types-requests"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=6.0)", "setuptools (>=67.0)"] + +[[package]] +name = "sphinx-rtd-theme" +version = "1.3.0" +description = "Read the Docs theme for Sphinx" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["docs"] +files = [ + {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, + {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, +] + +[package.dependencies] +docutils = "<0.19" +sphinx = ">=1.6,<8" +sphinxcontrib-jquery = ">=4,<5" + +[package.extras] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] [[package]] name = "sphinxcontrib-applehelp" @@ -2636,6 +2656,21 @@ lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +description = "Extension to include jQuery on newer Sphinx releases" +optional = false +python-versions = ">=2.7" +groups = ["docs"] +files = [ + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, +] + +[package.dependencies] +Sphinx = ">=1.8" + [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" @@ -3021,4 +3056,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = ">=3.11,<4.0" -content-hash = "fb23da8c54844851b0f7b7af45edcf219dcf07c5286e26ea685379696387ac42" +content-hash = "4a27def5957fabf64e9dc6885e04324be378588256e5247aae0874f1de895f98" diff --git a/pyproject.toml b/pyproject.toml index 610a15a..00e1b1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,7 @@ optional = true [tool.poetry.group.docs.dependencies] sphinx = "^7.1.2" +sphinx_rtd_theme = "^1.3.0" [[tool.poetry.source]] name = "PyPI" diff --git a/src/chemged/__init__.py b/src/chemged/__init__.py index bc3f043..f9966b5 100644 --- a/src/chemged/__init__.py +++ b/src/chemged/__init__.py @@ -3,6 +3,7 @@ __version__ = "0.1.0a0" __author__ = "James Wellnitz" +from .chem_utils import mol_to_nx from .chemged import ApproximateChemicalGED from .cost import ChemicalGEDCostMatrix, UniformElementCostMatrix @@ -11,4 +12,5 @@ "ChemicalGEDCostMatrix", "UniformElementCostMatrix", "ApproximateChemicalGED", + "mol_to_nx", ] diff --git a/src/chemged/chem_utils.py b/src/chemged/chem_utils.py index 311ddda..cdd8049 100644 --- a/src/chemged/chem_utils.py +++ b/src/chemged/chem_utils.py @@ -1,7 +1,8 @@ +"""Utilities for converting between RDKit Mol objects and NetworkX Graphs.""" + from typing import Literal, Union, overload import networkx as nx - from rdkit import Chem from rdkit.rdBase import BlockLogs @@ -98,14 +99,13 @@ def mol_to_nx(mol: Chem.Mol) -> nx.Graph: is_aromatic=atom.GetIsAromatic(), is_in_ring=atom.IsInRing(), hydrogen=atom.GetTotalNumHs(), - degree=atom.GetDegree() + degree=atom.GetDegree(), + formal_charge=atom.GetFormalCharge(), ) if atom.GetIdx() > max_idx: max_idx = atom.GetIdx() for bond in mol.GetBonds(): - graph.add_edge(bond.GetBeginAtomIdx(), - bond.GetEndAtomIdx(), - bond_type=bond.GetBondType()) + graph.add_edge(bond.GetBeginAtomIdx(), bond.GetEndAtomIdx(), bond_type=bond.GetBondType()) return graph @@ -128,9 +128,9 @@ def nx_to_mol(graph: nx.Graph) -> Chem.Mol: Chem.Mol """ mol = Chem.RWMol() - atomic_nums = nx.get_node_attributes(graph, 'atomic_num') - node_is_aromatics = nx.get_node_attributes(graph, 'is_aromatic') - node_hybridization = nx.get_node_attributes(graph, 'hybridization') + atomic_nums = nx.get_node_attributes(graph, "atomic_num") + node_is_aromatics = nx.get_node_attributes(graph, "is_aromatic") + node_hybridization = nx.get_node_attributes(graph, "hybridization") node_to_idx = {} for node in graph.nodes(): a = Chem.Atom(atomic_nums[node]) @@ -139,7 +139,7 @@ def nx_to_mol(graph: nx.Graph) -> Chem.Mol: idx = mol.AddAtom(a) node_to_idx[node] = idx - bond_types = nx.get_edge_attributes(graph, 'bond_type') + bond_types = nx.get_edge_attributes(graph, "bond_type") for edge in graph.edges(): first, second = edge idx_first = node_to_idx[first] diff --git a/src/chemged/chemged.py b/src/chemged/chemged.py index ae1d090..1c892ac 100644 --- a/src/chemged/chemged.py +++ b/src/chemged/chemged.py @@ -50,7 +50,7 @@ class ApproximateChemicalGED: \\end{bmatrix} where :math:`c_{n,m}` is the cost of substituting node :math:`n` in graph :math:`n1` - with node :math:`m` in graph :math:`n2, :math:`c_{n,\\epsilon}` is the cost of deleting + with node :math:`m` in graph :math:`n2`, :math:`c_{n,\\epsilon}` is the cost of deleting a node from :math:`n1`, and :math:`c_{\\epsilon,m}` is the cost of inserting node :math:`m` from :math:`n2` into :math:`n1`.