Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions .github/dependabot.yml

This file was deleted.

5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.12']
python-version: ['3.9', '3.12']
steps:
- uses: actions/checkout@v4
- name: Set up PDM
Expand All @@ -26,6 +26,9 @@ jobs:
- name: Run linter
run: |
pdm run ruff check .
- name: Run type checker
run: |
pdm run mypy xyz2graph
- name: Run tests
run: |
pdm run pytest --cov --cov-report=xml --junitxml=junit.xml -o junit_family=legacy
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Run linter
run: python -m ruff check .
- name: Run type checker
run: python -m mypy .
run: python -m mypy xyz2graph
- name: Build package
run: |
python -m pip install hatchling hatch-vcs
Expand All @@ -44,7 +44,7 @@ jobs:
- name: Create Release
uses: softprops/action-gh-release@v2
with:
body_path: CHANGELOG.md
generate_release_notes: true
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/zotko/xyz2graph/ci.yml)](https://github.com/zotko/xyz2graph/actions)
[![codecov](https://codecov.io/gh/zotko/xyz2graph/branch/develop/graph/badge.svg?token=TXP63VKUIP)](https://codecov.io/gh/zotko/xyz2graph)
[![Dependabot](https://img.shields.io/badge/dependabot-enabled-025E8C?logo=dependabot&logoColor=white)](https://github.com/zotko/xyz2graph/network/updates)

[![PyPI Downloads](https://static.pepy.tech/badge/xyz2graph/month)](https://pepy.tech/projects/xyz2graph)
[![GitHub Stars](https://img.shields.io/github/stars/zotko/xyz2graph)](https://github.com/zotko/xyz2graph/stargazers)
Expand All @@ -33,7 +32,7 @@ pip install xyz2graph

## Requirements

- Python 3.8+
- Python 3.9+
- Dependencies: NumPy, Plotly, NetworkX

## Quick Start
Expand Down
43 changes: 24 additions & 19 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,28 +41,10 @@ Repository = "https://github.com/zotko/xyz2graph"
Documentation = "https://zotko.github.io/xyz2graph"
"Bug Tracker" = "https://github.com/zotko/xyz2graph/issues"

[tool.pdm.dev-dependencies]
docs = [
"mkdocs>=1.6.0",
"mkdocs-material>=9.5.0",
"mkdocstrings-python>=1.12.0",
"mkdocs-include-markdown-plugin>=7.1.5"
]
dev = [
"pytest>=7.0.0,<8.0.0",
"pytest-cov>=4.0.0,<5.0.0",
"ruff>=0.1.0,<1.0.0",
"mypy>=1.0.0,<2.0.0",
"build>=1.0.0,<2.0.0",
"twine>=4.0.0,<5.0.0",
"pre-commit>=4.1.0",
]

[tool.ruff]
target-version = "py38"
target-version = "py39"
line-length = 100
src = ["xyz2graph"]
include = ["test/test_*.py"]

[tool.ruff.lint.per-file-ignores]
"{__init__.py,tests/*,helpers.py,logging.py}" = ["D"]
Expand All @@ -79,16 +61,39 @@ lines-after-imports = 2
convention = "google"

[tool.mypy]
files = ["xyz2graph"]
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
check_untyped_defs = true

[[tool.mypy.overrides]]
module = ["plotly.*"]
ignore_missing_imports = true

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
addopts = "--cov=xyz2graph --cov-report=term-missing"

[project.scripts]
xyz2graph = "xyz2graph.cli:main"

[dependency-groups]
dev = [
"pytest>=7.0.0,<8.0.0",
"pytest-cov>=4.0.0,<5.0.0",
"ruff>=0.1.0,<1.0.0",
"mypy>=1.0.0,<2.0.0",
"types-networkx>=3.4.2.20250509",
"build>=1.0.0,<2.0.0",
"twine>=4.0.0,<5.0.0",
"pre-commit>=4.1.0",
]
docs = [
"mkdocs>=1.6.0",
"mkdocs-material>=9.5.0",
"mkdocstrings-python>=1.12.0",
"mkdocs-include-markdown-plugin>=7.1.5",
]
3 changes: 1 addition & 2 deletions tests/test_molgraph.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from pathlib import Path
from typing import List

import numpy as np
import pytest
Expand Down Expand Up @@ -181,7 +180,7 @@ def test_property_consistency() -> None:
(["Fe", "C", "O", "O", "O", "O", "O"], "CFeO5"), # Complex case
],
)
def test_formula(elements: List[str], expected_formula: str) -> None:
def test_formula(elements: list[str], expected_formula: str) -> None:
"""Test molecular formula generation."""
mol = MolGraph()
for i, el in enumerate(elements):
Expand Down
6 changes: 3 additions & 3 deletions xyz2graph/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import sys
import tempfile
from pathlib import Path
from typing import List, Tuple, Union
from typing import Union

import plotly.offline as offline
from plotly.io import write_html
Expand All @@ -15,7 +15,7 @@
from .logging import logger


def parse_remove_arg(remove_str: str) -> Tuple[List[int], List[str]]:
def parse_remove_arg(remove_str: str) -> tuple[list[int], list[str]]:
"""Parse remove argument string into indices and elements.

Args:
Expand Down Expand Up @@ -48,7 +48,7 @@ def parse_remove_arg(remove_str: str) -> Tuple[List[int], List[str]]:
return indices, elements


def format_remove_message(indices: List[int], elements: List[str]) -> str:
def format_remove_message(indices: list[int], elements: list[str]) -> str:
"""Format the message showing which atoms will be removed.

Args:
Expand Down
7 changes: 2 additions & 5 deletions xyz2graph/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
CPK colors.
"""

from typing import Dict


_DEFAULT_RADII: Dict[str, float] = {
_DEFAULT_RADII: dict[str, float] = {
"Ac": 1.88,
"Ag": 1.59,
"Al": 1.35,
Expand Down Expand Up @@ -100,7 +97,7 @@
"Zn": 1.45,
"Zr": 1.56,
}
_DEFAULT_CPK_COLORS: Dict[str, str] = {
_DEFAULT_CPK_COLORS: dict[str, str] = {
"Ar": "cyan",
"B": "salmon",
"Ba": "darkgreen",
Expand Down
45 changes: 21 additions & 24 deletions xyz2graph/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@
from itertools import compress
from pathlib import Path
from typing import (
Dict,
FrozenSet,
List,
Optional,
Set,
Union,
)

Expand Down Expand Up @@ -64,17 +60,17 @@ class MolGraph:
adj_matrix (NDArray[np.int_]): Square matrix representing molecular connectivity.
"""

atoms: List[Atom] = field(default_factory=list)
bonds: List[Bond] = field(default_factory=list)
atoms: list[Atom] = field(default_factory=list)
bonds: list[Bond] = field(default_factory=list)
comment: str = field(default="")

# Customizable parameters with defaults
default_radii: Dict[str, float] = field(default_factory=lambda: dict(_DEFAULT_RADII))
cpk_colors: Dict[str, str] = field(default_factory=lambda: dict(_DEFAULT_CPK_COLORS))
default_radii: dict[str, float] = field(default_factory=lambda: dict(_DEFAULT_RADII))
cpk_colors: dict[str, str] = field(default_factory=lambda: dict(_DEFAULT_CPK_COLORS))
cpk_color_rest: str = field(default="pink")

@property
def elements(self) -> List[str]:
def elements(self) -> list[str]:
"""Get list of elements in the molecule.

Returns:
Expand All @@ -83,7 +79,7 @@ def elements(self) -> List[str]:
return [atom.element for atom in self.atoms]

@property
def indices(self) -> List[int]:
def indices(self) -> list[int]:
"""Get list of atom indices.

Returns:
Expand All @@ -92,7 +88,7 @@ def indices(self) -> List[int]:
return [atom.index for atom in self.atoms]

@property
def x(self) -> List[float]:
def x(self) -> list[float]:
"""Get x coordinates of all atoms.

Returns:
Expand All @@ -101,7 +97,7 @@ def x(self) -> List[float]:
return [atom.x for atom in self.atoms]

@property
def y(self) -> List[float]:
def y(self) -> list[float]:
"""Get y coordinates of all atoms.

Returns:
Expand All @@ -110,7 +106,7 @@ def y(self) -> List[float]:
return [atom.y for atom in self.atoms]

@property
def z(self) -> List[float]:
def z(self) -> list[float]:
"""Get z coordinates of all atoms.

Returns:
Expand All @@ -128,7 +124,7 @@ def xyz(self) -> NDArray[np.float64]:
return np.array([[atom.x, atom.y, atom.z] for atom in self.atoms])

@property
def atomic_radii(self) -> List[float]:
def atomic_radii(self) -> list[float]:
"""Get atomic radii for all atoms.

Returns:
Expand All @@ -137,7 +133,7 @@ def atomic_radii(self) -> List[float]:
return [atom.radius for atom in self.atoms]

@property
def bond_lengths(self) -> Dict[FrozenSet[int], float]:
def bond_lengths(self) -> dict[frozenset[int], float]:
"""Get dictionary of bond lengths.

Returns:
Expand All @@ -146,13 +142,13 @@ def bond_lengths(self) -> Dict[FrozenSet[int], float]:
return {bond.indices: bond.length for bond in self.bonds}

@property
def adj_list(self) -> Dict[int, Set[int]]:
def adj_list(self) -> dict[int, set[int]]:
"""Get adjacency list representation of molecular graph.

Returns:
Dict[int, Set[int]]: Dictionary mapping atom indices to sets of bonded atom indices.
"""
adj: Dict[int, Set[int]] = {}
adj: dict[int, set[int]] = {}
for bond in self.bonds:
i1, i2 = tuple(bond.indices)
adj.setdefault(i1, set()).add(i2)
Expand All @@ -168,7 +164,7 @@ def adj_matrix(self) -> NDArray[np.int_]:
are bonded and 0 otherwise.
"""
n = len(self.atoms)
matrix = np.zeros((n, n), dtype=np.int_)
matrix: NDArray[np.int_] = np.zeros((n, n), dtype=np.int_)
for bond in self.bonds:
i, j = tuple(bond.indices)
matrix[i, j] = matrix[j, i] = 1
Expand Down Expand Up @@ -230,7 +226,7 @@ def from_string(self, xyz_string: str) -> None:
)
except (IndexError, ValueError) as err:
raise ValueError(
f"Invalid format in line {i+3}, expected: element x y z"
f"Invalid format in line {i + 3}, expected: element x y z"
) from err

if len(self.atoms) != n_atoms:
Expand Down Expand Up @@ -326,12 +322,13 @@ def distance_matrix(self) -> NDArray[np.float64]:
"""
try:
distances = self.xyz[:, np.newaxis, :] - self.xyz
return np.sqrt(np.einsum("ijk,ijk->ij", distances, distances))
result: NDArray[np.float64] = np.sqrt(np.einsum("ijk,ijk->ij", distances, distances))
return result
except MemoryError:
# Fall back to loop-based method
n_atoms = len(self.atoms)
logger.info("Using memory-efficient method for distance calculation")
distance_matrix = np.zeros((n_atoms, n_atoms), dtype=np.float64)
distance_matrix: NDArray[np.float64] = np.zeros((n_atoms, n_atoms), dtype=np.float64)
for i in range(n_atoms):
diff = self.xyz[i] - self.xyz
distance_matrix[i] = np.sqrt(np.sum(diff * diff, axis=1))
Expand Down Expand Up @@ -382,8 +379,8 @@ def set_element_radius(self, element: str, radius: float) -> None:

def remove(
self,
indices: Optional[List[int]] = None,
elements: Optional[List[str]] = None,
indices: Optional[list[int]] = None,
elements: Optional[list[str]] = None,
inplace: bool = False,
) -> Optional["MolGraph"]:
"""Remove atoms by indices and/or elements.
Expand Down Expand Up @@ -460,7 +457,7 @@ def to_networkx(self) -> nx.Graph:
"""
logger.debug("Creating NetworkX graph")

G = nx.Graph()
G: nx.Graph = nx.Graph()

# Add nodes with attributes
for atom in self.atoms:
Expand Down
6 changes: 3 additions & 3 deletions xyz2graph/molecule.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""

from dataclasses import dataclass, field
from typing import FrozenSet, cast
from typing import cast

import numpy as np
from numpy.typing import NDArray
Expand Down Expand Up @@ -51,12 +51,12 @@ def __post_init__(self) -> None:
self.length = round(np.sqrt(dx * dx + dy * dy + dz * dz), 5)

@property
def atoms(self) -> FrozenSet[Atom]:
def atoms(self) -> frozenset[Atom]:
"""Get the atoms involved in the bond as an order-independent set."""
return frozenset([self.atom1, self.atom2])

@property
def indices(self) -> FrozenSet[int]:
def indices(self) -> frozenset[int]:
"""Get indices of bonded atoms."""
return frozenset([self.atom1.index, self.atom2.index])

Expand Down
Loading
Loading