Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
55 changes: 55 additions & 0 deletions src/lammpsio/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,61 @@ def cast(cls, value):
else:
raise TypeError(f"Unable to cast boxlike object with shape {v.shape}")

@classmethod
def from_matrix(cls, low, matrix, force_triclinic=False):
"""Create a Box from low and matrix.

Parameters
----------
low : list
Origin of the box.
matrix : :class:`numpy.ndarray`
Upper triangular matrix in LAMMPS style::

[[lx, xy, xz],
[0, ly, yz],
[0, 0, lz]]
force_triclinic : bool
If ``True``, forces the box to be triclinic even if the tilt
factors are zero. Default is ``False``.

Returns
-------
:class:`Box`
A simulation box.

Raises
------
TypeError
If `low` is not length 3.
TypeError
If `matrix` is not a 3x3 array.
ValueError
If `matrix` is not upper triangular.

"""
low = numpy.array(low, dtype=float)
arr = numpy.array(matrix, dtype=float)

if low.shape != (3,):
raise TypeError("Low must be a 3-tuple")
if arr.shape != (3, 3):
raise TypeError("Box matrix must be a 3x3 array")
if arr[1, 0] != 0 or arr[2, 0] != 0 or arr[2, 1] != 0:
raise ValueError("Box matrix must be upper triangular")

# Calculate high from the matrix
high = low + numpy.diag(arr)

# Extract tilt factors
xy, xz, yz = arr[0, 1], arr[0, 2], arr[1, 2]
if force_triclinic:
tilt = [xy, xz, yz] if numpy.any([xy, xz, yz]) else [0, 0, 0]
else:
tilt = [xy, xz, yz] if numpy.any([xy, xz, yz]) else None

return cls(low, high, tilt)

@property
def low(self):
""":class:`numpy.ndarray`: Box low."""
Expand Down
40 changes: 40 additions & 0 deletions tests/test_box.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import numpy
import pytest
from pytest_lazy_fixtures import lf

import lammpsio


def test_orthorhombic(orthorhombic):
Expand Down Expand Up @@ -39,3 +42,40 @@ def test_triclinic(triclinic):
assert numpy.allclose(box.tilt, [0, 0, 0])
with pytest.raises(TypeError):
box.tilt = [0, 0]


@pytest.mark.parametrize("box", [lf("orthorhombic"), lf("triclinic")])
@pytest.mark.parametrize("force_triclinic", [True, False])
def test_from_matrix(box, force_triclinic):
lx, ly, lz = box.high - box.low
xy, xz, yz = box.tilt if box.tilt is not None else (0, 0, 0)
matrix = numpy.array([[lx, xy, xz], [0, ly, yz], [0, 0, lz]])
new_box = lammpsio.Box.from_matrix(box.low, matrix, force_triclinic=force_triclinic)

assert numpy.allclose(new_box.low, box.low)
assert numpy.allclose(new_box.high, box.high)
if force_triclinic:
assert new_box.tilt is not None
if box.tilt is not None:
assert numpy.allclose(new_box.tilt, box.tilt)
else:
assert numpy.allclose(new_box.tilt, [0, 0, 0])
else:
if box.tilt is not None:
assert numpy.allclose(new_box.tilt, box.tilt)
else:
assert new_box.tilt is None

# test with invalid low
with pytest.raises(TypeError):
lammpsio.Box.from_matrix([0, 0], matrix)

# test with invalid matrix shape
invalid_matrix_shape = numpy.array([[lx, xy], [ly, yz]])
with pytest.raises(TypeError):
lammpsio.Box.from_matrix(box.low, invalid_matrix_shape)

# test with invalid matrix values
invalid_matrix = numpy.array([[lx, xy, xz], [ly, 0, yz], [lz, 0, 0]])
with pytest.raises(ValueError):
lammpsio.Box.from_matrix(box.low, invalid_matrix)