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
16 changes: 7 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: debug-statements
- repo: https://github.com/timothycrosley/isort
rev: 5.13.2
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.0
hooks:
- id: isort
- repo: https://github.com/pycqa/flake8
rev: 7.1.1
hooks:
- id: flake8
- id: ruff-check
args: ["--fix"]
- id: ruff-format
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.7.0
rev: v2.15.0
hooks:
- id: pyproject-fmt
13 changes: 2 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,7 @@ Thank you for your interest in contributing to dimsim! This document provides gu
pytest
```

4. Check code style (or use pre-commit hooks):
```bash
black dimsim
isort dimsim
flake8 dimsim
```

Or manually run all pre-commit hooks:
4. Check code style (easiest to use pre-commit hooks):
```bash
pre-commit run --all-files
```
Expand All @@ -73,9 +66,7 @@ Thank you for your interest in contributing to dimsim! This document provides gu
## Code Style

This project uses:
- **black** for code formatting (line length: 88)
- **isort** for import sorting
- **flake8** for linting
- **ruff** for code formatting and linting
- Type hints are encouraged but not required

## Testing
Expand Down
12 changes: 4 additions & 8 deletions dimsim/_tests/coordinates/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from dimsim.coordinates.box import BoxCoordinates, MoleculeSpecies, Substance

rng = np.random.default_rng(42)
# ============================================================================
# Fixtures
# ============================================================================
Expand Down Expand Up @@ -43,9 +44,7 @@ def sample_molecule_species():
"""
return [
MoleculeSpecies(mapped_smiles="[H:1][O:2][H:3]", count=100),
MoleculeSpecies(
mapped_smiles="[C:1]([H:2])([H:3])([H:4])[C:5]([H:6])([H:7])[H:8]", count=50
),
MoleculeSpecies(mapped_smiles="[C:1]([H:2])([H:3])([H:4])[C:5]([H:6])([H:7])[H:8]", count=50),
]


Expand Down Expand Up @@ -77,8 +76,7 @@ def sample_coordinates():
np.ndarray
Sample coordinates array (n_atoms, 3)
"""
np.random.seed(42)
return np.random.randn(700, 3) * 10.0 # 100*3 + 50*8 atoms
return rng.normal(size=(700, 3)) * 10.0 # 100*3 + 50*8 atoms


@pytest.fixture
Expand All @@ -95,9 +93,7 @@ def sample_box_vectors():


@pytest.fixture
def sample_binary_box_coordinates(
sample_substance, sample_coordinates, sample_box_vectors
):
def sample_binary_box_coordinates(sample_substance, sample_coordinates, sample_box_vectors):
"""
Create a sample BoxCoordinates object for testing.

Expand Down
88 changes: 22 additions & 66 deletions dimsim/_tests/coordinates/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
Substance,
)

rng = np.random.default_rng(42)


class TestMoleculeSpecies:
"""Tests for MoleculeSpecies class."""
Expand Down Expand Up @@ -92,10 +94,7 @@ def test_create_substance(self, sample_molecule_species):
def test_to_string(self, sample_substance):
"""Test converting Substance to string representation."""
result = sample_substance.to_string()
expected = (
"[H:1][O:2][H:3]<100>|"
"[C:1]([H:2])([H:3])([H:4])[C:5]([H:6])([H:7])[H:8]<50>"
)
expected = "[H:1][O:2][H:3]<100>|[C:1]([H:2])([H:3])([H:4])[C:5]([H:6])([H:7])[H:8]<50>"
assert result == expected

def test_from_string(self):
Expand Down Expand Up @@ -137,9 +136,7 @@ def test_is_equivalent_to_different(self, sample_substance):
other = Substance(
molecule_species=[
MoleculeSpecies(mapped_smiles="[H:1][O:2][H:3]", count=100),
MoleculeSpecies(
mapped_smiles="[C:1][H:2][H:3][H:4][H:5]", count=25
), # Different molecule
MoleculeSpecies(mapped_smiles="[C:1][H:2][H:3][H:4][H:5]", count=25), # Different molecule
]
)
assert not sample_substance.is_equivalent_to(other)
Expand All @@ -158,7 +155,7 @@ def test_to_openff_topology(self, sample_substance):

def test_from_openff_topology(self):
jsonfile = get_test_data_path("thermoml/isopropanol-water-topology.json")
with open(jsonfile, "r") as f:
with open(jsonfile) as f:
contents = f.read()
topology = Topology.from_json(contents)

Expand All @@ -183,10 +180,7 @@ def test_create_box_coordinates(self, sample_binary_box_coordinates):
def test_get_molecule_string(self, sample_binary_box_coordinates):
"""Test getting string representation of molecules."""
result = sample_binary_box_coordinates.get_molecule_string()
expected = (
"[H:1][O:2][H:3]<100>|"
"[C:1]([H:2])([H:3])([H:4])[C:5]([H:6])([H:7])[H:8]<50>"
)
expected = "[H:1][O:2][H:3]<100>|[C:1]([H:2])([H:3])([H:4])[C:5]([H:6])([H:7])[H:8]<50>"
assert result == expected

def test_get_composition_key(self, sample_binary_box_coordinates):
Expand Down Expand Up @@ -221,16 +215,14 @@ def test_to_openff_topology(self, sample_binary_box_coordinates):

def test_from_openff_topology(self):
jsonfile = get_test_data_path("thermoml/isopropanol-water-topology.json")
with open(jsonfile, "r") as f:
with open(jsonfile) as f:
contents = f.read()
topology = Topology.from_json(contents)

box = BoxCoordinates.from_openff_topology(topology)
assert box.n_molecules == 1000
assert len(box.substance.molecule_species) == 2
expected_composition_key = (
"InChI=1/C3H8O/c1-3(2)4/h3-4H,1-2H3:505|InChI=1/H2O/h1H2:495"
)
expected_composition_key = "InChI=1/C3H8O/c1-3(2)4/h3-4H,1-2H3:505|InChI=1/H2O/h1H2:495"
assert box.get_composition_key() == expected_composition_key
first_coordinates = [1.4742e01, 1.7520e00, 6.1683e01]
assert np.allclose(box.coordinates[0], first_coordinates)
Expand Down Expand Up @@ -299,33 +291,23 @@ def test_has_equivalent_molecular_species_resonance_fails(self):
def test_has_equivalent_molecular_species_robust_to_remapping(self, smiles):
mol = Molecule.from_smiles(smiles)
explicit_h_smiles = mol.to_smiles(mapped=True)
randomized_order = np.random.permutation(np.arange(len(mol.atoms)))
randomized_order = rng.permutation(np.arange(len(mol.atoms)))
new_atom_map = dict(zip(range(len(mol.atoms)), randomized_order))
new_molecule = mol.remap(new_atom_map)
randomized_smiles = new_molecule.to_smiles(mapped=True)

assert explicit_h_smiles != randomized_smiles

box1 = BoxCoordinates(
substance=Substance(
molecule_species=[
MoleculeSpecies(mapped_smiles=explicit_h_smiles, count=10)
]
),
substance=Substance(molecule_species=[MoleculeSpecies(mapped_smiles=explicit_h_smiles, count=10)]),
)
box2 = BoxCoordinates(
substance=Substance(
molecule_species=[
MoleculeSpecies(mapped_smiles=randomized_smiles, count=10)
]
),
substance=Substance(molecule_species=[MoleculeSpecies(mapped_smiles=randomized_smiles, count=10)]),
)
assert box1.get_composition_key() == box2.get_composition_key()
assert box1.has_equivalent_molecular_species(box2)

def test_load_coordinates_from_other_without_coords_fails(
self, sample_binary_box_coordinates
):
def test_load_coordinates_from_other_without_coords_fails(self, sample_binary_box_coordinates):
"""Test that loading from box without coordinates fails."""
box_no_coords = BoxCoordinates(
substance=sample_binary_box_coordinates.substance,
Expand All @@ -351,12 +333,7 @@ def test_load_coordinates_from_other_remapped(self):
original_coords = np.vstack(
[
np.array([[1.0, 2.0, 3.0], [1.1, 2.1, 3.1], [1.2, 2.2, 3.2]] * 3),
np.array(
[
[4.0, 5.0, 6.0], [4.1, 5.1, 6.1], [4.2, 5.2, 6.2],
[4.3, 5.3, 6.3], [4.4, 5.4, 6.4]
] * 2
),
np.array([[4.0, 5.0, 6.0], [4.1, 5.1, 6.1], [4.2, 5.2, 6.2], [4.3, 5.3, 6.3], [4.4, 5.4, 6.4]] * 2),
]
)
assert original_coords.shape == (19, 3)
Expand All @@ -376,12 +353,7 @@ def test_load_coordinates_from_other_remapped(self):
# just scale for testing
new_coords = np.vstack(
[
np.array(
[
[4.0, 5.0, 6.0], [4.1, 5.1, 6.1], [4.2, 5.2, 6.2],
[4.3, 5.3, 6.3], [4.4, 5.4, 6.4]]
* 2
)
np.array([[4.0, 5.0, 6.0], [4.1, 5.1, 6.1], [4.2, 5.2, 6.2], [4.3, 5.3, 6.3], [4.4, 5.4, 6.4]] * 2)
* 10,
np.array([[1.0, 2.0, 3.0], [1.1, 2.1, 3.1], [1.2, 2.2, 3.2]] * 3) * 10,
]
Expand Down Expand Up @@ -427,15 +399,11 @@ def test_load_coordinates_from_other_remapped(self):

def test_with_coordinates_from_other(self, sample_binary_box_coordinates):
"""Test creating new box with coordinates from another."""
result = sample_binary_box_coordinates.with_coordinates_from_other(
sample_binary_box_coordinates
)
result = sample_binary_box_coordinates.with_coordinates_from_other(sample_binary_box_coordinates)
assert result is not None
assert isinstance(result, BoxCoordinates)
assert result is not sample_binary_box_coordinates
assert np.allclose(
result.coordinates, sample_binary_box_coordinates.coordinates
)
assert np.allclose(result.coordinates, sample_binary_box_coordinates.coordinates)

def test_to_db_model(self, sample_binary_box_coordinates):
"""Test converting BoxCoordinates to database model."""
Expand All @@ -445,9 +413,7 @@ def test_to_db_model(self, sample_binary_box_coordinates):
assert db_model.temperature == sample_binary_box_coordinates.temperature
assert db_model.pressure == sample_binary_box_coordinates.pressure
assert db_model.force_field_id == sample_binary_box_coordinates.force_field_id
assert (
db_model.potential_energy == sample_binary_box_coordinates.potential_energy
)
assert db_model.potential_energy == sample_binary_box_coordinates.potential_energy
assert db_model.n_molecules == 150
assert isinstance(db_model.coordinates, bytes)
assert isinstance(db_model.box_vectors, bytes)
Expand Down Expand Up @@ -484,17 +450,11 @@ def test_from_db_model(self, sample_binary_box_coordinates):
assert result.force_field_id == sample_binary_box_coordinates.force_field_id
assert result.potential_energy == sample_binary_box_coordinates.potential_energy
assert len(result.substance.molecule_species) == 2
assert (
result.coordinates.shape == sample_binary_box_coordinates.coordinates.shape
)
assert (
result.box_vectors.shape == sample_binary_box_coordinates.box_vectors.shape
)
assert result.coordinates.shape == sample_binary_box_coordinates.coordinates.shape
assert result.box_vectors.shape == sample_binary_box_coordinates.box_vectors.shape
assert result.box_metadata["equilibration_steps"] == 10000

def test_from_db_model_without_box_vectors_fails(
self, sample_binary_box_coordinates
):
def test_from_db_model_without_box_vectors_fails(self, sample_binary_box_coordinates):
"""Test creating BoxCoordinates from DB model without box vectors."""
sample_binary_box_coordinates.box_vectors = None

Expand All @@ -514,9 +474,5 @@ def test_roundtrip_db_conversion(self, sample_binary_box_coordinates):
assert result.pressure == sample_binary_box_coordinates.pressure
assert result.force_field_id == sample_binary_box_coordinates.force_field_id
assert result.n_molecules == sample_binary_box_coordinates.n_molecules
assert np.allclose(
result.coordinates, sample_binary_box_coordinates.coordinates
)
assert np.allclose(
result.box_vectors, sample_binary_box_coordinates.box_vectors
)
assert np.allclose(result.coordinates, sample_binary_box_coordinates.coordinates)
assert np.allclose(result.box_vectors, sample_binary_box_coordinates.box_vectors)
Loading