diff --git a/environment-dev.yml b/environment-dev.yml index 425d7d8cb..163ac4e5b 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -25,6 +25,7 @@ dependencies: - ipywidgets - ele>=0.2.0 - pre-commit + - molbox - pandas - symengine - python-symengine diff --git a/environment.yml b/environment.yml index 85a309123..33d2b66b0 100644 --- a/environment.yml +++ b/environment.yml @@ -11,7 +11,8 @@ dependencies: - lxml - pydantic>=2 - networkx - - ele>=0.2.0 + - ele >= 0.2.0 + - molbox - foyer>=0.11.3 - forcefield-utilities>=0.2.1 - symengine diff --git a/gmso/core/box.py b/gmso/core/box.py index 3e2c2f831..0929ef0f2 100644 --- a/gmso/core/box.py +++ b/gmso/core/box.py @@ -4,6 +4,7 @@ import numpy as np import unyt as u +from molbox import Box as MolBox from unyt.array import allclose_units @@ -22,6 +23,7 @@ def _validate_lengths(lengths): np.reshape(lengths, newshape=(3,), order="C") lengths *= input_unit + if input_unit != u.Unit("dimensionless"): lengths.convert_to_units(u.nm) @@ -119,30 +121,74 @@ class Box(object): """ - def __init__(self, lengths, angles=None): + def __init__(self, lengths, angles=None, precision=None): """Construct a `Box` based on lengths and angles.""" - self._lengths = _validate_lengths(lengths) - self._angles = _validate_angles(angles) + lengths = _validate_lengths(lengths) + angles = _validate_angles(angles) + print(lengths, angles) + precision = int(precision or 6) + self._box = MolBox( + lengths=lengths.value, angles=angles.value, precision=precision + ) @property def lengths(self): """Return edge lengths of the box.""" - return self._lengths + return self._box.lengths * u.nm @property def angles(self): """Return angles of the box.""" - return self._angles + return self._box.angles * u.degree @lengths.setter def lengths(self, lengths): """Set the lengths of the box.""" - self._lengths = _validate_lengths(lengths) + lengths = _validate_lengths(lengths).value + self._set_internal_box_vectors(lengths=lengths) @angles.setter def angles(self, angles): """Set the angles of the box.""" - self._angles = _validate_angles(angles) + angles = _validate_angles(angles).value + self._set_internal_box_vectors(angles=angles) + + @property + def precision(self): + """Amount of decimals to represent floating point values.""" + return self._box._precision + + @precision.setter + def precision(self, precision): + """Decimal point precision(default=16).""" + self._box.precision = precision + + def _set_internal_box_vectors(self, lengths=None, angles=None): + from molbox.box import _lengths_angles_to_vectors + + if angles is None: + angles = self.angles.value + if lengths is None: + lengths = self.lengths.value + + self._box._vectors = _lengths_angles_to_vectors( + lengths, angles, self._box.precision + ) + + ( + Lx, + Ly, + Lz, + xy, + xz, + yz, + ) = self._box._from_vecs_to_lengths_tilt_factors() + self._box._Lx = Lx + self._box._Ly = Ly + self._box._Lz = Lz + self._box._xy = xy + self._box._xz = xz + self._box._yz = yz def _unit_vectors_from_angles(self): """Return unit vectors describing prism from angles.""" @@ -175,7 +221,7 @@ def _unit_vectors_from_angles(self): def get_vectors(self): """Return the vectors of the box.""" - return (self._lengths * self.get_unit_vectors().T).T + return (self.lengths * self.get_unit_vectors().T).T def get_unit_vectors(self): """Return the normalized vectors of the box.""" @@ -185,14 +231,14 @@ def json_dict(self): from gmso.abc.serialization_utils import unyt_to_dict return { - "lengths": unyt_to_dict(self._lengths), - "angles": unyt_to_dict(self._angles), + "lengths": unyt_to_dict(self.lengths), + "angles": unyt_to_dict(self.angles), } def __repr__(self): """Return formatted representation of the box.""" return "Box(a={}, b={}, c={}, alpha={}, beta={}, gamma={})".format( - *self._lengths, *self._angles + *self.lengths, *self.angles ) def __eq__(self, other): diff --git a/gmso/formats/lammpsdata.py b/gmso/formats/lammpsdata.py index 579da21e2..0bf43c27e 100644 --- a/gmso/formats/lammpsdata.py +++ b/gmso/formats/lammpsdata.py @@ -115,6 +115,7 @@ def write_lammpsdata( unit_style ) ) + if unit_style != "lj" and lj_cfactorsDict: raise ValueError( "lj_cfactorsDict argument is only used if unit_style is lj." diff --git a/gmso/tests/test_box.py b/gmso/tests/test_box.py index 3939167ff..61f57b07e 100644 --- a/gmso/tests/test_box.py +++ b/gmso/tests/test_box.py @@ -3,6 +3,7 @@ import numpy as np import pytest import unyt as u +from molbox.box import BoxError from unyt.testing import assert_allclose_units from gmso.core.box import Box @@ -25,7 +26,7 @@ def test_bad_lengths(self, lengths, angles): Box(lengths=lengths, angles=angles) def test_build_2D_Box(self): - with pytest.warns(UserWarning): + with pytest.raises(BoxError): Box(lengths=u.nm * [4, 4, 0]) def test_dtype(self, box): @@ -47,7 +48,8 @@ def test_angles_setter(self, lengths, angles): box = Box(lengths=lengths, angles=u.degree * np.ones(3)) angles *= u.degree box.angles = angles - assert (box.angles == angles).all() + print(angles, box.angles) + assert u.allclose_units(angles, box.angles, rtol=10 ** (-box.precision)) @pytest.mark.parametrize("lengths", [[3, 3, 3], [4, 4, 4], [4, 6, 4]]) def test_setters_with_lists(self, lengths): diff --git a/gmso/tests/test_lammps.py b/gmso/tests/test_lammps.py index f2ae5ab88..402013df6 100644 --- a/gmso/tests/test_lammps.py +++ b/gmso/tests/test_lammps.py @@ -162,16 +162,10 @@ def test_read_lammps_triclinic(self, typed_ar_system): read = gmso.Topology.load("triclinic.lammps") assert_allclose_units( - read.box.lengths, - u.unyt_array([1, 1, 1], u.nm), - rtol=1e-5, - atol=1e-8, + read.box.lengths, u.unyt_array([1.0, 1.0, 1.0], u.nm) ) assert_allclose_units( - read.box.angles, - u.unyt_array([60, 90, 120], u.degree), - rtol=1e-5, - atol=1e-8, + read.box.angles, u.unyt_array([60, 90, 120], u.degree), rtol=1e-5 ) def test_read_n_bonds(self, typed_ethane_opls):