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

@classmethod
def from_matrix(cls, low, matrix):
"""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]]

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")

# Extract diagonal elements for box lengths
lx, ly, lz = arr[0, 0], arr[1, 1], arr[2, 2]
high = low + [lx, ly, lz]

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one area of LAMMPS that is slightly grey. You may want to have a triclinic box that has zero tilt factors, say in order to allow the box to deform later.

Should we add a default argument like check_orthorhombic=True or force_triclinic=False that will cause this check to occur? And otherwise, accept the tilts as they come from the matrix?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I've implemented a force_triclinic argument to cover this scenario!


return cls(low, high, tilt)

@property
def low(self):
""":class:`numpy.ndarray`: Box low."""
Expand Down
30 changes: 30 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,30 @@ 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")])
def test_from_matrix(box):
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)

assert numpy.allclose(new_box.low, box.low)
assert numpy.allclose(new_box.high, box.high)
if box.tilt is not None:
assert numpy.allclose(new_box.tilt, box.tilt)

# 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)