diff --git a/hoomd/CMakeLists.txt b/hoomd/CMakeLists.txt index 03fac4ce00..34f27d3f24 100644 --- a/hoomd/CMakeLists.txt +++ b/hoomd/CMakeLists.txt @@ -95,6 +95,7 @@ set(_hoomd_sources Action.cc Tuner.cc Updater.cc Variant.cc + VectorVariant.cc extern/BVLSSolver.cc extern/gsd.c extern/kiss_fft.cc @@ -187,6 +188,7 @@ set(_hoomd_headers TextureTools.h Updater.h Variant.h + VectorVariant.h VectorMath.h WarpTools.cuh ) @@ -352,7 +354,6 @@ set(files box.py operations.py pytest_plugin_validate.py util.py - variant.py simulation.py state.py trigger.py @@ -384,6 +385,7 @@ add_subdirectory(write) add_subdirectory(pytest) add_subdirectory(tune) add_subdirectory(update) +add_subdirectory(variant) if (BUILD_TESTING) # add_subdirectory(test-py) diff --git a/hoomd/Variant.h b/hoomd/Variant.h index c077c69216..2d551f1c50 100644 --- a/hoomd/Variant.h +++ b/hoomd/Variant.h @@ -11,10 +11,10 @@ namespace hoomd { -/** Defines quantities that vary with time steps. +/** Defines scalar quantities that vary with time steps. - Variant provides an interface to define quanties (such as kT) that vary over time. The base - class provides a callable interface. Derived classes implement specific kinds of varying + Variant provides an interface to define scalar quanties (such as kT) that vary over time. The + base class provides a callable interface. Derived classes implement specific kinds of varying quantities. */ class PYBIND11_EXPORT Variant diff --git a/hoomd/VectorVariant.cc b/hoomd/VectorVariant.cc new file mode 100644 index 0000000000..371f86c474 --- /dev/null +++ b/hoomd/VectorVariant.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +#include "VectorVariant.h" +#include + +namespace hoomd + { +//* Trampoline for classes inherited in python +class VectorVariantBoxPy : public VectorVariantBox + { + public: + // Inherit the constructors + using VectorVariantBox::VectorVariantBox; + + // trampoline method + array_type operator()(uint64_t timestep) override + { + PYBIND11_OVERLOAD_NAME(array_type, // Return type + VectorVariantBox, // Parent class + "__call__", // name of function in python + operator(), // Name of function in C++ + timestep // Argument(s) + ); + } + }; + +namespace detail + { + +// This testVariantCall function allows us to test that Python custom vector +// variants work properly in C++. This ensures we can test that the function +// itself can be called in C++ when defined in Python. + +/// Method to enable unit testing of C++ variant calls from pytest +std::array testVectorVariantBoxCall(std::shared_ptr t, uint64_t step) + { + return (*t)(step); + } + +void export_VectorVariantBoxClasses(pybind11::module& m) + { + pybind11::class_>( + m, + "VectorVariantBox") + .def(pybind11::init<>()) + .def("__call__", &VectorVariantBox::operator()); + + pybind11::class_>(m, "VectorVariantBoxConstant") + .def(pybind11::init>()) + .def_property("_box", &VectorVariantBoxConstant::getBox, &VectorVariantBoxConstant::setBox); + + pybind11::class_>(m, "VectorVariantBoxInterpolate") + .def(pybind11:: + init, std::shared_ptr, std::shared_ptr>()) + .def_property("_initial_box", + &VectorVariantBoxInterpolate::getInitialBox, + &VectorVariantBoxInterpolate::setInitialBox) + .def_property("_final_box", + &VectorVariantBoxInterpolate::getFinalBox, + &VectorVariantBoxInterpolate::setFinalBox) + .def_property("variant", + &VectorVariantBoxInterpolate::getVariant, + &VectorVariantBoxInterpolate::setVariant); + + pybind11::class_>( + m, + "VectorVariantBoxInverseVolumeRamp") + .def(pybind11::init, Scalar, uint64_t, uint64_t>()) + .def_property("_initial_box", + &VectorVariantBoxInverseVolumeRamp::getInitialBox, + &VectorVariantBoxInverseVolumeRamp::setInitialBox) + .def_property("t_start", + &VectorVariantBoxInverseVolumeRamp::getTStart, + &VectorVariantBoxInverseVolumeRamp::setTStart) + .def_property("t_ramp", + &VectorVariantBoxInverseVolumeRamp::getTRamp, + &VectorVariantBoxInverseVolumeRamp::setTRamp) + .def_property("final_volume", + &VectorVariantBoxInverseVolumeRamp::getFinalVolume, + &VectorVariantBoxInverseVolumeRamp::setFinalVolume); + + m.def("_test_vector_variant_box_call", &testVectorVariantBoxCall); + } + + } // end namespace detail + + } // end namespace hoomd diff --git a/hoomd/VectorVariant.h b/hoomd/VectorVariant.h new file mode 100644 index 0000000000..78fabdbfd1 --- /dev/null +++ b/hoomd/VectorVariant.h @@ -0,0 +1,313 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +#pragma once + +#include +#include +#include + +#include "BoxDim.h" +#include "HOOMDMath.h" +#include "Variant.h" + +namespace hoomd + { +/** Defines vector quantities that vary with time steps. + + VectorVariant provides an interface to define vector quanties (such as box dimensions) that vary + over time. The base class provides a callable interface. Derived classes implement specific kinds + of varying quantities. +*/ +template class PYBIND11_EXPORT VectorVariant + { + public: + virtual ~VectorVariant() { } + typedef std::array array_type; + + /** Return the value of the Variant at the given time step. + + @param timestep Time step to query. + @returns The value of the variant. + */ + virtual array_type operator()(uint64_t timestep) + { + std::array ret; + ret.fill(0); + return ret; + } + }; + +/** Box vector variant. + + VectorVariant class for representing box parameters. The operator() returns an array with 6 + elements that represent Lx, Ly, Lz, xy, xz, and yz. +*/ +class PYBIND11_EXPORT VectorVariantBox : public VectorVariant<6> + { + protected: + static std::array box_to_array(std::shared_ptr box) + { + return std::array {box->getL().x, + box->getL().y, + box->getL().z, + box->getTiltFactorXY(), + box->getTiltFactorXZ(), + box->getTiltFactorYZ()}; + } + }; + +/** Constant box vector variant + + Returns a constant vector. + */ +class PYBIND11_EXPORT VectorVariantBoxConstant : public VectorVariantBox + { + public: + /** Construct a VectorVariantBoxConstant. + + @param box The box. + */ + VectorVariantBoxConstant(std::shared_ptr box) : m_box(box) { } + + virtual ~VectorVariantBoxConstant() { } + + /// Return the value. + virtual array_type operator()(uint64_t timestep) + { + return box_to_array(m_box); + } + + std::shared_ptr getBox() + { + return m_box; + } + + void setBox(std::shared_ptr box) + { + m_box = box; + } + + protected: + std::shared_ptr m_box; + }; + +/** Interpolate box vector variant + + Vector variant that interpolates between two boxes based on a given scalar variant. + Returns the vector corresponding to initial_box when the scalar variant evaluates to its minimum + value. Returns the vector correspolding to final_box when the scalar variant evaluates to its + maximum value. Returns the array corresponding to the interpolated box when the scalar variant + evaluates to values between its maximum and minimum values. The i-th component of the + interpolated box vector corresponds to the weighted average of the i-th components of initial_box + and final_box, where the weight f given to final_box is equal to the difference in the value of + the scalar variant and the minimum value of the scalar variant, normalized by the difference in + the maximum and minimum values of the scalar variant. I.e., f = (variant(timestep) - + variant.minimum) / (variant.maximum - variant.minimum). + +*/ +class PYBIND11_EXPORT VectorVariantBoxInterpolate : public VectorVariantBox + { + public: + /** Construct a VectorVariantBoxInterpolate to interpolate between two boxes. + + @param initial_box The initial box + @param final_box The final box + */ + VectorVariantBoxInterpolate(std::shared_ptr initial_box, + std::shared_ptr final_box, + std::shared_ptr variant) + : m_initial_box(initial_box), m_final_box(final_box), m_variant(variant) + { + } + + /// Return the value. + virtual array_type operator()(uint64_t timestep) + { + Scalar min = m_variant->min(); + Scalar max = m_variant->max(); + Scalar cur_value = (*m_variant)(timestep); + Scalar scale = 0; + if (cur_value == max) + { + scale = 1; + } + else if (cur_value > min) + { + scale = (cur_value - min) / (max - min); + } + + const auto& initial_box = *m_initial_box; + const auto& final_box = *m_final_box; + Scalar3 new_L = final_box.getL() * scale + initial_box.getL() * (1.0 - scale); + Scalar xy + = final_box.getTiltFactorXY() * scale + (1.0 - scale) * initial_box.getTiltFactorXY(); + Scalar xz + = final_box.getTiltFactorXZ() * scale + (1.0 - scale) * initial_box.getTiltFactorXZ(); + Scalar yz + = final_box.getTiltFactorYZ() * scale + (1.0 - scale) * initial_box.getTiltFactorYZ(); + array_type value = {new_L.x, new_L.y, new_L.z, xy, xz, yz}; + return value; + } + + std::shared_ptr getInitialBox() + { + return m_initial_box; + } + + void setInitialBox(std::shared_ptr box) + { + m_initial_box = box; + } + + std::shared_ptr getFinalBox() + { + return m_final_box; + } + + void setFinalBox(std::shared_ptr box) + { + m_final_box = box; + } + + /// Set the variant for interpolation + void setVariant(std::shared_ptr variant) + { + m_variant = variant; + } + + /// Get the variant for interpolation + std::shared_ptr getVariant() + { + return m_variant; + } + + protected: + /// The starting box, associated with the minimum of the variant. + std::shared_ptr m_initial_box; + + /// The final box, associated with the maximum of the variant. + std::shared_ptr m_final_box; + + /// Variant that interpolates between boxes. + std::shared_ptr m_variant; + }; + +/** Inverse volume interpolation box vector variant. + + Returns the array corresponding to the box whose inverse volume (i.e., density) ramps from + initial_box.volume to final_volume over t_ramp steps while keeping the box shape constant. +*/ +class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBox + { + public: + VectorVariantBoxInverseVolumeRamp(std::shared_ptr initial_box, + Scalar final_volume, + uint64_t t_start, + uint64_t t_ramp) + : m_initial_box(initial_box), m_final_volume(final_volume), m_variant(0, 1, t_start, t_ramp) + { + m_is_2d = m_initial_box->getL().z == 0; + m_initial_volume = m_initial_box->getVolume(m_is_2d); + } + + virtual array_type operator()(uint64_t timestep) + { + Scalar s = m_variant(timestep); + // current inverse volume = s * (1 / m_final_volume) + (1-s) * (1/m_vol1) + // current volume = 1 / (current inverse volume) + Scalar current_volume = 1 / (s / m_final_volume + (1.0 - s) / m_initial_volume); + Scalar L_scale; + if (m_is_2d) + { + L_scale = pow(current_volume / m_initial_volume, Scalar(1.0 / 2.0)); + } + else + { + L_scale = pow(current_volume / m_initial_volume, Scalar(1.0 / 3.0)); + } + + std::array value; + Scalar3 L1 = m_initial_box->getL(); + value[0] = L1.x * L_scale; + value[1] = L1.y * L_scale; + value[2] = L1.z * L_scale; + value[3] = m_initial_box->getTiltFactorXY(); + value[4] = m_initial_box->getTiltFactorXZ(); + value[5] = m_initial_box->getTiltFactorYZ(); + return value; + } + + std::shared_ptr getInitialBox() + { + return m_initial_box; + } + + void setInitialBox(std::shared_ptr box) + { + m_initial_box = box; + m_is_2d = box->getL().z == 0; + m_initial_volume = box->getVolume(m_is_2d); + } + + /// Set the starting time step. + void setTStart(uint64_t t_start) + { + m_variant.setTStart(t_start); + } + + /// Get the starting time step. + uint64_t getTStart() const + { + return m_variant.getTStart(); + } + + /// Set the length of the ramp. + void setTRamp(uint64_t t_ramp) + { + m_variant.setTRamp(t_ramp); + } + + /// Get the length of the ramp. + uint64_t getTRamp() const + { + return m_variant.getTRamp(); + } + + /// Set the final volume + void setFinalVolume(Scalar volume) + { + m_final_volume = volume; + } + + /// Get the final volume + Scalar getFinalVolume() const + { + return m_final_volume; + } + + protected: + /// The starting box. + std::shared_ptr m_initial_box; + + /// The volume of the initial box. + Scalar m_initial_volume; + + /// The volume of the box at the end of the ramp. + Scalar m_final_volume; + + /// Whether initial_box is 2-dimensional or not + bool m_is_2d; + + /// Variant for computing scale value + VariantRamp m_variant; + }; + +namespace detail + { +/// Export Variant classes to Python +void export_VectorVariantBoxClasses(pybind11::module& m); + + } // end namespace detail + + } // end namespace hoomd diff --git a/hoomd/__init__.py b/hoomd/__init__.py index 4e2ad74476..6ab8536822 100644 --- a/hoomd/__init__.py +++ b/hoomd/__init__.py @@ -59,8 +59,8 @@ from hoomd import version from hoomd import trigger -from hoomd import variant from hoomd.box import Box, box_like +from hoomd import variant from hoomd import data from hoomd import filter from hoomd import device diff --git a/hoomd/module.cc b/hoomd/module.cc index a9778b3d60..f4f15aa1c9 100644 --- a/hoomd/module.cc +++ b/hoomd/module.cc @@ -38,6 +38,7 @@ #include "Updater.h" #include "UpdaterRemoveDrift.h" #include "Variant.h" +#include "VectorVariant.h" // ParticleFilter objects #include "filter/export_filters.h" @@ -353,6 +354,9 @@ PYBIND11_MODULE(_hoomd, m) // variant export_Variant(m); + // vector variant + export_VectorVariantBoxClasses(m); + // messenger export_Messenger(m); } diff --git a/hoomd/pytest/CMakeLists.txt b/hoomd/pytest/CMakeLists.txt index f37ffa62e9..cc1b070267 100644 --- a/hoomd/pytest/CMakeLists.txt +++ b/hoomd/pytest/CMakeLists.txt @@ -4,6 +4,7 @@ set(files __init__.py test_balance.py test_box.py test_box_resize.py + test_box_variant.py test_collections.py test_communicator.py test_custom_tuner.py diff --git a/hoomd/pytest/test_box_variant.py b/hoomd/pytest/test_box_variant.py new file mode 100644 index 0000000000..d369758d5f --- /dev/null +++ b/hoomd/pytest/test_box_variant.py @@ -0,0 +1,169 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import numpy as np +import numpy.testing as npt +import hoomd +import hoomd.variant +import pytest + + +def box_to_array(box): + return np.array([box.Lx, box.Ly, box.Lz, box.xy, box.xz, box.yz]) + + +test_box1 = hoomd.Box(10, 50, 20, 0.2, 0.4, 0.6) +test_box2 = hoomd.Box(16, 25, 36, 0.1, 0.2, 0.3) +scalar_variant1 = hoomd.variant.Ramp(0, 1, 100, 200) +scalar_variant2 = hoomd.variant.Ramp(0, 1, 10, 30) + +valid_constructors = [ + (hoomd.variant.box.Constant, { + 'box': test_box1 + }), + (hoomd.variant.box.Interpolate, { + 'initial_box': test_box1, + 'final_box': test_box2, + 'variant': scalar_variant1 + }), + (hoomd.variant.box.InverseVolumeRamp, { + 'initial_box': test_box2, + 'final_volume': 1000, + 't_start': 10, + 't_ramp': 50 + }), +] + +# variant: dict(attr: [val1, val2,...]) +valid_attrs = [ + (hoomd.variant.box.Constant, { + 'box': [test_box1, test_box2] + }), + (hoomd.variant.box.Interpolate, { + 'initial_box': [test_box1, test_box2], + 'final_box': [test_box2, test_box1], + 'variant': [scalar_variant1, scalar_variant2] + }), + (hoomd.variant.box.InverseVolumeRamp, { + 'initial_box': [test_box1, test_box2], + 'final_volume': [1000, 300], + 't_start': [0, 10], + 't_ramp': [10, 50, 100] + }), +] + + +@pytest.mark.parametrize('cls, kwargs', valid_constructors) +def test_construction(cls, kwargs): + variant = cls(**kwargs) + for key, value in kwargs.items(): + assert getattr(variant, key) == value + + +@pytest.mark.parametrize('cls, attrs', valid_attrs) +def test_setattr(cls, attrs): + kwargs = {k: v[0] for k, v in attrs.items()} + variant = cls(**kwargs) + new_attrs = [(k, v[1]) for k, v in attrs.items()] + for attr, value in new_attrs: + setattr(variant, attr, value) + assert getattr(variant, attr) == value + + +class VolumeRampBoxVariant(hoomd.variant.box.BoxVariant): + + def __init__(self, box1, final_volume, t_start, t_ramp): + self._initial_volume = box1.volume + self._box1 = box1 + self._volume_variant = hoomd.variant.Ramp(box1.volume, final_volume, + t_start, t_ramp) + hoomd.variant.box.BoxVariant.__init__(self) + + def __call__(self, timestep): + current_volume = self._volume_variant(timestep) + scale_L = (current_volume / self._initial_volume)**(1 / 3) + return np.concatenate((self._box1.L * scale_L, self._box1.tilts)) + + def __eq__(self, other): + return isinstance(other, type(self)) + + +def test_custom(): + # test that the custom variant can be called from c++ and that it returns + # the expected values + + final_volume = test_box1.volume * 2 + test_box = hoomd.Box(test_box1.Lx, test_box1.Ly, test_box1.Lz, test_box1.xy, + test_box1.xz, test_box1.yz) + custom_variant = VolumeRampBoxVariant(test_box1, final_volume, 100, 100) + + def box_t(custom_variant, timestep): + return hoomd._hoomd._test_vector_variant_box_call( + custom_variant, timestep) + + for t, f in ((0, 0), (42, 0), (100, 0), (101, 0.01), (150, 0.5), + (175, 0.75), (199, 0.99), (200, 1.0), (250, 1.0), (123456789, + 1.0)): + test_box.volume = (1 - f) * test_box1.volume + f * final_volume + npt.assert_allclose(box_t(custom_variant, t), box_to_array(test_box)) + + +def test_interpolate_evaluation(): + t_start = 50 + t_ramp = 100 + scalar_variant = hoomd.variant.Ramp(0, 1, t_start, t_ramp) + box_variant = hoomd.variant.box.Interpolate(test_box1, test_box2, + scalar_variant) + npt.assert_allclose(box_variant(0), box_to_array(test_box1)) + npt.assert_allclose(box_variant(25), box_to_array(test_box1)) + npt.assert_allclose(box_variant(t_start), box_to_array(test_box1)) + + npt.assert_allclose( + box_variant(51), + 0.99 * box_to_array(test_box1) + 0.01 * box_to_array(test_box2)) + npt.assert_allclose( + box_variant(75), + 0.75 * box_to_array(test_box1) + 0.25 * box_to_array(test_box2)) + npt.assert_allclose( + box_variant(100), + 0.5 * box_to_array(test_box1) + 0.5 * box_to_array(test_box2)) + npt.assert_allclose( + box_variant(125), + 0.25 * box_to_array(test_box1) + 0.75 * box_to_array(test_box2)) + npt.assert_allclose( + box_variant(149), + 0.01 * box_to_array(test_box1) + 0.99 * box_to_array(test_box2)) + + npt.assert_allclose(box_variant(t_start + t_ramp), box_to_array(test_box2)) + npt.assert_allclose(box_variant(t_start + t_ramp + 100), + box_to_array(test_box2)) + npt.assert_allclose(box_variant(t_start + t_ramp + 1000000), + box_to_array(test_box2)) + + +def test_inverse_volume_ramp_evaluation(): + box1 = hoomd.Box(10, 10, 10, 0.1, 0.2, 0.3) + final_volume = 500 + t_start = 10 + t_ramp = 100 + variant = hoomd.variant.box.InverseVolumeRamp(box1, final_volume, t_start, + t_ramp) + + def get_volume(variant, timestep): + return hoomd.Box(*variant(timestep)).volume + + assert get_volume(variant, 0) == box1.volume + assert get_volume(variant, 5) == box1.volume + assert get_volume(variant, 10) == box1.volume + assert get_volume(variant, 11) != box1.volume + npt.assert_allclose(get_volume(variant, 35), + (0.75 / box1.volume + 0.25 / final_volume)**-1) + npt.assert_allclose(get_volume(variant, 60), + (0.5 / box1.volume + 0.5 / final_volume)**-1) + npt.assert_allclose(get_volume(variant, 85), + (0.25 / box1.volume + 0.75 / final_volume)**-1) + npt.assert_allclose(get_volume(variant, 110), final_volume) + npt.assert_allclose(get_volume(variant, 1010), final_volume) + # make sure tilts don't change + for step in (0, 5, 252, 125, 625): + npt.assert_allclose(box1.tilts, variant(step)[3:]) diff --git a/hoomd/variant/CMakeLists.txt b/hoomd/variant/CMakeLists.txt new file mode 100644 index 0000000000..1a26e157b7 --- /dev/null +++ b/hoomd/variant/CMakeLists.txt @@ -0,0 +1,10 @@ +set(files __init__.py + scalar.py + box.py + ) + +install(FILES ${files} + DESTINATION ${PYTHON_SITE_INSTALL_DIR}/variant + ) + +copy_files_to_build("${files}" "variant" "*.py") diff --git a/hoomd/variant/__init__.py b/hoomd/variant/__init__.py new file mode 100644 index 0000000000..1440bd8d83 --- /dev/null +++ b/hoomd/variant/__init__.py @@ -0,0 +1,16 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +"""Define quantities that vary over the simulation. + +A `Variant` object represents a function of the time step. Some +operations accept `Variant` values for certain parameters, such as the +``kT`` parameter to `hoomd.md.methods.thermostats.Bussi`. + +See `Variant` for details on creating user-defined variants or use one of the +provided subclasses. +""" + +from hoomd.variant.scalar import (Variant, Constant, Ramp, Cycle, Power, + variant_like) +from hoomd.variant import box diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py new file mode 100644 index 0000000000..f2d93a6ecd --- /dev/null +++ b/hoomd/variant/box.py @@ -0,0 +1,180 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +"""Implement variants that return box parameters as a function of time.""" + +from hoomd import _hoomd, Box +from hoomd.data.typeconverter import box_preprocessing, variant_preprocessing + + +class BoxVariant(_hoomd.VectorVariantBox): + """Box-like vector variant base class. + + `hoomd.variant.box.BoxVariant` provides an interface to vector variants that + are valid `hoomd.box.box_like` objects. The return value of the + `__call__` method is a list of scalar values that represent the + quantities ``Lx``, ``Ly``, ``Lz``, ``xy``, ``xz``, and ``yz`` of a + simulation box. + + Subclasses should override the `__call__` method and must explicitly call + the base class constructor in ``__init__``: + + .. code-block:: python + + class CustomBoxVariant(hoomd.variant.box.BoxVariant): + def __init__(self): + hoomd.variant.box.BoxVariant.__init__(self) + + def __call__(self, timestep): + return [10 + timestep/1e6, 10, 10, 0, 0, 0] + + .. py:method:: __call__(timestep) + + Evaluate the function. + + :param timestep: The time step. + :type timestep: int + :return: The value of the function at the given time step. + :rtype: [float, float, float, float, float, float] + """ + pass + + +class Constant(_hoomd.VectorVariantBoxConstant, BoxVariant): + """A constant box variant. + + Args: + box (hoomd.box.box_like): The box. + + `Constant` returns ``[box.Lx, box.Ly, box.Lz, box.xz, box.xz, box.yz]`` at + all time steps. + """ + + def __init__(self, box): + box = box_preprocessing(box) + BoxVariant.__init__(self) + _hoomd.VectorVariantBoxConstant.__init__(self, box._cpp_obj) + + @property + def box(self): + """hoomd.Box: The box.""" + return Box._from_cpp(self._box) + + @box.setter + def box(self, box): + box = box_preprocessing(box) + self._box = box._cpp_obj + + +class Interpolate(_hoomd.VectorVariantBoxInterpolate, BoxVariant): + """Interpolate between two boxes linearly. + + Args: + initial_box (hoomd.box.box_like): The initial box. + final_box (hoomd.box.box_like): The final box. + variant (hoomd.variant.variant_like): A variant used to interpolate + between the two boxes. + + ``Interpolate`` returns arrays corresponding to a linear interpolation + between the initial and final boxes where the minimum of the variant gives + ``initial_box`` and the maximum gives ``final_box``: + + .. math:: + + \\begin{align*} + L_{x}' &= \\lambda L_{2x} + (1 - \\lambda) L_{1x} \\\\ + L_{y}' &= \\lambda L_{2y} + (1 - \\lambda) L_{1y} \\\\ + L_{z}' &= \\lambda L_{2z} + (1 - \\lambda) L_{1z} \\\\ + xy' &= \\lambda xy_{2} + (1 - \\lambda) xy_{1} \\\\ + xz' &= \\lambda xz_{2} + (1 - \\lambda) xz_{1} \\\\ + yz' &= \\lambda yz_{2} + (1 - \\lambda) yz_{1} \\\\ + \\end{align*} + + Where ``initial_box`` is :math:`(L_{ix}, L_{iy}, L_{iz}, xy_i, xz_i, yz_i)`, + ``final_box`` is :math:`(L_{fx}, L_{fy}, L_{fz}, xy_f, xz_f, yz_f)`, + :math:`\\lambda = \\frac{f(t) - \\min f}{\\max f - \\min f}`, :math:`t` + is the timestep, and :math:`f(t)` is given by `variant`. + + Attributes: + variant (hoomd.variant.Variant): A variant used to interpolate between + the two boxes. + """ + + def __init__(self, initial_box, final_box, variant): + box1 = box_preprocessing(initial_box) + box2 = box_preprocessing(final_box) + variant = variant_preprocessing(variant) + BoxVariant.__init__(self) + _hoomd.VectorVariantBoxInterpolate.__init__(self, box1._cpp_obj, + box2._cpp_obj, variant) + + @property + def initial_box(self): + """hoomd.Box: The initial box.""" + return Box._from_cpp(self._initial_box) + + @initial_box.setter + def initial_box(self, box): + box = box_preprocessing(box) + self._initial_box = box._cpp_obj + + @property + def final_box(self): + """hoomd.Box: The final box.""" + return Box._from_cpp(self._final_box) + + @final_box.setter + def final_box(self, box): + box = box_preprocessing(box) + self._final_box = box._cpp_obj + + +class InverseVolumeRamp(_hoomd.VectorVariantBoxInverseVolumeRamp, BoxVariant): + """Produce box arrays whose inverse volume changes linearly. + + Args: + initial_box (hoomd.box.box_like): The initial box. + final_volume (float): The final volume of the box. + t_start (int): The time step at the start of the ramp. + t_ramp (int): The length of the ramp. + + ``InverseVolumeRamp`` produces box arrays that correspond to a box whose + **inverse volume** (i.e., number density for a constant number of particles) + varies linearly. The shape of the box remains constant, that is, the ratios + of the lengths of the box vectors (:math:`L_y / L_x` and :math:`L_z / L_x`) + and the tilt factors (:math:`xy`, :math:`xz`, :math:`yz`) remain constant. + For ``initial_box`` with volume :math:`V_0` and `final_volume` :math:`V_f`, + ``InverseVolumeRamp`` returns arrays corresponding to boxes with volume + :math:`V(t)`: + + .. math:: + + V(t) &= \\begin{cases} V_0 & t < t_{\\mathrm{start}} \\\\ \\left( + \\lambda V_f^{-1} + (1 - \\lambda) V_0^{-1} \\right)^{-1} & + t_{\\mathrm{start}} \\leq t < t_{\\mathrm{start}} + t_{\\mathrm{ramp}} + \\\\ V_f & t \\geq t_{\\mathrm{start}} + t_{\\mathrm{ramp}} \\end{cases} + + where :math:`\\lambda = \\frac{t - t_{\\mathrm{start}}}{t_{\\mathrm{ramp}} - + t_{\\mathrm{start}}}`. + + Attributes: + final_volume (float): The volume of the final box. + t_start (int): The time step at the start of the ramp. + t_ramp (int): The length of the ramp. + """ + + def __init__(self, initial_box, final_volume, t_start, t_ramp): + BoxVariant.__init__(self) + box = box_preprocessing(initial_box) + _hoomd.VectorVariantBoxInverseVolumeRamp.__init__( + self, box._cpp_obj, final_volume, t_start, t_ramp) + + @property + def initial_box(self): + """hoomd.Box: The initial box.""" + return Box._from_cpp(self._initial_box) + + @initial_box.setter + def initial_box(self, box): + box = box_preprocessing(box) + self._initial_box = box._cpp_obj diff --git a/hoomd/variant.py b/hoomd/variant/scalar.py similarity index 95% rename from hoomd/variant.py rename to hoomd/variant/scalar.py index e16719de3f..1a9597eb15 100644 --- a/hoomd/variant.py +++ b/hoomd/variant/scalar.py @@ -1,15 +1,8 @@ # Copyright (c) 2009-2024 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -"""Define quantities that vary over the simulation. +"""Implement variants that return scalar values.""" -A `Variant` object represents a scalar function of the time step. Some -operations accept `Variant` values for certain parameters, such as the -``kT`` parameter to `hoomd.md.methods.thermostats.Bussi`. - -See `Variant` for detains on creating user-defined variants or use one of the -provided subclasses. -""" import typing from hoomd import _hoomd diff --git a/sphinx-doc/module-hoomd-variant-box.rst b/sphinx-doc/module-hoomd-variant-box.rst new file mode 100644 index 0000000000..a7693e715e --- /dev/null +++ b/sphinx-doc/module-hoomd-variant-box.rst @@ -0,0 +1,31 @@ +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. +.. Part of HOOMD-blue, released under the BSD 3-Clause License. + +hoomd.variant.box +----------------- + +.. rubric:: Overview + +.. py:currentmodule:: hoomd.variant.box + +.. autosummary:: + :nosignatures: + + BoxVariant + Constant + Interpolate + InverseVolumeRamp + +.. rubric:: Details + +.. automodule:: hoomd.variant.box + :synopsis: Box variants. + :no-members: + + .. autoclass:: BoxVariant() + .. autoclass:: Constant(box) + :show-inheritance: + .. autoclass:: Interpolate(initial_box, final_box, variant) + :show-inheritance: + .. autoclass:: InverseVolumeRamp(initial_box, final_volume, t_start, t_ramp) + :show-inheritance: diff --git a/sphinx-doc/module-hoomd-variant.rst b/sphinx-doc/module-hoomd-variant.rst index 410fdb514c..3ca75c2e9b 100644 --- a/sphinx-doc/module-hoomd-variant.rst +++ b/sphinx-doc/module-hoomd-variant.rst @@ -39,3 +39,11 @@ hoomd.variant .. autoclass:: Variant() :members: min, max, __getstate__, __setstate__ .. autodata:: variant_like + + +.. rubric:: Modules + +.. toctree:: + :maxdepth: 1 + + module-hoomd-variant-box