From 8cde22d43bacf130e2202ea6ff7aee6204ea028c Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Fri, 15 Dec 2023 16:27:28 -0500 Subject: [PATCH 01/37] Add VectorVariant class Adds a VectorVariant class. Adds VectorVariantBox as a subclass of VectorVariant. Implements VectorVariantBoxConstant and VectorVariantBoxLinear. Moves variant from a module to a package. The code compiles and I am able to get the expected boxes as a function of time for both VectorVariantBoxConstant and VectorVariantBoxLinear objects. More to come before I open a PR. --- hoomd/CMakeLists.txt | 4 +- hoomd/Variant.h | 6 +- hoomd/VectorVariant.cc | 61 ++++++ hoomd/VectorVariant.h | 224 ++++++++++++++++++++++ hoomd/module.cc | 4 + hoomd/variant/CMakeLists.txt | 9 + hoomd/{variant.py => variant/__init__.py} | 1 + hoomd/variant/box.py | 2 + 8 files changed, 307 insertions(+), 4 deletions(-) create mode 100644 hoomd/VectorVariant.cc create mode 100644 hoomd/VectorVariant.h create mode 100644 hoomd/variant/CMakeLists.txt rename hoomd/{variant.py => variant/__init__.py} (99%) create mode 100644 hoomd/variant/box.py 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 29f772f6ae..04a6f56b64 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..1b4826c159 --- /dev/null +++ b/hoomd/VectorVariant.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2009-2023 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 + { + +void export_VectorVariantBox(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, "VectorVariantBoxLinear") + .def(pybind11::init, std::shared_ptr, uint64_t, uint64_t>()) + .def_property("box1", &VectorVariantBoxLinear::getBox1, &VectorVariantBoxLinear::setBox1) + .def_property("box2", &VectorVariantBoxLinear::getBox2, &VectorVariantBoxLinear::setBox2) + .def_property("t_start", + &VectorVariantBoxLinear::getTStart, + &VectorVariantBoxLinear::setTStart) + .def_property("t_ramp", + &VectorVariantBoxLinear::getTRamp, + &VectorVariantBoxLinear::setTRamp); + } + + } // end namespace detail + + } // end namespace hoomd diff --git a/hoomd/VectorVariant.h b/hoomd/VectorVariant.h new file mode 100644 index 0000000000..b1cae0c973 --- /dev/null +++ b/hoomd/VectorVariant.h @@ -0,0 +1,224 @@ +// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +// Copyright (c) 2009-2023 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" + +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; + } + }; + +class PYBIND11_EXPORT VectorVariantBox : public VectorVariant<6> + { + }; + +class PYBIND11_EXPORT VectorVariantBoxConstant : public VectorVariantBox + { + public: + /** Construct a VectorVariantBoxConstant. + + @param values The values. + */ + VectorVariantBoxConstant(std::shared_ptr box) + : m_value({box->getL().x, + box->getL().y, + box->getL().z, + box->getTiltFactorXY(), + box->getTiltFactorXZ(), + box->getTiltFactorYZ()}), + m_box(box) + { + } + + virtual ~VectorVariantBoxConstant() { } + + /// Return the value. + virtual array_type operator()(uint64_t timestep) + { + return m_value; + } + + std::shared_ptr getBox() + { + return m_box; + } + + void setBox(std::shared_ptr box) + { + m_box = box; + m_value = {box->getL().x, + box->getL().y, + box->getL().z, + box->getTiltFactorXY(), + box->getTiltFactorXZ(), + box->getTiltFactorYZ()}; + } + + protected: + std::array m_value; + std::shared_ptr m_box; + }; + +class PYBIND11_EXPORT VectorVariantBoxLinear : public VectorVariantBox + { + public: + /** Construct a VectorVariantBoxLinear to interpolate between two boxes linearly in time. + + @param box1 The initial box + @param box2 The final box + */ + VectorVariantBoxLinear(std::shared_ptr box1, + std::shared_ptr box2, + uint64_t t_start, + uint64_t t_ramp) + : m_box1(box1), m_box2(box2), m_t_start(t_start), m_t_ramp(t_ramp) + { + } + + /// Return the value. + virtual array_type operator()(uint64_t timestep) + { + if (timestep < m_t_start) + { + return std::array {m_box1->getL().x, + m_box1->getL().y, + m_box1->getL().z, + m_box1->getTiltFactorXY(), + m_box1->getTiltFactorXZ(), + m_box1->getTiltFactorYZ()}; + } + else if (timestep >= m_t_start + m_t_ramp) + { + return std::array {m_box2->getL().x, + m_box2->getL().y, + m_box2->getL().z, + m_box2->getTiltFactorXY(), + m_box2->getTiltFactorXZ(), + m_box2->getTiltFactorYZ()}; + } + else + { + double s = double(timestep - m_t_start) / double(m_t_ramp); + std::array value; + Scalar3 L1 = m_box1->getL(); + Scalar3 L2 = m_box2->getL(); + Scalar xy1 = m_box1->getTiltFactorXY(); + Scalar xy2 = m_box2->getTiltFactorXY(); + Scalar xz1 = m_box1->getTiltFactorXZ(); + Scalar xz2 = m_box2->getTiltFactorXZ(); + Scalar yz1 = m_box1->getTiltFactorYZ(); + Scalar yz2 = m_box2->getTiltFactorYZ(); + value[0] = L2.x * s + L1.x * (1.0 - s); + value[1] = L2.y * s + L1.y * (1.0 - s); + value[2] = L2.z * s + L1.z * (1.0 - s); + value[3] = xy2 * s + xy1 * (1.0 - s); + value[4] = xz2 * s + xz1 * (1.0 - s); + value[5] = yz2 * s + yz1 * (1.0 - s); + return value; + } + } + + std::shared_ptr getBox1() + { + return m_box1; + } + + void setBox1(std::shared_ptr box) + { + m_box1 = box; + } + + std::shared_ptr getBox2() + { + return m_box2; + } + + void setBox2(std::shared_ptr box) + { + m_box2 = box; + } + + /// Set the starting time step. + void setTStart(uint64_t t_start) + { + m_t_start = t_start; + } + + /// Get the starting time step. + uint64_t getTStart() const + { + return m_t_start; + } + + /// Set the length of the ramp. + void setTRamp(uint64_t t_ramp) + { + // doubles can only represent integers accuracy up to 2**53. + if (t_ramp >= 9007199254740992ull) + { + throw std::invalid_argument("t_ramp must be less than 2**53"); + } + m_t_ramp = t_ramp; + } + + /// Get the length of the ramp. + uint64_t getTRamp() const + { + return m_t_ramp; + } + + protected: + /// The starting box. + std::shared_ptr m_box1; + + /// The final box. + std::shared_ptr m_box2; + + /// The starting time step. + uint64_t m_t_start; + + /// The length of the ramp. + uint64_t m_t_ramp; + }; + +namespace detail + { +/// Export Variant classes to Python +void export_VectorVariantBox(pybind11::module& m); +void export_VectorVariantBoxConstant(pybind11::module& m); + + } // end namespace detail + + } // end namespace hoomd diff --git a/hoomd/module.cc b/hoomd/module.cc index e779e765a7..dbfb2e76ef 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_VectorVariantBox(m); + // messenger export_Messenger(m); } diff --git a/hoomd/variant/CMakeLists.txt b/hoomd/variant/CMakeLists.txt new file mode 100644 index 0000000000..8f2cad5efc --- /dev/null +++ b/hoomd/variant/CMakeLists.txt @@ -0,0 +1,9 @@ +set(files __init__.py + box.py + ) + +install(FILES ${files} + DESTINATION ${PYTHON_SITE_INSTALL_DIR}/variant + ) + +copy_files_to_build("${files}" "variant" "*.py") diff --git a/hoomd/variant.py b/hoomd/variant/__init__.py similarity index 99% rename from hoomd/variant.py rename to hoomd/variant/__init__.py index 9e075275eb..30b1d15a2a 100644 --- a/hoomd/variant.py +++ b/hoomd/variant/__init__.py @@ -13,6 +13,7 @@ import typing from hoomd import _hoomd +from . import box class Variant(_hoomd.Variant): diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py new file mode 100644 index 0000000000..d3ed08d68e --- /dev/null +++ b/hoomd/variant/box.py @@ -0,0 +1,2 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. From a6157765cd8f1f8ad5b1e6e8fce30a7312fa981a Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Mon, 18 Dec 2023 15:09:53 -0500 Subject: [PATCH 02/37] Add a linear density ramp box variant --- hoomd/VectorVariant.cc | 16 +++++ hoomd/VectorVariant.h | 137 ++++++++++++++++++++++++++++++++++++++ hoomd/__init__.py | 1 + hoomd/variant/__init__.py | 3 +- hoomd/variant/box.py | 71 ++++++++++++++++++++ 5 files changed, 226 insertions(+), 2 deletions(-) diff --git a/hoomd/VectorVariant.cc b/hoomd/VectorVariant.cc index 1b4826c159..17b5a0aa24 100644 --- a/hoomd/VectorVariant.cc +++ b/hoomd/VectorVariant.cc @@ -54,6 +54,22 @@ void export_VectorVariantBox(pybind11::module& m) .def_property("t_ramp", &VectorVariantBoxLinear::getTRamp, &VectorVariantBoxLinear::setTRamp); + + pybind11::class_>( + m, + "VectorVariantBoxInverseVolumeRamp") + .def(pybind11::init, Scalar, uint64_t, uint64_t>()) + .def_property("box1", + &VectorVariantBoxInverseVolumeRamp::getBox1, + &VectorVariantBoxInverseVolumeRamp::setBox1) + .def_property("t_start", + &VectorVariantBoxInverseVolumeRamp::getTStart, + &VectorVariantBoxInverseVolumeRamp::setTStart) + .def_property("t_ramp", + &VectorVariantBoxInverseVolumeRamp::getTRamp, + &VectorVariantBoxInverseVolumeRamp::setTRamp); } } // end namespace detail diff --git a/hoomd/VectorVariant.h b/hoomd/VectorVariant.h index b1cae0c973..aea106d552 100644 --- a/hoomd/VectorVariant.h +++ b/hoomd/VectorVariant.h @@ -42,6 +42,16 @@ template class PYBIND11_EXPORT VectorVariant class PYBIND11_EXPORT VectorVariantBox : public VectorVariant<6> { + protected: + 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()}; + } }; class PYBIND11_EXPORT VectorVariantBoxConstant : public VectorVariantBox @@ -213,6 +223,133 @@ class PYBIND11_EXPORT VectorVariantBoxLinear : public VectorVariantBox uint64_t m_t_ramp; }; +class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBox + { + public: + VectorVariantBoxInverseVolumeRamp(std::shared_ptr box1, + Scalar final_volume, + uint64_t t_start, + uint64_t t_ramp) + : m_box1(box1), m_final_volume(final_volume), m_t_start(t_start), m_t_ramp(t_ramp) + { + m_is2D = m_box1->getL().z == 0; + m_vol1 = m_box1->getVolume(m_is2D); + } + + virtual array_type operator()(uint64_t timestep) + { + if (timestep < m_t_start) + { + return box_to_array(m_box1); + } + else if (timestep >= m_t_start + m_t_ramp) + { + Scalar scale; + if (m_is2D) + { + scale = pow(m_final_volume / m_vol1, Scalar(1.0 / 2.0)); + } + else + { + scale = pow(m_final_volume / m_vol1, Scalar(1.0 / 3.0)); + } + std::array value; + Scalar3 L1 = m_box1->getL(); + value[0] = L1.x * scale; + value[1] = L1.y * scale; + value[2] = L1.z * scale; + value[3] = m_box1->getTiltFactorXY(); + value[4] = m_box1->getTiltFactorXZ(); + value[5] = m_box1->getTiltFactorYZ(); + return value; + } + else + { + double s = double(timestep - m_t_start) / double(m_t_ramp); + Scalar current_volume = s * m_final_volume + (1.0 - s) * m_vol1; + Scalar scale; + if (m_is2D) + { + scale = pow(current_volume / m_vol1, Scalar(1.0 / 2.0)); + } + else + { + scale = pow(current_volume / m_vol1, Scalar(1.0 / 3.0)); + } + + std::array value; + Scalar3 L1 = m_box1->getL(); + value[0] = L1.x * scale; + value[1] = L1.y * scale; + value[2] = L1.z * scale; + value[3] = m_box1->getTiltFactorXY(); + value[4] = m_box1->getTiltFactorXZ(); + value[5] = m_box1->getTiltFactorYZ(); + return value; + } + } + + std::shared_ptr getBox1() + { + return m_box1; + } + + void setBox1(std::shared_ptr box) + { + m_box1 = box; + } + + /// Set the starting time step. + void setTStart(uint64_t t_start) + { + m_t_start = t_start; + } + + /// Get the starting time step. + uint64_t getTStart() const + { + return m_t_start; + } + + /// Set the length of the ramp. + void setTRamp(uint64_t t_ramp) + { + // doubles can only represent integers accuracy up to 2**53. + if (t_ramp >= 9007199254740992ull) + { + throw std::invalid_argument("t_ramp must be less than 2**53"); + } + m_t_ramp = t_ramp; + } + + /// Get the length of the ramp. + uint64_t getTRamp() const + { + return m_t_ramp; + } + + protected: + /// The starting box. + std::shared_ptr m_box1; + + /// The volume of box1. + Scalar m_vol1; + + /// The volume of the box at the end of the ramp. + Scalar m_final_volume; + + /// The starting time step. + uint64_t m_t_start; + + /// The length of the ramp. + uint64_t m_t_ramp; + + /// Whether box1 is 2-dimensional or not + bool m_is2D; + + Scalar m_current_volume; + }; + namespace detail { /// Export Variant classes to Python diff --git a/hoomd/__init__.py b/hoomd/__init__.py index 6513b84665..448abfc948 100644 --- a/hoomd/__init__.py +++ b/hoomd/__init__.py @@ -61,6 +61,7 @@ from hoomd import trigger from hoomd import variant from hoomd.box import Box, box_like +from hoomd.variant import box from hoomd import data from hoomd import filter from hoomd import device diff --git a/hoomd/variant/__init__.py b/hoomd/variant/__init__.py index 30b1d15a2a..5e07ee4511 100644 --- a/hoomd/variant/__init__.py +++ b/hoomd/variant/__init__.py @@ -7,13 +7,12 @@ 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 +See `Variant` for details on creating user-defined variants or use one of the provided subclasses. """ import typing from hoomd import _hoomd -from . import box class Variant(_hoomd.Variant): diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index d3ed08d68e..3fad1fc5f2 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -1,2 +1,73 @@ # Copyright (c) 2009-2023 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 +from hoomd.data.typeconverter import box_preprocessing + + +class Box(_hoomd.VectorVariantBox): + """Box-like vector variant base class. + + Provides an interface to length-6 vector variants. The return value of the + ``__call__`` method returns a length-6 array of scalar values representing + the ``Box`` attributes ``Lx``, ``Ly``, ``Lz``, ``xy``, ``xz``, and ``yz``. + """ + pass + + +class Constant(_hoomd.VectorVariantBoxConstant): + """A constant box variant. + + Args: + box (hoomd.box.box_like): The box. + """ + + def __init__(self, box): + box = box_preprocessing(box) + _hoomd.VectorVariantBoxConstant.__init__(self, box._cpp_obj) + + +class Ramp(_hoomd.VectorVariantBoxLinear): + """Interpolate between two boxes linearly in time. + + Args: + initial_box (hoomd.box.box_like): The initial box. + final_box (hoomd.box.box_like): The final box. + t_start (int): The time step at the start of the ramp. + t_ramp (int): The length of the ramp. + + Ramp returns the array corresponding to *initial_box* for + :math:`t \\leq t_{\\mathrm{start}}` and *final_box* for + :math:`t \\geq t_{\\mathrm{start}} + t_{\\mathrm{ramp}}`. + """ + + def __init__(self, initial_box, final_box, t_start, t_ramp): + box1 = box_preprocessing(initial_box) + box2 = box_preprocessing(final_box) + _hoomd.VectorVariantBoxLinear.__init__(self, box1._cpp_obj, + box2._cpp_obj, t_start, t_ramp) + + +class LinearInverseVolume(_hoomd.VectorVariantBoxInverseVolumeRamp): + """Produce dependent box arrays whose inverse volume is linear in time. + + ``LinearInverseVolume`` produces box arrays that correspond to a box whose + **inverse volume** (i.e., density for a constant number of particles) varies + linearly with time. 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. + + 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. + """ + + def __init__(self, initial_box, final_volume, t_start, t_ramp): + box = box_preprocessing(initial_box) + _hoomd.VectorVariantBoxInverseVolumeRamp.__init__( + self, box._cpp_obj, final_volume, t_start, t_ramp) From 91acbe93c01345eb89c5a5ead5587bcba357ed24 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Mon, 18 Dec 2023 16:41:44 -0500 Subject: [PATCH 03/37] First bit of sphinx docs for box variants --- sphinx-doc/module-hoomd-variant-box.rst | 30 +++++++++++++++++++++++++ sphinx-doc/module-hoomd-variant.rst | 10 +++++++++ 2 files changed, 40 insertions(+) create mode 100644 sphinx-doc/module-hoomd-variant-box.rst diff --git a/sphinx-doc/module-hoomd-variant-box.rst b/sphinx-doc/module-hoomd-variant-box.rst new file mode 100644 index 0000000000..ab808d7874 --- /dev/null +++ b/sphinx-doc/module-hoomd-variant-box.rst @@ -0,0 +1,30 @@ +.. Copyright (c) 2009-2023 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: + + Box + Constant + Ramp + LinearInverseVolume + +.. rubric:: Details + +.. automodule:: hoomd.variant.box + :synopsis: Box variants. + :no-members: + + .. autoclass:: Constant(box) + :show-inheritance: + .. autoclass:: Ramp(initial_box, final_box, t_start, t_ramp) + :show-inheritance: + .. autoclass:: LinearInverseVolume(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 fc5dc68167..a09fbf6e5f 100644 --- a/sphinx-doc/module-hoomd-variant.rst +++ b/sphinx-doc/module-hoomd-variant.rst @@ -39,3 +39,13 @@ hoomd.variant .. autoclass:: Variant() :members: min, max, __getstate__, __setstate__ .. autodata:: variant_like + +.. autosummary:: + :nosignatures: + +.. rubric:: Modules + +.. toctree:: + :maxdepth: 1 + + module-hoomd-variant-box From 4dceea3a53eb8b3cb6e902e27d847e5976016335 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 19 Dec 2023 12:35:06 -0500 Subject: [PATCH 04/37] More sphinx documentation --- hoomd/VectorVariant.cc | 34 ++++++++++++++++++++--- hoomd/variant/box.py | 36 ++++++++++++++++++------- sphinx-doc/module-hoomd-variant-box.rst | 1 + 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/hoomd/VectorVariant.cc b/hoomd/VectorVariant.cc index 17b5a0aa24..ec05074c31 100644 --- a/hoomd/VectorVariant.cc +++ b/hoomd/VectorVariant.cc @@ -40,14 +40,42 @@ void export_VectorVariantBox(pybind11::module& m) VectorVariantBox, std::shared_ptr>(m, "VectorVariantBoxConstant") .def(pybind11::init>()) - .def_property("box", &VectorVariantBoxConstant::getBox, &VectorVariantBoxConstant::setBox); + .def_property("box", &VectorVariantBoxConstant::getBox, &VectorVariantBoxConstant::setBox) + /* + .def( + pybind11::pickle( + [](VectorVariantBoxConstant& variant) + { + std::shared_ptr box = variant.getBox(); + std::array arr = {box->getL().x, + box->getL().y, + box->getL().z, + box->getTiltFactorXY(), + box->getTiltFactorXZ(), + box->getTiltFactorYZ()}; + return pybind11::make_tuple(arr); + }, + [](pybind11::tuple params) + { + Scalar Lx = params[0][0].cast(); + Scalar Ly = params[0][1].cast(); + Scalar Lz = params[0][2].cast(); + Scalar xy = params[0][3].cast(); + Scalar xz = params[0][4].cast(); + Scalar yz = params[0][5].cast(); + std::shared_ptr box(Lx, Ly, Lz); + box->setTiltFactors(xy, xz, yz); + return VectorVariantBoxConstant(box); + })); + */ + ; pybind11::class_>(m, "VectorVariantBoxLinear") .def(pybind11::init, std::shared_ptr, uint64_t, uint64_t>()) - .def_property("box1", &VectorVariantBoxLinear::getBox1, &VectorVariantBoxLinear::setBox1) - .def_property("box2", &VectorVariantBoxLinear::getBox2, &VectorVariantBoxLinear::setBox2) + .def_property("initial_box", &VectorVariantBoxLinear::getBox1, &VectorVariantBoxLinear::setBox1) + .def_property("final_box", &VectorVariantBoxLinear::getBox2, &VectorVariantBoxLinear::setBox2) .def_property("t_start", &VectorVariantBoxLinear::getTStart, &VectorVariantBoxLinear::setTStart) diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index 3fad1fc5f2..bf22b53d59 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -10,20 +10,27 @@ class Box(_hoomd.VectorVariantBox): """Box-like vector variant base class. - Provides an interface to length-6 vector variants. The return value of the - ``__call__`` method returns a length-6 array of scalar values representing - the ``Box`` attributes ``Lx``, ``Ly``, ``Lz``, ``xy``, ``xz``, and ``yz``. + `hoomd.variant.box.Box` provides an interface to length-6 vector variants that + are valid `hoomd.box.box_like` objects. + The return value of the ``__call__`` method returns a length-6 array of + scalar values that represent the quantities ``Lx``, ``Ly``, ``Lz``, + ``xy``, ``xz``, and ``yz`` of a simulation box. """ pass -class Constant(_hoomd.VectorVariantBoxConstant): +class Constant(_hoomd.VectorVariantBoxConstant, Box): """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. + + Attributes: + box (hoomd.Box): The box. + """ def __init__(self, box): box = box_preprocessing(box) _hoomd.VectorVariantBoxConstant.__init__(self, box._cpp_obj) @@ -41,6 +48,12 @@ class Ramp(_hoomd.VectorVariantBoxLinear): Ramp returns the array corresponding to *initial_box* for :math:`t \\leq t_{\\mathrm{start}}` and *final_box* for :math:`t \\geq t_{\\mathrm{start}} + t_{\\mathrm{ramp}}`. + + Attributes: + initial_box (hoomd.Box): The initial box. + final_box (hoomd.Box): 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_box, t_start, t_ramp): @@ -53,6 +66,12 @@ def __init__(self, initial_box, final_box, t_start, t_ramp): class LinearInverseVolume(_hoomd.VectorVariantBoxInverseVolumeRamp): """Produce dependent box arrays whose inverse volume is linear in time. + 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. + ``LinearInverseVolume`` produces box arrays that correspond to a box whose **inverse volume** (i.e., density for a constant number of particles) varies linearly with time. The shape of the box remains constant, that is, the @@ -60,13 +79,12 @@ class LinearInverseVolume(_hoomd.VectorVariantBoxInverseVolumeRamp): :math:`L_z / L_x` and the tilt factors (:math:`xy`, :math:`xz`, :math:`yz`) remain constant. - Args: - initial_box (hoomd.box.box_like): The initial box. - final_volume (float): The final volume of the box. + Attributes: + initial_box (hoomd.Box): The initial box. + 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): box = box_preprocessing(initial_box) _hoomd.VectorVariantBoxInverseVolumeRamp.__init__( diff --git a/sphinx-doc/module-hoomd-variant-box.rst b/sphinx-doc/module-hoomd-variant-box.rst index ab808d7874..72d7ecccec 100644 --- a/sphinx-doc/module-hoomd-variant-box.rst +++ b/sphinx-doc/module-hoomd-variant-box.rst @@ -22,6 +22,7 @@ hoomd.variant.box :synopsis: Box variants. :no-members: + .. autoclass:: Box() .. autoclass:: Constant(box) :show-inheritance: .. autoclass:: Ramp(initial_box, final_box, t_start, t_ramp) From f5bd904d99168fb45ffdad487ca8e1690cb08cee Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 19 Dec 2023 13:15:03 -0500 Subject: [PATCH 05/37] Run pre-commit --- hoomd/VectorVariant.cc | 64 ++++++++++++++++++++++-------------------- hoomd/variant/box.py | 12 ++++---- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/hoomd/VectorVariant.cc b/hoomd/VectorVariant.cc index ec05074c31..e9058948b7 100644 --- a/hoomd/VectorVariant.cc +++ b/hoomd/VectorVariant.cc @@ -41,41 +41,45 @@ void export_VectorVariantBox(pybind11::module& m) std::shared_ptr>(m, "VectorVariantBoxConstant") .def(pybind11::init>()) .def_property("box", &VectorVariantBoxConstant::getBox, &VectorVariantBoxConstant::setBox) - /* - .def( - pybind11::pickle( - [](VectorVariantBoxConstant& variant) - { - std::shared_ptr box = variant.getBox(); - std::array arr = {box->getL().x, - box->getL().y, - box->getL().z, - box->getTiltFactorXY(), - box->getTiltFactorXZ(), - box->getTiltFactorYZ()}; - return pybind11::make_tuple(arr); - }, - [](pybind11::tuple params) - { - Scalar Lx = params[0][0].cast(); - Scalar Ly = params[0][1].cast(); - Scalar Lz = params[0][2].cast(); - Scalar xy = params[0][3].cast(); - Scalar xz = params[0][4].cast(); - Scalar yz = params[0][5].cast(); - std::shared_ptr box(Lx, Ly, Lz); - box->setTiltFactors(xy, xz, yz); - return VectorVariantBoxConstant(box); - })); - */ - ; + /* + .def( + pybind11::pickle( + [](VectorVariantBoxConstant& variant) + { + std::shared_ptr box = variant.getBox(); + std::array arr = {box->getL().x, + box->getL().y, + box->getL().z, + box->getTiltFactorXY(), + box->getTiltFactorXZ(), + box->getTiltFactorYZ()}; + return pybind11::make_tuple(arr); + }, + [](pybind11::tuple params) + { + Scalar Lx = params[0][0].cast(); + Scalar Ly = params[0][1].cast(); + Scalar Lz = params[0][2].cast(); + Scalar xy = params[0][3].cast(); + Scalar xz = params[0][4].cast(); + Scalar yz = params[0][5].cast(); + std::shared_ptr box(Lx, Ly, Lz); + box->setTiltFactors(xy, xz, yz); + return VectorVariantBoxConstant(box); + })); + */ + ; pybind11::class_>(m, "VectorVariantBoxLinear") .def(pybind11::init, std::shared_ptr, uint64_t, uint64_t>()) - .def_property("initial_box", &VectorVariantBoxLinear::getBox1, &VectorVariantBoxLinear::setBox1) - .def_property("final_box", &VectorVariantBoxLinear::getBox2, &VectorVariantBoxLinear::setBox2) + .def_property("initial_box", + &VectorVariantBoxLinear::getBox1, + &VectorVariantBoxLinear::setBox1) + .def_property("final_box", + &VectorVariantBoxLinear::getBox2, + &VectorVariantBoxLinear::setBox2) .def_property("t_start", &VectorVariantBoxLinear::getTStart, &VectorVariantBoxLinear::setTStart) diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index bf22b53d59..00b8b370e2 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -10,11 +10,11 @@ class Box(_hoomd.VectorVariantBox): """Box-like vector variant base class. - `hoomd.variant.box.Box` provides an interface to length-6 vector variants that - are valid `hoomd.box.box_like` objects. - The return value of the ``__call__`` method returns a length-6 array of - scalar values that represent the quantities ``Lx``, ``Ly``, ``Lz``, - ``xy``, ``xz``, and ``yz`` of a simulation box. + `hoomd.variant.box.Box` provides an interface to length-6 vector variants + that are valid `hoomd.box.box_like` objects. The return value of the + ``__call__`` method returns a length-6 array of scalar values that represent + the quantities ``Lx``, ``Ly``, ``Lz``, ``xy``, ``xz``, and ``yz`` of a + simulation box. """ pass @@ -31,6 +31,7 @@ class Constant(_hoomd.VectorVariantBoxConstant, Box): Attributes: box (hoomd.Box): The box. """ + def __init__(self, box): box = box_preprocessing(box) _hoomd.VectorVariantBoxConstant.__init__(self, box._cpp_obj) @@ -85,6 +86,7 @@ class LinearInverseVolume(_hoomd.VectorVariantBoxInverseVolumeRamp): 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): box = box_preprocessing(initial_box) _hoomd.VectorVariantBoxInverseVolumeRamp.__init__( From 152459aa95e4c4c5bae8e83fe8159c01fd1314b8 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 19 Dec 2023 13:30:03 -0500 Subject: [PATCH 06/37] Fix references in sphinx-doc --- hoomd/hpmc/update.py | 2 +- hoomd/update/box_resize.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hoomd/hpmc/update.py b/hoomd/hpmc/update.py index 804c220fa0..5bf42ac0c2 100644 --- a/hoomd/hpmc/update.py +++ b/hoomd/hpmc/update.py @@ -860,7 +860,7 @@ class QuickCompress(Updater): Attributes: trigger (Trigger): Update the box dimensions on triggered time steps. - target_box (Box): Dimensions of the target box. + target_box (hoomd.Box): Dimensions of the target box. max_overlaps_per_particle (float): The maximum number of overlaps to allow per particle (may be less than 1 - e.g. diff --git a/hoomd/update/box_resize.py b/hoomd/update/box_resize.py index d3cc1c2e42..17a7a73825 100644 --- a/hoomd/update/box_resize.py +++ b/hoomd/update/box_resize.py @@ -185,7 +185,7 @@ def get_box(self, timestep): box. Returns: - Box: The box used at the given timestep. + hoomd.Box: The box used at the given timestep. `None` before the first call to `Simulation.run`. .. rubric:: Example: From 19d17a8589de368b04530320b37532ede8bf8fff Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 4 Jan 2024 11:14:05 -0500 Subject: [PATCH 07/37] Apply suggestions from code review Co-authored-by: Joshua A. Anderson --- hoomd/VectorVariant.h | 2 +- sphinx-doc/module-hoomd-variant.rst | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/hoomd/VectorVariant.h b/hoomd/VectorVariant.h index aea106d552..2d9d0b703d 100644 --- a/hoomd/VectorVariant.h +++ b/hoomd/VectorVariant.h @@ -43,7 +43,7 @@ template class PYBIND11_EXPORT VectorVariant class PYBIND11_EXPORT VectorVariantBox : public VectorVariant<6> { protected: - std::array box_to_array(std::shared_ptr box) + static std::array box_to_array(std::shared_ptr box) { return std::array {box->getL().x, box->getL().y, diff --git a/sphinx-doc/module-hoomd-variant.rst b/sphinx-doc/module-hoomd-variant.rst index a09fbf6e5f..e9f916d5cb 100644 --- a/sphinx-doc/module-hoomd-variant.rst +++ b/sphinx-doc/module-hoomd-variant.rst @@ -40,8 +40,6 @@ hoomd.variant :members: min, max, __getstate__, __setstate__ .. autodata:: variant_like -.. autosummary:: - :nosignatures: .. rubric:: Modules From 366a98a2ff27f7273eba5c1f3e0e0d1aebcf2bf3 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 4 Jan 2024 12:02:30 -0500 Subject: [PATCH 08/37] Get rid of m_value in constant box variant --- hoomd/VectorVariant.h | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/hoomd/VectorVariant.h b/hoomd/VectorVariant.h index 2d9d0b703d..989f051e28 100644 --- a/hoomd/VectorVariant.h +++ b/hoomd/VectorVariant.h @@ -59,16 +59,10 @@ class PYBIND11_EXPORT VectorVariantBoxConstant : public VectorVariantBox public: /** Construct a VectorVariantBoxConstant. - @param values The values. + @param box The box. */ VectorVariantBoxConstant(std::shared_ptr box) - : m_value({box->getL().x, - box->getL().y, - box->getL().z, - box->getTiltFactorXY(), - box->getTiltFactorXZ(), - box->getTiltFactorYZ()}), - m_box(box) + : m_box(box) { } @@ -77,7 +71,7 @@ class PYBIND11_EXPORT VectorVariantBoxConstant : public VectorVariantBox /// Return the value. virtual array_type operator()(uint64_t timestep) { - return m_value; + return box_to_array(m_box); } std::shared_ptr getBox() @@ -88,16 +82,9 @@ class PYBIND11_EXPORT VectorVariantBoxConstant : public VectorVariantBox void setBox(std::shared_ptr box) { m_box = box; - m_value = {box->getL().x, - box->getL().y, - box->getL().z, - box->getTiltFactorXY(), - box->getTiltFactorXZ(), - box->getTiltFactorYZ()}; } protected: - std::array m_value; std::shared_ptr m_box; }; From ca60602999fccb7ad4906d421e714f7350bc956d Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 4 Jan 2024 14:22:41 -0500 Subject: [PATCH 09/37] Replace t_start/t_ramp with a vaiant in VectorVariantBoxLinear --- hoomd/VectorVariant.cc | 11 ++--- hoomd/VectorVariant.h | 97 +++++++++++++----------------------------- hoomd/variant/box.py | 12 +++--- 3 files changed, 40 insertions(+), 80 deletions(-) diff --git a/hoomd/VectorVariant.cc b/hoomd/VectorVariant.cc index e9058948b7..fd1f31fe42 100644 --- a/hoomd/VectorVariant.cc +++ b/hoomd/VectorVariant.cc @@ -73,19 +73,16 @@ void export_VectorVariantBox(pybind11::module& m) pybind11::class_>(m, "VectorVariantBoxLinear") - .def(pybind11::init, std::shared_ptr, uint64_t, uint64_t>()) + .def(pybind11::init, std::shared_ptr, std::shared_ptr>()) .def_property("initial_box", &VectorVariantBoxLinear::getBox1, &VectorVariantBoxLinear::setBox1) .def_property("final_box", &VectorVariantBoxLinear::getBox2, &VectorVariantBoxLinear::setBox2) - .def_property("t_start", - &VectorVariantBoxLinear::getTStart, - &VectorVariantBoxLinear::setTStart) - .def_property("t_ramp", - &VectorVariantBoxLinear::getTRamp, - &VectorVariantBoxLinear::setTRamp); + .def_property("variant", + &VectorVariantBoxLinear::getVariant, + &VectorVariantBoxLinear::setVariant); pybind11::class_ box1, std::shared_ptr box2, - uint64_t t_start, - uint64_t t_ramp) - : m_box1(box1), m_box2(box2), m_t_start(t_start), m_t_ramp(t_ramp) + std::shared_ptr variant) + : m_box1(box1), m_box2(box2), m_variant(variant) { } /// Return the value. virtual array_type operator()(uint64_t timestep) { - if (timestep < m_t_start) + Scalar min = m_variant->min(); + Scalar max = m_variant->max(); + Scalar cur_value = (*m_variant)(timestep); + Scalar scale = 0; + if (cur_value == max) { - return std::array {m_box1->getL().x, - m_box1->getL().y, - m_box1->getL().z, - m_box1->getTiltFactorXY(), - m_box1->getTiltFactorXZ(), - m_box1->getTiltFactorYZ()}; + scale = 1; } - else if (timestep >= m_t_start + m_t_ramp) - { - return std::array {m_box2->getL().x, - m_box2->getL().y, - m_box2->getL().z, - m_box2->getTiltFactorXY(), - m_box2->getTiltFactorXZ(), - m_box2->getTiltFactorYZ()}; - } - else + else if (cur_value > min) { - double s = double(timestep - m_t_start) / double(m_t_ramp); - std::array value; - Scalar3 L1 = m_box1->getL(); - Scalar3 L2 = m_box2->getL(); - Scalar xy1 = m_box1->getTiltFactorXY(); - Scalar xy2 = m_box2->getTiltFactorXY(); - Scalar xz1 = m_box1->getTiltFactorXZ(); - Scalar xz2 = m_box2->getTiltFactorXZ(); - Scalar yz1 = m_box1->getTiltFactorYZ(); - Scalar yz2 = m_box2->getTiltFactorYZ(); - value[0] = L2.x * s + L1.x * (1.0 - s); - value[1] = L2.y * s + L1.y * (1.0 - s); - value[2] = L2.z * s + L1.z * (1.0 - s); - value[3] = xy2 * s + xy1 * (1.0 - s); - value[4] = xz2 * s + xz1 * (1.0 - s); - value[5] = yz2 * s + yz1 * (1.0 - s); - return value; + scale = (cur_value - min) / (max - min); } + + const auto& box1 = *m_box1; + const auto& box2 = *m_box2; + Scalar3 new_L = box2.getL() * scale + box1.getL() * (1.0 - scale); + Scalar xy = box2.getTiltFactorXY() * scale + (1.0 - scale) * box1.getTiltFactorXY(); + Scalar xz = box2.getTiltFactorXZ() * scale + (1.0 - scale) * box1.getTiltFactorXZ(); + Scalar yz = box2.getTiltFactorYZ() * scale + (1.0 - scale) * box1.getTiltFactorYZ(); + array_type value = {new_L.x, new_L.y, new_L.z, xy, xz, yz}; + return value; } std::shared_ptr getBox1() @@ -167,47 +150,27 @@ class PYBIND11_EXPORT VectorVariantBoxLinear : public VectorVariantBox m_box2 = box; } - /// Set the starting time step. - void setTStart(uint64_t t_start) + /// Set the variant for interpolation + void setVariant(std::shared_ptr variant) { - m_t_start = t_start; + m_variant = variant; } - /// Get the starting time step. - uint64_t getTStart() const + /// Get the variant for interpolation + std::shared_ptr getVariant() { - return m_t_start; - } - - /// Set the length of the ramp. - void setTRamp(uint64_t t_ramp) - { - // doubles can only represent integers accuracy up to 2**53. - if (t_ramp >= 9007199254740992ull) - { - throw std::invalid_argument("t_ramp must be less than 2**53"); - } - m_t_ramp = t_ramp; - } - - /// Get the length of the ramp. - uint64_t getTRamp() const - { - return m_t_ramp; + return m_variant; } protected: - /// The starting box. + /// The starting box, associated with the minimum of the variant. std::shared_ptr m_box1; - /// The final box. + /// The final box, associated with the maximum of the variant. std::shared_ptr m_box2; - /// The starting time step. - uint64_t m_t_start; - - /// The length of the ramp. - uint64_t m_t_ramp; + /// Variant that interpolates between boxes. + std::shared_ptr m_variant; }; class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBox diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index 00b8b370e2..da8957cc5c 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -43,8 +43,8 @@ class Ramp(_hoomd.VectorVariantBoxLinear): Args: initial_box (hoomd.box.box_like): The initial box. final_box (hoomd.box.box_like): The final box. - t_start (int): The time step at the start of the ramp. - t_ramp (int): The length of the ramp. + variant (hoomd.variant.variant_like): A variant used to interpolate + between the two boxes. Ramp returns the array corresponding to *initial_box* for :math:`t \\leq t_{\\mathrm{start}}` and *final_box* for @@ -53,15 +53,15 @@ class Ramp(_hoomd.VectorVariantBoxLinear): Attributes: initial_box (hoomd.Box): The initial box. final_box (hoomd.Box): The final box. - t_start (int): The time step at the start of the ramp. - t_ramp (int): The length of the ramp. + variant (hoomd.variant.Variant): A variant used to interpolate between + the two boxes. """ - def __init__(self, initial_box, final_box, t_start, t_ramp): + def __init__(self, initial_box, final_box, variant): box1 = box_preprocessing(initial_box) box2 = box_preprocessing(final_box) _hoomd.VectorVariantBoxLinear.__init__(self, box1._cpp_obj, - box2._cpp_obj, t_start, t_ramp) + box2._cpp_obj, variant) class LinearInverseVolume(_hoomd.VectorVariantBoxInverseVolumeRamp): From 7701e308c0484bbfa41c47b1b1ddd9f4cae80ee5 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 4 Jan 2024 14:25:00 -0500 Subject: [PATCH 10/37] Fix imports --- hoomd/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hoomd/__init__.py b/hoomd/__init__.py index 448abfc948..6513b84665 100644 --- a/hoomd/__init__.py +++ b/hoomd/__init__.py @@ -61,7 +61,6 @@ from hoomd import trigger from hoomd import variant from hoomd.box import Box, box_like -from hoomd.variant import box from hoomd import data from hoomd import filter from hoomd import device From e114e20fd6e03e33eaf407fc677edb02925d5c0e Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 4 Jan 2024 16:51:40 -0500 Subject: [PATCH 11/37] Attempt to fix imports --- hoomd/variant/__init__.py | 1 + hoomd/variant/box.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/hoomd/variant/__init__.py b/hoomd/variant/__init__.py index 5e07ee4511..67a96dafee 100644 --- a/hoomd/variant/__init__.py +++ b/hoomd/variant/__init__.py @@ -11,6 +11,7 @@ provided subclasses. """ import typing +from . import box from hoomd import _hoomd diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index da8957cc5c..6e99765b52 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -4,7 +4,7 @@ """Implement variants that return box parameters as a function of time.""" from hoomd import _hoomd -from hoomd.data.typeconverter import box_preprocessing +from hoomd.data.typeconverter import box_preprocessing, variant_preprocessing class Box(_hoomd.VectorVariantBox): @@ -58,8 +58,9 @@ class Ramp(_hoomd.VectorVariantBoxLinear): """ def __init__(self, initial_box, final_box, variant): - box1 = box_preprocessing(initial_box) - box2 = box_preprocessing(final_box) + box1 = box_preprocessing(box1) + box2 = box_preprocessing(box2) + variant = variant_preprocessing(variant) _hoomd.VectorVariantBoxLinear.__init__(self, box1._cpp_obj, box2._cpp_obj, variant) From 31d55e36d5d416f63a92084247386d2cc16c94de Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Wed, 10 Jan 2024 17:18:42 -0500 Subject: [PATCH 12/37] Move scalar variants to new module Moves the scalar variant classes to a separate module, but imports them in hoomd/variant/__init__.py so that they still live in the hoomd.variant namespace. I did this as a workaround for python's import behavior since I kept getting circular import errors when keeping the scalar variant classes in the package's __init__.py file. --- hoomd/variant/CMakeLists.txt | 1 + hoomd/variant/__init__.py | 250 +---------------------------------- hoomd/variant/scalar.py | 247 ++++++++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+), 244 deletions(-) create mode 100644 hoomd/variant/scalar.py diff --git a/hoomd/variant/CMakeLists.txt b/hoomd/variant/CMakeLists.txt index 8f2cad5efc..1a26e157b7 100644 --- a/hoomd/variant/CMakeLists.txt +++ b/hoomd/variant/CMakeLists.txt @@ -1,4 +1,5 @@ set(files __init__.py + scalar.py box.py ) diff --git a/hoomd/variant/__init__.py b/hoomd/variant/__init__.py index 67a96dafee..84ea08f75f 100644 --- a/hoomd/variant/__init__.py +++ b/hoomd/variant/__init__.py @@ -10,248 +10,10 @@ See `Variant` for details on creating user-defined variants or use one of the provided subclasses. """ -import typing -from . import box -from hoomd import _hoomd - - -class Variant(_hoomd.Variant): - """Variant base class. - - Provides methods common to all variants and a base class for user-defined - variants. - - Subclasses should override the ``__call__``, ``_min``, and ``_max`` methods - and must explicitly call the base class constructor in ``__init__``: - - .. code-block:: python - - class CustomVariant(hoomd.variant.Variant): - def __init__(self): - hoomd.variant.Variant.__init__(self) - - def __call__(self, timestep): - return (float(timestep)**(1 / 2)) - - def _min(self): - return 0.0 - - def _max(self): - return float('inf') - - Note: - Provide the minimum and maximum values in the ``_min`` and ``_max`` - methods respectively. - - .. 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 - """ - - @property - def min(self): - """The minimum value of this variant for :math:`t \\in [0,\\infty)`.""" - return self._min() - - @property - def max(self): - """The maximum value of this variant for :math:`t \\in [0,\\infty)`.""" - return self._max() - - def __getstate__(self): - """Get the variant's ``__dict__`` attribute.""" - return self.__dict__ - - def __setstate__(self, state): - """Restore the state of the variant.""" - _hoomd.Variant.__init__(self) - self.__dict__ = state - - def _private_eq(self, other): - """Return whether two variants are equivalent.""" - if not isinstance(other, Variant): - return NotImplemented - if not isinstance(other, type(self)): - return False - return all( - getattr(self, attr) == getattr(other, attr) - for attr in self._eq_attrs) - - -class Constant(_hoomd.VariantConstant, Variant): - """A constant value. - - Args: - value (float): The value. - - `Constant` returns `value` at all time steps. - - .. rubric:: Example: - - .. code-block:: python - - variant = hoomd.variant.Constant(1.0) - - Attributes: - value (float): The value. - """ - _eq_attrs = ("value",) - - def __init__(self, value): - Variant.__init__(self) - _hoomd.VariantConstant.__init__(self, value) - - __eq__ = Variant._private_eq - - -class Ramp(_hoomd.VariantRamp, Variant): - """A linear ramp. - - Args: - A (float): The start value. - B (float): The end value. - t_start (int): The start time step. - t_ramp (int): The length of the ramp. - - `Ramp` holds the value *A* until time *t_start*. Then it ramps linearly from - *A* to *B* over *t_ramp* steps and holds the value *B*. - - .. image:: variant-ramp.svg - :alt: Example plot of a ramp variant. - - .. rubric:: Example: - - .. code-block:: python - - variant = hoomd.variant.Ramp(A=1.0, - B=2.0, - t_start=10_000, - t_ramp=100_000) - - Attributes: - A (float): The start value. - B (float): The end value. - t_start (int): The start time step. - t_ramp (int): The length of the ramp. - """ - _eq_attrs = ("A", "B", "t_start", "t_ramp") - - def __init__(self, A, B, t_start, t_ramp): - Variant.__init__(self) - _hoomd.VariantRamp.__init__(self, A, B, t_start, t_ramp) - - __eq__ = Variant._private_eq - - -class Cycle(_hoomd.VariantCycle, Variant): - """A cycle of linear ramps. - - Args: - A (float): The first value. - B (float): The second value. - t_start (int): The start time step. - t_A (int): The hold time at the first value. - t_AB (int): The time spent ramping from A to B. - t_B (int): The hold time at the second value. - t_BA (int): The time spent ramping from B to A. - - `Cycle` holds the value *A* until time *t_start*. It continues holding that - value until *t_start + t_A*. Then it ramps linearly from *A* to *B* over - *t_AB* steps and holds the value *B* for *t_B* steps. After this, it ramps - back from *B* to *A* over *t_BA* steps and repeats the cycle starting with - *t_A*. `Cycle` repeats this cycle indefinitely. - - .. image:: variant-cycle.svg - :alt: Example plot of a cycle variant. - - .. rubric:: Example: - - .. code-block:: python - - variant = hoomd.variant.Cycle(A=1.0, - B=2.0, - t_start=10_000, - t_A=100_000, - t_AB=1_000_000, - t_B=200_000, - t_BA=2_000_000) - - Attributes: - A (float): The first value. - B (float): The second value. - t_start (int): The start time step. - t_A (int): The holding time at A. - t_AB (int): The time spent ramping from A to B. - t_B (int): The holding time at B. - t_BA (int): The time spent ramping from B to A. - """ - _eq_attrs = ("A", "B", "t_start", "t_A", "t_AB", "t_B", "t_BA") - - def __init__(self, A, B, t_start, t_A, t_AB, t_B, t_BA): - Variant.__init__(self) - _hoomd.VariantCycle.__init__(self, A, B, t_start, t_A, t_AB, t_B, t_BA) - - __eq__ = Variant._private_eq - - -class Power(_hoomd.VariantPower, Variant): - """An approach from initial to final value following ``t**power``. - - Args: - A (float): The start value. - B (float): The end value. - power (float): The power of the approach to ``B``. - t_start (int): The start time step. - t_ramp (int): The length of the ramp. - - `Power` holds the value *A* until time *t_start*. Then it progresses at - :math:`t^{\\mathrm{power}}` from *A* to *B* over *t_ramp* steps and holds - the value *B* after that. - - .. image:: variant-power.svg - :alt: Example plot of a power variant. - - .. rubric:: Example: - - .. code-block:: python - - variant = hoomd.variant.Power(A=2, - B=8, - power=1 / 10, - t_start=10, t_ramp=20) - - Attributes: - A (float): The start value. - B (float): The end value. - power (float): The power of the approach to ``B``. - t_start (int): The start time step. - t_ramp (int): The length of the ramp. - """ - _eq_attrs = ("A", "B", "power", "t_start", "t_ramp") - - def __init__(self, A, B, power, t_start, t_ramp): - Variant.__init__(self) - _hoomd.VariantPower.__init__(self, A, B, power, t_start, t_ramp) - - __eq__ = Variant._private_eq - - -variant_like = typing.Union[Variant, float] -""" -Objects that are like a variant. - -Any subclass of `Variant` is accepted along with float instances and objects -convertible to float. They are internally converted to variants of type -`Constant` via ``Constant(float(a))`` where ``a`` is the float or float -convertible object. - -Note: - Attributes that are `Variant` objects can be set via a `variant_like` - object. -""" +from hoomd.variant.scalar import Variant +from hoomd.variant.scalar import Constant +from hoomd.variant.scalar import Ramp +from hoomd.variant.scalar import Cycle +from hoomd.variant.scalar import Power +from hoomd.variant.scalar import variant_like diff --git a/hoomd/variant/scalar.py b/hoomd/variant/scalar.py new file mode 100644 index 0000000000..588ce299cf --- /dev/null +++ b/hoomd/variant/scalar.py @@ -0,0 +1,247 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import typing + +from hoomd import _hoomd + + +class Variant(_hoomd.Variant): + """Variant base class. + + Provides methods common to all variants and a base class for user-defined + variants. + + Subclasses should override the ``__call__``, ``_min``, and ``_max`` methods + and must explicitly call the base class constructor in ``__init__``: + + .. code-block:: python + + class CustomVariant(hoomd.variant.Variant): + def __init__(self): + hoomd.variant.Variant.__init__(self) + + def __call__(self, timestep): + return (float(timestep)**(1 / 2)) + + def _min(self): + return 0.0 + + def _max(self): + return float('inf') + + Note: + Provide the minimum and maximum values in the ``_min`` and ``_max`` + methods respectively. + + .. 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 + """ + + @property + def min(self): + """The minimum value of this variant for :math:`t \\in [0,\\infty)`.""" + return self._min() + + @property + def max(self): + """The maximum value of this variant for :math:`t \\in [0,\\infty)`.""" + return self._max() + + def __getstate__(self): + """Get the variant's ``__dict__`` attribute.""" + return self.__dict__ + + def __setstate__(self, state): + """Restore the state of the variant.""" + _hoomd.Variant.__init__(self) + self.__dict__ = state + + def _private_eq(self, other): + """Return whether two variants are equivalent.""" + if not isinstance(other, Variant): + return NotImplemented + if not isinstance(other, type(self)): + return False + return all( + getattr(self, attr) == getattr(other, attr) + for attr in self._eq_attrs) + + +class Constant(_hoomd.VariantConstant, Variant): + """A constant value. + + Args: + value (float): The value. + + `Constant` returns `value` at all time steps. + + .. rubric:: Example: + + .. code-block:: python + + variant = hoomd.variant.Constant(1.0) + + Attributes: + value (float): The value. + """ + _eq_attrs = ("value",) + + def __init__(self, value): + Variant.__init__(self) + _hoomd.VariantConstant.__init__(self, value) + + __eq__ = Variant._private_eq + + +class Ramp(_hoomd.VariantRamp, Variant): + """A linear ramp. + + Args: + A (float): The start value. + B (float): The end value. + t_start (int): The start time step. + t_ramp (int): The length of the ramp. + + `Ramp` holds the value *A* until time *t_start*. Then it ramps linearly from + *A* to *B* over *t_ramp* steps and holds the value *B*. + + .. image:: variant-ramp.svg + :alt: Example plot of a ramp variant. + + .. rubric:: Example: + + .. code-block:: python + + variant = hoomd.variant.Ramp(A=1.0, + B=2.0, + t_start=10_000, + t_ramp=100_000) + + Attributes: + A (float): The start value. + B (float): The end value. + t_start (int): The start time step. + t_ramp (int): The length of the ramp. + """ + _eq_attrs = ("A", "B", "t_start", "t_ramp") + + def __init__(self, A, B, t_start, t_ramp): + Variant.__init__(self) + _hoomd.VariantRamp.__init__(self, A, B, t_start, t_ramp) + + __eq__ = Variant._private_eq + + +class Cycle(_hoomd.VariantCycle, Variant): + """A cycle of linear ramps. + + Args: + A (float): The first value. + B (float): The second value. + t_start (int): The start time step. + t_A (int): The hold time at the first value. + t_AB (int): The time spent ramping from A to B. + t_B (int): The hold time at the second value. + t_BA (int): The time spent ramping from B to A. + + `Cycle` holds the value *A* until time *t_start*. It continues holding that + value until *t_start + t_A*. Then it ramps linearly from *A* to *B* over + *t_AB* steps and holds the value *B* for *t_B* steps. After this, it ramps + back from *B* to *A* over *t_BA* steps and repeats the cycle starting with + *t_A*. `Cycle` repeats this cycle indefinitely. + + .. image:: variant-cycle.svg + :alt: Example plot of a cycle variant. + + .. rubric:: Example: + + .. code-block:: python + + variant = hoomd.variant.Cycle(A=1.0, + B=2.0, + t_start=10_000, + t_A=100_000, + t_AB=1_000_000, + t_B=200_000, + t_BA=2_000_000) + + Attributes: + A (float): The first value. + B (float): The second value. + t_start (int): The start time step. + t_A (int): The holding time at A. + t_AB (int): The time spent ramping from A to B. + t_B (int): The holding time at B. + t_BA (int): The time spent ramping from B to A. + """ + _eq_attrs = ("A", "B", "t_start", "t_A", "t_AB", "t_B", "t_BA") + + def __init__(self, A, B, t_start, t_A, t_AB, t_B, t_BA): + Variant.__init__(self) + _hoomd.VariantCycle.__init__(self, A, B, t_start, t_A, t_AB, t_B, t_BA) + + __eq__ = Variant._private_eq + + +class Power(_hoomd.VariantPower, Variant): + """An approach from initial to final value following ``t**power``. + + Args: + A (float): The start value. + B (float): The end value. + power (float): The power of the approach to ``B``. + t_start (int): The start time step. + t_ramp (int): The length of the ramp. + + `Power` holds the value *A* until time *t_start*. Then it progresses at + :math:`t^{\\mathrm{power}}` from *A* to *B* over *t_ramp* steps and holds + the value *B* after that. + + .. image:: variant-power.svg + :alt: Example plot of a power variant. + + .. rubric:: Example: + + .. code-block:: python + + variant = hoomd.variant.Power(A=2, + B=8, + power=1 / 10, + t_start=10, t_ramp=20) + + Attributes: + A (float): The start value. + B (float): The end value. + power (float): The power of the approach to ``B``. + t_start (int): The start time step. + t_ramp (int): The length of the ramp. + """ + _eq_attrs = ("A", "B", "power", "t_start", "t_ramp") + + def __init__(self, A, B, power, t_start, t_ramp): + Variant.__init__(self) + _hoomd.VariantPower.__init__(self, A, B, power, t_start, t_ramp) + + __eq__ = Variant._private_eq + + +variant_like = typing.Union[Variant, float] +""" +Objects that are like a variant. + +Any subclass of `Variant` is accepted along with float instances and objects +convertible to float. They are internally converted to variants of type +`Constant` via ``Constant(float(a))`` where ``a`` is the float or float +convertible object. + +Note: + Attributes that are `Variant` objects can be set via a `variant_like` + object. +""" From 5991e76f48346a69b4d19a82e1abcd3607f73b2b Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 11 Jan 2024 16:00:25 -0500 Subject: [PATCH 13/37] Use internal variant for inverse volume box ramp --- hoomd/VectorVariant.h | 68 +++++++++++++++---------------------------- 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/hoomd/VectorVariant.h b/hoomd/VectorVariant.h index 7c0e7b5f3a..fe3953ebe3 100644 --- a/hoomd/VectorVariant.h +++ b/hoomd/VectorVariant.h @@ -184,59 +184,35 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo { m_is2D = m_box1->getL().z == 0; m_vol1 = m_box1->getVolume(m_is2D); + std::shared_ptr variant(new VariantRamp{0, 1, t_start, t_ramp}); + m_variant = variant; } virtual array_type operator()(uint64_t timestep) { - if (timestep < m_t_start) - { - return box_to_array(m_box1); - } - else if (timestep >= m_t_start + m_t_ramp) + 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_vol1); + Scalar L_scale; + if (m_is2D) { - Scalar scale; - if (m_is2D) - { - scale = pow(m_final_volume / m_vol1, Scalar(1.0 / 2.0)); - } - else - { - scale = pow(m_final_volume / m_vol1, Scalar(1.0 / 3.0)); - } - std::array value; - Scalar3 L1 = m_box1->getL(); - value[0] = L1.x * scale; - value[1] = L1.y * scale; - value[2] = L1.z * scale; - value[3] = m_box1->getTiltFactorXY(); - value[4] = m_box1->getTiltFactorXZ(); - value[5] = m_box1->getTiltFactorYZ(); - return value; + L_scale = pow(current_volume / m_vol1, Scalar(1.0 / 2.0)); } else { - double s = double(timestep - m_t_start) / double(m_t_ramp); - Scalar current_volume = s * m_final_volume + (1.0 - s) * m_vol1; - Scalar scale; - if (m_is2D) - { - scale = pow(current_volume / m_vol1, Scalar(1.0 / 2.0)); - } - else - { - scale = pow(current_volume / m_vol1, Scalar(1.0 / 3.0)); - } - - std::array value; - Scalar3 L1 = m_box1->getL(); - value[0] = L1.x * scale; - value[1] = L1.y * scale; - value[2] = L1.z * scale; - value[3] = m_box1->getTiltFactorXY(); - value[4] = m_box1->getTiltFactorXZ(); - value[5] = m_box1->getTiltFactorYZ(); - return value; + L_scale = pow(current_volume / m_vol1, Scalar(1.0 / 3.0)); } + + std::array value; + Scalar3 L1 = m_box1->getL(); + value[0] = L1.x * L_scale; + value[1] = L1.y * L_scale; + value[2] = L1.z * L_scale; + value[3] = m_box1->getTiltFactorXY(); + value[4] = m_box1->getTiltFactorXZ(); + value[5] = m_box1->getTiltFactorYZ(); + return value; } std::shared_ptr getBox1() @@ -297,7 +273,11 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo /// Whether box1 is 2-dimensional or not bool m_is2D; + /// The current value of the volume Scalar m_current_volume; + + /// Variant for computing scale value + std::shared_ptr m_variant; //!< Variant that interpolates between boxes }; namespace detail From 60eb93a9fec106fa3206ec7ebbbc4bd0528f4a1f Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 11 Jan 2024 16:01:09 -0500 Subject: [PATCH 14/37] Call box_preprocessing on correct boxes --- hoomd/variant/box.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index 6e99765b52..48e6a47556 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -58,8 +58,8 @@ class Ramp(_hoomd.VectorVariantBoxLinear): """ def __init__(self, initial_box, final_box, variant): - box1 = box_preprocessing(box1) - box2 = box_preprocessing(box2) + box1 = box_preprocessing(initial_box) + box2 = box_preprocessing(final_box) variant = variant_preprocessing(variant) _hoomd.VectorVariantBoxLinear.__init__(self, box1._cpp_obj, box2._cpp_obj, variant) From 14719e3903f8e2669b02c2151f300f427b7b42b5 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 11 Jan 2024 16:11:30 -0500 Subject: [PATCH 15/37] Fix box references in python docstrings --- hoomd/hpmc/update.py | 2 +- hoomd/update/box_resize.py | 2 +- hoomd/variant/box.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hoomd/hpmc/update.py b/hoomd/hpmc/update.py index 5bf42ac0c2..804c220fa0 100644 --- a/hoomd/hpmc/update.py +++ b/hoomd/hpmc/update.py @@ -860,7 +860,7 @@ class QuickCompress(Updater): Attributes: trigger (Trigger): Update the box dimensions on triggered time steps. - target_box (hoomd.Box): Dimensions of the target box. + target_box (Box): Dimensions of the target box. max_overlaps_per_particle (float): The maximum number of overlaps to allow per particle (may be less than 1 - e.g. diff --git a/hoomd/update/box_resize.py b/hoomd/update/box_resize.py index 17a7a73825..d3cc1c2e42 100644 --- a/hoomd/update/box_resize.py +++ b/hoomd/update/box_resize.py @@ -185,7 +185,7 @@ def get_box(self, timestep): box. Returns: - hoomd.Box: The box used at the given timestep. + Box: The box used at the given timestep. `None` before the first call to `Simulation.run`. .. rubric:: Example: diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index 48e6a47556..c8e6114b90 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -82,7 +82,7 @@ class LinearInverseVolume(_hoomd.VectorVariantBoxInverseVolumeRamp): remain constant. Attributes: - initial_box (hoomd.Box): The initial box. + initial_box (hoomd.box.box_like): The initial box. 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. From 1baa52d55484fd568352be6df6815616b985d2e6 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 11 Jan 2024 17:38:30 -0500 Subject: [PATCH 16/37] Fix order of imports --- hoomd/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hoomd/__init__.py b/hoomd/__init__.py index 6513b84665..56ecfd115e 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 From 42a5de57c49e41b057456493c26bf58f1b5b0f87 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 11 Jan 2024 17:38:53 -0500 Subject: [PATCH 17/37] Import variant.box --- hoomd/variant/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hoomd/variant/__init__.py b/hoomd/variant/__init__.py index 84ea08f75f..6e233ddbef 100644 --- a/hoomd/variant/__init__.py +++ b/hoomd/variant/__init__.py @@ -17,3 +17,5 @@ from hoomd.variant.scalar import Cycle from hoomd.variant.scalar import Power from hoomd.variant.scalar import variant_like + +from hoomd.variant import box From 27ae0479d755ff294484f4a742e53f5fb7a0f958 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 11 Jan 2024 17:39:44 -0500 Subject: [PATCH 18/37] Update docstring --- hoomd/variant/box.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index c8e6114b90..48e6a47556 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -82,7 +82,7 @@ class LinearInverseVolume(_hoomd.VectorVariantBoxInverseVolumeRamp): remain constant. Attributes: - initial_box (hoomd.box.box_like): The initial box. + initial_box (hoomd.Box): The initial box. 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. From 0ba47f35352b16805cbfe34ae334d27d377f90d7 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 11 Jan 2024 17:40:32 -0500 Subject: [PATCH 19/37] Format --- hoomd/VectorVariant.cc | 3 ++- hoomd/VectorVariant.h | 9 +++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/hoomd/VectorVariant.cc b/hoomd/VectorVariant.cc index fd1f31fe42..b66d561716 100644 --- a/hoomd/VectorVariant.cc +++ b/hoomd/VectorVariant.cc @@ -73,7 +73,8 @@ void export_VectorVariantBox(pybind11::module& m) pybind11::class_>(m, "VectorVariantBoxLinear") - .def(pybind11::init, std::shared_ptr, std::shared_ptr>()) + .def(pybind11:: + init, std::shared_ptr, std::shared_ptr>()) .def_property("initial_box", &VectorVariantBoxLinear::getBox1, &VectorVariantBoxLinear::setBox1) diff --git a/hoomd/VectorVariant.h b/hoomd/VectorVariant.h index fe3953ebe3..dcfe42e032 100644 --- a/hoomd/VectorVariant.h +++ b/hoomd/VectorVariant.h @@ -62,10 +62,7 @@ class PYBIND11_EXPORT VectorVariantBoxConstant : public VectorVariantBox @param box The box. */ - VectorVariantBoxConstant(std::shared_ptr box) - : m_box(box) - { - } + VectorVariantBoxConstant(std::shared_ptr box) : m_box(box) { } virtual ~VectorVariantBoxConstant() { } @@ -184,7 +181,7 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo { m_is2D = m_box1->getL().z == 0; m_vol1 = m_box1->getVolume(m_is2D); - std::shared_ptr variant(new VariantRamp{0, 1, t_start, t_ramp}); + std::shared_ptr variant(new VariantRamp {0, 1, t_start, t_ramp}); m_variant = variant; } @@ -277,7 +274,7 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo Scalar m_current_volume; /// Variant for computing scale value - std::shared_ptr m_variant; //!< Variant that interpolates between boxes + std::shared_ptr m_variant; //!< Variant that interpolates between boxes }; namespace detail From 72238eadfb8a5a6bdd8a32db954412ef2ff202dc Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 11 Jan 2024 17:44:20 -0500 Subject: [PATCH 20/37] Add docstring to variant/scalar.py --- hoomd/variant/scalar.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hoomd/variant/scalar.py b/hoomd/variant/scalar.py index 588ce299cf..9519adff2a 100644 --- a/hoomd/variant/scalar.py +++ b/hoomd/variant/scalar.py @@ -1,6 +1,8 @@ # Copyright (c) 2009-2023 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. +"""Implement variants that return scalar values.""" + import typing from hoomd import _hoomd From 31fd15ea1e2e25cb1599a922a7032812a70944dc Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Fri, 12 Jan 2024 14:55:42 -0500 Subject: [PATCH 21/37] Rename LinearInverseVolume to InverseVolumeRamp --- hoomd/variant/box.py | 6 +++--- sphinx-doc/module-hoomd-variant-box.rst | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index 48e6a47556..aefaa45b5b 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -65,8 +65,8 @@ def __init__(self, initial_box, final_box, variant): box2._cpp_obj, variant) -class LinearInverseVolume(_hoomd.VectorVariantBoxInverseVolumeRamp): - """Produce dependent box arrays whose inverse volume is linear in time. +class InverseVolumeRamp(_hoomd.VectorVariantBoxInverseVolumeRamp): + """Produce box arrays whose inverse volume changes linearly with time. Args: initial_box (hoomd.box.box_like): The initial box. @@ -74,7 +74,7 @@ class LinearInverseVolume(_hoomd.VectorVariantBoxInverseVolumeRamp): t_start (int): The time step at the start of the ramp. t_ramp (int): The length of the ramp. - ``LinearInverseVolume`` produces box arrays that correspond to a box whose + ``InverseVolumeRamp`` produces box arrays that correspond to a box whose **inverse volume** (i.e., density for a constant number of particles) varies linearly with time. The shape of the box remains constant, that is, the ratios of the lengths of the box vectors (:math:`L_y / L_x` and diff --git a/sphinx-doc/module-hoomd-variant-box.rst b/sphinx-doc/module-hoomd-variant-box.rst index 72d7ecccec..fb89095ea7 100644 --- a/sphinx-doc/module-hoomd-variant-box.rst +++ b/sphinx-doc/module-hoomd-variant-box.rst @@ -14,7 +14,7 @@ hoomd.variant.box Box Constant Ramp - LinearInverseVolume + InverseVolumeRamp .. rubric:: Details @@ -25,7 +25,7 @@ hoomd.variant.box .. autoclass:: Box() .. autoclass:: Constant(box) :show-inheritance: - .. autoclass:: Ramp(initial_box, final_box, t_start, t_ramp) + .. autoclass:: Ramp(initial_box, final_box, variant) :show-inheritance: - .. autoclass:: LinearInverseVolume(initial_box, final_volume, t_start, t_ramp) + .. autoclass:: InverseVolumeRamp(initial_box, final_volume, t_start, t_ramp) :show-inheritance: From 53ee85d29e63c00bec2904f7b04c0bb6b407a02d Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 16 Jan 2024 09:26:28 -0500 Subject: [PATCH 22/37] Start implementing tests --- hoomd/VectorVariant.cc | 29 ++++++++++++++++------ hoomd/VectorVariant.h | 33 ++++++++----------------- hoomd/pytest/CMakeLists.txt | 1 + hoomd/variant/box.py | 18 ++++++++------ sphinx-doc/module-hoomd-variant-box.rst | 8 +++--- 5 files changed, 47 insertions(+), 42 deletions(-) diff --git a/hoomd/VectorVariant.cc b/hoomd/VectorVariant.cc index b66d561716..c0ac7fd2a5 100644 --- a/hoomd/VectorVariant.cc +++ b/hoomd/VectorVariant.cc @@ -28,6 +28,16 @@ class VectorVariantBoxPy : public VectorVariantBox 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_VectorVariantBox(pybind11::module& m) { pybind11::class_>( @@ -70,20 +80,20 @@ void export_VectorVariantBox(pybind11::module& m) */ ; - pybind11::class_>(m, "VectorVariantBoxLinear") + std::shared_ptr>(m, "VectorVariantBoxInterpolate") .def(pybind11:: init, std::shared_ptr, std::shared_ptr>()) .def_property("initial_box", - &VectorVariantBoxLinear::getBox1, - &VectorVariantBoxLinear::setBox1) + &VectorVariantBoxInterpolate::getBox1, + &VectorVariantBoxInterpolate::setBox1) .def_property("final_box", - &VectorVariantBoxLinear::getBox2, - &VectorVariantBoxLinear::setBox2) + &VectorVariantBoxInterpolate::getBox2, + &VectorVariantBoxInterpolate::setBox2) .def_property("variant", - &VectorVariantBoxLinear::getVariant, - &VectorVariantBoxLinear::setVariant); + &VectorVariantBoxInterpolate::getVariant, + &VectorVariantBoxInterpolate::setVariant); pybind11::class_ m_box; }; -class PYBIND11_EXPORT VectorVariantBoxLinear : public VectorVariantBox +class PYBIND11_EXPORT VectorVariantBoxInterpolate : public VectorVariantBox { public: - /** Construct a VectorVariantBoxLinear to interpolate between two boxes linearly in time. + /** Construct a VectorVariantBoxInterpolate to interpolate between two boxes linearly in time. @param box1 The initial box @param box2 The final box */ - VectorVariantBoxLinear(std::shared_ptr box1, + VectorVariantBoxInterpolate(std::shared_ptr box1, std::shared_ptr box2, std::shared_ptr variant) : m_box1(box1), m_box2(box2), m_variant(variant) @@ -177,17 +177,15 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo Scalar final_volume, uint64_t t_start, uint64_t t_ramp) - : m_box1(box1), m_final_volume(final_volume), m_t_start(t_start), m_t_ramp(t_ramp) + : m_box1(box1), m_final_volume(final_volume), m_variant(0, 1, t_start, t_ramp) { m_is2D = m_box1->getL().z == 0; m_vol1 = m_box1->getVolume(m_is2D); - std::shared_ptr variant(new VariantRamp {0, 1, t_start, t_ramp}); - m_variant = variant; } virtual array_type operator()(uint64_t timestep) { - Scalar s = (*m_variant)(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_vol1); @@ -225,30 +223,25 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo /// Set the starting time step. void setTStart(uint64_t t_start) { - m_t_start = t_start; + m_variant.setTStart(t_start); } /// Get the starting time step. uint64_t getTStart() const { - return m_t_start; + return m_variant.getTStart(); } /// Set the length of the ramp. void setTRamp(uint64_t t_ramp) { - // doubles can only represent integers accuracy up to 2**53. - if (t_ramp >= 9007199254740992ull) - { - throw std::invalid_argument("t_ramp must be less than 2**53"); - } - m_t_ramp = t_ramp; + m_variant.setTRamp(t_ramp); } /// Get the length of the ramp. uint64_t getTRamp() const { - return m_t_ramp; + return m_variant.getTRamp(); } protected: @@ -261,12 +254,6 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo /// The volume of the box at the end of the ramp. Scalar m_final_volume; - /// The starting time step. - uint64_t m_t_start; - - /// The length of the ramp. - uint64_t m_t_ramp; - /// Whether box1 is 2-dimensional or not bool m_is2D; @@ -274,7 +261,7 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo Scalar m_current_volume; /// Variant for computing scale value - std::shared_ptr m_variant; //!< Variant that interpolates between boxes + VariantRamp m_variant; //!< Variant that interpolates between boxes }; namespace detail diff --git a/hoomd/pytest/CMakeLists.txt b/hoomd/pytest/CMakeLists.txt index bf12b630eb..83cdc05e97 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/variant/box.py b/hoomd/variant/box.py index aefaa45b5b..fbb172a549 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -3,14 +3,15 @@ """Implement variants that return box parameters as a function of time.""" -from hoomd import _hoomd +from hoomd import _hoomd, Box +from hoomd.data.parameterdicts import ParameterDict from hoomd.data.typeconverter import box_preprocessing, variant_preprocessing -class Box(_hoomd.VectorVariantBox): +class BoxVariant(_hoomd.VectorVariantBox): """Box-like vector variant base class. - `hoomd.variant.box.Box` provides an interface to length-6 vector variants + `hoomd.variant.box.BoxVariant` provides an interface to length-6 vector variants that are valid `hoomd.box.box_like` objects. The return value of the ``__call__`` method returns a length-6 array of scalar values that represent the quantities ``Lx``, ``Ly``, ``Lz``, ``xy``, ``xz``, and ``yz`` of a @@ -19,7 +20,7 @@ class Box(_hoomd.VectorVariantBox): pass -class Constant(_hoomd.VectorVariantBoxConstant, Box): +class Constant(_hoomd.VectorVariantBoxConstant, BoxVariant): """A constant box variant. Args: @@ -34,10 +35,11 @@ class Constant(_hoomd.VectorVariantBoxConstant, Box): def __init__(self, box): box = box_preprocessing(box) + BoxVariant.__init__(self) _hoomd.VectorVariantBoxConstant.__init__(self, box._cpp_obj) -class Ramp(_hoomd.VectorVariantBoxLinear): +class Interpolate(_hoomd.VectorVariantBoxInterpolate, BoxVariant): """Interpolate between two boxes linearly in time. Args: @@ -61,11 +63,12 @@ def __init__(self, initial_box, final_box, variant): box1 = box_preprocessing(initial_box) box2 = box_preprocessing(final_box) variant = variant_preprocessing(variant) - _hoomd.VectorVariantBoxLinear.__init__(self, box1._cpp_obj, + BoxVariant.__init__(self) + _hoomd.VectorVariantBoxInterpolate.__init__(self, box1._cpp_obj, box2._cpp_obj, variant) -class InverseVolumeRamp(_hoomd.VectorVariantBoxInverseVolumeRamp): +class InverseVolumeRamp(_hoomd.VectorVariantBoxInverseVolumeRamp, BoxVariant): """Produce box arrays whose inverse volume changes linearly with time. Args: @@ -89,6 +92,7 @@ class InverseVolumeRamp(_hoomd.VectorVariantBoxInverseVolumeRamp): """ 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) diff --git a/sphinx-doc/module-hoomd-variant-box.rst b/sphinx-doc/module-hoomd-variant-box.rst index fb89095ea7..34260ea44e 100644 --- a/sphinx-doc/module-hoomd-variant-box.rst +++ b/sphinx-doc/module-hoomd-variant-box.rst @@ -11,9 +11,9 @@ hoomd.variant.box .. autosummary:: :nosignatures: - Box + BoxVariant Constant - Ramp + Interpolate InverseVolumeRamp .. rubric:: Details @@ -22,10 +22,10 @@ hoomd.variant.box :synopsis: Box variants. :no-members: - .. autoclass:: Box() + .. autoclass:: BoxVariant() .. autoclass:: Constant(box) :show-inheritance: - .. autoclass:: Ramp(initial_box, final_box, variant) + .. autoclass:: Interpolate(initial_box, final_box, variant) :show-inheritance: .. autoclass:: InverseVolumeRamp(initial_box, final_volume, t_start, t_ramp) :show-inheritance: From 944fb6129a7ee6a922ee25adb5ea3dd0455791b3 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 16 Jan 2024 14:23:51 -0500 Subject: [PATCH 23/37] Add more tests --- hoomd/VectorVariant.cc | 17 +++++++++------- hoomd/VectorVariant.h | 46 +++++++++++++++++++++++++++--------------- hoomd/variant/box.py | 46 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 81 insertions(+), 28 deletions(-) diff --git a/hoomd/VectorVariant.cc b/hoomd/VectorVariant.cc index c0ac7fd2a5..e223314611 100644 --- a/hoomd/VectorVariant.cc +++ b/hoomd/VectorVariant.cc @@ -50,7 +50,7 @@ void export_VectorVariantBox(pybind11::module& m) VectorVariantBox, std::shared_ptr>(m, "VectorVariantBoxConstant") .def(pybind11::init>()) - .def_property("box", &VectorVariantBoxConstant::getBox, &VectorVariantBoxConstant::setBox) + .def_property("_box", &VectorVariantBoxConstant::getBox, &VectorVariantBoxConstant::setBox) /* .def( pybind11::pickle( @@ -85,10 +85,10 @@ void export_VectorVariantBox(pybind11::module& m) std::shared_ptr>(m, "VectorVariantBoxInterpolate") .def(pybind11:: init, std::shared_ptr, std::shared_ptr>()) - .def_property("initial_box", + .def_property("_initial_box", &VectorVariantBoxInterpolate::getBox1, &VectorVariantBoxInterpolate::setBox1) - .def_property("final_box", + .def_property("_final_box", &VectorVariantBoxInterpolate::getBox2, &VectorVariantBoxInterpolate::setBox2) .def_property("variant", @@ -101,15 +101,18 @@ void export_VectorVariantBox(pybind11::module& m) m, "VectorVariantBoxInverseVolumeRamp") .def(pybind11::init, Scalar, uint64_t, uint64_t>()) - .def_property("box1", - &VectorVariantBoxInverseVolumeRamp::getBox1, - &VectorVariantBoxInverseVolumeRamp::setBox1) + .def_property("_initial_box", + &VectorVariantBoxInverseVolumeRamp::getInitialBox, + &VectorVariantBoxInverseVolumeRamp::setInitialBox) .def_property("t_start", &VectorVariantBoxInverseVolumeRamp::getTStart, &VectorVariantBoxInverseVolumeRamp::setTStart) .def_property("t_ramp", &VectorVariantBoxInverseVolumeRamp::getTRamp, - &VectorVariantBoxInverseVolumeRamp::setTRamp); + &VectorVariantBoxInverseVolumeRamp::setTRamp) + .def_property("final_volume", + &VectorVariantBoxInverseVolumeRamp::getFinalVolume, + &VectorVariantBoxInverseVolumeRamp::setFinalVolume); m.def("_test_vector_variant_call", &testVectorVariantBoxCall); } diff --git a/hoomd/VectorVariant.h b/hoomd/VectorVariant.h index e4247b5b2a..79e6b200f5 100644 --- a/hoomd/VectorVariant.h +++ b/hoomd/VectorVariant.h @@ -177,10 +177,10 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo Scalar final_volume, uint64_t t_start, uint64_t t_ramp) - : m_box1(box1), m_final_volume(final_volume), m_variant(0, 1, t_start, t_ramp) + : m_initial_box(box1), m_final_volume(final_volume), m_variant(0, 1, t_start, t_ramp) { - m_is2D = m_box1->getL().z == 0; - m_vol1 = m_box1->getVolume(m_is2D); + m_is2D = m_initial_box->getL().z == 0; + m_initial_volume = m_initial_box->getVolume(m_is2D); } virtual array_type operator()(uint64_t timestep) @@ -188,36 +188,38 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo 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_vol1); + Scalar current_volume = 1 / (s / m_final_volume + (1.0 - s) / m_initial_volume); Scalar L_scale; if (m_is2D) { - L_scale = pow(current_volume / m_vol1, Scalar(1.0 / 2.0)); + L_scale = pow(current_volume / m_initial_volume, Scalar(1.0 / 2.0)); } else { - L_scale = pow(current_volume / m_vol1, Scalar(1.0 / 3.0)); + L_scale = pow(current_volume / m_initial_volume, Scalar(1.0 / 3.0)); } std::array value; - Scalar3 L1 = m_box1->getL(); + 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_box1->getTiltFactorXY(); - value[4] = m_box1->getTiltFactorXZ(); - value[5] = m_box1->getTiltFactorYZ(); + value[3] = m_initial_box->getTiltFactorXY(); + value[4] = m_initial_box->getTiltFactorXZ(); + value[5] = m_initial_box->getTiltFactorYZ(); return value; } - std::shared_ptr getBox1() + std::shared_ptr getInitialBox() { - return m_box1; + return m_initial_box; } - void setBox1(std::shared_ptr box) + void setInitialBox(std::shared_ptr box) { - m_box1 = box; + m_initial_box = box; + m_is2D = box->getL().z == 0; + m_initial_volume = box->getVolume(m_is2D); } /// Set the starting time step. @@ -244,12 +246,24 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo 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_box1; + std::shared_ptr m_initial_box; /// The volume of box1. - Scalar m_vol1; + Scalar m_initial_volume; /// The volume of the box at the end of the ramp. Scalar m_final_volume; diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index fbb172a549..57ebd506e3 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -29,8 +29,6 @@ class Constant(_hoomd.VectorVariantBoxConstant, BoxVariant): `Constant` returns ``[box.Lx, box.Ly, box.Lz, box.xz, box.xz, box.yz]`` at all time steps. - Attributes: - box (hoomd.Box): The box. """ def __init__(self, box): @@ -38,6 +36,17 @@ def __init__(self, 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 in time. @@ -53,8 +62,6 @@ class Interpolate(_hoomd.VectorVariantBoxInterpolate, BoxVariant): :math:`t \\geq t_{\\mathrm{start}} + t_{\\mathrm{ramp}}`. Attributes: - initial_box (hoomd.Box): The initial box. - final_box (hoomd.Box): The final box. variant (hoomd.variant.Variant): A variant used to interpolate between the two boxes. """ @@ -67,6 +74,26 @@ def __init__(self, initial_box, final_box, variant): _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 with time. @@ -85,7 +112,6 @@ class InverseVolumeRamp(_hoomd.VectorVariantBoxInverseVolumeRamp, BoxVariant): remain constant. Attributes: - initial_box (hoomd.Box): The initial box. 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. @@ -96,3 +122,13 @@ def __init__(self, initial_box, final_volume, t_start, t_ramp): 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 From 6569e65d8c972be04900d33435b0e1e536c4ad45 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 16 Jan 2024 17:54:53 -0500 Subject: [PATCH 24/37] Actually add test file --- hoomd/pytest/test_box_variant.py | 121 +++++++++++++++++++++++++++++++ hoomd/variant/box.py | 1 - 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 hoomd/pytest/test_box_variant.py diff --git a/hoomd/pytest/test_box_variant.py b/hoomd/pytest/test_box_variant.py new file mode 100644 index 0000000000..a76b960ec8 --- /dev/null +++ b/hoomd/pytest/test_box_variant.py @@ -0,0 +1,121 @@ +# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import copy +from inspect import isclass +import itertools +import pickle + +import numpy as np +import numpy.testing as npt +from hoomd.conftest import pickling_check +import hoomd +import hoomd.variant +import pytest + + +_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) +_test_scalar_variant1 = hoomd.variant.Ramp(0, 1, 100, 200) +_test_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': _test_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': [_test_scalar_variant1, _test_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 CustomBoxVariant(hoomd.variant.box.BoxVariant): + + def __init__(self): + hoomd.variant.box.Box.__init__(self) + self._a = 1 + + def __call__(self, timestep): + return (float(timestep)**(1 / 2)) + + def _min(self): + return 0.0 + + def _max(self): + return 1.0 + + def __eq__(self, other): + return isinstance(other, type(self)) + +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) + box_to_array = lambda box: np.array([box.Lx, box.Ly, box.Lz, box.xy, box.xz, box.yz]) + 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) + volume = lambda variant, timestep: hoomd.Box(*variant(timestep)).volume + assert volume(variant, 0) == box1.volume + assert volume(variant, 5) == box1.volume + assert volume(variant, 10) == box1.volume + assert volume(variant, 11) != box1.volume + npt.assert_allclose(volume(variant, 35), 1 / (0.75 / box1.volume + 0.25 / final_volume)) + npt.assert_allclose(volume(variant, 60), 1 / (0.5 * (1 / box1.volume + 1 / final_volume))) + npt.assert_allclose(volume(variant, 85), 1 / (0.25 / box1.volume + 0.75 / final_volume)) + npt.assert_allclose(volume(variant, 110), final_volume) + npt.assert_allclose(volume(variant, 1010), final_volume) + # make sure tilts don't change + npt.assert_allclose(box1.tilts, variant(0)[3:]) + npt.assert_allclose(box1.tilts, variant(5)[3:]) + npt.assert_allclose(box1.tilts, variant(25)[3:]) + npt.assert_allclose(box1.tilts, variant(125)[3:]) + npt.assert_allclose(box1.tilts, variant(625)[3:]) diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index 57ebd506e3..4074d6d2dd 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -4,7 +4,6 @@ """Implement variants that return box parameters as a function of time.""" from hoomd import _hoomd, Box -from hoomd.data.parameterdicts import ParameterDict from hoomd.data.typeconverter import box_preprocessing, variant_preprocessing From 0386557745c958715e7106324bfe90b980379d43 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 16 Jan 2024 21:18:16 -0500 Subject: [PATCH 25/37] Test custom box variant --- hoomd/VectorVariant.cc | 2 +- hoomd/pytest/test_box_variant.py | 40 +++++++++++++++++++++----------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/hoomd/VectorVariant.cc b/hoomd/VectorVariant.cc index e223314611..77bcb8be87 100644 --- a/hoomd/VectorVariant.cc +++ b/hoomd/VectorVariant.cc @@ -114,7 +114,7 @@ void export_VectorVariantBox(pybind11::module& m) &VectorVariantBoxInverseVolumeRamp::getFinalVolume, &VectorVariantBoxInverseVolumeRamp::setFinalVolume); - m.def("_test_vector_variant_call", &testVectorVariantBoxCall); + m.def("_test_vector_variant_box_call", &testVectorVariantBoxCall); } diff --git a/hoomd/pytest/test_box_variant.py b/hoomd/pytest/test_box_variant.py index a76b960ec8..50578da146 100644 --- a/hoomd/pytest/test_box_variant.py +++ b/hoomd/pytest/test_box_variant.py @@ -14,6 +14,7 @@ import pytest +box_to_array = lambda box: 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) _test_scalar_variant1 = hoomd.variant.Ramp(0, 1, 100, 200) @@ -47,30 +48,43 @@ def test_setattr(cls, attrs): setattr(variant, attr, value) assert getattr(variant, attr) == value -class CustomBoxVariant(hoomd.variant.box.BoxVariant): +class VolumeRampBoxVariant(hoomd.variant.box.BoxVariant): - def __init__(self): - hoomd.variant.box.Box.__init__(self) - self._a = 1 + 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): - return (float(timestep)**(1 / 2)) - - def _min(self): - return 0.0 - - def _max(self): - return 1.0 + 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) + + box_t = lambda t: hoomd._hoomd._test_vector_variant_box_call(custom_variant, t) + 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(_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) - box_to_array = lambda box: np.array([box.Lx, box.Ly, box.Lz, box.xy, box.xz, box.yz]) 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)) @@ -95,8 +109,6 @@ def test_interpolate_evaluation(): 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 From 776ed3a7d5d0f8ddc224985cc2a097f5f741edb4 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 16 Jan 2024 21:26:38 -0500 Subject: [PATCH 26/37] formatting --- hoomd/VectorVariant.cc | 1 - hoomd/VectorVariant.h | 6 +-- hoomd/pytest/test_box_variant.py | 83 +++++++++++++++++++++++--------- hoomd/variant/box.py | 7 ++- 4 files changed, 66 insertions(+), 31 deletions(-) diff --git a/hoomd/VectorVariant.cc b/hoomd/VectorVariant.cc index 77bcb8be87..67dbba4779 100644 --- a/hoomd/VectorVariant.cc +++ b/hoomd/VectorVariant.cc @@ -117,7 +117,6 @@ void export_VectorVariantBox(pybind11::module& m) m.def("_test_vector_variant_box_call", &testVectorVariantBoxCall); } - } // end namespace detail } // end namespace hoomd diff --git a/hoomd/VectorVariant.h b/hoomd/VectorVariant.h index 79e6b200f5..82fa25c643 100644 --- a/hoomd/VectorVariant.h +++ b/hoomd/VectorVariant.h @@ -95,8 +95,8 @@ class PYBIND11_EXPORT VectorVariantBoxInterpolate : public VectorVariantBox @param box2 The final box */ VectorVariantBoxInterpolate(std::shared_ptr box1, - std::shared_ptr box2, - std::shared_ptr variant) + std::shared_ptr box2, + std::shared_ptr variant) : m_box1(box1), m_box2(box2), m_variant(variant) { } @@ -251,7 +251,7 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo { m_final_volume = volume; } - + /// Get the final volume Scalar getFinalVolume() const { diff --git a/hoomd/pytest/test_box_variant.py b/hoomd/pytest/test_box_variant.py index 50578da146..1c2ccbe859 100644 --- a/hoomd/pytest/test_box_variant.py +++ b/hoomd/pytest/test_box_variant.py @@ -13,32 +13,56 @@ import hoomd.variant import pytest - -box_to_array = lambda box: np.array([box.Lx, box.Ly, box.Lz, box.xy, box.xz, box.yz]) +box_to_array = lambda box: 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) _test_scalar_variant1 = hoomd.variant.Ramp(0, 1, 100, 200) _test_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': _test_scalar_variant1}), - (hoomd.variant.box.InverseVolumeRamp, {'initial_box': _test_box2, 'final_volume': 1000, 't_start': 10, 't_ramp': 50}), + (hoomd.variant.box.Constant, { + 'box': _test_box1 + }), + (hoomd.variant.box.Interpolate, { + 'initial_box': _test_box1, + 'final_box': _test_box2, + 'variant': _test_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': [_test_scalar_variant1, _test_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]}), + (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': [_test_scalar_variant1, _test_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()} @@ -48,17 +72,19 @@ def test_setattr(cls, 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) + 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) + scale_L = (current_volume / self._initial_volume)**(1 / 3) return np.concatenate((self._box1.L * scale_L, self._box1.tilts)) def __eq__(self, other): @@ -70,21 +96,25 @@ def test_custom(): # 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) + 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) - box_t = lambda t: hoomd._hoomd._test_vector_variant_box_call(custom_variant, t) - 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)): + box_t = lambda t: hoomd._hoomd._test_vector_variant_box_call( + custom_variant, t) + 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(_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) + 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)) @@ -105,24 +135,31 @@ def test_interpolate_evaluation(): 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)) + 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) + variant = hoomd.variant.box.InverseVolumeRamp(box1, final_volume, t_start, + t_ramp) volume = lambda variant, timestep: hoomd.Box(*variant(timestep)).volume assert volume(variant, 0) == box1.volume assert volume(variant, 5) == box1.volume assert volume(variant, 10) == box1.volume assert volume(variant, 11) != box1.volume - npt.assert_allclose(volume(variant, 35), 1 / (0.75 / box1.volume + 0.25 / final_volume)) - npt.assert_allclose(volume(variant, 60), 1 / (0.5 * (1 / box1.volume + 1 / final_volume))) - npt.assert_allclose(volume(variant, 85), 1 / (0.25 / box1.volume + 0.75 / final_volume)) + npt.assert_allclose(volume(variant, 35), 1 / + (0.75 / box1.volume + 0.25 / final_volume)) + npt.assert_allclose(volume(variant, 60), 1 / + (0.5 * (1 / box1.volume + 1 / final_volume))) + npt.assert_allclose(volume(variant, 85), 1 / + (0.25 / box1.volume + 0.75 / final_volume)) npt.assert_allclose(volume(variant, 110), final_volume) npt.assert_allclose(volume(variant, 1010), final_volume) # make sure tilts don't change diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index 4074d6d2dd..e17f477b0d 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -46,7 +46,6 @@ def box(self, box): self._box = box._cpp_obj - class Interpolate(_hoomd.VectorVariantBoxInterpolate, BoxVariant): """Interpolate between two boxes linearly in time. @@ -71,7 +70,7 @@ def __init__(self, initial_box, final_box, variant): variant = variant_preprocessing(variant) BoxVariant.__init__(self) _hoomd.VectorVariantBoxInterpolate.__init__(self, box1._cpp_obj, - box2._cpp_obj, variant) + box2._cpp_obj, variant) @property def initial_box(self): @@ -87,7 +86,7 @@ def initial_box(self, box): 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) @@ -126,7 +125,7 @@ def __init__(self, initial_box, final_volume, t_start, t_ramp): 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) From 4c7a5eeff77ef28fb51828e0524f8eaf22c6dbb0 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 16 Jan 2024 21:47:36 -0500 Subject: [PATCH 27/37] formatting --- hoomd/pytest/test_box_variant.py | 50 +++++++++++++++++--------------- hoomd/variant/box.py | 10 +++---- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/hoomd/pytest/test_box_variant.py b/hoomd/pytest/test_box_variant.py index 1c2ccbe859..eda4700e25 100644 --- a/hoomd/pytest/test_box_variant.py +++ b/hoomd/pytest/test_box_variant.py @@ -1,20 +1,17 @@ # Copyright (c) 2009-2023 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -import copy -from inspect import isclass -import itertools -import pickle - import numpy as np import numpy.testing as npt -from hoomd.conftest import pickling_check import hoomd import hoomd.variant import pytest -box_to_array = lambda box: np.array( - [box.Lx, box.Ly, box.Lz, box.xy, box.xz, box.yz]) + +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) _test_scalar_variant1 = hoomd.variant.Ramp(0, 1, 100, 200) @@ -100,13 +97,15 @@ def test_custom(): _test_box1.xy, _test_box1.xz, _test_box1.yz) custom_variant = VolumeRampBoxVariant(_test_box1, final_volume, 100, 100) - box_t = lambda t: hoomd._hoomd._test_vector_variant_box_call( - custom_variant, t) + 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(_t), box_to_array(test_box)) + npt.assert_allclose(box_t(custom_variant, _t), box_to_array(test_box)) def test_interpolate_evaluation(): @@ -149,19 +148,22 @@ def test_inverse_volume_ramp_evaluation(): t_ramp = 100 variant = hoomd.variant.box.InverseVolumeRamp(box1, final_volume, t_start, t_ramp) - volume = lambda variant, timestep: hoomd.Box(*variant(timestep)).volume - assert volume(variant, 0) == box1.volume - assert volume(variant, 5) == box1.volume - assert volume(variant, 10) == box1.volume - assert volume(variant, 11) != box1.volume - npt.assert_allclose(volume(variant, 35), 1 / - (0.75 / box1.volume + 0.25 / final_volume)) - npt.assert_allclose(volume(variant, 60), 1 / - (0.5 * (1 / box1.volume + 1 / final_volume))) - npt.assert_allclose(volume(variant, 85), 1 / - (0.25 / box1.volume + 0.75 / final_volume)) - npt.assert_allclose(volume(variant, 110), final_volume) - npt.assert_allclose(volume(variant, 1010), final_volume) + + 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), 1 / (0.75 / box1.volume + 0.25 / final_volume)) + npt.assert_allclose( + get_volume(variant, 60), 1 / (0.5 / box1.volume + 0.5 / final_volume)) + npt.assert_allclose( + get_volume(variant, 85), 1 / (0.25 / box1.volume + 0.75 / final_volume)) + npt.assert_allclose(get_volume(variant, 110), final_volume) + npt.assert_allclose(get_volume(variant, 1010), final_volume) # make sure tilts don't change npt.assert_allclose(box1.tilts, variant(0)[3:]) npt.assert_allclose(box1.tilts, variant(5)[3:]) diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index e17f477b0d..262d445d4a 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -10,11 +10,11 @@ class BoxVariant(_hoomd.VectorVariantBox): """Box-like vector variant base class. - `hoomd.variant.box.BoxVariant` provides an interface to length-6 vector variants - that are valid `hoomd.box.box_like` objects. The return value of the - ``__call__`` method returns a length-6 array of scalar values that represent - the quantities ``Lx``, ``Ly``, ``Lz``, ``xy``, ``xz``, and ``yz`` of a - simulation box. + `hoomd.variant.box.BoxVariant` provides an interface to length-6 vector + variants that are valid `hoomd.box.box_like` objects. The return value of + the ``__call__`` method returns a length-6 array of scalar values that + represent the quantities ``Lx``, ``Ly``, ``Lz``, ``xy``, ``xz``, and ``yz`` + of a simulation box. """ pass From 2b933b4806ecf21189689738d7bccf499a318fd1 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 16 Jan 2024 22:01:30 -0500 Subject: [PATCH 28/37] Fix error in docs and formatting hack --- hoomd/pytest/test_box_variant.py | 12 ++++++------ hoomd/variant/box.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/hoomd/pytest/test_box_variant.py b/hoomd/pytest/test_box_variant.py index eda4700e25..771aa264f4 100644 --- a/hoomd/pytest/test_box_variant.py +++ b/hoomd/pytest/test_box_variant.py @@ -156,12 +156,12 @@ def get_volume(variant, timestep): 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), 1 / (0.75 / box1.volume + 0.25 / final_volume)) - npt.assert_allclose( - get_volume(variant, 60), 1 / (0.5 / box1.volume + 0.5 / final_volume)) - npt.assert_allclose( - get_volume(variant, 85), 1 / (0.25 / box1.volume + 0.75 / final_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 diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index 262d445d4a..c9804f2816 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -55,7 +55,7 @@ class Interpolate(_hoomd.VectorVariantBoxInterpolate, BoxVariant): variant (hoomd.variant.variant_like): A variant used to interpolate between the two boxes. - Ramp returns the array corresponding to *initial_box* for + `Interpolate` returns the array corresponding to *initial_box* for :math:`t \\leq t_{\\mathrm{start}}` and *final_box* for :math:`t \\geq t_{\\mathrm{start}} + t_{\\mathrm{ramp}}`. From db878a947b36e48f4ba0515f53d8a919db02dc88 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 16 Jan 2024 22:07:02 -0500 Subject: [PATCH 29/37] Remove pybind11 pickle code --- hoomd/VectorVariant.cc | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/hoomd/VectorVariant.cc b/hoomd/VectorVariant.cc index 67dbba4779..c51182cf05 100644 --- a/hoomd/VectorVariant.cc +++ b/hoomd/VectorVariant.cc @@ -50,35 +50,7 @@ void export_VectorVariantBox(pybind11::module& m) VectorVariantBox, std::shared_ptr>(m, "VectorVariantBoxConstant") .def(pybind11::init>()) - .def_property("_box", &VectorVariantBoxConstant::getBox, &VectorVariantBoxConstant::setBox) - /* - .def( - pybind11::pickle( - [](VectorVariantBoxConstant& variant) - { - std::shared_ptr box = variant.getBox(); - std::array arr = {box->getL().x, - box->getL().y, - box->getL().z, - box->getTiltFactorXY(), - box->getTiltFactorXZ(), - box->getTiltFactorYZ()}; - return pybind11::make_tuple(arr); - }, - [](pybind11::tuple params) - { - Scalar Lx = params[0][0].cast(); - Scalar Ly = params[0][1].cast(); - Scalar Lz = params[0][2].cast(); - Scalar xy = params[0][3].cast(); - Scalar xz = params[0][4].cast(); - Scalar yz = params[0][5].cast(); - std::shared_ptr box(Lx, Ly, Lz); - box->setTiltFactors(xy, xz, yz); - return VectorVariantBoxConstant(box); - })); - */ - ; + .def_property("_box", &VectorVariantBoxConstant::getBox, &VectorVariantBoxConstant::setBox); pybind11::class_ Date: Mon, 12 Feb 2024 22:17:51 -0500 Subject: [PATCH 30/37] Address reviews --- hoomd/VectorVariant.cc | 10 ++-- hoomd/VectorVariant.h | 101 ++++++++++++++++++++++---------------- hoomd/module.cc | 2 +- hoomd/variant/__init__.py | 10 +--- hoomd/variant/box.py | 47 +++++++++++++----- 5 files changed, 104 insertions(+), 66 deletions(-) diff --git a/hoomd/VectorVariant.cc b/hoomd/VectorVariant.cc index c51182cf05..068b4682e8 100644 --- a/hoomd/VectorVariant.cc +++ b/hoomd/VectorVariant.cc @@ -38,7 +38,7 @@ std::array testVectorVariantBoxCall(std::shared_ptr return (*t)(step); } -void export_VectorVariantBox(pybind11::module& m) +void export_VectorVariantBoxClasses(pybind11::module& m) { pybind11::class_>( m, @@ -58,11 +58,11 @@ void export_VectorVariantBox(pybind11::module& m) .def(pybind11:: init, std::shared_ptr, std::shared_ptr>()) .def_property("_initial_box", - &VectorVariantBoxInterpolate::getBox1, - &VectorVariantBoxInterpolate::setBox1) + &VectorVariantBoxInterpolate::getInitialBox, + &VectorVariantBoxInterpolate::setInitialBox) .def_property("_final_box", - &VectorVariantBoxInterpolate::getBox2, - &VectorVariantBoxInterpolate::setBox2) + &VectorVariantBoxInterpolate::getFinalBox, + &VectorVariantBoxInterpolate::setFinalBox) .def_property("variant", &VectorVariantBoxInterpolate::getVariant, &VectorVariantBoxInterpolate::setVariant); diff --git a/hoomd/VectorVariant.h b/hoomd/VectorVariant.h index 82fa25c643..015791ef83 100644 --- a/hoomd/VectorVariant.h +++ b/hoomd/VectorVariant.h @@ -1,9 +1,6 @@ // Copyright (c) 2009-2023 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. -// Copyright (c) 2009-2023 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - #pragma once #include @@ -41,6 +38,11 @@ template class PYBIND11_EXPORT VectorVariant } }; +/** 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: @@ -55,6 +57,10 @@ class PYBIND11_EXPORT VectorVariantBox : public VectorVariant<6> } }; +/** Constant box vector variant + + Returns a constant vector. + */ class PYBIND11_EXPORT VectorVariantBoxConstant : public VectorVariantBox { public: @@ -86,18 +92,30 @@ class PYBIND11_EXPORT VectorVariantBoxConstant : public VectorVariantBox 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 linearly in time. + /** Construct a VectorVariantBoxInterpolate to interpolate between two boxes. - @param box1 The initial box - @param box2 The final box + @param initial_box The initial box + @param final_box The final box */ - VectorVariantBoxInterpolate(std::shared_ptr box1, - std::shared_ptr box2, + VectorVariantBoxInterpolate(std::shared_ptr initial_box, + std::shared_ptr final_box, std::shared_ptr variant) - : m_box1(box1), m_box2(box2), m_variant(variant) + : m_initial_box(initial_box), m_final_box(final_box), m_variant(variant) { } @@ -117,34 +135,34 @@ class PYBIND11_EXPORT VectorVariantBoxInterpolate : public VectorVariantBox scale = (cur_value - min) / (max - min); } - const auto& box1 = *m_box1; - const auto& box2 = *m_box2; - Scalar3 new_L = box2.getL() * scale + box1.getL() * (1.0 - scale); - Scalar xy = box2.getTiltFactorXY() * scale + (1.0 - scale) * box1.getTiltFactorXY(); - Scalar xz = box2.getTiltFactorXZ() * scale + (1.0 - scale) * box1.getTiltFactorXZ(); - Scalar yz = box2.getTiltFactorYZ() * scale + (1.0 - scale) * box1.getTiltFactorYZ(); + 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 getBox1() + std::shared_ptr getInitialBox() { - return m_box1; + return m_initial_box; } - void setBox1(std::shared_ptr box) + void setInitialBox(std::shared_ptr box) { - m_box1 = box; + m_initial_box = box; } - std::shared_ptr getBox2() + std::shared_ptr getFinalBox() { - return m_box2; + return m_final_box; } - void setBox2(std::shared_ptr box) + void setFinalBox(std::shared_ptr box) { - m_box2 = box; + m_final_box = box; } /// Set the variant for interpolation @@ -161,26 +179,31 @@ class PYBIND11_EXPORT VectorVariantBoxInterpolate : public VectorVariantBox protected: /// The starting box, associated with the minimum of the variant. - std::shared_ptr m_box1; + std::shared_ptr m_initial_box; /// The final box, associated with the maximum of the variant. - std::shared_ptr m_box2; + 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 box1, + VectorVariantBoxInverseVolumeRamp(std::shared_ptr initial_box, Scalar final_volume, uint64_t t_start, uint64_t t_ramp) - : m_initial_box(box1), m_final_volume(final_volume), m_variant(0, 1, t_start, t_ramp) + : m_initial_box(initial_box), m_final_volume(final_volume), m_variant(0, 1, t_start, t_ramp) { - m_is2D = m_initial_box->getL().z == 0; - m_initial_volume = m_initial_box->getVolume(m_is2D); + 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) @@ -190,7 +213,7 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo // 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_is2D) + if (m_is_2d) { L_scale = pow(current_volume / m_initial_volume, Scalar(1.0 / 2.0)); } @@ -218,8 +241,8 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo void setInitialBox(std::shared_ptr box) { m_initial_box = box; - m_is2D = box->getL().z == 0; - m_initial_volume = box->getVolume(m_is2D); + m_is_2d = box->getL().z == 0; + m_initial_volume = box->getVolume(m_is_2d); } /// Set the starting time step. @@ -262,27 +285,23 @@ class PYBIND11_EXPORT VectorVariantBoxInverseVolumeRamp : public VectorVariantBo /// The starting box. std::shared_ptr m_initial_box; - /// The volume of box1. + /// 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 box1 is 2-dimensional or not - bool m_is2D; - - /// The current value of the volume - Scalar m_current_volume; + /// Whether initial_box is 2-dimensional or not + bool m_is_2d; /// Variant for computing scale value - VariantRamp m_variant; //!< Variant that interpolates between boxes + VariantRamp m_variant; }; namespace detail { /// Export Variant classes to Python -void export_VectorVariantBox(pybind11::module& m); -void export_VectorVariantBoxConstant(pybind11::module& m); +void export_VectorVariantBoxClasses(pybind11::module& m); } // end namespace detail diff --git a/hoomd/module.cc b/hoomd/module.cc index dbfb2e76ef..6bfc04bd1c 100644 --- a/hoomd/module.cc +++ b/hoomd/module.cc @@ -355,7 +355,7 @@ PYBIND11_MODULE(_hoomd, m) export_Variant(m); // vector variant - export_VectorVariantBox(m); + export_VectorVariantBoxClasses(m); // messenger export_Messenger(m); diff --git a/hoomd/variant/__init__.py b/hoomd/variant/__init__.py index 6e233ddbef..a0f50b5aec 100644 --- a/hoomd/variant/__init__.py +++ b/hoomd/variant/__init__.py @@ -3,7 +3,7 @@ """Define quantities that vary over the simulation. -A `Variant` object represents a scalar function of the time step. Some +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`. @@ -11,11 +11,5 @@ provided subclasses. """ -from hoomd.variant.scalar import Variant -from hoomd.variant.scalar import Constant -from hoomd.variant.scalar import Ramp -from hoomd.variant.scalar import Cycle -from hoomd.variant.scalar import Power -from hoomd.variant.scalar import variant_like - +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 index c9804f2816..9829b19438 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -27,7 +27,6 @@ class Constant(_hoomd.VectorVariantBoxConstant, BoxVariant): `Constant` returns ``[box.Lx, box.Ly, box.Lz, box.xz, box.xz, box.yz]`` at all time steps. - """ def __init__(self, box): @@ -47,7 +46,7 @@ def box(self, box): class Interpolate(_hoomd.VectorVariantBoxInterpolate, BoxVariant): - """Interpolate between two boxes linearly in time. + """Interpolate between two boxes linearly. Args: initial_box (hoomd.box.box_like): The initial box. @@ -55,9 +54,25 @@ class Interpolate(_hoomd.VectorVariantBoxInterpolate, BoxVariant): variant (hoomd.variant.variant_like): A variant used to interpolate between the two boxes. - `Interpolate` returns the array corresponding to *initial_box* for - :math:`t \\leq t_{\\mathrm{start}}` and *final_box* for - :math:`t \\geq t_{\\mathrm{start}} + t_{\\mathrm{ramp}}`. + ``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 @@ -74,7 +89,7 @@ def __init__(self, initial_box, final_box, variant): @property def initial_box(self): - """hoomd.Box: the initial box.""" + """hoomd.Box: The initial box.""" return Box._from_cpp(self._initial_box) @initial_box.setter @@ -94,7 +109,7 @@ def final_box(self, box): class InverseVolumeRamp(_hoomd.VectorVariantBoxInverseVolumeRamp, BoxVariant): - """Produce box arrays whose inverse volume changes linearly with time. + """Produce box arrays whose inverse volume changes linearly. Args: initial_box (hoomd.box.box_like): The initial box. @@ -103,11 +118,21 @@ class InverseVolumeRamp(_hoomd.VectorVariantBoxInverseVolumeRamp, BoxVariant): t_ramp (int): The length of the ramp. ``InverseVolumeRamp`` produces box arrays that correspond to a box whose - **inverse volume** (i.e., density for a constant number of particles) varies - linearly with time. The shape of the box remains constant, that is, the + **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`) + :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. @@ -123,7 +148,7 @@ def __init__(self, initial_box, final_volume, t_start, t_ramp): @property def initial_box(self): - """hoomd.Box: the initial box.""" + """hoomd.Box: The initial box.""" return Box._from_cpp(self._initial_box) @initial_box.setter From 8b5facd3a4c70d8c622798b342ae8161949534ac Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 13 Feb 2024 11:42:41 -0500 Subject: [PATCH 31/37] formatting --- hoomd/VectorVariant.h | 33 +++++++++++++++++++-------------- hoomd/variant/__init__.py | 3 ++- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/hoomd/VectorVariant.h b/hoomd/VectorVariant.h index 015791ef83..ff591c6280 100644 --- a/hoomd/VectorVariant.h +++ b/hoomd/VectorVariant.h @@ -40,8 +40,8 @@ template class PYBIND11_EXPORT VectorVariant /** 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. + 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> { @@ -95,13 +95,15 @@ class PYBIND11_EXPORT VectorVariantBoxConstant : public VectorVariantBox /** 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). + 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 @@ -138,9 +140,12 @@ class PYBIND11_EXPORT VectorVariantBoxInterpolate : public VectorVariantBox 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(); + 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; } @@ -190,8 +195,8 @@ class PYBIND11_EXPORT VectorVariantBoxInterpolate : public VectorVariantBox /** 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. + 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 { diff --git a/hoomd/variant/__init__.py b/hoomd/variant/__init__.py index a0f50b5aec..c981454de3 100644 --- a/hoomd/variant/__init__.py +++ b/hoomd/variant/__init__.py @@ -11,5 +11,6 @@ provided subclasses. """ -from hoomd.variant.scalar import (Variant, Constant, Ramp, Cycle, Power, variant_like) +from hoomd.variant.scalar import (Variant, Constant, Ramp, Cycle, Power, + variant_like) from hoomd.variant import box From 4c926c81bc81a36ed4475ded47cd27cb590d9fb9 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 13 Feb 2024 11:48:04 -0500 Subject: [PATCH 32/37] flake8 --- hoomd/variant/box.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index 9829b19438..83129b7c1c 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -54,9 +54,9 @@ class Interpolate(_hoomd.VectorVariantBoxInterpolate, BoxVariant): 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``: + ``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:: @@ -118,21 +118,23 @@ class InverseVolumeRamp(_hoomd.VectorVariantBoxInverseVolumeRamp, BoxVariant): 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. + **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)`: + ``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} + 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}}}`. + where :math:`\\lambda = \\frac{t - t_{\\mathrm{start}}}{t_{\\mathrm{ramp}} - + t_{\\mathrm{start}}}`. Attributes: final_volume (float): The volume of the final box. From 75659e713c8eebd5961158c38989a587bd3eade1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Anderson" Date: Wed, 28 Feb 2024 07:06:03 -0500 Subject: [PATCH 33/37] Apply suggestions from code review Co-authored-by: Brandon Butler --- hoomd/pytest/test_box_variant.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/hoomd/pytest/test_box_variant.py b/hoomd/pytest/test_box_variant.py index 771aa264f4..16b5ff6d40 100644 --- a/hoomd/pytest/test_box_variant.py +++ b/hoomd/pytest/test_box_variant.py @@ -101,11 +101,11 @@ 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), + 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)) + 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(): @@ -165,8 +165,5 @@ def get_volume(variant, timestep): npt.assert_allclose(get_volume(variant, 110), final_volume) npt.assert_allclose(get_volume(variant, 1010), final_volume) # make sure tilts don't change - npt.assert_allclose(box1.tilts, variant(0)[3:]) - npt.assert_allclose(box1.tilts, variant(5)[3:]) - npt.assert_allclose(box1.tilts, variant(25)[3:]) - npt.assert_allclose(box1.tilts, variant(125)[3:]) - npt.assert_allclose(box1.tilts, variant(625)[3:]) + for step in (0, 5, 252 125, 625): + npt.assert_allclose(box1.tilts, variant(step)[3:]) From 00d009046a057124d127b54e52aa2d66260b7bd5 Mon Sep 17 00:00:00 2001 From: "Joshua A. Anderson" Date: Wed, 28 Feb 2024 08:12:20 -0500 Subject: [PATCH 34/37] Apply changes from code review. --- hoomd/pytest/test_box_variant.py | 70 ++++++++++++++++---------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/hoomd/pytest/test_box_variant.py b/hoomd/pytest/test_box_variant.py index 16b5ff6d40..d369758d5f 100644 --- a/hoomd/pytest/test_box_variant.py +++ b/hoomd/pytest/test_box_variant.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# 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 @@ -12,22 +12,22 @@ 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) -_test_scalar_variant1 = hoomd.variant.Ramp(0, 1, 100, 200) -_test_scalar_variant2 = hoomd.variant.Ramp(0, 1, 10, 30) +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 + 'box': test_box1 }), (hoomd.variant.box.Interpolate, { - 'initial_box': _test_box1, - 'final_box': _test_box2, - 'variant': _test_scalar_variant1 + 'initial_box': test_box1, + 'final_box': test_box2, + 'variant': scalar_variant1 }), (hoomd.variant.box.InverseVolumeRamp, { - 'initial_box': _test_box2, + 'initial_box': test_box2, 'final_volume': 1000, 't_start': 10, 't_ramp': 50 @@ -37,15 +37,15 @@ def box_to_array(box): # variant: dict(attr: [val1, val2,...]) valid_attrs = [ (hoomd.variant.box.Constant, { - 'box': [_test_box1, _test_box2] + 'box': [test_box1, test_box2] }), (hoomd.variant.box.Interpolate, { - 'initial_box': [_test_box1, _test_box2], - 'final_box': [_test_box2, _test_box1], - 'variant': [_test_scalar_variant1, _test_scalar_variant2] + '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], + 'initial_box': [test_box1, test_box2], 'final_volume': [1000, 300], 't_start': [0, 10], 't_ramp': [10, 50, 100] @@ -92,19 +92,19 @@ 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) + 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 + (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)) @@ -112,33 +112,33 @@ 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, + 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(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)) + 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)) + 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)) + 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)) + 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)) + 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), box_to_array(test_box2)) npt.assert_allclose(box_variant(t_start + t_ramp + 100), - box_to_array(_test_box2)) + box_to_array(test_box2)) npt.assert_allclose(box_variant(t_start + t_ramp + 1000000), - box_to_array(_test_box2)) + box_to_array(test_box2)) def test_inverse_volume_ramp_evaluation(): @@ -165,5 +165,5 @@ def get_volume(variant, timestep): 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): + for step in (0, 5, 252, 125, 625): npt.assert_allclose(box1.tilts, variant(step)[3:]) From 75c7de360aedcb4259d9440aa599f1ff6853bc76 Mon Sep 17 00:00:00 2001 From: "Joshua A. Anderson" Date: Wed, 28 Feb 2024 08:13:08 -0500 Subject: [PATCH 35/37] Revise docs. --- hoomd/variant/box.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index 83129b7c1c..81409038ba 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -10,11 +10,32 @@ class BoxVariant(_hoomd.VectorVariantBox): """Box-like vector variant base class. - `hoomd.variant.box.BoxVariant` provides an interface to length-6 vector - variants that are valid `hoomd.box.box_like` objects. The return value of - the ``__call__`` method returns a length-6 array of scalar values that - represent the quantities ``Lx``, ``Ly``, ``Lz``, ``xy``, ``xz``, and ``yz`` - of a simulation box. + `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: list[float] """ pass @@ -99,7 +120,7 @@ def initial_box(self, box): @property def final_box(self): - """hoomd.Box: the final box.""" + """hoomd.Box: The final box.""" return Box._from_cpp(self._final_box) @final_box.setter From 4679d2c116a332b7c3ffc9baae1dc869afd5559a Mon Sep 17 00:00:00 2001 From: "Joshua A. Anderson" Date: Wed, 28 Feb 2024 08:13:19 -0500 Subject: [PATCH 36/37] Run pre-commit. --- hoomd/VectorVariant.cc | 2 +- hoomd/VectorVariant.h | 2 +- hoomd/variant/__init__.py | 2 +- hoomd/variant/box.py | 2 +- sphinx-doc/module-hoomd-variant-box.rst | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hoomd/VectorVariant.cc b/hoomd/VectorVariant.cc index 068b4682e8..371f86c474 100644 --- a/hoomd/VectorVariant.cc +++ b/hoomd/VectorVariant.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// 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" diff --git a/hoomd/VectorVariant.h b/hoomd/VectorVariant.h index ff591c6280..78fabdbfd1 100644 --- a/hoomd/VectorVariant.h +++ b/hoomd/VectorVariant.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2023 The Regents of the University of Michigan. +// Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. #pragma once diff --git a/hoomd/variant/__init__.py b/hoomd/variant/__init__.py index c981454de3..1440bd8d83 100644 --- a/hoomd/variant/__init__.py +++ b/hoomd/variant/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# 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. diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index 81409038ba..245b8f15ee 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009-2023 The Regents of the University of Michigan. +# 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.""" diff --git a/sphinx-doc/module-hoomd-variant-box.rst b/sphinx-doc/module-hoomd-variant-box.rst index 34260ea44e..a7693e715e 100644 --- a/sphinx-doc/module-hoomd-variant-box.rst +++ b/sphinx-doc/module-hoomd-variant-box.rst @@ -1,4 +1,4 @@ -.. Copyright (c) 2009-2023 The Regents of the University of Michigan. +.. 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 From 7f123a58a75e451d84bf0de0a713a8a977c6d686 Mon Sep 17 00:00:00 2001 From: "Joshua A. Anderson" Date: Wed, 28 Feb 2024 08:27:15 -0500 Subject: [PATCH 37/37] Explicitly document length of return type. --- hoomd/variant/box.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hoomd/variant/box.py b/hoomd/variant/box.py index 245b8f15ee..f2d93a6ecd 100644 --- a/hoomd/variant/box.py +++ b/hoomd/variant/box.py @@ -35,7 +35,7 @@ def __call__(self, timestep): :param timestep: The time step. :type timestep: int :return: The value of the function at the given time step. - :rtype: list[float] + :rtype: [float, float, float, float, float, float] """ pass