diff --git a/.github/workflows/dockerWF.yml b/.github/workflows/dockerWF.yml index e37f001c4f..ae1d42618a 100644 --- a/.github/workflows/dockerWF.yml +++ b/.github/workflows/dockerWF.yml @@ -29,3 +29,6 @@ jobs: - name: Build and run tests run: | make -C docker ${{ matrix.variant }} + - name: PyCV tests + run: | + make -C docker ${{ matrix.variant }}-pycv diff --git a/.github/workflows/linuxWF.yml b/.github/workflows/linuxWF.yml index 0811812765..ec40112783 100644 --- a/.github/workflows/linuxWF.yml +++ b/.github/workflows/linuxWF.yml @@ -24,6 +24,7 @@ jobs: - -coverage-mpi- - -debug- - -debug-mpi- + - -pycv-mpi- # temporarily commented out # see https://github.com/plumed/plumed2/issues/976 # - -intel- @@ -148,7 +149,7 @@ jobs: make nmcheck ccache -s -M 100M - name: Run tests - if: ${{ ! contains( matrix.variant, '-doc-mpi-' ) }} + if: ${{ ! contains( matrix.variant, '-doc-mpi-' ) && ! contains( matrix.variant, '-pycv-mpi-' ) }} run: | (while true; do # see https://github.com/actions/virtual-environments/issues/1860 df -h @@ -181,3 +182,12 @@ jobs: GIT_TOKEN: ${{ secrets.GIT_TOKEN_PLUMEDBOT }} run: | .ci/push doc + - name: Compile and test pycv + if: contains( matrix.variant, '-pycv-' ) + working-directory: ./plugins/pycv/ + run: | + pip install --user pybind11 + source ../../sourceme.sh + ln -s $(realpath ../../regtest/scripts) ./regtest/scripts + ./prepareMakeForDevelop.sh + make check diff --git a/docker/Makefile b/docker/Makefile index e3675eff78..fd21e15e4e 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -1,14 +1,20 @@ -.PHONY: ubuntu plumed2.tgz clean fedora38 rocky8 +.PHONY: ubuntu plumed2.tgz clean fedora38 fedora38-pycv rocky8 rocky8-pycv ubuntu: plumed2.tgz - docker build -t plumed . + docker build -t plumed --progress=plain . fedora38: plumed2.tgz - docker build -t plumed -f fedora38 . + docker build -t plumed:fedora38 --progress=plain -f fedora38 . +fedora38-pycv: plumed2.tgz + docker build -t plumed:fedora38-pycv --progress=plain -f fedora38-pycv . + rocky8: plumed2.tgz - docker build -t plumed -f rocky8 . + docker build -t plumed:rocky8 --progress=plain -f rocky8 . + +rocky8-pycv: plumed2.tgz + docker build -t plumed:rocky8-pycv --progress=plain -f rocky8-pycv . plumed2.tgz: cd ../ ; git archive HEAD -o "$(CURDIR)"/plumed2.tgz --prefix plumed2/ ; cd - diff --git a/docker/fedora38 b/docker/fedora38 index a52c8ade91..6c2b4cffa7 100644 --- a/docker/fedora38 +++ b/docker/fedora38 @@ -43,4 +43,3 @@ RUN . /etc/bashrc \ && ./configure --enable-modules=all --enable-boost_serialization \ && make -j 4 \ && make check - diff --git a/docker/fedora38-pycv b/docker/fedora38-pycv new file mode 100644 index 0000000000..22b89ebb11 --- /dev/null +++ b/docker/fedora38-pycv @@ -0,0 +1,11 @@ +FROM plumed:fedora38 + +RUN source /etc/bashrc \ + && module load mpi \ + && cd plumed2 \ + && source ./sourceme.sh \ + && cd plugins/pycv \ + && pip3 install pybind11 \ + && ln -s $(realpath ../../regtest/scripts) ./regtest/scripts \ + && ./prepareMakeForDevelop.sh \ + && make check diff --git a/docker/rocky8 b/docker/rocky8 index ff0b6bd1f0..ca37f92a05 100644 --- a/docker/rocky8 +++ b/docker/rocky8 @@ -7,23 +7,41 @@ RUN yum -y update \ && dnf config-manager --set-enabled powertools \ && yum -y group install "Development Tools" \ && yum -y install environment-modules gawk vim wget \ - lapack-devel blas-devel zlib-devel gsl-devel fftw-devel openmpi-devel boost-devel \ - python3 python3-devel python3-pip \ - && pip3 install pillow==8.3.2 \ - && pip3 install numpy==1.19 \ - && pip3 install "cython<3" numpy pandas mdtraj \ - && pip3 install MDAnalysis==1.0.0 + lapack-devel blas-devel zlib-devel gsl-devel fftw-devel openmpi-devel boost-devel \ + openssl-devel libffi-devel RUN useradd -ms /bin/bash plumed +#These are needed for installing python + USER plumed WORKDIR /home/plumed +#Install python with --embedded +#COPY installPythonForDocker.sh /home/plumed +RUN PYVERSION="3.9.16" \ + && curl -LO https://www.python.org/ftp/python/${PYVERSION}/Python-${PYVERSION}.tgz \ + && tar xzf Python-${PYVERSION}.tgz \ + && cd Python-${PYVERSION} \ + && ./configure --enable-shared --prefix=$HOME/.local \ + && make -j 4 \ + && make install \ + && echo "export LD_LIBRARY_PATH=$HOME/.local/lib/${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH}" >> "$HOME/.bashrc" + +RUN . ./.bashrc \ + && pip3 install --user -U pip \ + && pip3 install --user pillow==8.3.2 \ + && pip3 install --user "Cython<3" \ + && pip3 install --user pandas \ + && pip3 install --user mdtraj \ + && pip3 install --user MDAnalysis==1.0.0 +# && pip3 install --user numpy==1.19 + # import plumed code. # assumes a file plumed2.tgz is present in the Dockerfile directory COPY plumed2.tgz /home/plumed # build and test plumed (no install) -RUN . /etc/bashrc \ +RUN . ./.bashrc \ && module load mpi \ && export OMPI_MCA_btl_base_warn_component_unused=0 \ && export OMPI_MCA_btl_base_verbose=0 \ @@ -42,4 +60,3 @@ RUN . /etc/bashrc \ && ./configure --enable-modules=all --enable-boost_serialization \ && make -j 4 \ && make check - diff --git a/docker/rocky8-pycv b/docker/rocky8-pycv new file mode 100644 index 0000000000..5b29e9c12d --- /dev/null +++ b/docker/rocky8-pycv @@ -0,0 +1,11 @@ +FROM plumed:rocky8 + +RUN source ./.bashrc \ + && module load mpi \ + && cd plumed2 \ + && source ./sourceme.sh \ + && cd plugins/pycv \ + && pip3 install --user pybind11 \ + && ln -s $(realpath ../../regtest/scripts) ./regtest/scripts \ + && ./prepareMakeForDevelop.sh \ + && make check diff --git a/plugins/pycv/.gitignore b/plugins/pycv/.gitignore new file mode 100644 index 0000000000..ce1d56103b --- /dev/null +++ b/plugins/pycv/.gitignore @@ -0,0 +1,4 @@ +Make.inc +*.o +*.so +env diff --git a/plugins/pycv/ActionWithPython.cpp b/plugins/pycv/ActionWithPython.cpp new file mode 100644 index 0000000000..097e868e1b --- /dev/null +++ b/plugins/pycv/ActionWithPython.cpp @@ -0,0 +1,142 @@ +/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +Copyright (c) 2019 of Toni Giorgino + +The pycv module is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The pycv module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with plumed. If not, see . ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ + +#include "ActionWithPython.h" + +#include "core/ActionWithValue.h" + +#include // everything needed for embedding +#include + +#include + + +namespace py = pybind11; + + +namespace PLMD { +namespace pycv { + + +// We can only have one interpreter globally. This is less than ideal +// because CVs can interfere with each other. The whole purpose of +// this superclass is to make a singleton. Putting it in the +// constructor makes it so that the interpreter is only initialized if +// one of the PYCV actions are used. +// https://pybind11.readthedocs.io/en/stable/reference.html#_CPPv422initialize_interpreterb + +unsigned PlumedScopedPythonInterpreter::use_count=0; +std::unique_ptr PlumedScopedPythonInterpreter::interpreterGuard = + nullptr; +std::mutex PlumedScopedPythonInterpreter::interpreterMutex{}; + +PlumedScopedPythonInterpreter::PlumedScopedPythonInterpreter(::PLMD::Log&outlog) + :log(outlog) { + std::lock_guard lk(interpreterMutex); + if(use_count==0 && Py_IsInitialized()) { + //this addresses the "calling pycv within a python interpreter problem" + ++use_count; + } + + if(use_count==0) { + std::cerr<< "------ initialized Python interpreter\n"; + log<< "------ initialized Python interpreter\n"; + interpreterGuard = std::make_unique(); + } else { + std::cerr << "------ Python interpreter already initializated\n"; + log << "------ Python interpreter already initializated\n"; + } + ++use_count; +} + +PlumedScopedPythonInterpreter::~PlumedScopedPythonInterpreter() { + // Finalization is tricky, because it should happen AFTER the + // destruction of ALL the python declared variables + std::lock_guard lk(interpreterMutex); + --use_count; + if(use_count==0) { + interpreterGuard.reset(nullptr); + std::cerr << "------ Python interpreter finalized\n"; + log << "------ Python interpreter finalized\n"; + } +} + +ActionWithPython::ActionWithPython (const ActionOptions&ao) + :Action(ao),guard(log) {} + +void ActionWithPython::pyParseFlag(const char* key, const ::pybind11::dict &initDict, bool& returnValue) { + parseFlag(key, returnValue); + if(initDict.contains(key)) { + bool defaultRet; + keywords.getLogicalDefault(key,defaultRet); + if (returnValue!=defaultRet) { + error(std::string("you specified the same keyword ").append(key)+ " both in python and in the settings file"); + } + returnValue = initDict[key].cast(); + } +} +/******************************************************************************/ +//Value/components init +void initializeValue(::PLMD::ActionWithValue& action,pybind11::dict &settingsDict) { + action.log << " will have a single component"; + bool withDerivatives=false; + if(settingsDict.contains("derivative")) { + withDerivatives=settingsDict["derivative"].cast(); + } + if(withDerivatives) { + action.addValueWithDerivatives(); + action.log << " WITH derivatives\n"; + } else { + action.addValue(); + action.log << " WITHOUT derivatives\n"; + } +} + +void initializeComponent(::PLMD::ActionWithValue& action,const std::string&name,py::dict &settingsDict) { + bool withDerivatives=false; + if(settingsDict.contains("derivative")) { + withDerivatives=settingsDict["derivative"].cast(); + } + + if(withDerivatives) { + action.addComponentWithDerivatives(name); + action.log << " WITH derivatives\n"; + } else { + action.addComponent(name); + action.log << " WITHOUT derivatives\n"; + } +} + +void valueSettings(py::dict &settings, Value* valPtr) { + if(settings.contains("period")) { + if (settings["period"].is_none()) { + valPtr->setNotPeriodic(); + } else { + py::tuple t = settings["period"]; + if(t.size()!=2) { + plumed_merror("period must have exactly 2 components"); + } + //the ballad py::str(t[0]).cast() is to not care about the type of input of the user + std::string min=py::str(t[0]).cast(); + std::string max=py::str(t[1]).cast(); + valPtr->setDomain(min, max); + } + } +} + +} // namespace pycv +} // namespace PLMD diff --git a/plugins/pycv/ActionWithPython.h b/plugins/pycv/ActionWithPython.h new file mode 100644 index 0000000000..a941eb4bae --- /dev/null +++ b/plugins/pycv/ActionWithPython.h @@ -0,0 +1,81 @@ +/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +Copyright (c) 2019 of Toni Giorgino + +The pycv module is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The pycv module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with plumed. If not, see . ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ +#ifndef __PLUMED_pycv_ActionWithPython_h //{ +#define __PLUMED_pycv_ActionWithPython_h + +#include +#include + +#include "core/Action.h" + +#include // everything needed for embedding + +namespace PLMD { +class Value; +namespace pycv { + +using pycvComm_t = double; +static constexpr auto PYTHONCV_CITATION = "Giorgino, (2019). PYCV: a PLUMED 2 Module Enabling the Rapid Prototyping of Collective Variables in Python. Journal of Open Source Software, 4(42), 1773. doi:10.21105/joss.01773"; +static constexpr auto BIASING_DISABLED = "PYCV: Gradient was expected as a second return value but is missing. Biasing won't work\n"; +static constexpr std::string_view PYCV_COMPONENTPREFIX="py"; + +///This class act both as a guard for the interpreter and a a case container for the python module and functions +class PlumedScopedPythonInterpreter final { +public: + PlumedScopedPythonInterpreter(::PLMD::Log&); + ~PlumedScopedPythonInterpreter(); +private: + ::PLMD::Log& log; + static unsigned use_count; + static std::unique_ptr<::pybind11::scoped_interpreter> interpreterGuard; + static std::mutex interpreterMutex; +}; + +class ActionWithPython: public virtual ::PLMD::Action { + //the guard MUST be set up before the python objects + // (so that it can be destroyed after them) + PlumedScopedPythonInterpreter guard; +public: + explicit ActionWithPython (const ActionOptions&); + ///redefinition of parse to avoid confict between plumed.dat and python options + template + void pyParse(const char* key, const ::pybind11::dict &initDict, T& returnValue); +///redefinition of parseFlag to avoid confict between plumed.dat and python options + void pyParseFlag(const char* key, const ::pybind11::dict &initDict, bool& returnValue); +}; + +template +void ActionWithPython::pyParse( + const char* key, const ::pybind11::dict &initDict, T& returnValue) { + T initVal(returnValue); + parse(key,returnValue); + //this is not robust, but with no access to Action::line we cannot use Tools::findKeyword + if(initDict.contains(key)) { + if (returnValue != initVal) { + error(std::string("you specified the same keyword ").append(key)+ " both in python and in the settings file"); + } + returnValue = initDict[key].cast(); + } +} + +void initializeValue(::PLMD::ActionWithValue&, pybind11::dict &); +void initializeComponent(::PLMD::ActionWithValue&,const std::string&,pybind11::dict &); +void valueSettings( pybind11::dict &r, Value* valPtr); + +} // namespace pycv +} // namespace PLMD +#endif //__PLUMED_pycv_ActionWithPython_h //} diff --git a/plugins/pycv/COPYRIGHT b/plugins/pycv/COPYRIGHT new file mode 100644 index 0000000000..31f6df6c8b --- /dev/null +++ b/plugins/pycv/COPYRIGHT @@ -0,0 +1,14 @@ +Copyright (c) 2019 of Toni Giorgino + +The pycv module is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The pycv module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with plumed. If not, see . diff --git a/plugins/pycv/Makefile b/plugins/pycv/Makefile new file mode 100644 index 0000000000..6435f491f2 --- /dev/null +++ b/plugins/pycv/Makefile @@ -0,0 +1,42 @@ +include Make.inc + +#Dependency tracking based on https://make.mad-scientist.net/papers/advanced-auto-dependency-generation/#tldr +#this assumes gcc +DEPDIR := .deps +DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d +PYBIND11FLAG = + +OBJS = ActionWithPython.o PythonCVInterface.o PythonFunction.o PlumedPythonEmbeddedModule.o + +ifeq ($(SOEXT),dylib) + SONAME_OPTION:=-Wl,-install_name +else + SONAME_OPTION:=-Wl,-soname +endif + +all: PythonCVInterface.$(SOEXT) + +#-fvisibility=hidden is needed for pybind11 (to not conflict with different pybind11 versions) +ActionWithPython.o PythonCVInterface.o PythonFunction.o PlumedPythonEmbeddedModule.o: PYBIND11FLAG= -fvisibility=hidden + +%.o: %.cpp $(DEPDIR)/%.d | $(DEPDIR) + @echo Compiling object $@ + @$(CXX) -c $(DEPFLAGS) $(CPPFLAGS) $(PYBIND11FLAG) $(ADDCPPFLAGS) $(CXXFLAGS) $< -o $@ + + +$(DEPDIR): ; @mkdir -p $@ + +DEPFILES := $(OBJS:%.o=$(DEPDIR)/%.d) +$(DEPFILES): +include $(wildcard $(DEPFILES)) + +PythonCVInterface.$(SOEXT): $(OBJS) + @echo Compiling linking $@ + @$(LDSHARED) $(ADDCLDFLAGS) $(SONAME_OPTION),"$(notdir $@)" $(DYNAMIC_LIBS) $(PLUMED_KERNEL) -o $@ $^ + +clean: + rm -f $(OBJS) PythonCVInterface.$(SOEXT) + +check: all + $(MAKE) -C regtest testclean + $(MAKE) -C regtest checkfail diff --git a/plugins/pycv/PlumedPythonEmbeddedModule.cpp b/plugins/pycv/PlumedPythonEmbeddedModule.cpp new file mode 100644 index 0000000000..8f1de1191f --- /dev/null +++ b/plugins/pycv/PlumedPythonEmbeddedModule.cpp @@ -0,0 +1,273 @@ +/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +Copyright (c) 2023 Daniele Rapetti + +The pycv module is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The pycv module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with plumed. If not, see . ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ + +#include // everything needed for embedding +#include +#include +#include +#include + +#include "tools/Vector.h" +#include "tools/NeighborList.h" + +#include "PythonCVInterface.h" +#include "PythonFunction.h" + +namespace py=pybind11; + +#define defGetter(pyfun,classname, cppfun, type, description) \ + def(pyfun, [](classname* self) -> type{ \ + return self->cppfun(); }, description) + +//NB: the action methods are written two times due to the diamond inheritance + +PYBIND11_EMBEDDED_MODULE(plumedCommunications, m) { + /*******************************default submodule****************************/ + py::module_ defaults = m.def_submodule("defaults", "Submodule with the default definitions"); + defaults.attr("COMPONENT") = py::dict(py::arg("period")=py::none(),py::arg("derivative")=true); + defaults.attr("COMPONENT_NODEV") = py::dict(py::arg("period")=py::none(),py::arg("derivative")=false); + + /*************************PLMD::pycv::PythonCVInterface**********************/ + py::class_(m, "PythonCVInterface") + .def_readwrite("data",&PLMD::pycv::PythonCVInterface::dataContainer,"Return an accessible dictionary that persist along all the simulation") + /***************************Start of Action methods***************************/ + //using "PLMD::pycv::PythonCVInterface::getStep" instead of the lambda gives compilation errors + .defGetter("getStep",PLMD::pycv::PythonCVInterface,getStep, long int,"Returns the current step") + .defGetter("getTime",PLMD::pycv::PythonCVInterface,getTime,double,"Return the present time") + .defGetter("getTimeStep",PLMD::pycv::PythonCVInterface,getTimeStep,double,"Return the timestep") + .defGetter("isRestart",PLMD::pycv::PythonCVInterface,getRestart,bool,"Return true if we are doing a restart") + .defGetter("isExchangeStep",PLMD::pycv::PythonCVInterface,getExchangeStep,bool,"Check if we are on an exchange step") + .def_property_readonly("label", + [](PLMD::pycv::PythonCVInterface* self) -> std::string { + return self->getLabel(); + }, + "returns the label") + .def("log",[](PLMD::pycv::PythonCVInterface* self, py::object data) { + self->log << py::str(data).cast(); + }, + "puts a string in the PLUMED output",py::arg("s")) + .def("lognl",[](PLMD::pycv::PythonCVInterface* self, py::object data) { + self->log << py::str(data).cast()<< "\n"; + }, + "puts a string in the PLUMED output (and appends a newline)",py::arg("s")) + /****************************End of Action methods***************************/ + .def("getPosition", + [](PLMD::pycv::PythonCVInterface* self, int i) -> py::array_t { + py::array_t::ShapeContainer shape({3}); + py::array_t atom(shape,&self->getPosition(i)[0]); + return atom; + }, + "Returns an ndarray with the position of the \"i\"th" + " atom requested by the action",py::arg("i")) + .def_property_readonly("nat", + [](PLMD::pycv::PythonCVInterface* self) -> size_t { + return self->getPositions().size(); + }, + "return the number of atoms" + ) + + //here I can use &PLMD::pycv::PythonCVInterface::makeWhole because is not in Action + // that is inherithed both by withValue and Atomistic + .def("makeWhole",&PLMD::pycv::PythonCVInterface::makeWhole,"Make atoms whole, assuming they are in the proper order") + .def("getPositions",[](PLMD::pycv::PythonCVInterface* self) -> py::array_t { + auto nat=self->getPositions().size(); + ///nat and 3 must be the same type: no warning + py::array_t::ShapeContainer shape({nat,static_cast(3)}); + py::array_t atomList(shape, + &self->getPositions()[0][0] + ); + return atomList; + }, + "Returns a numpy.array that contains the atomic positions of the atoms requested by the action") + .def("getPbc",&PLMD::pycv::PythonCVInterface::getPbc, + "returns an interface to the current pbcs") + .def("getNeighbourList",&PLMD::pycv::PythonCVInterface::getNL, + "returns an interface to the current Neighborlist") + +//https://pybind11.readthedocs.io/en/stable/advanced/functions.html#return-value-policies + /*.def("getAbsoluteIndexes", &PLMD::pycv::PythonCVInterface::getAbsoluteIndexes , + "Get the vector of absolute indexes.", + py::return_value_policy::reference)*/ + //using unsigned: if AtomNumber changes, also this must change + .def("absoluteIndexes",[](PLMD::pycv::PythonCVInterface* self) -> py::array_t { + auto nat=self->getPositions().size(); + py::array_t::ShapeContainer shape({nat}); + py::array_t atomIndexes(shape); + auto retAccessor = atomIndexes.mutable_unchecked<1>(); + for (decltype(nat) i=0; i < nat; ++i) { + //at time of writing getAbsoluteIndexes returns const std::vector & + retAccessor(i)=self->getAbsoluteIndexes()[i].index(); + } + return atomIndexes; + }, + "Get the vector of absolute indexes.") + + .def("charge", &PLMD::pycv::PythonCVInterface::getCharge, + "Get charge of i-th atom", py::arg("i")) + + .def("mass", &PLMD::pycv::PythonCVInterface::getMass, + "Get mass of i-th atom", py::arg("i")) + + .def("masses", + [](PLMD::pycv::PythonCVInterface* self) -> py::array_t { + auto nat=self->getPositions().size(); + py::array_t::ShapeContainer shape({nat}); + py::array_t masses(shape); + auto retAccessor = masses.mutable_unchecked<1>(); + for (decltype(nat) i=0; i < nat; ++i) { + retAccessor(i)=self->getMass(i); + } + return masses; + }, + "Returns and ndarray with the masses of the atoms requested by the action") + + .def("charges", + [](PLMD::pycv::PythonCVInterface* self) -> py::array_t { + auto nat=self->getPositions().size(); + py::array_t::ShapeContainer shape({nat}); + py::array_t charges(shape); + auto retAccessor = charges.mutable_unchecked<1>(); + for (decltype(nat) i=0; i < nat; ++i) { + retAccessor(i)=self->getCharge(i); + } + return charges; + }, + "Returns and ndarray with the charges of the atoms requested by the action"); + + /***********************************PLMD::Pbc********************************/ + py::class_(m, "Pbc") + //.def(py::init<>()) + .def("apply",[](const PLMD::Pbc* self, py::array_t& deltas) -> py::array_t { + //TODO:shape check + //TODO: this may be set up to modify the passed deltas, so no new intialization + // ^this needs to be VERY clear to te user + auto accessor = deltas.unchecked<2>(); + auto nat=deltas.shape(0); + py::array_t toRet({nat,deltas.shape(1)}); + auto retAccessor = toRet.mutable_unchecked<2>(); + for (decltype(nat) i=0; i < nat; ++i) { + auto t= self->distance(PLMD::Vector(0.0,0.0,0.0), + //I think this may be slow, but serves as a demonstration as a base for the tests + PLMD::Vector(accessor(i,0),accessor(i,1),accessor(i,2)), + nullptr); + retAccessor(i,0) = t[0]; + retAccessor(i,1) = t[1]; + retAccessor(i,2) = t[2]; + } + return toRet; + }, + "Apply PBC to a set of positions or distance vectors") + + //this should be optimized for speed +#define T3x3toArray( boxGetter ) \ +py::array_t toRet({3,3}); \ +auto retAccessor = toRet.mutable_unchecked<2>(); \ +PLMD::Tensor box=boxGetter; \ +for(unsigned i=0;i<3;++i){ \ + for(unsigned j=0;j<3;++j) \ + retAccessor(i,j) = box(i, j); \ +} \ +return toRet; + .def("getBox",[](const PLMD::Pbc* self) -> py::array_t { + T3x3toArray( self->getBox() ) + }, + "Get a numpy array of shape (3,3) with the box vectors") + + .def("getInvBox",[](const PLMD::Pbc* self) -> py::array_t { + T3x3toArray( self->getInvBox() ) + }, + "Get a numpy array of shape (3,3) with the inverted box vectors"); +#undef T3x3toArray + + /******************************PLMD::NeighborList****************************/ + py::class_(m, "NeighborList") + //.def(py::init<>()) + //https://numpy.org/doc/stable/user/basics.types.html + //numpy.uint=unsigned long + .def_property_readonly("size",&PLMD::NeighborList::size,"the number of pairs") + + .def("__len__", &PLMD::NeighborList::size) + + .def("getClosePairs",[](const PLMD::NeighborList* self)->py::array_t { + auto ncouples = self->size(); + py::array_t::ShapeContainer shape({ncouples,2}); + py::array_t couples(shape); + auto retAccessor = couples.mutable_unchecked<2>(); + for(size_t c=0; c< ncouples; ++c) { + retAccessor(c,0) = self->getClosePair(c).first; + retAccessor(c,1) = self->getClosePair(c).second; + } + return couples; + }, + "get a (NC,2) nd array with the list of couple indexes"); + + /**************************PLMD::pycv::PythonFunction************************/ + py::class_(m, "PythonFunction") + /***************************Start of Action methods***************************/ + //usin "PLMD::pycv::PythonCVInterface::getStep" instead of the lambda gives compilation errors + .defGetter("getStep",PLMD::pycv::PythonFunction,getStep, long int,"Returns the current step") + .defGetter("getTime",PLMD::pycv::PythonFunction,getTime,double,"Return the present time") + .defGetter("getTimeStep",PLMD::pycv::PythonFunction,getTimeStep,double,"Return the timestep") + .defGetter("isRestart",PLMD::pycv::PythonFunction,getRestart,bool,"Return true if we are doing a restart") + .defGetter("isExchangeStep",PLMD::pycv::PythonFunction,getExchangeStep,bool,"Check if we are on an exchange step") + .def_property_readonly("label", + [](PLMD::pycv::PythonFunction* self) -> std::string { + return self->getLabel(); + }, + "returns the label") + + .def("log",[](PLMD::pycv::PythonFunction* self, py::object data) { + self->log << py::str(data).cast(); + }, + "puts a string in the PLUMED output",py::arg("s")) + .def("lognl",[](PLMD::pycv::PythonFunction* self, py::object data) { + self->log << py::str(data).cast()<< "\n"; + }, + "puts a string in the PLUMED output (and appends a newline)",py::arg("s")) + + /****************************End of Action methods***************************/ + .def("argument", &PLMD::pycv::PythonFunction:: + getArgument,"Get value of the of i-th argument", py::arg("i")) + + .def("arguments", [](PLMD::pycv::PythonFunction* self) -> py::array_t { + auto nargs=self->getNumberOfArguments(); + py::array_t::ShapeContainer shape({nargs}); + py::array_t arguments(shape); + for(auto i=0u; i < nargs; ++i) + arguments.mutable_at(i) = self->getArgument(i); + return arguments; + } + ,"Retuns a ndarray with the values of the arguments") + + .def_property_readonly("nargs", &PLMD::pycv::PythonFunction:: + getNumberOfArguments,"Get the number of arguments") + + .def("difference", &PLMD::pycv::PythonFunction:: + difference,"Takes the difference taking into account pbc for argument i", + py::arg("i"),py::arg("x"),py::arg("y")) + + .def("bringBackInPbc", &PLMD::pycv::PythonFunction:: + bringBackInPbc,"Takes one value and brings it back into the pbc of argument i", + py::arg("i"),py::arg("x")) + + //I cannot find a way of testing properly this: + // .def("getProjection", &PLMD::pycv::PythonFunction:: + // getProjection,"Get the scalar product between the gradients of two variables", + // py::arg("i"),py::arg("j")) + //to be added and tested getNumberOfArguments + ; +} diff --git a/plugins/pycv/PythonCVInterface.cpp b/plugins/pycv/PythonCVInterface.cpp new file mode 100644 index 0000000000..e7e6a0cf09 --- /dev/null +++ b/plugins/pycv/PythonCVInterface.cpp @@ -0,0 +1,773 @@ +/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +Copyright (c) 2019-2023 of Toni Giorgino, Daniele Rapetti + +The pycv module is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The pycv module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with plumed. If not, see . ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ +#include "PythonCVInterface.h" + +#include "core/ActionRegister.h" +#include "core/PlumedMain.h" +#include "tools/NeighborList.h" +#include "tools/Pbc.h" + +#include // everything needed for embedding +#include +#include + +#include +#include +#include +#include + +//+PLUMEDOC COLVAR PYCVINTERFACE +/* +Define collective variables in the Python language. + +Plumed will import the module chosen wiht the `IMPORT` keyword. +The module can be a simple py file or a directory with the standard module +configuration (a directory that contains at least an __init__.py, see the +rt-persistentData test for an example). + +In the module there must be a function that will be used in caclulation and the +definition of the component(s) (meaning period and if derivatives will be +returned) of your cv. + +PYCVINTERFACE will call two or more elements from the module. +Each element or function can be selected with its dedicated keyword: + - `CALCULATE` will select the function to be called at each step (defaults to + `"plumedCalculate"`) + - `INIT` will select the function (or the dict, see down) to be called during + the initilization (defaults to `"plumedInit"`) + - `UPDATE` will select the function to be called at each step, during the + update() invocation + - `PREPARE` will select the function to be called at each step, during the + prepare() invocation +All the function called will need a single argument of type + `plumedCommunications.PythonCVInterface` + + +\par Getting started + +The minimal plumed input is: +\plumedfile +cv1: PYTHONCV IMPORT=hello ATOMS=1 +PRINT FILE=colvar.out ARG=* +\endplumedfile + +this should be paired with the `hello.py` file: +@code{.py} +import plumedCommunications as PLMD + +plumedInit={"Value":PLMD.defaults.COMPONENT_NODEV} + +def plumedCompute(action: PLMD.PythonCVInterface): + action.log("Hello, world, from calculate!") + return 0.0 +@endcode + +If `INIT` is not specified, plumed will search for an object "plumedInit", +that can be either a function that returns a dict or a dict +This dict MUST contain at least the informations about the presence of the +derivatives and on the periodicity of the variable. +We will refer to this dict as "the init dict" from now. + +If `CALCULATE` is not specified, plumed will search for a function named +"plumedCalculate" plumed will read the variable returned accordingly to what it +was specified in the initialization dict. + +The init dict will tell plumed how many components the calculate function will +return and how they shall behave. +Along this the dict can contain all the keyword that are compatible with +PYCVINTERFACE. +Mind that if the same keyword is specified both in the init dict and in the +plumed file the calculation will be aborted to avoid unwated settings confict. +In case of flags the dict entry must be a bool, differently from the standard +plumed input. + +The only keyword that can only be specified in python is `COMPONENTS`. +The `COMPONENTS` key must point to a dict that has as keys the names of the +componensts. +Each component dictionary must have two keys: + - `"period"`: `None` of a list of two values, min and max (like `[0,1]` or also + strings like `["0.5*pi","2*pi"]`) + - `"derivative"`: `True` or `False` +If you want to use a single component you can create the `"COMPONENTS"` dict +with as single key, the name will be ignored. +In the previous example the key `"Value"` is used instead of `"COMPONENTS"`: +it is a shorter form for `"COMPONENTS":{"any":{...}}`. +To avoid confusion you cannot specify both `"COMPONENTS"` and `"Value"` in the + same dict. + +To speed up the declarations of the components the `plumedCommunications` module +contains a submodule `defaults` with the default dictionaries already set up: + - plumedCommunications.defaults.COMPONENT={"period":None, "derivative":True} + - plumedCommunications.defaults.COMPONENT_NODEV={"period":None, "derivative":False} + +\par The calculate function + +The calculate funtion must, as all the other functions accept a +PLMD.PythonCVInterface object as only input. + +The calculate function must either return a float or a tuple or, in the case of +multiple components, a dict whose keys are the name of the components, whose +elements are either float or tuple. + +Plumed will assign automatically assign the result to the CV (to the key named +element), if the name of the component is missing the calculation will be +interrupted with an error message. +If derivatives are disabled it will expect a float(or a double). +In case of activated derivatives it will interrupt the calculation if the +return value would not be a tuple. +The tuple should be (float, ndArray(nat,3),ndArray(3,3)) with the first +elements the value, the second the atomic derivatives and the third the box +derivative (that can also have shape(9), with format (x_x, x_y, x_z, y_x, y_y, +y_z, z_x, z_y, z_z)), if the box derivative are not present a WARNING will be +raised, but the calculation won't be interrupted. + +\par The prepare and update functions and the "data" attribute + +If the `PREPARE` keyword is used, the defined function will be called at +prepare time, before calculate. +The prepare dictionary can contain a `"setAtomRequest"` key with a parseable +ATOM string, like in the input (or a list of indexes, 0 based). +@code{.py} +#this , with "PREPARE=changeAtom" in the plumed file will select a new atom at each new step +def changeAtom(plmdAction: plumedCommunications.PythonCVInterface): + toret = {"setAtomRequest": f"1, {int(plmdAction.getStep()) + 2}"} + if plmdAction.getStep() == 3: + toret["setAtomRequest"] = "1,2" + return toret +@endcode + +If the `UPDATE` keyword is used, the defined function will be called at update +time, after calculate. As now plumed will ignore the return of this function +(but it stills need to return a dict) and it is intended to accumulate things +or post process data afer calculate + +In the example `plmdAction.data["pycv"]=0` is intialized in `pyinit` and its +value is updated in calculate. + +\plumedfile +cv1: PYCVINTERFACE ... + ATOMS=@mdatoms + IMPORT=pycvPersistentData + CALCULATE=pydist + INIT=pyinit +... + +PRINT FILE=colvar.out ARG=* +\endplumedfile + +@code{.py} +import plumedCommunications as PLMD +from plumedCommunications.defaults import COMPONENT_NODEV + +def pyinit(plmdAction: PLMD.PythonCVInterface): + plmdAction.data["pycv"]=0 + print(f"{plmdAction.data=}", file=log) + return {"Value":COMPONENT_NODEV} + +def pydist(plmdAction: PLMD.PythonCVInterface): + plmdAction.data["pycv"]+=plmdAction.getStep() + d=plmdAction.data["pycv"] + return d +@endcode + +The plumedCommunications.PythonCVInterface has a `data` attribute that is a +dictionary and can be used to store data during the calculations + + +\par Getting the manual +You can obtain the manual of the module (and of its components) +by running plumed driver with this plumed file +\plumedfile +LOAD GLOBAL FILE=PythonCVInterface.so +cvdist: PYCVINTERFACE IMPORT=pyhelp +PRINT FILE=colvar.out ARG=* +\endplumedfile +and this py module +@code{.py} +import plumedCommunications +import pydoc + +def plumedInit(_): + with open('PythonCVInterface.help.txt', 'w') as f: + h = pydoc.Helper(output=f) + h(plumedCommunications.PythonCVInterface) + with open('plumedCommunications.help.txt', 'w') as f: + h = pydoc.Helper(output=f) + h(plumedCommunications) + with open('plumedCommunications.defaults.help.txt', 'w') as f: + h = pydoc.Helper(output=f) + h(plumedCommunications.defaults) + return {"Value":plumedCommunications.defaults.COMPONENT_NODEV, "ATOMS":"1"} + +def plumedCalculate(_): + return 0 +@endcode + + +\par Tips and tricks and examples + +Automatic differentiation and transparent compilation (including to +GPU) can be performed via Google's [JAX +library](https://github.com/google/jax): see the example below. + + +The following input tells PLUMED to print the distance between atoms 1 +and 4. + +\plumedfile +cv1: PYTHONCV ATOMS=1,4 IMPORT=distcv CALCULATE=cv +PRINT FILE=colvar.out ARG=* +\endplumedfile + +The file `distcv.py` should contain something as follows. + +@code{.py} +import numpy as np +import plumedCommunications as PLMD + +plumedInit={"Value":PLMD.defaults.COMPONENT} +# Define the distance function +def dist_f(x): + r = x[0,:]-x[1,:] + d2 = np.dot(r,r) + return np.sqrt(d2) + +def grad_dist(x): + d = dist_f(x) + r = x[0,:]-x[1,:] + g = r/d + return np.array([g,-g]) + +# The CV function actually called +def cv(action:PLMD.PythonCVInterface): + return dist_f(action.getPositions()), grad_dist(action.getPositions()) + +@endcode + + +\par JAX for automatic differentiation and compilation + +Automatic differentiation and transparent compilation (including to +GPU) can be performed via Google's [JAX +library](https://github.com/google/jax). In a nutshell, it's sufficient +to replace `numpy` with `jax.numpy`. See the following example. + + +\plumedfile +cv1: PYTHONCV ATOMS=1,2,3 IMPORT=jaxcv CALCULATE=angle +PRINT FILE=colvar.out ARG=* + +\endplumedfile + + +And, in `jaxcv.py`... + +@code{.py} +# Import the JAX library +import jax.numpy as np +from jax import grad, jit, vmap +import plumedCommunications as PLMD + +plumedInit={"Value":PLMD.defaults.COMPONENT} + +# Implementation of the angle function +def angle_f(x): + r1 = x[0,:]-x[1,:] + r2 = x[2,:]-x[1,:] + + costheta = np.dot(r1,r2) / np.linalg.norm(r1) / np.linalg.norm(r2) + theta = np.arccos(costheta) + return theta + +# Use JAX to auto-gradient it +angle_grad = grad(angle_f) + +def cv(action:PLMD.PythonCVInterface): + return angle_f(action.getPositions()), angle_grad(action.getPositions()) +@endcode + + +There are however +[limitations](https://github.com/google/jax#current-gotchas), the most +notable of which is that indexed assignments such as `x[i]=y` are not +allowed, and should instead be replaced by functional equivalents such +as `x=jax.ops.index_update(x, jax.ops.index[i], y)`. + + +\par Multiple components + +It is possible to return multiple components at a time. This may be +useful e.g. if they reuse part of the computation. To do so, pass the +declare the `"COMPONENTS"` key in the init dict, and assign to each key a dict +with the same rules of the key `"Value"`. In this case, the function must +return a dict with the component names as keys, see the "calculate" paragraph. +Inside PLUMED, component names +will be prefixed by `py-`. + +Note that you can use JAX's Jacobian function `jax.jacrev()` to +conveniently compute the gradients all at once (see regtests). For +example: + +\plumedfile +cv1: PYTHONCV ATOMS=1,3,4 IMPORT=distcv FUNCTION=cv COMPONENTS=d12,d13 +\endplumedfile + +@code{.py} +import jax.numpy as np +from jax import jacrev, jit +import plumedCommunications as PLMD + +plumedInit = dict( + COMPONENTS=dict(d12=PLMD.defaults.COMPONENT, d13=PLMD.defaults.COMPONENT) +) + +# Define the distance function +@jit +def dist_f(X): + return { + 'd12': np.linalg.norm( X[0,:]-X[1,:] ), + 'd13': np.linalg.norm( X[0,:]-X[2,:] ) + } + +dist_grad=jacrev(dist_f) + +def cv(action: PLMD.PythonCVInterface): + toret = {} + d=dist_f(action.getPositions()) + g=dist_grad(action.getPositions()) + for key in ["d12","d13"]: + toret[key] = (d[key],g[key]) + return toret +@endcode + +\par Installation + +A use of an virtual environment or equivalent is recomended. +To compile pycv you just need numpy and pybind11, jax is not necessary for +compilation and installation. + +To compile the shared object library you need also plumed in your path. +You need to export the following environmental variables: +\verbatim +export PLUMED_MKLIB_CFLAGS="$(python3-config --cflags --embed) $(python -m pybind11 --includes)" +export PLUMED_MKLIB_LDFLAGS="$(python3-config --ldflags --embed)" +\endverbatim +and then compile the shared object: +\verbatim +plumed mklib PythonCVInterface.cpp ActionWithPython.cpp PlumedPythonEmbeddedModule.cpp +\endverbatim + +If you are on linux you can use pycv only with a plumed version that is +compatible with the `GLOBAL` keyword for the action `LOAD` + +*/ +//+ENDPLUMEDOC + + +#define vdbg(...) \ + std::cerr << std::boolalpha<(initFcn)) { + initDict = initFcn; + } else { + initDict = initFcn(this); + } + } else if(initFunName!=PYCV_DEFAULTINIT) { + //If the default INIT is not preset, is not a problem + error("the function "+ initFunName + " is not present in "+ import); + } + + std::string prepareFunName; + parse("PREPARE",prepareFunName); + if (prepareFunName!=PYCV_NOTIMPLEMENTED) { + if (!py::hasattr(pyModule,prepareFunName.c_str())) { + error("the function " + prepareFunName + " is not present in "+ import); + } + hasPrepare=true; + pyPrepare=pyModule.attr(prepareFunName.c_str()); + log.printf(" will use %s while calling prepare() before calculate()\n", prepareFunName.c_str()); + } + + std::string updateFunName; + parse("UPDATE",updateFunName); + if (updateFunName!=PYCV_NOTIMPLEMENTED) { + if (!py::hasattr(pyModule,updateFunName.c_str())) { + error("the function " + updateFunName + " is not present in " + import); + } + pyUpdate=pyModule.attr(updateFunName.c_str()); + hasUpdate=true; + log.printf(" will use %s while calling update() after calculate()\n", updateFunName.c_str()); + } + + { + std::vector components; + parseVector("COMPONENTS", components); + + if (components.size()>1) { + error("Please define multiple COMPONENTS from INIT in python."); + } + } + if(initDict.contains("COMPONENTS")) { + if(initDict.contains("Value")) { + error("The initialize dict cannot contain both \"Value\" and \"COMPONENTS\""); + } + if(!py::isinstance(initDict["COMPONENTS"])) { + error("COMPONENTS must be a dictionary using with the name of the components as keys"); + } + py::dict components=initDict["COMPONENTS"]; + for(auto comp: components) { + auto settings = py::cast(comp.second); + if(components.size()==1) { //a single component + initializeValue(dynamic_cast<::PLMD::ActionWithValue&>(*this), settings); + valueSettings(settings,getPntrToValue()); + } else { + auto name=std::string(PYCV_COMPONENTPREFIX) + +"-"+py::cast(comp.first); + initializeComponent(dynamic_cast<::PLMD::ActionWithValue&>(*this), + name, + settings); + valueSettings(settings,getPntrToComponent(name)); + } + } + + } else if(initDict.contains("Value")) { + py::dict settingsDict=initDict["Value"]; + initializeValue(dynamic_cast<::PLMD::ActionWithValue&>(*this),settingsDict); + valueSettings(settingsDict,getPntrToValue()); + } else { + warning(" WARNING: by defaults components periodicity is not set and component is added without derivatives - see manual\n"); + //this will crash with an error, beacuse periodicity is not explicitly set + addValue(); + } + + std::vector atoms; + pyParseAtomList("ATOMS",initDict,atoms); + std::vector groupA; + pyParseAtomList("GROUPA",initDict,groupA); + std::vector groupB; + pyParseAtomList("GROUPB",initDict,groupB); + + if(atoms.size() !=0 && groupA.size()!=0) + error("you can choose only between using the neigbourlist OR the atoms"); + + if(atoms.size()==0&& groupA.size()==0 && groupB.size()==0) + error("At least one atom is required"); + + if (atoms.size() != 0 && groupA.size() != 0) + error("you can choose only between using the neigbourlist OR the atoms"); + + if (atoms.size() == 0 && groupA.size() == 0 && groupB.size() == 0) + error("At least one atom is required"); + + bool nopbc; + pyParseFlag("NOPBC",initDict, nopbc); + pbc = !nopbc; + + if (groupA.size() > 0) { + // parse the NL things only in the NL case + bool dopair; + pyParseFlag("PAIR",initDict, dopair); + // this is a WIP + + bool serial = false; + bool doneigh; + pyParseFlag("NLIST",initDict,doneigh); + double nl_cut = 0.0; + int nl_st = 0; + if (doneigh) { + //parse("NL_CUTOFF", nl_cut); + pyParse("NL_CUTOFF", initDict, nl_cut); + if (nl_cut <= 0.0) + error("NL_CUTOFF should be explicitly specified and positive"); + pyParse("NL_STRIDE",initDict, nl_st); + if (nl_st <= 0) + error("NL_STRIDE should be explicitly specified and positive"); + } + // endof WIP + if (groupB.size() > 0) { + if (doneigh) + nl = Tools::make_unique( + groupA, groupB, serial, dopair, pbc, getPbc(), comm, nl_cut, nl_st); + else + nl = Tools::make_unique(groupA, groupB, serial, dopair, + pbc, getPbc(), comm); + } else { + if (doneigh) + nl = Tools::make_unique(groupA, serial, pbc, getPbc(), + comm, nl_cut, nl_st); + else + nl = Tools::make_unique(groupA, serial, pbc, getPbc(), + comm); + } + requestAtoms(nl->getFullAtomList()); + } else { + requestAtoms(atoms); + } + + if (getNumberOfComponents()>1) { + log.printf(" it is expected to return dictionaries with %d components\n", + getNumberOfComponents()); + } + + log << " Bibliography " << plumed.cite(PYTHONCV_CITATION) << "\n"; + // NB: the NL kewywords will be counted as error when using ATOMS + checkRead(); +} catch (const py::error_already_set &e) { + plumed_merror(e.what()); + //vdbg(e.what()); +} + +void PythonCVInterface::prepare() try { + if (nl) { + if (nl->getStride() > 0) { + if (firsttime || (getStep() % nl->getStride() == 0)) { + requestAtoms(nl->getFullAtomList()); + invalidateList = true; + firsttime = false; + } else { + requestAtoms(nl->getReducedAtomList()); + invalidateList = false; + if (getExchangeStep()) + error("Neighbor lists should be updated on exchange steps - choose a " + "NL_STRIDE which divides the exchange stride!"); + } + if (getExchangeStep()) + firsttime = true; + } + } + if (hasPrepare) { + py::dict prepareDict = pyPrepare(this); + if (prepareDict.contains("setAtomRequest")) { + //should I use "interpretAtomList"? + std::vector myatoms; + if(py::isinstance(prepareDict["setAtomRequest"])|| + py::isinstance(prepareDict["setAtomRequest"])) { + py::tuple t = prepareDict["setAtomRequest"]; + + for (const auto &i : t) { + auto at = PLMD::AtomNumber::index(i.cast()); + myatoms.push_back(at); + } + } else { + auto atomlist=PLMD::Tools::getWords( + py::str(prepareDict["setAtomRequest"]).cast(), + "\t\n ,"); + interpretAtomList( atomlist, myatoms ); + } + requestAtoms(myatoms); + } + } +} catch (const py::error_already_set &e) { + plumed_merror(e.what()); +} + +void PythonCVInterface::update() try { + if(hasUpdate) { + py::dict updateDict=pyUpdate(this); + //See what to do here + } +} catch (const py::error_already_set &e) { + plumed_merror(e.what()); +} + +// calculator +void PythonCVInterface::calculate() try { + if (nl) { + if (nl->getStride() > 0 && invalidateList) { + nl->update(getPositions()); + } + } + // Call the function + py::object r = pyCalculate(this); + if(getNumberOfComponents()>1) { // MULTIPLE NAMED COMPONENTS + calculateMultiComponent(r); + } else { // SINGLE COMPONENT + readReturn(r, getPntrToValue()); + } + +} catch (const py::error_already_set &e) { + plumed_merror(e.what()); +} + +void PythonCVInterface::readReturn(const py::object &r, Value* valPtr) { + // Is there more than 1 return value? + if (py::isinstance(r)||py::isinstance(r)) { + // 1st return value: CV + py::list rl=r.cast(); + pycvComm_t value = rl[0].cast(); + valPtr->set(value); + //shape returns long int + auto natoms = static_cast (getPositions().size()); + if (rl.size() > 1) { + if(!valPtr->hasDerivatives()) + error(valPtr->getName()+" was declared without derivatives, but python returned with derivatives"); + // 2nd return value: gradient: numpy array of (natoms, 3) + py::array_t grad(rl[1]); + // Assert correct gradient shape + if (grad.ndim() != 2 || grad.shape(0) != natoms || grad.shape(1) != 3) { + log.printf("Error: wrong shape for the gradient return argument: should be " + "(natoms=%d,3), received %ld x %ld\n", + natoms, grad.shape(0), grad.shape(1)); + error("Python CV returned wrong gradient shape error"); + } + // To optimize, see "direct access" + // https://pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html + for (unsigned i = 0; i < natoms; i++) { + Vector3d gi(grad.at(i, 0), grad.at(i, 1), grad.at(i, 2)); + setAtomsDerivatives(valPtr, i, gi); + } + } else if (valPtr->hasDerivatives()) + error(valPtr->getName()+" was declared with derivatives, but python returned none"); + + if (rl.size() > 2) { + if(!valPtr->hasDerivatives()) + plumed_merror(valPtr->getName()+" was declared without derivatives, but python returned with box derivatives"); + py::array_t pyBoxDev(rl[2]); + // expecting the box derivatives + Tensor boxDev; + if (pyBoxDev.ndim() == 2 && + (pyBoxDev.shape(0) == 3 && pyBoxDev.shape(1) == 3)) { // boxDev is 3x3 + boxDev = + Tensor({pyBoxDev.at(0, 0), pyBoxDev.at(0, 1), pyBoxDev.at(0, 2), + pyBoxDev.at(1, 0), pyBoxDev.at(1, 1), pyBoxDev.at(1, 2), + pyBoxDev.at(2, 0), pyBoxDev.at(2, 1), pyBoxDev.at(2, 2)}); + } else if (pyBoxDev.ndim() == 1 && pyBoxDev.shape(0) == 9) { + boxDev = Tensor({pyBoxDev.at(0), pyBoxDev.at(1), pyBoxDev.at(2), + pyBoxDev.at(3), pyBoxDev.at(4), pyBoxDev.at(5), + pyBoxDev.at(6), pyBoxDev.at(7), pyBoxDev.at(8)}); + } else { + log.printf( + "Error: wrong shape for the box derivatives return argument: " + "should be (size 3,3 or 9), received %ld x %ld\n", + natoms, pyBoxDev.shape(0), pyBoxDev.shape(1)); + error("Python CV returned wrong box derivatives shape error"); + } + setBoxDerivatives(valPtr, boxDev); + } else if (valPtr->hasDerivatives()) + warning(valPtr->getName()+" was declared with derivatives, but python returned no box derivatives"); + } else { + // Only value returned. Might be an error as well. + if (valPtr->hasDerivatives()) + warning(BIASING_DISABLED); + pycvComm_t value = r.cast(); + valPtr->set(value); + } + //TODO: is this ok? + if (!pbc) + setBoxDerivativesNoPbc(valPtr); +} + + +void PythonCVInterface::calculateMultiComponent(py::object &r) { + + const auto nc = getNumberOfComponents(); + if (py::isinstance(r)) { + py::dict dataDict = r.cast(); // values + for(int i=0; i < nc; ++i) { + auto component=getPntrToComponent(i); + //get the without "label.prefix-" + std::string key=component->getName().substr( + 2 + getLabel().size() + +PYCV_COMPONENTPREFIX.size()); + if (dataDict.contains(key.c_str())) + readReturn(dataDict[key.c_str()], component); + else + error( "python did not returned " + key ); + } + } else { + // In principle one could handle a "list" return case. + error("Multi-components pyCVs need to return dictionaries"); + } +} + +void PythonCVInterface::pyParseAtomList(const char* key, const ::pybind11::dict &initDict, std::vector &myatoms) { + parseAtomList(key,myatoms); + + if(initDict.contains(key)) { + if (myatoms.size()>0) + error(std::string("you specified the same keyword ").append(key)+ " both in python and in the settings file"); + auto atomlist=PLMD::Tools::getWords( + py::str(initDict[key]).cast(), + "\t\n ,"); + interpretAtomList( atomlist, myatoms ); + } +} + +NeighborList &PythonCVInterface::getNL() { return *nl; } +} // namespace pycvs +} // namespace PLMD diff --git a/plugins/pycv/PythonCVInterface.h b/plugins/pycv/PythonCVInterface.h new file mode 100644 index 0000000000..b5971029c1 --- /dev/null +++ b/plugins/pycv/PythonCVInterface.h @@ -0,0 +1,67 @@ +/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +Copyright (c) 2023 Daniele Rapetti + +The pycv module is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The pycv module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with plumed. If not, see . ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ +#ifndef __PLUMED_pycv_PythonCVInterface_h +#define __PLUMED_pycv_PythonCVInterface_h +#include "ActionWithPython.h" + +#include "colvar/Colvar.h" + +namespace PLMD { + +class NeighborList; + +namespace pycv { + +///TODO: manual "you have to specify ATOMS=something for default atoms" +///TODO: add interface to pbc +///TODO: the topology can be assumed fixed and done on the go at each run by loading the pdb in the python code +class PythonCVInterface : public Colvar, public ActionWithPython { + static constexpr auto PYCV_NOTIMPLEMENTED="PYCV_NOTIMPLEMENTED"; + static constexpr auto PYCV_DEFAULTINIT="plumedInit"; + static constexpr auto PYCV_DEFAULTCALCULATE="plumedCalculate"; + + std::unique_ptr nl{nullptr}; + + ::pybind11::module_ pyModule {}; + ::pybind11::object pyCalculate{}; + ::pybind11::object pyPrepare; + ::pybind11::object pyUpdate; + + bool pbc=false; + bool hasPrepare = false; + bool hasUpdate = false; + bool invalidateList = true; + bool firsttime = true; + void calculateMultiComponent(pybind11::object &); + void readReturn(const pybind11::object &, Value* ); +public: + ::pybind11::dict dataContainer {}; + explicit PythonCVInterface(const ActionOptions&); + static void registerKeywords( Keywords& keys ); +// active methods: + void calculate() override; + void prepare() override; + void update() override; + + NeighborList& getNL(); + ///redefinition of parseAtomList to avoid confict between plumed.dat and python options + void pyParseAtomList(const char* key, const ::pybind11::dict &initDict, std::vector &); +}; + +} // namespace pycv +} // namespace PLMD +#endif //__PLUMED_pycv_PythonCVInterface_h diff --git a/plugins/pycv/PythonFunction.cpp b/plugins/pycv/PythonFunction.cpp new file mode 100644 index 0000000000..d5c5556f57 --- /dev/null +++ b/plugins/pycv/PythonFunction.cpp @@ -0,0 +1,279 @@ +/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +Copyright (c) 2019-2023 of Toni Giorgino, Daniele Rapetti + +The pycv module is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The pycv module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with plumed. If not, see . ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ +#include "PythonFunction.h" + +#include "core/ActionRegister.h" +#include "core/PlumedMain.h" // cite + +#include // everything needed for embedding +#include + +#include +#include + + +using namespace std; +namespace py = pybind11; + + +namespace PLMD { +namespace pycv { + +//+PLUMEDOC FUNCTION PYTHONFUNCTION +/* +Define collective variables in the Python language. + +A Python module named in the `IMPORT` keyword is first imported. The +function passed as the `FUNCTION` keyword is called at each time +step. It is assumed to receive a numpy array of shape `(N,1)`, N being +the number of arguments passed at the `ARG` keyword. See also \ref +CUSTOM. + +The function should return two values: a scalar (the CV value), and +its gradient with respect to each coordinate (an array of the same +shape as the input). Not returning the gradient will prevent biasing +from working (with warnings). + +Automatic differentiation and transparent compilation (including to +GPU) can be performed via Google's [JAX +library](https://github.com/google/jax): see \ref PYTHONCV. + + +\par Examples + +The following example mimics the one in \ref CUSTOM. + +\plumedfile +dAB: DISTANCE ATOMS=10,12 +dAC: DISTANCE ATOMS=10,15 +diff: PYTHONFUNCTION ARG=dAB,dAC IMPORT=pythonfunction FUNCTION=diff PERIODIC=NO +METAD ARG=diff WIDTH=0.1 HEIGHT=0.5 BIASFACTOR=10 PACE=100 + +\endplumedfile + +The file `pythonfunction.py` should contain something as follows. + +@code{.py} +import jax.numpy as np + +def diff_f(X): + x, y = X + return y-x + + +# Just to demonstrate how auto grad is done. +# In this specific case it is just np.array([-1., 1.]) + +diff_grad = grad(diff_f) + + +# The function actually being called +def diff(x): + return diff_f(x), diff_grad(x) + +@endcode + +\par See also + +Use \ref PYTHONCV if you are dealing with atom coordinates directly. + +Please see \ref PYTHONCV for installation and automatic +differentiation. + +See \ref CUSTOM for a non-Python equivalent. + + +*/ +//+ENDPLUMEDOC + +PLUMED_REGISTER_ACTION(PythonFunction,"PYFUNCTION") + +void PythonFunction::registerKeywords( Keywords& keys ) { + Function::registerKeywords( keys ); + keys.use("ARG"); keys.use("PERIODIC"); + keys.add("compulsory","IMPORT","the python file to import, containing the function"); + keys.add("compulsory","CALCULATE",PYCV_DEFAULTCALCULATE,"the function to call"); + keys.add("compulsory","INIT",PYCV_DEFAULTINIT,"the function to call during the construction method of the function"); + keys.add("hidden","COMPONENTS","if provided, the function will return multiple components, with the names given"); + keys.addOutputComponent(PYCV_COMPONENTPREFIX.data(),"COMPONENTS","Each of the components output py the Python code, prefixed by py-"); + // Why is NOPBC not listed here? +} + +// Everything being copied from Custom.cpp +PythonFunction::PythonFunction(const ActionOptions&ao)try: + Action(ao), + Function(ao), + ActionWithPython(ao) { + + //Loading the python module + std::string import; + parse("IMPORT",import); + std::string calculateFunName; + //setting up the calculate function + parse("CALCULATE",calculateFunName); + log.printf(" will import %s and call function %s\n", import.c_str(), + calculateFunName.c_str()); + // Initialize the module and function pointers + pyModule = py::module::import(import.c_str()); + if (!py::hasattr(pyModule,calculateFunName.c_str())) { + error("the function " + calculateFunName + " is not present in "+ import); + } + + pyCalculate = pyModule.attr(calculateFunName.c_str()); + std::string initFunName; + parse("INIT",initFunName); + py::dict initDict; + if(py::hasattr(pyModule,initFunName.c_str())) { + log.printf(" will use %s during the initialization\n", initFunName.c_str()); + auto initFcn = pyModule.attr(initFunName.c_str()); + if (py::isinstance(initFcn)) { + initDict = initFcn; + } else { + initDict = initFcn(this); + } + } else if(initFunName!=PYCV_DEFAULTINIT) { + //If the default INIT is not preset, is not a problem + error("the function "+ initFunName + " is not present in "+ import); + } + { + std::vector components; + parseVector("COMPONENTS", components); + + if (components.size()>1) { + error("Please define multiple COMPONENTS from INIT in python."); + } + } + + if(initDict.contains("COMPONENTS")) { + if(initDict.contains("Value")) { + error("The initialize dict cannot contain both \"Value\" and \"COMPONENTS\""); + } + if(!py::isinstance(initDict["COMPONENTS"])) { + error("COMPONENTS must be a dictionary using with the name of the components as keys"); + } + py::dict components=initDict["COMPONENTS"]; + for(auto comp: components) { + auto settings = py::cast(comp.second); + if(components.size()==1) { //a single component + initializeValue(dynamic_cast<::PLMD::ActionWithValue&>(*this), settings); + valueSettings(settings,getPntrToValue()); + } else { + auto name=std::string(PYCV_COMPONENTPREFIX) + +"-"+py::cast(comp.first); + initializeComponent(dynamic_cast<::PLMD::ActionWithValue&>(*this), + name, + settings); + valueSettings(settings,getPntrToComponent(name)); + } + } + + } else if(initDict.contains("Value")) { + py::dict settingsDict=initDict["Value"]; + initializeValue(dynamic_cast<::PLMD::ActionWithValue&>(*this),settingsDict); + valueSettings(settingsDict,getPntrToValue()); + } else { + warning(" WARNING: by defaults components periodicity is not set and component is added without derivatives - see manual\n"); + //this will crash with an error, beacuse periodicity is not explicitly set + addValue(); + + } + + log.printf(" with function : %s\n",calculateFunName.c_str()); + + log<<" Bibliography " + <1) { // MULTIPLE NAMED COMPONENTS + calculateMultiComponent(r); + } else { // SINGLE COMPONENT + readReturn(r, getPntrToValue()); + } +} catch (const py::error_already_set &e) { + plumed_merror(e.what()); + //vdbg(e.what()); +} +void PythonFunction::readReturn(const py::object &r, Value* valPtr) { +// Is there more than 1 return value? + if (py::isinstance(r)||py::isinstance(r)) { + // 1st return value: CV + py::list rl=r.cast(); + pycvComm_t value = rl[0].cast(); + valPtr->set(value); + if (rl.size() > 1) { + auto nargs = getNumberOfArguments(); + if(!valPtr->hasDerivatives()) + error(valPtr->getName()+" was declared without derivatives, but python returned with derivatives"); + // 2nd return value: gradient: numpy array + py::array_t grad(rl[1]); + if(grad.ndim() != 1 || grad.shape(0) != nargs) { + log.printf("Error: wrong shape for the gradient return argument: should be (nargs=%lu), received %ld \n", + (unsigned long) nargs, grad.shape(0)); + error("PYFUNCTION returned wrong gradient shape error"); + } + + // To optimize, see "direct access" + // https://pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html + for(size_t i=0; isetDerivative(i,grad.at(i)); + } + } else if (valPtr->hasDerivatives()) + plumed_merror(valPtr->getName()+" was declared with derivatives, but python returned none"); + + } else { + // Only value returned. Might be an error as well. + log.printf(BIASING_DISABLED); + pycvComm_t value = r.cast(); + valPtr->set(value); + } +} + +void PythonFunction::calculateMultiComponent(py::object &r) { + + const auto nc = getNumberOfComponents(); + if (py::isinstance(r)) { + py::dict dataDict = r.cast(); // values + for(int i=0; i < nc; ++i) { + auto component=getPntrToComponent(i); + //get the without "label.prefix-" + std::string key=component->getName().substr( + 2 + getLabel().size() + +PYCV_COMPONENTPREFIX.size()); + if (dataDict.contains(key.c_str())) + readReturn(dataDict[key.c_str()], component); + else + error( "python did not returned " + key ); + } + } else { + // In principle one could handle a "list" return case. + error("Multi-components pyCVs need to return dictionaries"); + } +} + +}// namespace pycv +}// namespace PLMD + diff --git a/plugins/pycv/PythonFunction.h b/plugins/pycv/PythonFunction.h new file mode 100644 index 0000000000..1f1059b46d --- /dev/null +++ b/plugins/pycv/PythonFunction.h @@ -0,0 +1,44 @@ +/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +Copyright (c) 2023 Daniele Rapetti + +The pycv module is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The pycv module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with plumed. If not, see . ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ +#ifndef __PLUMED_pycv_PythonFunction_h +#define __PLUMED_pycv_PythonFunction_h +#include "ActionWithPython.h" +#include "function/Function.h" +#include +namespace PLMD { + +class NeighborList; + +namespace pycv { +class PythonFunction : + public function::Function, + public ActionWithPython { + static constexpr auto PYCV_DEFAULTINIT="plumedInit"; + static constexpr auto PYCV_DEFAULTCALCULATE="plumedCalculate"; + ::pybind11::module_ pyModule {}; + ::pybind11::object pyCalculate{}; + void calculateMultiComponent(pybind11::object &); + void readReturn(const pybind11::object &, Value* ); +public: + explicit PythonFunction(const ActionOptions&); +// active methods: + virtual void calculate(); + static void registerKeywords( Keywords& keys ); +}; +} // namespace pycv +} // namespace PLMD +#endif //__PLUMED_pycv_PythonFunction_h diff --git a/plugins/pycv/PythonInterface.md b/plugins/pycv/PythonInterface.md new file mode 100644 index 0000000000..db646aedfb --- /dev/null +++ b/plugins/pycv/PythonInterface.md @@ -0,0 +1,83 @@ +# The Python interface + +## Getting the manual +To obtain a **VERY BASIC** reference/manual with all the function and method definitions, run the test rt-doc or call `PYFUNCTION` in with: + +**plumed.dat** +``` +LOAD GLOBAL FILE=path/to/PythonCVInterface.so +PYFUNCTION IMPORT=pycv +``` + +**pycv.py** +```python +import plumedCommunications +import pydoc + +def plumedInit(_): + pydoc.writedoc(plumedCommunications) + pydoc.writedoc(plumedCommunications.defaults) + return {"Value":plumedCommunications.defaults.COMPONENT_NODEV} + +def plumedCalculate(_): + return 0.0 +``` + +## Specifics +In the following section +### Common interface +Both `PYFUNCTION` and `PYCVINTERFACE` have the following attribute: + - `label` (readonly) returns the label + +And the following functions: + - `log(s:object)` puts a string in the PLUMED output + - `lognl(s:object)` puts a string in the PLUMED output (and appends a newline) + - `getStep()` Returns the current step + - `getTime()` Return the present time + - `getTimeStep()` Return the timestep + - `isExchangeStep()` Check if we are on an exchange step + - `isRestart()` Return true if we are doing a restart + +### PYFUNCTION: arguments + +`PYFUNCTION` accepts arguments in the plumed file with the `ARG` keyword, the arguments are then accessible in python with the functions: + - `PythonFunction.argument(argID:int)` Get value of the of argID-th argument (as `float`) + - `PythonFunction.arguments()` Retuns a ndarray with the values of the arguments (as `numpy.ndarray[numpy.float64]`) + +and the argument: +- `PythonFunction.nargs` (readonly) Get the number of arguments + +`PYFUNCTION` also has a few functions that can be used to interact with the periodicity of the arguments: + - `PythonFunction.bringBackInPbc(argID:int,x:float)` Takes one value and brings it back into the pbc of argument argID + - `PythonFunction.difference(argID:int,x:float,y:float)` Takes the difference taking into account pbc for argument argID + +### PYCVINTERFACE: atoms, pbc, neigbourlist, makeWhole +`PYCVINTERFACE` works with atoms: + +Has the following attributes: + - `PythonCVInterface.nat` (readonly) Return the number of atoms + +For getting the atomic data you can call the following functions: + - `PythonCVInterface.getPosition(atomID:int)` Returns an ndarray with the position of the atomID-th atom + - `PythonCVInterface.getPositions()` Returns a numpy.array that contains the atomic positions of the atoms + - `PythonCVInterface.mass(atomID:int)` Get mass of atomID-th atom + - `PythonCVInterface.masses()` Returns and ndarray with the masses + - `PythonCVInterface.charge(atomID:int)` Get charge of atomID-th atom + - `PythonCVInterface.charges()` Returns and ndarray with the charges + - `PythonCVInterface.absoluteIndexes()` Get the npArray of the absolute indexes (like in AtomNumber::index()). + + And in general you can use some support-function from plumed: + - `PythonCVInterface.getNeighbourList()` returns an interface to the current Neighborlist + - `PythonCVInterface.getPbc()` returns an interface to the current pbcs + - `PythonCVInterface.makeWhole()` Make atoms whole, assuming they are in the proper order + +`plumedCommunications.Pbc` and `plumedCommunications.NeighborList` are simple methods container to help with the calculations: + +`plumedCommunications.Pbc` has the following functions: + - `Pbc.apply(d:ndarray)` pply PBC to a set of positions or distance vectors (`d.shape==(n,3)`) + - `Pbc.getBox()` Get a numpy array of shape (3,3) with the box vectors + - `Pbc.getInvBox()` Get a numpy array of shape (3,3) with the inverted box vectors + +`plumedCommunications.NeighborList` has the following functions and attributes: +- `NeigbourList.getClosePairs()` get the (nl.size,2) nd array with the list of couple indexes +- `NeigbourList.size` and `len(nl:NeigbourList.size)` give the number of couples \ No newline at end of file diff --git a/plugins/pycv/README.md b/plugins/pycv/README.md new file mode 100644 index 0000000000..8824d0de5c --- /dev/null +++ b/plugins/pycv/README.md @@ -0,0 +1,313 @@ +# The PYCV module/plugin for PLUMED 2 + +The [PYCV module](https://giorginolab.github.io/plumed2-pycv) enables +PLUMED2 Collective Variables (CVs) and arbitrary functions to be +defined and auto-differentiated in the Python language. + +Advantages of using PYCV over standard development of CVs in C++ are: + 1. functions may be prototyped in high-level code, using + extensive mathematical libraries, and no boilerplate; + 2. just-in-time compilation + occurs transparently: there are no compilation and link delays + for code changes; + 3. CVs may be automatically differentiated in common cases. + +The code is organized as a standard PLUMED module: development occurs +in a [fork of the original +repository](https://github.com/giorginolab/plumed2-pycv/tree/v2.5.2-pycv/src/pycv). All +code is in the `src/pycv` and `regtest/pycv` directories. + +[![DOI](https://joss.theoj.org/papers/10.21105/joss.01773/status.svg)](https://doi.org/10.21105/joss.01773) +[![plumID:19.075](https://www.plumed-nest.org/eggs/19/075/badge.svg)](https://www.plumed-nest.org/eggs/19/075/) + + +## Documentation + +The PYCV module defines the following actions: + + * `PYCVINTERFACE`, to implement single- and multi-component CVs in Python; + * `PYFUNCTION`, to implement arbitrary functions. + +Plumed will start a a Python interpreter. +Then Plumed will import a python module with the `IMPORT=` keyword, this module +must contain at least two objects: a calculate function that will be called at +the `calculate()` step, and an init function or dictionary that will be used at +time of constructing the pycv. +The module that can be imported can be a `*.py` file or a directory that contains +an `__init__.py`, or a module in the your python path. + +`PYCVINTERFACE` will accept also functions during the `prepare()` and `update()` +steps. + +`PYCVINTERFACE` will accept also the `ATOMS` keyword (see `DISTANCE` cv) +**or** a series of keyword relative to the neighbour list (see `COORDINATION` cv). +If both the flavour of asking atom is used, `PYCVINTERFACE` will raise and error. + +The interface to python as anticipated before depends on the following keywords: + +| keyword | description | PYCVINTERFACE | PYFUNCTION | +|-----------|----------------------------------------------|---------------|------------| +| IMPORT | the module to import | ✅ | ✅ | +| INIT | the function/dict to call/get @ construction | ✅ | ✅ | +| CALCULATE | the function to call at `calculate()` step | ✅ | ✅ | +| PREPARE | the function to call at `prepare()` step | ✅ | ❌ | +| UPDATE | the function to call at `update()` step | ✅ | ❌ | + +If not specified INIT will default to `"plumedInit` and CALCULATE to +`"plumedCalculate"`, on the other hand, if PREPARE and UPDATE are not present, will be ignored. + +## Preparation + +For compiling the plugin you just need pybind11 and numpy. +I always recomend to create and ad-hoc environment for your projects: +```bash +python3 -m venv pycvenv +source ./pycvenv/bin/activate +pip install -U pip +pip install -r requirements.txt +``` +The requirements.txt file is in the home of the plug in + +### Standard compilation + +If you have a plumed that supports plumed mklib (that will be release in the 2.10 version, but it is avaiable in the master branch) with multiple files you can simply +```bash +./standaloneCompile.sh +``` + +### Developer compilation + +If your plumed version is inferior to 2.10, or you want to contribute to this module, +the procedure is slighly more compex: +```bash +./prepareMakeForDevelop.sh +``` +will prepare a Make.inc in this directory that will be included by the Makefile. +Then simply: +```bash +make +``` + +#### Set up tests + +If you are interested in running the test regarding this plugin you can use the same procedure as with the standard plumed, but in the subdir regtest of this plugin. +The only requirement is to copy or to create a symbolic link to the `regtest/scripts` directory in a plumed source. Plumed must be runnable to execute tests +```bash +cd regtest +ln -s path/to/plumed/source/regtest/scripts . +make +``` +### About older Plumed versions + +If you are using an older plumed version you must know that: + - On linux the plug-in can be loaded only if `LOAD` supports the `GLOBAL` keyword + - mklib won't work (supports only single file compilations), so you'll need to use `./prepareMakeForDevelop.sh` + +## Getting started + +### Initialization: the INIT keyword + +Both `PYFUNCTION` and `PYCVINTERFACE` use the keyword INIT to finalize the set up for the action. +If not specified INIT wil default to `"plumedInit"`. +INIT instruct plumed to call a function that returns a dict or read directly a dict. + +The function must accept a `plumedCommunications.PythonFunction` or a `plumedCommunications.PythonCVInterface` object that can be used to interact with plumed (this will be explained better in the CALCULATE section) +The init dict must contain at least the "Value" or the "COMPONENTS" keys: + - to the "Value" key must be assigned a value-dict + - to the "COMPONENTS" key must be assigne a dict whose keys are assigned to a value-dict. The keys of the COMPONTENTS-dict will be used as name of the components of the action. +What I am calling a "value-dict" is a dict with two entries: `{'derivative': bool, 'period': None or [min,max]}` "derivative" simply tells plumed that the component will returns derivatice, the "period" will set up the periodicity for that componentd ("min" an "max" are parsed with lepton, so you can also use strings containing `pi`, `e`, etc...) + +To help setting up a value/components plumedCommunications has a submodule defaults that contains 2 possible defaults (see the example): + - `plumedCommunications.defaults.COMPONENT = {'derivative': True, 'period': None}` + - `plumedCommunications.defaults.COMPONENT_NODEV = {'derivative': False, 'period': None}` + +The init dictionary for `PYCVINTERFACE` can contain nearly all the keywords of the input line: + - NOPBC -flag, needs True/False explicitly set- + - ATOMS + - GROUPA + - GROUPB + - PAIR -flag, needs True/False explicitly set- + - NLIST -flag, needs True/False explicitly set- + - NL_CUTOFF + - NL_STRIDE +Note that if the keyword is both in the input line of the plumed file and in the init-dict an error will be raised. + +Nothe that if you use ATOMS and the NL keywords (GROUPA, GROUPB, PAIR, NLIST, NL_CUTOFF, NL_STRIDE) an error will be raised. + +`PYCVINTERFACE` has a `.data` attribute that is a dict, that can be used to store long term data, this dict will not be accessed by plumed. + +```python +import plumedCommunications as PLMD + +def plumedInit(action: PLMD.PythonCVInterface): + action.data["count"]=0.0 + return {"NOPBC": True, "Value": PLMD.defaults.COMPONENT_NODEV} + +def plumedCalculate(action: PLMD.PythonCVInterface): + #...something is defined... + if g(something): + action.data["example"]+=1.0 + return f(something) +``` + +### Calculate step: the CALCULATE keyword +Both `PYFUNCTION` and `PYCVINTERFACE` use the keyword CALCULATE to call a function in the `calculate()` step +If not specified CALCULATE will default to `"plumedCalculate"`. + +Plumed will expect CALCULATE to return either a tuple or a dict: + - the tuple can have up to 3 components: `[value, derivative, boxDerivative]`: if "derivative" is set to `True` an error will be raised if the tuple contain only one element, or if contains more than one element in the other case. + - in the case of multiple COMPONETS plumed will expect a dict with a key per defined component. Each key must contain a tuple with the previous criterions. + +Instead of a tuple you can return a single float (or a dict of floats), but plumed will complain with a warning. + +```python +import plumedCommunications as PLMD + + +plumedInit = { + "COMPONENTS": { + "first": PLMD.defaults.COMPONENT_NODEV, + "second": {"derivative": True, "period": ["-pi", "pi"]}, + } +} + +def plumedCalculate(action: PLMD.PythonCVInterface): + #...complex calculation are called here... + return { + "first": [resForFirst], + "second": [resForSecond, derivativeForSecond, boxDerivativeForSecond], + } +``` + +### Prepare step: the PREPARE keyword +**Only** `PYCVINTERFACE` use the keyword PREPARE to call a function in the `prepare()` step, before `calculate()` + +If not specified PREPARE will be ignored. +Python expect this PREPARE to return a dict. + +If that dict contains a key called 'setAtomRequest' with a list of atoms (you can pass a string that will be parsed by plumed like the one in ATOMS) the new list will be requested. + +### Update step: the UPDATE keyword +**Only** `PYCVINTERFACE` use the keyword UPDATE to call a function in the `update()` step, after `calculate()` + +If not specified UPDATE will be ignored. +Python expect this UPDATE to return a dict. + +As now nothing will be done after the python UPDATE call. + +You can however act on the `.data` dict or access to all the parameters accessible from python, and for example update a plot or accumulate an histogram or any other thing that may be useful for your analysis/simulation. + +## Some Examples + +In the following paragraphs I am showing a few examples, for a short description of the python interface look [here](PythonInterface.md) + +### Bare minimum +The minimum invocation can be the following: + +**plumed.dat** +``` +LOAD GLOBAL FILE=path/to/PythonCVInterface.so +cvPY: PYFUNCTION IMPORT=pycv +PRINT FILE=colvar.out ARG=* +``` + +**pycv.py** +```python +import plumedCommunications as PLMD +plumedInit={"Value":PLMD.defaults.COMPONENT_NODEV} + +def plumedCalculate(action: PLMD.PythonFunction): + action.lognl("Hello, world!") + return 0.0 +``` +This simply prints an "Hello, world!" at each step of the simulation/trajectory. + +### An example, gradient calculation with jax + +Here's a quick example with calculation of an angle between three atoms + +**plumed.dat** + +``` +cv1: PYTHONCV ATOMS=1,4,3 IMPORT=jaxcv CALCULATE=cv1 +``` + +**jaxcv.py** + +```py +# Import the JAX library +import jax.numpy as np +from jax import grad, jit, vmap +import plumedCommunications + +plumedInit={"Value": plumedCommunications.defaults.COMPONENT} +# Implementation of the angle function. @jit really improves speed +@jit +def angle(x): + r1 = x[0,:]-x[1,:] + r2 = x[2,:]-x[1,:] + + costheta = np.dot(r1,r2) / np.sqrt(np.dot(r1,r1) * np.dot(r2,r2)) + # OR: costheta = np.dot(r1,r2) / np.linalg.norm(r1) / np.linalg.norm(r2) + theta = np.arccos(costheta) + return theta + +# Use JAX to auto-gradient it +grad_angle = grad(angle) + +# The CV function actually called +def cv1(action): + x=action.getPositions() + return angle(x), grad_angle(x) + +``` +## EXTRA: JAX + +Transparent auto-differentiation, JIT compilation, and vectorization +are available through Google's [JAX +library](https://github.com/google/jax) (recommended). + +### Install jax: + +Go to the original guide in the [jax documenation](https://jax.readthedocs.io/en/latest/installation.html) + +jax has different method of installation, and can be accelerated with various different hardware, +(as stated before, trying to install things in a virtual environment make doing error less costly) +The command for installing should be similar to: + - example if you have a cuda12 compatible device (a wheel for cuda will be installed alongside jax): +`pip install "jax[cuda12_pip]" -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html` + - example if you have a cuda12 compatible device, and **cuda already installed on your system**: +`pip install "jax[cuda12_local]" -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html` + + +## Limitations + +- No test have been done with MPI +- JAX's GPU/TPU offloading are not 100% testes. + +## Authors + +Original author: Toni Giorgino + +Daniele Rapetti +## Contributing + +Please report bugs and ideas via this repository's *Issues*. + + +## Citation + +Giorgino T. PYCV: a PLUMED 2 Module Enabling the Rapid Prototyping of +Collective Variables in Python. The Journal of Open Source Software +4(42):1773 + +[![DOI](https://joss.theoj.org/papers/10.21105/joss.01773/status.svg)](https://doi.org/10.21105/joss.01773) +[![plumID:19.075](https://www.plumed-nest.org/eggs/19/075/badge.svg)](https://www.plumed-nest.org/eggs/19/075/) + + +## Copyright + +PYCV is distributed under the LGPL terms: see COPYRIGHT. + +PYCV includes the [pybind11](https://github.com/pybind/pybind11) +library, distributed under its own license terms (BSD-3). diff --git a/plugins/pycv/astyle.sh b/plugins/pycv/astyle.sh new file mode 100755 index 0000000000..0338316fbe --- /dev/null +++ b/plugins/pycv/astyle.sh @@ -0,0 +1,20 @@ +#! /usr/bin/env bash + +for file in *.c *.cpp *.h *.inc.in; do + + test -f "$file" || continue + + echo -n "astyle $file" + + ../../astyle/astyle --options=../../.astyle.options <$file >$file.tmp && { + if cmp -s $file $file.tmp; then + echo + else + cp $file.tmp $file + echo " +++ PATCHED" + fi + } + + rm $file.tmp + +done diff --git a/plugins/pycv/prepareMakeForDevelop.sh b/plugins/pycv/prepareMakeForDevelop.sh new file mode 100755 index 0000000000..3c8539e132 --- /dev/null +++ b/plugins/pycv/prepareMakeForDevelop.sh @@ -0,0 +1,21 @@ +#! /usr/bin/env bash + +if [[ -z $PLUMED_KERNEL ]]; then + echo "$(basename $0) can work only if \"PLUMED_KERNEL\" is defined" + echo "either via module load or sourceme.sh, or manually exported" + exit 1 +fi + +if ! python3-config --embed >/dev/null 2>/dev/null; then +#TODO: verify that this does not give problems with conda + echo "PyCV needs python to be built to be embedable" + echo "(compiling python with --enable-shared should be enough)" + exit 1 +fi + +{ + plumed --no-mpi config makefile_conf + echo "PLUMED_KERNEL=-L${PLUMED_KERNEL}" + echo "ADDCPPFLAGS=$(python3-config --cflags --embed) $(python3 -m pybind11 --includes) -I$(plumed --no-mpi info --include-dir)/plumed" + echo "ADDCLDFLAGS=$(python3-config --ldflags --embed)" +} > Make.inc diff --git a/plugins/pycv/regtest/.gitignore b/plugins/pycv/regtest/.gitignore new file mode 100644 index 0000000000..db3a96c3cc --- /dev/null +++ b/plugins/pycv/regtest/.gitignore @@ -0,0 +1,3 @@ +tmp/ +report.txt +scripts diff --git a/plugins/pycv/regtest/Makefile b/plugins/pycv/regtest/Makefile new file mode 100644 index 0000000000..ccba4eff62 --- /dev/null +++ b/plugins/pycv/regtest/Makefile @@ -0,0 +1,37 @@ +SUBDIRS := $(subst /,,$(dir $(shell ls */Makefile))) + +SUBDIRSCLEAN := $(addsuffix .clean,$(SUBDIRS)) +SUBDIRSVALGRIND := $(addsuffix .valgrind,$(SUBDIRS)) +SUBDIRSTESTCLEAN := $(addsuffix .testclean,$(SUBDIRS)) + +.PHONY: all checkfail copytodoc clean valgrind $(SUBDIRS) $(SUBDIRSCLEAN) $(SUBDIRSVALGRIND) + +all: $(SUBDIRS) + scripts/check + + +checkfail: + scripts/check --fail + +copytodoc: + scripts/copytodoc + +clean: $(SUBDIRSCLEAN) + +valgrind: $(SUBDIRSVALGRIND) + scripts/check + +testclean: $(SUBDIRSTESTCLEAN) + +$(SUBDIRS): + $(MAKE) -C $@ + +$(SUBDIRSCLEAN): %.clean: + $(MAKE) -C $* clean + +$(SUBDIRSVALGRIND): %.valgrind: + $(MAKE) -C $* valgrind + +$(SUBDIRSTESTCLEAN): %.testclean: + $(MAKE) -C $* testclean + diff --git a/plugins/pycv/regtest/pycvcomm/Makefile b/plugins/pycv/regtest/pycvcomm/Makefile new file mode 100644 index 0000000000..430b3123ed --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/Makefile @@ -0,0 +1 @@ +include ../scripts/module.make diff --git a/plugins/pycv/regtest/pycvcomm/maker.sh b/plugins/pycv/regtest/pycvcomm/maker.sh new file mode 100755 index 0000000000..9f3256c811 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/maker.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +failed="" +for a in rt-*; do + echo $a + ( + cd "$a" || exit + make + + if make; then + echo ok + else + failed="${failed} ${a}" + fi + ) +done +echo $failed diff --git a/plugins/pycv/regtest/pycvcomm/rt-MDinformations/Makefile b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-MDinformations/config b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/config new file mode 100644 index 0000000000..7429ccc9fa --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz --mc massCharges.dat" diff --git a/plugins/pycv/regtest/pycvcomm/rt-MDinformations/massCharges.dat b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/massCharges.dat new file mode 100644 index 0000000000..778f45f8a0 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/massCharges.dat @@ -0,0 +1,6 @@ +#! FIELDS index mass charge + 0 2 0.2 + 1 3 0.3 + 2 4 0.4 + 3 5 0.5 + \ No newline at end of file diff --git a/plugins/pycv/regtest/pycvcomm/rt-MDinformations/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/plumed.dat new file mode 100644 index 0000000000..e1c1f2fcb6 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/plumed.dat @@ -0,0 +1,15 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: ... +PYCVINTERFACE +ATOMS=2,4 +IMPORT=unitTest +CALCULATE=mypytest +INIT=myInit +PREPARE=myPrepare +... + +PRINT FILE=colvar.out ARG=* + + + diff --git a/plugins/pycv/regtest/pycvcomm/rt-MDinformations/pytest.log.reference b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/pytest.log.reference new file mode 100644 index 0000000000..ff675fcbd8 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/pytest.log.reference @@ -0,0 +1,21 @@ +action label: cvPY +@step 0 +action.getTime()=0.0 +action.getTimeStep()=1.0 +action.isRestart()=False +action.isExchangeStep()=False +@step 1 +action.getTime()=1.0 +action.getTimeStep()=1.0 +action.isRestart()=False +action.isExchangeStep()=False +@step 2 +action.getTime()=2.0 +action.getTimeStep()=1.0 +action.isRestart()=False +action.isExchangeStep()=False +@step 3 +action.getTime()=3.0 +action.getTimeStep()=1.0 +action.isRestart()=False +action.isExchangeStep()=False diff --git a/plugins/pycv/regtest/pycvcomm/rt-MDinformations/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/traj.xyz new file mode 100644 index 0000000000..c0e66cf2b2 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 5 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X -5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 -5 diff --git a/plugins/pycv/regtest/pycvcomm/rt-MDinformations/unitTest.py b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/unitTest.py new file mode 100644 index 0000000000..ea8e5056db --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-MDinformations/unitTest.py @@ -0,0 +1,22 @@ +import plumedCommunications as PLMD + +# import plumedUtilities +log = open("pytest.log", "w") + +def myPrint(*args,**kwargs): print(*args,**kwargs, file=log) + +def myInit(action: PLMD.PythonCVInterface): + myPrint(f"action label: {action.label}") + return{"Value": PLMD.defaults.COMPONENT_NODEV,} + +def myPrepare(action: PLMD.PythonCVInterface): + myPrint(f"@step {action.getStep()}") + myPrint(f"{action.getTime()=}") + myPrint(f"{action.getTimeStep()=}") + myPrint(f"{action.isRestart()=}") + myPrint(f"{action.isExchangeStep()=}") + return {} + + +def mypytest(action: PLMD.PythonCVInterface): + return 0.0 diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/Makefile b/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/colvar.out.reference new file mode 100644 index 0000000000..458ff63b29 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/colvar.out.reference @@ -0,0 +1,6 @@ +#! FIELDS time cvPY.py-ax cvPY.py-ay cvPY.py-az cvPY.py-bx cvPY.py-by cvPY.py-bz cvPY.py-cx cvPY.py-cy cvPY.py-cz + 0.000000 6.000000 0.000000 0.000000 0.000000 6.000000 0.000000 0.000000 0.000000 6.000000 + 1.000000 6.000000 0.000000 0.000000 0.000000 5.000000 0.000000 0.000000 0.000000 4.000000 + 2.000000 1.000000 1.000000 0.000000 1.000000 -1.000000 0.000000 0.000000 0.000000 1.000000 + 3.000000 1.000000 1.000000 0.000000 1.000000 0.250000 0.000000 1.000000 1.000000 -1.000000 + 4.000000 2.000000 3.000000 0.000000 6.000000 0.250000 -3.200000 0.000000 1.000000 -1.000000 diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/config b/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/plumed.dat new file mode 100644 index 0000000000..51cc22945b --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/plumed.dat @@ -0,0 +1,10 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: ... + PYCVINTERFACE + ATOMS=1 + IMPORT=pyGetBoxes + CALCULATE=pyBox +... + +PRINT FILE=colvar.out ARG=* diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/pyGetBoxes.py b/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/pyGetBoxes.py new file mode 100644 index 0000000000..803a4937b7 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/pyGetBoxes.py @@ -0,0 +1,40 @@ +# This is only a partial example. This function does not compute the +# gradient, so it is useless for biasing. See the other regression +# tests for how to auto-grad. + +# And, of course, one should not call slow functions (such as print) +# in the CV calculation. + +import numpy as np +import plumedCommunications + +# import plumedUtilities +log = open("pydist.log", "w") + +print("Imported my pydist.", file=log) + +plumedInit = { + "COMPONENTS": { + "ax": plumedCommunications.defaults.COMPONENT, + "ay": plumedCommunications.defaults.COMPONENT, + "az": plumedCommunications.defaults.COMPONENT, + "bx": plumedCommunications.defaults.COMPONENT, + "by": plumedCommunications.defaults.COMPONENT, + "bz": plumedCommunications.defaults.COMPONENT, + "cx": plumedCommunications.defaults.COMPONENT, + "cy": plumedCommunications.defaults.COMPONENT, + "cz": plumedCommunications.defaults.COMPONENT, + } +} + + +def pyBox(action: plumedCommunications.PythonCVInterface): + d = action.getPbc().getBox() + print(f"{d=}", file=log) + ret = {} + grad = {} + for i, name in enumerate(["a", "b", "c"]): + for j, coord in enumerate(["x", "y", "z"]): + ret[f"{name}{coord}"] = (d[i, j], np.zeros((1, 3))) + print(ret, file=log) + return ret diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/traj.xyz new file mode 100644 index 0000000000..7aeffbc703 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBC-getBox/traj.xyz @@ -0,0 +1,15 @@ +1 +6 6 6 +X 0 0 0 +1 +6 5 4 +X 0 0 0 +1 +1 1 0 1 -1 0 0 0 1 +X 0 0 0 +1 +1 1 0 1 0.25 0 1 1 -1 +X 0 0 0 +1 +2 3 0 6 0.25 -3.2 0 1 -1 +X 0 0 0 diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/Makefile b/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/colvar.out.reference new file mode 100644 index 0000000000..e2eeeb61dc --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/colvar.out.reference @@ -0,0 +1,6 @@ +#! FIELDS time cvPY.py-aix cvPY.py-aiy cvPY.py-aiz cvPY.py-bix cvPY.py-biy cvPY.py-biz cvPY.py-cix cvPY.py-ciy cvPY.py-ciz + 0.000000 0.166667 0.000000 0.000000 0.000000 0.166667 0.000000 0.000000 0.000000 0.166667 + 1.000000 0.166667 0.000000 0.000000 0.000000 0.200000 0.000000 0.000000 0.000000 0.250000 + 2.000000 0.500000 0.500000 -0.000000 0.500000 -0.500000 -0.000000 -0.000000 -0.000000 1.000000 + 3.000000 -0.333333 1.333333 0.000000 1.333333 -1.333333 0.000000 1.000000 0.000000 -1.000000 + 4.000000 0.123431 0.125523 -0.401674 0.251046 -0.083682 0.267782 0.251046 -0.083682 -0.732218 diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/config b/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/plumed.dat new file mode 100644 index 0000000000..c0ee689288 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/plumed.dat @@ -0,0 +1,10 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: ... + PYCVINTERFACE + ATOMS=1 + IMPORT=pyGetBoxes + CALCULATE=pyInvBox +... + +PRINT FILE=colvar.out ARG=* diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/pyGetBoxes.py b/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/pyGetBoxes.py new file mode 100644 index 0000000000..4f9ccd7968 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/pyGetBoxes.py @@ -0,0 +1,40 @@ +# This is only a partial example. This function does not compute the +# gradient, so it is useless for biasing. See the other regression +# tests for how to auto-grad. + +# And, of course, one should not call slow functions (such as print) +# in the CV calculation. + +import numpy as np +import plumedCommunications + +# import plumedUtilities +log = open("pydist.log", "w") + +print("Imported my pydist.", file=log) +print(plumedCommunications.defaults.COMPONENT, file=log) + +plumedInit = dict( + COMPONENTS=dict( + aix=plumedCommunications.defaults.COMPONENT, + aiy=plumedCommunications.defaults.COMPONENT, + aiz=plumedCommunications.defaults.COMPONENT, + bix=plumedCommunications.defaults.COMPONENT, + biy=plumedCommunications.defaults.COMPONENT, + biz=plumedCommunications.defaults.COMPONENT, + cix=plumedCommunications.defaults.COMPONENT, + ciy=plumedCommunications.defaults.COMPONENT, + ciz=plumedCommunications.defaults.COMPONENT, + ) +) + + +def pyInvBox(action: plumedCommunications.PythonCVInterface): + invBox = action.getPbc().getInvBox() + print(f"{invBox=}", file=log) + ret = {} + for i, name in enumerate(["a", "b", "c"]): + for j, coord in enumerate(["x", "y", "z"]): + ret[f"{name}i{coord}"] = (invBox[i, j], np.zeros((1, 3))) + print(ret, file=log) + return ret diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/traj.xyz new file mode 100644 index 0000000000..7aeffbc703 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBC-getInvBox/traj.xyz @@ -0,0 +1,15 @@ +1 +6 6 6 +X 0 0 0 +1 +6 5 4 +X 0 0 0 +1 +1 1 0 1 -1 0 0 0 1 +X 0 0 0 +1 +1 1 0 1 0.25 0 1 1 -1 +X 0 0 0 +1 +2 3 0 6 0.25 -3.2 0 1 -1 +X 0 0 0 diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBCs/Makefile b/plugins/pycv/regtest/pycvcomm/rt-PBCs/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBCs/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBCs/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-PBCs/colvar.out.reference new file mode 100644 index 0000000000..b98552dff1 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBCs/colvar.out.reference @@ -0,0 +1,6 @@ +#! FIELDS time cvPY cvCPP + 0.000000 1.000000 1.000000 + 1.000000 1.000000 1.000000 + 2.000000 1.000000 1.000000 + 3.000000 1.000000 1.000000 + 4.000000 1.000000 1.000000 diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBCs/config b/plugins/pycv/regtest/pycvcomm/rt-PBCs/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBCs/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBCs/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-PBCs/plumed.dat new file mode 100644 index 0000000000..a5905a98e2 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBCs/plumed.dat @@ -0,0 +1,7 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: PYCVINTERFACE ATOMS=1,4 IMPORT=pydistancePBCs CALCULATE=pydist + +cvCPP: DISTANCE ATOMS=1,4 + +PRINT FILE=colvar.out ARG=* diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBCs/pydistancePBCs.py b/plugins/pycv/regtest/pycvcomm/rt-PBCs/pydistancePBCs.py new file mode 100644 index 0000000000..0da907ec60 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBCs/pydistancePBCs.py @@ -0,0 +1,28 @@ +# This is only a partial example. This function does not compute the +# gradient, so it is useless for biasing. See the other regression +# tests for how to auto-grad. + +# And, of course, one should not call slow functions (such as print) +# in the CV calculation. + +import numpy as np +import plumedCommunications + +# import plumedUtilities +log = open("pydist.log", "w") + +print("Imported my pydist.", file=log) + +plumedInit={"Value":plumedCommunications.defaults.COMPONENT_NODEV} + +def pydist(action: plumedCommunications.PythonCVInterface): + at: np.ndarray = action.getPositions() + + # print(at0,file=log) + d = at[0] - at[1] + d = action.getPbc().apply([d]) + assert d.shape[1] == 3, "d is not a (*,3) array" + d = np.linalg.norm(d[0]) + + # print(f"{at1},{at2}",file=log) + return d diff --git a/plugins/pycv/regtest/pycvcomm/rt-PBCs/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-PBCs/traj.xyz new file mode 100644 index 0000000000..90abff41f7 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-PBCs/traj.xyz @@ -0,0 +1,30 @@ +4 +6 6 6 +X 0 0 0 +X 0 5 5 +X 5 5 0 +X 5 0 0 +4 +6 6 6 +X 0 0 0 +X 5 5 0 +X 0 5 5 +X 0 5 0 +4 +6 6 6 +X 0 0 0 +X 0 5 0 +X -5 5 0 +X 0 0 5 +4 +6 6 6 +X 0 0 0 +X 0 5 0 +X 0 5 -5 +X 1 0 0 +4 +6 6 6 +X 0 0 0 +X 0 5 5 +X 0 5 -5 +X -1 0 0 diff --git a/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/Makefile b/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/colvar.out.reference new file mode 100644 index 0000000000..bec5227863 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/colvar.out.reference @@ -0,0 +1,5 @@ +#! FIELDS time cvPY.py-absoluteIndex0index cvPY.py-absoluteIndex0serial cvPY.py-absoluteIndex1index cvPY.py-absoluteIndex1serial + 0.000000 1.000000 2.000000 3.000000 4.000000 + 1.000000 1.000000 2.000000 3.000000 4.000000 + 2.000000 1.000000 2.000000 3.000000 4.000000 + 3.000000 1.000000 2.000000 3.000000 4.000000 diff --git a/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/config b/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/plumed.dat new file mode 100644 index 0000000000..e707444539 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/plumed.dat @@ -0,0 +1,13 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: ... +PYCVINTERFACE +ATOMS=2,4 +IMPORT=unitTest +CALCULATE=mypytest +... + +PRINT FILE=colvar.out ARG=* + + + diff --git a/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/traj.xyz new file mode 100644 index 0000000000..c0e66cf2b2 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 5 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X -5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 -5 diff --git a/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/unitTest.py b/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/unitTest.py new file mode 100644 index 0000000000..69ab4eb39f --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-absoluteIndexes/unitTest.py @@ -0,0 +1,27 @@ +import plumedCommunications as PLMD +from plumedCommunications.defaults import COMPONENT_NODEV as nodevnoperiod +# import plumedUtilities +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) +plumedInit = dict( + COMPONENTS=dict( + absoluteIndex0index=nodevnoperiod, + absoluteIndex0serial=nodevnoperiod, + absoluteIndex1index=nodevnoperiod, + absoluteIndex1serial=nodevnoperiod, + ) +) + + +def mypytest(action: PLMD.PythonCVInterface): + #this is the costly way of working: + ret = { + "absoluteIndex0index": action.absoluteIndexes()[0], + "absoluteIndex0serial":action.absoluteIndexes()[0]+1, + } + #this should be faster + indexes = action.absoluteIndexes() + ret["absoluteIndex1index"] = indexes[1] + ret["absoluteIndex1serial"] = indexes[1]+1 + return ret diff --git a/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/Makefile b/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/colvar.out.reference new file mode 100644 index 0000000000..f18f9f5841 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/colvar.out.reference @@ -0,0 +1,8 @@ +#! FIELDS time cvPY + 0.000000 2.000000 + 1.000000 2.000000 + 2.000000 2.000000 + 3.000000 2.000000 + 4.000000 2.000000 + 5.000000 2.000000 + 6.000000 2.000000 diff --git a/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/config b/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/plumed.dat new file mode 100644 index 0000000000..9e39640651 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/plumed.dat @@ -0,0 +1,11 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: ... +PYCVINTERFACE +IMPORT=unitTest +... + +PRINT FILE=colvar.out ARG=* + + + diff --git a/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/traj.xyz new file mode 100644 index 0000000000..1ed965943b --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/traj.xyz @@ -0,0 +1,28 @@ +2 +100 100 100 +X 0 0 0 +X 99 0 0 +2 +100 100 100 +X 0 0 0 +X 0 99 0 +2 +100 100 100 +X 0 0 0 +X 0 0 99 +2 +100 100 100 +X 0 0 0 +X 0 99 99 +2 +100 100 100 +X 0 0 0 +X 99 0 99 +2 +100 100 100 +X 0 0 0 +X 99 99 0 +2 +100 100 100 +X 0 0 0 +X 99 99 99 diff --git a/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/unitTest.py b/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/unitTest.py new file mode 100644 index 0000000000..c4e9c536be --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-autoFunctions/unitTest.py @@ -0,0 +1,19 @@ +import plumedCommunications as PLMD +import numpy +from sys import stderr as log +#log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + + +def plumedInitio(action: PLMD.PythonCVInterface): + # return {"Value": {"period": [None,0,0]}} + # return {"Value": {"period": None}} + return {"Value": {"period": ["0",0.3]}} +plumedInit={"Value": {"period": None},"NOPBC":False, + "ATOMS":"1,2"} + +def plumedCalculate(action: PLMD.PythonCVInterface): + ret = [action.nat] + + return ret diff --git a/plugins/pycv/regtest/pycvcomm/rt-doc/Makefile b/plugins/pycv/regtest/pycvcomm/rt-doc/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-doc/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-doc/config b/plugins/pycv/regtest/pycvcomm/rt-doc/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-doc/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-doc/config.reference b/plugins/pycv/regtest/pycvcomm/rt-doc/config.reference new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-doc/config.reference @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-doc/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-doc/plumed.dat new file mode 100644 index 0000000000..1c4fdefee5 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-doc/plumed.dat @@ -0,0 +1,5 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvdist: PYCVINTERFACE IMPORT=pyhelp + +#PRINT FILE=colvar.out ARG=* diff --git a/plugins/pycv/regtest/pycvcomm/rt-doc/pyhelp.py b/plugins/pycv/regtest/pycvcomm/rt-doc/pyhelp.py new file mode 100644 index 0000000000..bc2754d199 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-doc/pyhelp.py @@ -0,0 +1,24 @@ +# This is only a partial example. This function does not compute the +# gradient, so it is useless for biasing. See the other regression +# tests for how to auto-grad. + +# And, of course, one should not call slow functions (such as print) +# in the CV calculation. + +import plumedCommunications +import pydoc + +def plumedInit(_): + with open('PythonCVInterface.help.txt', 'w') as f: + h = pydoc.Helper(output=f) + h(plumedCommunications.PythonCVInterface) + with open('plumedCommunications.help.txt', 'w') as f: + h = pydoc.Helper(output=f) + h(plumedCommunications) + with open('plumedCommunications.defaults.help.txt', 'w') as f: + h = pydoc.Helper(output=f) + h(plumedCommunications.defaults) + return {"Value":plumedCommunications.defaults.COMPONENT_NODEV, "ATOMS":"1"} + +def plumedCalculate(_): + return 0 diff --git a/plugins/pycv/regtest/pycvcomm/rt-doc/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-doc/traj.xyz new file mode 100644 index 0000000000..b3b4e45d61 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-doc/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 5 5 5 +X 4 5 5 +X 5 4 5 +X 5 5 4 +4 +100 100 100 +X 5 5 5 +X 3 5 5 +X 5 3 5 +X 5 5 3 +4 +100 100 100 +X 5 5 5 +X 2 5 5 +X 5 2 5 +X 5 5 2 +4 +100 100 100 +X 5 5 5 +X 1 5 5 +X 5 1 5 +X 5 5 1 diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPosition/Makefile b/plugins/pycv/regtest/pycvcomm/rt-getPosition/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPosition/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPosition/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-getPosition/colvar.out.reference new file mode 100644 index 0000000000..c696b438b3 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPosition/colvar.out.reference @@ -0,0 +1,5 @@ +#! FIELDS time cvPY cvCPP + 0.000000 5.000000 5.000000 + 1.000000 8.660254 8.660254 + 2.000000 11.180340 11.180340 + 3.000000 8.660254 8.660254 diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPosition/config b/plugins/pycv/regtest/pycvcomm/rt-getPosition/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPosition/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPosition/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-getPosition/plumed.dat new file mode 100644 index 0000000000..dfdb9973c3 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPosition/plumed.dat @@ -0,0 +1,10 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: PYCVINTERFACE ATOMS=1,4 IMPORT=pydistancegetAtPos CALCULATE=pydist + +cvCPP: DISTANCE ATOMS=1,4 + +PRINT FILE=colvar.out ARG=* + + + diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPosition/pydistancegetAtPos.py b/plugins/pycv/regtest/pycvcomm/rt-getPosition/pydistancegetAtPos.py new file mode 100644 index 0000000000..a618c0636f --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPosition/pydistancegetAtPos.py @@ -0,0 +1,29 @@ +# This is only a partial example. This function does not compute the +# gradient, so it is useless for biasing. See the other regression +# tests for how to auto-grad. + +# And, of course, one should not call slow functions (such as print) +# in the CV calculation. + +import numpy as np +import plumedCommunications + +# import plumedUtilities +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + +plumedInit = {"Value": plumedCommunications.defaults.COMPONENT_NODEV} + + +def pydist(action: plumedCommunications.PythonCVInterface): + at: list = [ + action.getPosition(0), + action.getPosition(1), + ] + d = at[0] - at[1] + d = np.linalg.norm(d) + print(f"{at[0]},{at[1]},{d}", file=log) + + # print(f"{at1},{at2}",file=log) + return d diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPosition/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-getPosition/traj.xyz new file mode 100644 index 0000000000..c0e66cf2b2 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPosition/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 5 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X -5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 -5 diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/Makefile b/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/colvar.out.reference new file mode 100644 index 0000000000..c696b438b3 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/colvar.out.reference @@ -0,0 +1,5 @@ +#! FIELDS time cvPY cvCPP + 0.000000 5.000000 5.000000 + 1.000000 8.660254 8.660254 + 2.000000 11.180340 11.180340 + 3.000000 8.660254 8.660254 diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/config b/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/plumed.dat new file mode 100644 index 0000000000..63a85ce1d2 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/plumed.dat @@ -0,0 +1,7 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: PYCVINTERFACE GROUPA=1,4 IMPORT=pydistancegetAtPos CALCULATE=pydist + +cvCPP: DISTANCE ATOMS=1,4 + +PRINT FILE=colvar.out ARG=* diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/pydistancegetAtPos.py b/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/pydistancegetAtPos.py new file mode 100644 index 0000000000..9a632d3388 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/pydistancegetAtPos.py @@ -0,0 +1,34 @@ +# This is only a partial example. This function does not compute the +# gradient, so it is useless for biasing. See the other regression +# tests for how to auto-grad. + +# And, of course, one should not call slow functions (such as print) +# in the CV calculation. + +import numpy as np +import plumedCommunications +from sys import stderr as log + +# import plumedUtilities +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + +plumedInit = {"Value": plumedCommunications.defaults.COMPONENT_NODEV} + + +def pydist(action: plumedCommunications.PythonCVInterface): + # NB: This is not a realistic case of using the neigbour list!!! + # cvPY: PYCVINTERFACE GROUPA=1,4 IMPORT=pydistancegetAtPos CALCULATE=pydist + # ^ using this line should behave like calling "ATOMS=1,4" (with this function) + atoms = action.getPositions() + nl = action.getNeighbourList() + assert nl.size == 1 + assert len(nl) == nl.size + NLlist = nl.getClosePairs()[0] + + d = atoms[NLlist[0]] - atoms[NLlist[1]] + d = np.linalg.norm(d) + print(f"{atoms[0]},{atoms[1]},{d}", file=log) + + return d diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/traj.xyz new file mode 100644 index 0000000000..c0e66cf2b2 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositionNL/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 5 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X -5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 -5 diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/Makefile b/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/colvar.out.reference new file mode 100644 index 0000000000..0a41fa2874 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/colvar.out.reference @@ -0,0 +1,5 @@ +#! FIELDS time cvPY.py-x cvPY.py-y cvPY.py-z + 0.000000 1.000000 1.000000 1.000000 + 1.000000 2.000000 2.000000 2.000000 + 2.000000 3.000000 3.000000 3.000000 + 3.000000 4.000000 4.000000 4.000000 diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/config b/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/plumed.dat new file mode 100644 index 0000000000..15c9d4cedc --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/plumed.dat @@ -0,0 +1,11 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: ... +PYCVINTERFACE +GROUPA=1,1,1 GROUPB=2,3,4 +IMPORT=pydistancegetAtPos +CALCULATE=pydistInPair +PAIR +... + +PRINT FILE=colvar.out ARG=* diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/pydistancegetAtPos.py b/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/pydistancegetAtPos.py new file mode 100644 index 0000000000..e2cacfba41 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/pydistancegetAtPos.py @@ -0,0 +1,39 @@ +# This is only a partial example. This function does not compute the +# gradient, so it is useless for biasing. See the other regression +# tests for how to auto-grad. + +# And, of course, one should not call slow functions (such as print) +# in the CV calculation. + +import numpy as np +import plumedCommunications +from sys import stderr as log + +# import plumedUtilities +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + +plumedInit = { + "COMPONENTS": { + "x": plumedCommunications.defaults.COMPONENT, + "y": plumedCommunications.defaults.COMPONENT, + "z": plumedCommunications.defaults.COMPONENT, + } +} + + +def pydistInPair(action: plumedCommunications.PythonCVInterface): + # NB: This is not a realistic case of using the neigbour list!!! + # cvPY: PYCVINTERFACE GROUPA=1,4 IMPORT=pydistancegetAtPos CALCULATE=pydist + + atoms = action.getPositions() + nl = action.getNeighbourList() + assert nl.size == 3 + assert len(nl) == nl.size + + x, y, z = [atoms[pair[0]] - atoms[pair[1]] for pair in nl.getClosePairs()] + + zero = np.zeros(atoms.shape) + # MUST work with tuple or lists + return {"x": [x[0], zero], "y": (y[1], zero), "z": (z[2], zero)} diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/traj.xyz new file mode 100644 index 0000000000..b3b4e45d61 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositionNLPair/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 5 5 5 +X 4 5 5 +X 5 4 5 +X 5 5 4 +4 +100 100 100 +X 5 5 5 +X 3 5 5 +X 5 3 5 +X 5 5 3 +4 +100 100 100 +X 5 5 5 +X 2 5 5 +X 5 2 5 +X 5 5 2 +4 +100 100 100 +X 5 5 5 +X 1 5 5 +X 5 1 5 +X 5 5 1 diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositions/Makefile b/plugins/pycv/regtest/pycvcomm/rt-getPositions/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositions/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositions/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-getPositions/colvar.out.reference new file mode 100644 index 0000000000..c696b438b3 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositions/colvar.out.reference @@ -0,0 +1,5 @@ +#! FIELDS time cvPY cvCPP + 0.000000 5.000000 5.000000 + 1.000000 8.660254 8.660254 + 2.000000 11.180340 11.180340 + 3.000000 8.660254 8.660254 diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositions/config b/plugins/pycv/regtest/pycvcomm/rt-getPositions/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositions/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositions/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-getPositions/plumed.dat new file mode 100644 index 0000000000..b392518120 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositions/plumed.dat @@ -0,0 +1,7 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: PYCVINTERFACE ATOMS=1,4 IMPORT=pydistancegetAt CALCULATE=pydist + +cvCPP: DISTANCE ATOMS=1,4 + +PRINT FILE=colvar.out ARG=* diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositions/pydistancegetAt.py b/plugins/pycv/regtest/pycvcomm/rt-getPositions/pydistancegetAt.py new file mode 100644 index 0000000000..05aa36e1bc --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositions/pydistancegetAt.py @@ -0,0 +1,33 @@ +# This is only a partial example. This function does not compute the +# gradient, so it is useless for biasing. See the other regression +# tests for how to auto-grad. + +# And, of course, one should not call slow functions (such as print) +# in the CV calculation. + +import numpy as np +import plumedCommunications + +# import plumedUtilities +log = open("pydist.log", "w") + +print("Imported my pydist.", file=log) + +plumedInit = {"Value": plumedCommunications.defaults.COMPONENT_NODEV} + + +def pydist_(x): + # print("call",file=log) + return 0 + + +def pydist(action: plumedCommunications.PythonCVInterface): + at: np.ndarray = action.getPositions() + + # print(at0,file=log) + d = at[0] - at[1] + d = np.linalg.norm(d) + print(f"{at[0]},{at[1]},{d}", file=log) + + # print(f"{at1},{at2}",file=log) + return d diff --git a/plugins/pycv/regtest/pycvcomm/rt-getPositions/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-getPositions/traj.xyz new file mode 100644 index 0000000000..c0e66cf2b2 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-getPositions/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 5 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X -5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 -5 diff --git a/plugins/pycv/regtest/pycvcomm/rt-makeWhole/Makefile b/plugins/pycv/regtest/pycvcomm/rt-makeWhole/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-makeWhole/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-makeWhole/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-makeWhole/colvar.out.reference new file mode 100644 index 0000000000..7f5c42d6de --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-makeWhole/colvar.out.reference @@ -0,0 +1,8 @@ +#! FIELDS time cvPY.py-dbefore cvPY.py-dafter + 0.000000 99.000000 1.000000 + 1.000000 99.000000 1.000000 + 2.000000 99.000000 1.000000 + 3.000000 140.007143 1.414214 + 4.000000 140.007143 1.414214 + 5.000000 140.007143 1.414214 + 6.000000 171.473030 1.732051 diff --git a/plugins/pycv/regtest/pycvcomm/rt-makeWhole/config b/plugins/pycv/regtest/pycvcomm/rt-makeWhole/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-makeWhole/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-makeWhole/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-makeWhole/plumed.dat new file mode 100644 index 0000000000..3eb8d69361 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-makeWhole/plumed.dat @@ -0,0 +1,13 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: ... +PYCVINTERFACE +ATOMS=1,2 +IMPORT=unitTest +CALCULATE=mypytest +... + +PRINT FILE=colvar.out ARG=* + + + diff --git a/plugins/pycv/regtest/pycvcomm/rt-makeWhole/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-makeWhole/traj.xyz new file mode 100644 index 0000000000..1ed965943b --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-makeWhole/traj.xyz @@ -0,0 +1,28 @@ +2 +100 100 100 +X 0 0 0 +X 99 0 0 +2 +100 100 100 +X 0 0 0 +X 0 99 0 +2 +100 100 100 +X 0 0 0 +X 0 0 99 +2 +100 100 100 +X 0 0 0 +X 0 99 99 +2 +100 100 100 +X 0 0 0 +X 99 0 99 +2 +100 100 100 +X 0 0 0 +X 99 99 0 +2 +100 100 100 +X 0 0 0 +X 99 99 99 diff --git a/plugins/pycv/regtest/pycvcomm/rt-makeWhole/unitTest.py b/plugins/pycv/regtest/pycvcomm/rt-makeWhole/unitTest.py new file mode 100644 index 0000000000..5cd2aa6579 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-makeWhole/unitTest.py @@ -0,0 +1,26 @@ +import plumedCommunications as PLMD +import numpy + +# import plumedUtilities +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) +plumedInit = { + "COMPONENTS": { + "dbefore": PLMD.defaults.COMPONENT_NODEV, + "dafter": PLMD.defaults.COMPONENT_NODEV, + } +} + + +def mypytest(action: PLMD.PythonCVInterface): + atoms = action.getPositions() + d = atoms[0] - atoms[1] + ret = { + "dbefore": numpy.linalg.norm(d), + } + action.makeWhole() + atoms = action.getPositions() + d = atoms[0] - atoms[1] + ret["dafter"] = numpy.linalg.norm(d) + return ret diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/Makefile b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/colvar.out.reference new file mode 100644 index 0000000000..1037e53989 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/colvar.out.reference @@ -0,0 +1,5 @@ +#! FIELDS time cvPY.py-mass0 cvPY.py-mass1 cvPY.py-charge0 cvPY.py-charge1 + 0.000000 3.000000 5.000000 0.300000 0.500000 + 1.000000 3.000000 5.000000 0.300000 0.500000 + 2.000000 3.000000 5.000000 0.300000 0.500000 + 3.000000 3.000000 5.000000 0.300000 0.500000 diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/config b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/config new file mode 100644 index 0000000000..7429ccc9fa --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz --mc massCharges.dat" diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/massCharges.dat b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/massCharges.dat new file mode 100644 index 0000000000..778f45f8a0 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/massCharges.dat @@ -0,0 +1,6 @@ +#! FIELDS index mass charge + 0 2 0.2 + 1 3 0.3 + 2 4 0.4 + 3 5 0.5 + \ No newline at end of file diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/plumed.dat new file mode 100644 index 0000000000..e707444539 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/plumed.dat @@ -0,0 +1,13 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: ... +PYCVINTERFACE +ATOMS=2,4 +IMPORT=unitTest +CALCULATE=mypytest +... + +PRINT FILE=colvar.out ARG=* + + + diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/traj.xyz new file mode 100644 index 0000000000..c0e66cf2b2 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 5 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X -5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 -5 diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/unitTest.py b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/unitTest.py new file mode 100644 index 0000000000..77e7fac8fb --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndCharges/unitTest.py @@ -0,0 +1,26 @@ +import plumedCommunications as PLMD + +# import plumedUtilities +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + +plumedInit = { + "COMPONENTS": { + "mass0": PLMD.defaults.COMPONENT_NODEV, + "mass1": PLMD.defaults.COMPONENT_NODEV, + "charge0": PLMD.defaults.COMPONENT_NODEV, + "charge1": PLMD.defaults.COMPONENT_NODEV, + } +} + + +def mypytest(action: PLMD.PythonCVInterface): + ret = { + "mass0": action.mass(0), + "charge0": action.charge(0), + "mass1": action.mass(1), + "charge1": action.charge(1), + } + + return ret diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/Makefile b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/colvar.out.reference new file mode 100644 index 0000000000..5436dc9a34 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/colvar.out.reference @@ -0,0 +1,5 @@ +#! FIELDS time cvPY.py-mass0 cvPY.py-mass1 cvPY.py-charge0 cvPY.py-charge1 + 0.000000 1 1 1 1 + 1.000000 1 1 1 1 + 2.000000 1 1 1 1 + 3.000000 1 1 1 1 diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/config b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/config new file mode 100644 index 0000000000..7429ccc9fa --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz --mc massCharges.dat" diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/massCharges.dat b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/massCharges.dat new file mode 100644 index 0000000000..778f45f8a0 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/massCharges.dat @@ -0,0 +1,6 @@ +#! FIELDS index mass charge + 0 2 0.2 + 1 3 0.3 + 2 4 0.4 + 3 5 0.5 + \ No newline at end of file diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/plumed.dat new file mode 100644 index 0000000000..c420e88be1 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/plumed.dat @@ -0,0 +1,12 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: ... +PYCVINTERFACE +ATOMS=2,4 +IMPORT=unitTest +CALCULATE=mypytest +INIT=myInit +... + +#I am returnin booleans +PRINT FILE=colvar.out ARG=* FMT=%1.0f diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/traj.xyz new file mode 100644 index 0000000000..c0e66cf2b2 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 5 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X -5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 -5 diff --git a/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/unitTest.py b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/unitTest.py new file mode 100644 index 0000000000..356b739567 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-massesAndChargesArray/unitTest.py @@ -0,0 +1,37 @@ +import plumedCommunications as PLMD +from sys import stderr as log +import numpy as np + +print("Imported unitTest", file=log) + + +def myInit(action: PLMD.PythonCVInterface): + t = np.loadtxt("massCharges.dat") + action.data["masses"] = t[:, 1] + action.data["charges"] = t[:, 2] + print("Calling myInit", file=log) + return { + "COMPONENTS": { + "mass0": PLMD.defaults.COMPONENT_NODEV, + "mass1": PLMD.defaults.COMPONENT_NODEV, + "charge0": PLMD.defaults.COMPONENT_NODEV, + "charge1": PLMD.defaults.COMPONENT_NODEV, + } + } + + +def mypytest(action: PLMD.PythonCVInterface): + masses = action.masses() + charges = action.charges() + action.data["masses"] + action.data["charges"] + ret = {} + absoluteIndexes = action.absoluteIndexes() + for i in range(action.nat): + ret[f"mass{i}"] = ( + action.data["masses"][absoluteIndexes[i]] == masses[i] + ) + ret[f"charge{i}"] = ( + action.data["charges"][absoluteIndexes[i]] == charges[i] + ) + return ret diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/GRAD_d01.reference b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/GRAD_d01.reference new file mode 100644 index 0000000000..6a3ba30709 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/GRAD_d01.reference @@ -0,0 +1,73 @@ +#! FIELDS time parameter cvPY.py-d01 + 0.000000 0 1.0000 + 0.000000 1 0.0000 + 0.000000 2 0.0000 + 0.000000 3 -1.0000 + 0.000000 4 0.0000 + 0.000000 5 0.0000 + 0.000000 6 0.0000 + 0.000000 7 0.0000 + 0.000000 8 0.0000 + 0.000000 9 -5.0000 + 0.000000 10 0.0000 + 0.000000 11 0.0000 + 0.000000 12 0.0000 + 0.000000 13 0.0000 + 0.000000 14 0.0000 + 0.000000 15 0.0000 + 0.000000 16 0.0000 + 0.000000 17 0.0000 + 1.000000 0 1.0000 + 1.000000 1 0.0000 + 1.000000 2 0.0000 + 1.000000 3 -1.0000 + 1.000000 4 0.0000 + 1.000000 5 0.0000 + 1.000000 6 0.0000 + 1.000000 7 0.0000 + 1.000000 8 0.0000 + 1.000000 9 -5.0000 + 1.000000 10 0.0000 + 1.000000 11 0.0000 + 1.000000 12 0.0000 + 1.000000 13 0.0000 + 1.000000 14 0.0000 + 1.000000 15 0.0000 + 1.000000 16 0.0000 + 1.000000 17 0.0000 + 2.000000 0 1.0000 + 2.000000 1 0.0000 + 2.000000 2 0.0000 + 2.000000 3 -1.0000 + 2.000000 4 0.0000 + 2.000000 5 0.0000 + 2.000000 6 0.0000 + 2.000000 7 0.0000 + 2.000000 8 0.0000 + 2.000000 9 -5.0000 + 2.000000 10 0.0000 + 2.000000 11 0.0000 + 2.000000 12 0.0000 + 2.000000 13 0.0000 + 2.000000 14 0.0000 + 2.000000 15 0.0000 + 2.000000 16 0.0000 + 2.000000 17 0.0000 + 3.000000 0 1.0000 + 3.000000 1 0.0000 + 3.000000 2 0.0000 + 3.000000 3 -1.0000 + 3.000000 4 0.0000 + 3.000000 5 0.0000 + 3.000000 6 0.0000 + 3.000000 7 0.0000 + 3.000000 8 0.0000 + 3.000000 9 -5.0000 + 3.000000 10 0.0000 + 3.000000 11 0.0000 + 3.000000 12 0.0000 + 3.000000 13 0.0000 + 3.000000 14 0.0000 + 3.000000 15 0.0000 + 3.000000 16 0.0000 + 3.000000 17 0.0000 diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/GRAD_d02.reference b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/GRAD_d02.reference new file mode 100644 index 0000000000..bf6603954d --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/GRAD_d02.reference @@ -0,0 +1,73 @@ +#! FIELDS time parameter cvPY.py-d02 + 0.000000 0 0.0000 + 0.000000 1 -1.0000 + 0.000000 2 0.0000 + 0.000000 3 0.0000 + 0.000000 4 0.0000 + 0.000000 5 0.0000 + 0.000000 6 0.0000 + 0.000000 7 1.0000 + 0.000000 8 0.0000 + 0.000000 9 0.0000 + 0.000000 10 0.0000 + 0.000000 11 0.0000 + 0.000000 12 0.0000 + 0.000000 13 -5.0000 + 0.000000 14 0.0000 + 0.000000 15 0.0000 + 0.000000 16 0.0000 + 0.000000 17 0.0000 + 1.000000 0 0.5774 + 1.000000 1 -0.5774 + 1.000000 2 -0.5774 + 1.000000 3 0.0000 + 1.000000 4 0.0000 + 1.000000 5 0.0000 + 1.000000 6 -0.5774 + 1.000000 7 0.5774 + 1.000000 8 0.5774 + 1.000000 9 -2.8868 + 1.000000 10 2.8868 + 1.000000 11 2.8868 + 1.000000 12 2.8868 + 1.000000 13 -2.8868 + 1.000000 14 -2.8868 + 1.000000 15 2.8868 + 1.000000 16 -2.8868 + 1.000000 17 -2.8868 + 2.000000 0 0.8944 + 2.000000 1 -0.4472 + 2.000000 2 0.0000 + 2.000000 3 0.0000 + 2.000000 4 0.0000 + 2.000000 5 0.0000 + 2.000000 6 -0.8944 + 2.000000 7 0.4472 + 2.000000 8 0.0000 + 2.000000 9 -8.9443 + 2.000000 10 4.4721 + 2.000000 11 0.0000 + 2.000000 12 4.4721 + 2.000000 13 -2.2361 + 2.000000 14 0.0000 + 2.000000 15 0.0000 + 2.000000 16 0.0000 + 2.000000 17 0.0000 + 3.000000 0 0.5774 + 3.000000 1 -0.5774 + 3.000000 2 0.5774 + 3.000000 3 0.0000 + 3.000000 4 0.0000 + 3.000000 5 0.0000 + 3.000000 6 -0.5774 + 3.000000 7 0.5774 + 3.000000 8 -0.5774 + 3.000000 9 -2.8868 + 3.000000 10 2.8868 + 3.000000 11 -2.8868 + 3.000000 12 2.8868 + 3.000000 13 -2.8868 + 3.000000 14 2.8868 + 3.000000 15 -2.8868 + 3.000000 16 2.8868 + 3.000000 17 -2.8868 diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/Makefile b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/colvar.out.reference new file mode 100644 index 0000000000..0daaf3d811 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/colvar.out.reference @@ -0,0 +1,5 @@ +#! FIELDS time cvPY.py-d01 cvPY.py-d02 + 0.000000 5.000000 5.000000 + 1.000000 5.000000 8.660254 + 2.000000 5.000000 11.180340 + 3.000000 5.000000 8.660254 diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/config b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/plumed.dat new file mode 100644 index 0000000000..ced981eb49 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/plumed.dat @@ -0,0 +1,8 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: PYCVINTERFACE ATOMS=1,2,4 IMPORT=pydistancegetAt CALCULATE=pydist NOPBC + +DUMPDERIVATIVES ARG=cvPY.py-d01 FILE=GRAD_d01 FMT=%8.4f +DUMPDERIVATIVES ARG=cvPY.py-d02 FILE=GRAD_d02 FMT=%8.4f + +PRINT FILE=colvar.out ARG=* diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/pydistancegetAt.py b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/pydistancegetAt.py new file mode 100644 index 0000000000..56d2be1faa --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/pydistancegetAt.py @@ -0,0 +1,37 @@ +# This is only a partial example. This function does not compute the +# gradient, so it is useless for biasing. See the other regression +# tests for how to auto-grad. + +# And, of course, one should not call slow functions (such as print) +# in the CV calculation. + +import numpy as np +import plumedCommunications +from sys import stderr as log + +# import plumedUtilities +# log = open("pydist.log", "w") + +print("Imported my pydist.", file=log) +plumedInit = dict( + COMPONENTS=dict( + d01=plumedCommunications.defaults.COMPONENT, + d02=plumedCommunications.defaults.COMPONENT, + ) +) + + +def pydist(action: plumedCommunications.PythonCVInterface): + at: np.ndarray = action.getPositions() + nat = at.shape[0] + # print(at0,file=log) + ret = {} + for rn, j in [["d01", 1], ["d02", 2]]: + d = at[0] - at[j] + val = np.linalg.norm(d) + grad = np.zeros((nat, 3)) + grad[0] = d / val + grad[j] = -d / val + # print(f"{rn} {val} {grad}", file=log) + ret[rn] = (val, grad) + return ret diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/traj.xyz new file mode 100644 index 0000000000..c0e66cf2b2 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleComponents/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 5 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X -5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 -5 diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/Makefile b/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/colvar.out.reference new file mode 100644 index 0000000000..84a9ace59e --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/colvar.out.reference @@ -0,0 +1,12 @@ +#! FIELDS time cvPY.py-nonPeriodic cvPY.py-Periodic cvPY.py-PeriodicPI +#! SET min_cvPY.py-Periodic 0 +#! SET max_cvPY.py-Periodic 1.3 +#! SET min_cvPY.py-PeriodicPI 0 +#! SET max_cvPY.py-PeriodicPI pi + 0.000000 0.000000 0.000000 0.000000 + 1.000000 1.000000 1.000000 1.000000 + 2.000000 2.000000 0.700000 2.000000 + 3.000000 3.000000 0.400000 3.000000 + 4.000000 4.000000 0.100000 0.858407 + 5.000000 5.000000 1.100000 1.858407 + 6.000000 6.000000 0.800000 2.858407 diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/config b/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/plumed.dat new file mode 100644 index 0000000000..62bbc19ce9 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/plumed.dat @@ -0,0 +1,13 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: ... +PYCVINTERFACE +IMPORT=unitTest +INIT=init +CALCULATE=mypytest +... + +PRINT FILE=colvar.out ARG=* + + + diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/traj.xyz new file mode 100644 index 0000000000..1ed965943b --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/traj.xyz @@ -0,0 +1,28 @@ +2 +100 100 100 +X 0 0 0 +X 99 0 0 +2 +100 100 100 +X 0 0 0 +X 0 99 0 +2 +100 100 100 +X 0 0 0 +X 0 0 99 +2 +100 100 100 +X 0 0 0 +X 0 99 99 +2 +100 100 100 +X 0 0 0 +X 99 0 99 +2 +100 100 100 +X 0 0 0 +X 99 99 0 +2 +100 100 100 +X 0 0 0 +X 99 99 99 diff --git a/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/unitTest.py b/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/unitTest.py new file mode 100644 index 0000000000..eccd4b75f4 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multipleValuePeriodicity/unitTest.py @@ -0,0 +1,25 @@ +import plumedCommunications as PLMD +import numpy + +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + +init = dict( + COMPONENTS=dict( + nonPeriodic=dict(period=None), + Periodic={"period": ["0", "1.3"]}, + PeriodicPI={"period": ["0", "pi"]}, + ), + ATOMS="1,2", +) + + +def mypytest(action: PLMD.PythonCVInterface): + ret = { + "nonPeriodic": action.getStep(), + "Periodic": action.getStep(), + "PeriodicPI": action.getStep(), + } + + return ret diff --git a/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/Makefile b/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/colvar.out.reference new file mode 100644 index 0000000000..b67d75f789 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/colvar.out.reference @@ -0,0 +1,5 @@ +#! FIELDS time cv1 cv2 + 0.000000 0.360000 0.360000 + 1.000000 0.720000 0.720000 + 2.000000 1.080000 1.080000 + 3.000000 1.440000 1.440000 diff --git a/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/config b/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/config new file mode 100644 index 0000000000..82e4472874 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz --dump-forces forces --dump-forces-fmt=%10.6f" diff --git a/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/distcv.py b/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/distcv.py new file mode 100644 index 0000000000..f9b87de6a6 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/distcv.py @@ -0,0 +1,14 @@ +import numpy as np +import plumedCommunications + +# In reality one should not log stuff here... +log = open("log.txt", "w", 1) + +print("At import", file=log) + +plumedInit={"Value": plumedCommunications.defaults.COMPONENT_NODEV,} + +# The CV function actually called +def cv(X): + #0.36 is arbitrary + return 0.36*(X.getStep()+1) diff --git a/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/plumed.dat new file mode 100644 index 0000000000..b940f99eb7 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/plumed.dat @@ -0,0 +1,6 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cv1: PYCVINTERFACE ATOMS=1,3 IMPORT=distcv CALCULATE=cv +cv2: PYCVINTERFACE ATOMS=1,4 IMPORT=distcv CALCULATE=cv + +PRINT FILE=colvar.out ARG=* diff --git a/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/traj.xyz new file mode 100644 index 0000000000..c0e66cf2b2 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-multiplecalls/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 5 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X -5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 -5 diff --git a/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/Makefile b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/colvar.out.reference new file mode 100644 index 0000000000..90210dc277 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/colvar.out.reference @@ -0,0 +1,5 @@ +#! FIELDS time cv1 + 0.000000 1.000000 + 1.000000 2.000000 + 2.000000 3.000000 + 3.000000 4.000000 diff --git a/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/config b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/plumed.dat new file mode 100644 index 0000000000..5856aa6929 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/plumed.dat @@ -0,0 +1,5 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cv1: PYCVINTERFACE ATOMS=@mdatoms IMPORT=pycvPerFrame CALCULATE=pydist PREPARE=changeAtom + +PRINT FILE=colvar.out ARG=* diff --git a/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/pycvPerFrame.py b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/pycvPerFrame.py new file mode 100644 index 0000000000..a359216a20 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/pycvPerFrame.py @@ -0,0 +1,32 @@ +# This is only a partial example. This function does not compute the +# gradient, so it is useless for biasing. See the other regression +# tests for how to auto-grad. + +# And, of course, one should not call slow functions (such as print) +# in the CV calculation. + +import numpy as np +import plumedCommunications + +log = open("pycv.log", "w") + +print("Imported.", file=log) +plumedInit={"Value": plumedCommunications.defaults.COMPONENT,} + +def changeAtom(plmdAction: plumedCommunications.PythonCVInterface): + print(f"pyCVCALLED") + toret = {"setAtomRequest": [0, int(plmdAction.getStep()) + 1]} + # this is just for "fun" + if plmdAction.getStep() == 3: + toret["setAtomRequest"][1] = 1 + print(toret) + return toret + + +def pydist(action: plumedCommunications.PythonCVInterface): + at: np.ndarray = action.getPositions() + d = at[0] - at[1] + d = np.linalg.norm(d) + print(f"{at[0]},{at[1]},{d}", file=log) + + return d diff --git a/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/traj.xyz new file mode 100644 index 0000000000..ead294112f --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtom/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 0 0 0 +X 0 0 1 +X 0 0 0 +X 0 0 0 +4 +100 100 100 +X 0 0 0 +X 0 0 0 +X 0 0 2 +X 0 0 0 +4 +100 100 100 +X 0 0 0 +X 0 0 0 +X 0 0 0 +X 0 0 3 +4 +100 100 100 +X 0 0 0 +X 0 0 4 +X 0 0 0 +X 0 0 0 diff --git a/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/Makefile b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/colvar.out.reference new file mode 100644 index 0000000000..90210dc277 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/colvar.out.reference @@ -0,0 +1,5 @@ +#! FIELDS time cv1 + 0.000000 1.000000 + 1.000000 2.000000 + 2.000000 3.000000 + 3.000000 4.000000 diff --git a/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/config b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/plumed.dat new file mode 100644 index 0000000000..5856aa6929 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/plumed.dat @@ -0,0 +1,5 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cv1: PYCVINTERFACE ATOMS=@mdatoms IMPORT=pycvPerFrame CALCULATE=pydist PREPARE=changeAtom + +PRINT FILE=colvar.out ARG=* diff --git a/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/pycvPerFrame.py b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/pycvPerFrame.py new file mode 100644 index 0000000000..92729421ed --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/pycvPerFrame.py @@ -0,0 +1,32 @@ +# This is only a partial example. This function does not compute the +# gradient, so it is useless for biasing. See the other regression +# tests for how to auto-grad. + +# And, of course, one should not call slow functions (such as print) +# in the CV calculation. + +import numpy as np +import plumedCommunications + +log = open("pycv.log", "w") + +print("Imported.", file=log) +plumedInit={"Value": plumedCommunications.defaults.COMPONENT,} + +def changeAtom(plmdAction: plumedCommunications.PythonCVInterface): + print(f"pyCVCALLED") + toret = {"setAtomRequest": f"1, {int(plmdAction.getStep()) + 2}"} + # this is just for "fun" + if plmdAction.getStep() == 3: + toret["setAtomRequest"] = "1,2" + print(toret) + return toret + + +def pydist(action: plumedCommunications.PythonCVInterface): + at: np.ndarray = action.getPositions() + d = at[0] - at[1] + d = np.linalg.norm(d) + print(f"{at[0]},{at[1]},{d}", file=log) + + return d diff --git a/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/traj.xyz new file mode 100644 index 0000000000..ead294112f --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-newFrameNewAtomSTR/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 0 0 0 +X 0 0 1 +X 0 0 0 +X 0 0 0 +4 +100 100 100 +X 0 0 0 +X 0 0 0 +X 0 0 2 +X 0 0 0 +4 +100 100 100 +X 0 0 0 +X 0 0 0 +X 0 0 0 +X 0 0 3 +4 +100 100 100 +X 0 0 0 +X 0 0 4 +X 0 0 0 +X 0 0 0 diff --git a/plugins/pycv/regtest/pycvcomm/rt-persistentData/Makefile b/plugins/pycv/regtest/pycvcomm/rt-persistentData/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-persistentData/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-persistentData/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-persistentData/colvar.out.reference new file mode 100644 index 0000000000..972b5473fc --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-persistentData/colvar.out.reference @@ -0,0 +1,5 @@ +#! FIELDS time cv1 + 0.000000 0.000000 + 1.000000 1.000000 + 2.000000 3.000000 + 3.000000 6.000000 diff --git a/plugins/pycv/regtest/pycvcomm/rt-persistentData/config b/plugins/pycv/regtest/pycvcomm/rt-persistentData/config new file mode 100644 index 0000000000..66a5c0dad7 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-persistentData/config @@ -0,0 +1,8 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" + +#this will showcase that you can import a complex module!!! +plumed_regtest_before(){ + cp -r ../pycvPersistentData . +} diff --git a/plugins/pycv/regtest/pycvcomm/rt-persistentData/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-persistentData/plumed.dat new file mode 100644 index 0000000000..1a1d9d6cf0 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-persistentData/plumed.dat @@ -0,0 +1,5 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cv1: PYCVINTERFACE ATOMS=@mdatoms IMPORT=pycvPersistentData CALCULATE=pydist INIT=pyinit + +PRINT FILE=colvar.out ARG=* diff --git a/plugins/pycv/regtest/pycvcomm/rt-persistentData/pycvPersistentData/__init__.py b/plugins/pycv/regtest/pycvcomm/rt-persistentData/pycvPersistentData/__init__.py new file mode 100644 index 0000000000..b6a505fa38 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-persistentData/pycvPersistentData/__init__.py @@ -0,0 +1,33 @@ +# This is only a partial example. This function does not compute the +# gradient, so it is useless for biasing. See the other regression +# tests for how to auto-grad. + +# And, of course, one should not call slow functions (such as print) +# in the CV calculation. + +import numpy as np +import plumedCommunications +from plumedCommunications.defaults import COMPONENT_NODEV +from sys import stderr as log +#log = open("pycv.log", "w") + +print("Imported pycvPersistentData", file=log) + +def pyinit(plmdAction: plumedCommunications.PythonCVInterface): + print("Calling pyinit", file=log) + plmdAction.log("---Calling pyinit---") + plmdAction.lognl("Logging from Python :)") + print(f"{plmdAction.data=}", file=log) + plmdAction.data["pycv"]=0 + print(f"{plmdAction.data=}", file=log) + return {"Value":COMPONENT_NODEV} + +def pydist(plmdAction: plumedCommunications.PythonCVInterface): + plmdAction.log("Calling pydist: ") + plmdAction.lognl(plmdAction.getStep()) + print("Calling pydist", file=log) + print(f"{plmdAction.data=}, {plmdAction.getStep()=}", file=log) + + plmdAction.data["pycv"]+=plmdAction.getStep() + d=plmdAction.data["pycv"] + return d diff --git a/plugins/pycv/regtest/pycvcomm/rt-persistentData/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-persistentData/traj.xyz new file mode 100644 index 0000000000..ead294112f --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-persistentData/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 0 0 0 +X 0 0 1 +X 0 0 0 +X 0 0 0 +4 +100 100 100 +X 0 0 0 +X 0 0 0 +X 0 0 2 +X 0 0 0 +4 +100 100 100 +X 0 0 0 +X 0 0 0 +X 0 0 0 +X 0 0 3 +4 +100 100 100 +X 0 0 0 +X 0 0 4 +X 0 0 0 +X 0 0 0 diff --git a/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/Makefile b/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/colvar.out.reference b/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/colvar.out.reference new file mode 100644 index 0000000000..5dd8eb12e0 --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/colvar.out.reference @@ -0,0 +1,10 @@ +#! FIELDS time cvPY +#! SET min_cvPY 0 +#! SET max_cvPY 1.3 + 0.000000 0.000000 + 1.000000 1.000000 + 2.000000 0.700000 + 3.000000 0.400000 + 4.000000 0.100000 + 5.000000 1.100000 + 6.000000 0.800000 diff --git a/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/config b/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/plumed.dat b/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/plumed.dat new file mode 100644 index 0000000000..f46a9cdf4b --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/plumed.dat @@ -0,0 +1,14 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: ... +PYCVINTERFACE +ATOMS=1,2 +IMPORT=unitTest +INIT=init +CALCULATE=mypytest +... + +PRINT FILE=colvar.out ARG=* + + + diff --git a/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/traj.xyz b/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/traj.xyz new file mode 100644 index 0000000000..1ed965943b --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/traj.xyz @@ -0,0 +1,28 @@ +2 +100 100 100 +X 0 0 0 +X 99 0 0 +2 +100 100 100 +X 0 0 0 +X 0 99 0 +2 +100 100 100 +X 0 0 0 +X 0 0 99 +2 +100 100 100 +X 0 0 0 +X 0 99 99 +2 +100 100 100 +X 0 0 0 +X 99 0 99 +2 +100 100 100 +X 0 0 0 +X 99 99 0 +2 +100 100 100 +X 0 0 0 +X 99 99 99 diff --git a/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/unitTest.py b/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/unitTest.py new file mode 100644 index 0000000000..580f020f2d --- /dev/null +++ b/plugins/pycv/regtest/pycvcomm/rt-valuePeriodicity/unitTest.py @@ -0,0 +1,17 @@ +import plumedCommunications as PLMD +import numpy + +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + + +def init(action: PLMD.PythonCVInterface): + return {"COMPONENTS": {"val":{"period": ["0", 1.3]}}} + return {"Value": {"period": ["0", "1.3"]}} + + +def mypytest(action: PLMD.PythonCVInterface): + ret = [action.getStep()] + + return ret diff --git a/plugins/pycv/regtest/pycvfunc/Makefile b/plugins/pycv/regtest/pycvfunc/Makefile new file mode 100644 index 0000000000..430b3123ed --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/Makefile @@ -0,0 +1 @@ +include ../scripts/module.make diff --git a/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/Makefile b/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/colvar.out.reference b/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/colvar.out.reference new file mode 100644 index 0000000000..68d678ab8c --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/colvar.out.reference @@ -0,0 +1,10 @@ +#! FIELDS time fPY.py-difference fPY.py-bringBackInPbc dz +#! SET min_dz 0.0 +#! SET max_dz 1.3 + 0.000000 0.000000 0.000000 0.000000 + 1.000000 0.300000 0.000000 0.000000 + 2.000000 -0.400000 0.600000 0.300000 + 3.000000 -0.100000 0.900000 0.300000 + 4.000000 0.200000 1.200000 0.300000 + 5.000000 0.200000 0.000000 0.000000 + 6.000000 -0.500000 0.500000 0.300000 diff --git a/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/config b/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/plumed.dat b/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/plumed.dat new file mode 100644 index 0000000000..084470a2d6 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/plumed.dat @@ -0,0 +1,15 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +d12c: DISTANCE ATOMS=1,2 COMPONENTS + +dz: COMBINE ARG=d12c.z PERIODIC=0.0,1.3 + +fPY: ... +PYFUNCTION +IMPORT=unitTest +INIT=initForF +CALCULATE=function +ARG=dz +... + +PRINT FILE=colvar.out ARG=fPY.py-difference,fPY.py-bringBackInPbc,dz diff --git a/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/traj.xyz b/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/traj.xyz new file mode 100644 index 0000000000..eff86f6f4d --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/traj.xyz @@ -0,0 +1,35 @@ +3 +100 100 100 +X 0 0 0 +X 99 0 0 +X 99 0 50 +3 +100 100 100 +X 0 0 0 +X 0 99 0 +X 0 99 50 +3 +100 100 100 +X 0 0 0 +X 0 0 99 +X 0 0 50 +3 +100 100 100 +X 0 0 0 +X 0 99 99 +X 50 99 50 +3 +100 100 100 +X 0 0 0 +X 99 0 99 +X 98 0 50 +3 +100 100 100 +X 0 0 0 +X 99 99 0 +X 99 98 50 +3 +100 100 100 +X 0 0 0 +X 99 99 99 +X 99 99 50 diff --git a/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/unitTest.py b/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/unitTest.py new file mode 100644 index 0000000000..b59db34482 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-ArgsMethods/unitTest.py @@ -0,0 +1,22 @@ +import plumedCommunications as PLMD +import numpy + +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + +initForF = { + "COMPONENTS": { + "difference": PLMD.defaults.COMPONENT_NODEV, + "bringBackInPbc": PLMD.defaults.COMPONENT_NODEV, + } +} + + +def function(action: PLMD.PythonFunction): + arg = action.arguments() + + return { + "difference": action.difference(0, action.getStep(), arg[0]), + "bringBackInPbc": action.bringBackInPbc(0, arg[0] * action.getStep()), + } diff --git a/plugins/pycv/regtest/pycvfunc/rt-Components/Makefile b/plugins/pycv/regtest/pycvfunc/rt-Components/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-Components/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvfunc/rt-Components/colvar.x.out.reference b/plugins/pycv/regtest/pycvfunc/rt-Components/colvar.x.out.reference new file mode 100644 index 0000000000..4a5a76cb0c --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-Components/colvar.x.out.reference @@ -0,0 +1,8 @@ +#! FIELDS time dc.x fPY.py-x + 0.000000 -1.000000 -1.000000 + 1.000000 0.000000 0.000000 + 2.000000 0.000000 0.000000 + 3.000000 0.000000 0.000000 + 4.000000 -1.000000 -1.000000 + 5.000000 -1.000000 -1.000000 + 6.000000 -1.000000 -1.000000 diff --git a/plugins/pycv/regtest/pycvfunc/rt-Components/colvar.y.out.reference b/plugins/pycv/regtest/pycvfunc/rt-Components/colvar.y.out.reference new file mode 100644 index 0000000000..c9f63e3c51 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-Components/colvar.y.out.reference @@ -0,0 +1,8 @@ +#! FIELDS time dc.y fPY.py-y + 0.000000 0.000000 0.000000 + 1.000000 -1.000000 -1.000000 + 2.000000 0.000000 0.000000 + 3.000000 -1.000000 -1.000000 + 4.000000 0.000000 0.000000 + 5.000000 -1.000000 -1.000000 + 6.000000 -1.000000 -1.000000 diff --git a/plugins/pycv/regtest/pycvfunc/rt-Components/colvar.z.out.reference b/plugins/pycv/regtest/pycvfunc/rt-Components/colvar.z.out.reference new file mode 100644 index 0000000000..c14b32f42f --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-Components/colvar.z.out.reference @@ -0,0 +1,8 @@ +#! FIELDS time dc.z fPY.py-z + 0.000000 0.000000 0.000000 + 1.000000 0.000000 0.000000 + 2.000000 -1.000000 -1.000000 + 3.000000 -1.000000 -1.000000 + 4.000000 -1.000000 -1.000000 + 5.000000 0.000000 0.000000 + 6.000000 -1.000000 -1.000000 diff --git a/plugins/pycv/regtest/pycvfunc/rt-Components/config b/plugins/pycv/regtest/pycvfunc/rt-Components/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-Components/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvfunc/rt-Components/plumed.dat b/plugins/pycv/regtest/pycvfunc/rt-Components/plumed.dat new file mode 100644 index 0000000000..08d4b480ae --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-Components/plumed.dat @@ -0,0 +1,18 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +dc: DISTANCE ATOMS=1,2 COMPONENTS + +fPY: ... +PYFUNCTION +IMPORT=unitTest +INIT=initForF +CALCULATE=function +ARG=dc.x,dc.y,dc.z +... + +PRINT FILE=colvar.x.out ARG=dc.x,fPY.py-x +PRINT FILE=colvar.y.out ARG=dc.y,fPY.py-y +PRINT FILE=colvar.z.out ARG=dc.z,fPY.py-z + + + diff --git a/plugins/pycv/regtest/pycvfunc/rt-Components/traj.xyz b/plugins/pycv/regtest/pycvfunc/rt-Components/traj.xyz new file mode 100644 index 0000000000..eff86f6f4d --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-Components/traj.xyz @@ -0,0 +1,35 @@ +3 +100 100 100 +X 0 0 0 +X 99 0 0 +X 99 0 50 +3 +100 100 100 +X 0 0 0 +X 0 99 0 +X 0 99 50 +3 +100 100 100 +X 0 0 0 +X 0 0 99 +X 0 0 50 +3 +100 100 100 +X 0 0 0 +X 0 99 99 +X 50 99 50 +3 +100 100 100 +X 0 0 0 +X 99 0 99 +X 98 0 50 +3 +100 100 100 +X 0 0 0 +X 99 99 0 +X 99 98 50 +3 +100 100 100 +X 0 0 0 +X 99 99 99 +X 99 99 50 diff --git a/plugins/pycv/regtest/pycvfunc/rt-Components/unitTest.py b/plugins/pycv/regtest/pycvfunc/rt-Components/unitTest.py new file mode 100644 index 0000000000..686171fd47 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-Components/unitTest.py @@ -0,0 +1,24 @@ +import plumedCommunications as PLMD +import numpy + +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + +initForF = { + "COMPONENTS": { + "x": PLMD.defaults.COMPONENT_NODEV, + "y": PLMD.defaults.COMPONENT_NODEV, + "z": PLMD.defaults.COMPONENT_NODEV, + } +} + + +def function(action: PLMD.PythonFunction): + arg = action.arguments() + + return { + "x": arg[0], + "y": arg[1], + "z": arg[2], + } diff --git a/plugins/pycv/regtest/pycvfunc/rt-MDinformations/Makefile b/plugins/pycv/regtest/pycvfunc/rt-MDinformations/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-MDinformations/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvfunc/rt-MDinformations/config b/plugins/pycv/regtest/pycvfunc/rt-MDinformations/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-MDinformations/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvfunc/rt-MDinformations/plumed.dat b/plugins/pycv/regtest/pycvfunc/rt-MDinformations/plumed.dat new file mode 100644 index 0000000000..36b4fd9dd6 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-MDinformations/plumed.dat @@ -0,0 +1,11 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +fPY: ... +PYFUNCTION +IMPORT=unitTest +... + +PRINT FILE=colvar.out ARG=* + + + diff --git a/plugins/pycv/regtest/pycvfunc/rt-MDinformations/pytest.log.reference b/plugins/pycv/regtest/pycvfunc/rt-MDinformations/pytest.log.reference new file mode 100644 index 0000000000..ceeb9b0906 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-MDinformations/pytest.log.reference @@ -0,0 +1,21 @@ +action label: fPY +@step 0 +action.getTime()=0.0 +action.getTimeStep()=1.0 +action.isRestart()=False +action.isExchangeStep()=False +@step 1 +action.getTime()=1.0 +action.getTimeStep()=1.0 +action.isRestart()=False +action.isExchangeStep()=False +@step 2 +action.getTime()=2.0 +action.getTimeStep()=1.0 +action.isRestart()=False +action.isExchangeStep()=False +@step 3 +action.getTime()=3.0 +action.getTimeStep()=1.0 +action.isRestart()=False +action.isExchangeStep()=False diff --git a/plugins/pycv/regtest/pycvfunc/rt-MDinformations/traj.xyz b/plugins/pycv/regtest/pycvfunc/rt-MDinformations/traj.xyz new file mode 100644 index 0000000000..c0e66cf2b2 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-MDinformations/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 5 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X -5 5 0 +4 +100 100 100 +X 5 0 0 +X 0 0 0 +X 0 5 0 +X 0 5 -5 diff --git a/plugins/pycv/regtest/pycvfunc/rt-MDinformations/unitTest.py b/plugins/pycv/regtest/pycvfunc/rt-MDinformations/unitTest.py new file mode 100644 index 0000000000..38e8d5d3fb --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-MDinformations/unitTest.py @@ -0,0 +1,18 @@ +import plumedCommunications as PLMD + +# import plumedUtilities +log = open("pytest.log", "w") + +def myPrint(*args,**kwargs): print(*args,**kwargs, file=log) + +def plumedInit(action: PLMD.PythonFunction): + myPrint(f"action label: {action.label}") + return{"Value": PLMD.defaults.COMPONENT_NODEV,} + +def plumedCalculate(action: PLMD.PythonFunction): + myPrint(f"@step {action.getStep()}") + myPrint(f"{action.getTime()=}") + myPrint(f"{action.getTimeStep()=}") + myPrint(f"{action.isRestart()=}") + myPrint(f"{action.isExchangeStep()=}") + return 0.0 diff --git a/plugins/pycv/regtest/pycvfunc/rt-arguments/Makefile b/plugins/pycv/regtest/pycvfunc/rt-arguments/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-arguments/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvfunc/rt-arguments/colvar.out.reference b/plugins/pycv/regtest/pycvfunc/rt-arguments/colvar.out.reference new file mode 100644 index 0000000000..eb4c8a7633 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-arguments/colvar.out.reference @@ -0,0 +1,8 @@ +#! FIELDS time d1 d2 fPY + 0.000000 1.000000 50.000000 50.000000 + 1.000000 1.000000 50.000000 50.000000 + 2.000000 1.000000 50.000000 50.000000 + 3.000000 1.414214 25.000000 50.000000 + 4.000000 1.414214 25.000000 50.000000 + 5.000000 2.000000 12.500000 50.000000 + 6.000000 1.732051 50.000000 150.000000 diff --git a/plugins/pycv/regtest/pycvfunc/rt-arguments/config b/plugins/pycv/regtest/pycvfunc/rt-arguments/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-arguments/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvfunc/rt-arguments/plumed.dat b/plugins/pycv/regtest/pycvfunc/rt-arguments/plumed.dat new file mode 100644 index 0000000000..062124aef7 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-arguments/plumed.dat @@ -0,0 +1,18 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +d1: DISTANCE ATOMS=1,2 +d2: DISTANCE ATOMS=1,3 + + +fPY: ... +PYFUNCTION +IMPORT=unitTest +INIT=initForF +CALCULATE=function +ARG=d1,d2 +... + +PRINT FILE=colvar.out ARG=* + + + diff --git a/plugins/pycv/regtest/pycvfunc/rt-arguments/traj.xyz b/plugins/pycv/regtest/pycvfunc/rt-arguments/traj.xyz new file mode 100644 index 0000000000..7df2fa7133 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-arguments/traj.xyz @@ -0,0 +1,35 @@ +3 +100 100 100 +X 0 0 0 +X 99 0 0 +X 0 0 50 +3 +100 100 100 +X 0 0 0 +X 0 99 0 +X 0 0 50 +3 +100 100 100 +X 0 0 0 +X 0 0 99 +X 0 0 50 +3 +100 100 100 +X 0 0 0 +X 0 99 99 +X 25 0 0 +3 +100 100 100 +X 0 0 0 +X 99 0 99 +X 0 0 25 +3 +100 100 100 +X 0 0 0 +X 0 98 0 +X 12.5 0 0 +3 +100 100 100 +X 0 0 0 +X 99 99 99 +X 0 0 50 diff --git a/plugins/pycv/regtest/pycvfunc/rt-arguments/unitTest.py b/plugins/pycv/regtest/pycvfunc/rt-arguments/unitTest.py new file mode 100644 index 0000000000..d9dc7c4395 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-arguments/unitTest.py @@ -0,0 +1,14 @@ +import plumedCommunications as PLMD +import numpy + +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + +initForF={"Value": PLMD.defaults.COMPONENT_NODEV} + +def function(action: PLMD.PythonFunction): + arg = [action.argument(0), + action.argument(1) + ] + return arg[0]*arg[0]*arg[1] \ No newline at end of file diff --git a/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/Makefile b/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/colvar.out.reference b/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/colvar.out.reference new file mode 100644 index 0000000000..9dfd7ab2e0 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/colvar.out.reference @@ -0,0 +1,8 @@ +#! FIELDS time fPY + 0.000000 0.000000 + 1.000000 0.000000 + 2.000000 0.000000 + 3.000000 0.000000 + 4.000000 0.000000 + 5.000000 0.000000 + 6.000000 0.000000 diff --git a/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/config b/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/plumed.dat b/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/plumed.dat new file mode 100644 index 0000000000..b95108ff9d --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/plumed.dat @@ -0,0 +1,20 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +dc: DISTANCE ATOMS=1,2 COMPONENTS +d: DISTANCE ATOMS=1,2 + + +fPY: ... +PYFUNCTION +IMPORT=unitTest +INIT=initForF +CALCULATE=function +ARG=dc.x,dc.y,dc.z,d +... + + + +PRINT FILE=colvar.out ARG=fPY + + + diff --git a/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/traj.xyz b/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/traj.xyz new file mode 100644 index 0000000000..eff86f6f4d --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/traj.xyz @@ -0,0 +1,35 @@ +3 +100 100 100 +X 0 0 0 +X 99 0 0 +X 99 0 50 +3 +100 100 100 +X 0 0 0 +X 0 99 0 +X 0 99 50 +3 +100 100 100 +X 0 0 0 +X 0 0 99 +X 0 0 50 +3 +100 100 100 +X 0 0 0 +X 0 99 99 +X 50 99 50 +3 +100 100 100 +X 0 0 0 +X 99 0 99 +X 98 0 50 +3 +100 100 100 +X 0 0 0 +X 99 99 0 +X 99 98 50 +3 +100 100 100 +X 0 0 0 +X 99 99 99 +X 99 99 50 diff --git a/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/unitTest.py b/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/unitTest.py new file mode 100644 index 0000000000..aca9582b59 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-argumentsArray/unitTest.py @@ -0,0 +1,14 @@ +import plumedCommunications as PLMD +import numpy + +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + +initForF = {"Value": PLMD.defaults.COMPONENT_NODEV} + + +def function(action: PLMD.PythonFunction): + arg = action.arguments() + + return numpy.abs(numpy.sqrt(arg[0] ** 2 + arg[1] ** 2 + arg[2] ** 2) - arg[3]) diff --git a/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/Makefile b/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/colvar.out.reference b/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/colvar.out.reference new file mode 100644 index 0000000000..9dfd7ab2e0 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/colvar.out.reference @@ -0,0 +1,8 @@ +#! FIELDS time fPY + 0.000000 0.000000 + 1.000000 0.000000 + 2.000000 0.000000 + 3.000000 0.000000 + 4.000000 0.000000 + 5.000000 0.000000 + 6.000000 0.000000 diff --git a/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/config b/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/plumed.dat b/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/plumed.dat new file mode 100644 index 0000000000..b95108ff9d --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/plumed.dat @@ -0,0 +1,20 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +dc: DISTANCE ATOMS=1,2 COMPONENTS +d: DISTANCE ATOMS=1,2 + + +fPY: ... +PYFUNCTION +IMPORT=unitTest +INIT=initForF +CALCULATE=function +ARG=dc.x,dc.y,dc.z,d +... + + + +PRINT FILE=colvar.out ARG=fPY + + + diff --git a/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/traj.xyz b/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/traj.xyz new file mode 100644 index 0000000000..eff86f6f4d --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/traj.xyz @@ -0,0 +1,35 @@ +3 +100 100 100 +X 0 0 0 +X 99 0 0 +X 99 0 50 +3 +100 100 100 +X 0 0 0 +X 0 99 0 +X 0 99 50 +3 +100 100 100 +X 0 0 0 +X 0 0 99 +X 0 0 50 +3 +100 100 100 +X 0 0 0 +X 0 99 99 +X 50 99 50 +3 +100 100 100 +X 0 0 0 +X 99 0 99 +X 98 0 50 +3 +100 100 100 +X 0 0 0 +X 99 99 0 +X 99 98 50 +3 +100 100 100 +X 0 0 0 +X 99 99 99 +X 99 99 50 diff --git a/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/unitTest.py b/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/unitTest.py new file mode 100644 index 0000000000..5e30769949 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-argumentsWithComponents/unitTest.py @@ -0,0 +1,16 @@ +import plumedCommunications as PLMD +import numpy + +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + +initForF = {"Value": PLMD.defaults.COMPONENT_NODEV} + + +def function(action: PLMD.PythonFunction): + arg = [] + for i in range(action.nargs): + arg.append(action.argument(i)) + + return numpy.abs(numpy.sqrt(arg[0] ** 2 + arg[1] ** 2 + arg[2] ** 2) - arg[3]) diff --git a/plugins/pycv/regtest/pycvfunc/rt-derivative/Makefile b/plugins/pycv/regtest/pycvfunc/rt-derivative/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-derivative/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvfunc/rt-derivative/colvar.out.reference b/plugins/pycv/regtest/pycvfunc/rt-derivative/colvar.out.reference new file mode 100644 index 0000000000..f1f5820950 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-derivative/colvar.out.reference @@ -0,0 +1,8 @@ +#! FIELDS time d1 d2 fPY mul + 0.000000 1.000000 50.000000 50.000000 50.000000 + 1.000000 1.000000 50.000000 50.000000 50.000000 + 2.000000 1.000000 50.000000 50.000000 50.000000 + 3.000000 1.414214 25.000000 35.355339 35.355339 + 4.000000 1.414214 25.000000 35.355339 35.355339 + 5.000000 2.000000 12.500000 25.000000 25.000000 + 6.000000 1.732051 50.000000 86.602540 86.602540 diff --git a/plugins/pycv/regtest/pycvfunc/rt-derivative/config b/plugins/pycv/regtest/pycvfunc/rt-derivative/config new file mode 100644 index 0000000000..7e13a932fd --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-derivative/config @@ -0,0 +1,11 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" + +plumed_regtest_after() { + { + head -1 deriv | awk '{printf "%s %s %s %s %s-%s\n" , $1, $2,$3,$4,$5,$6 }' + #I'm giving some(a LOT OF) space to the floating point precision + awk 'function abs(v) {return v < 0 ? -v : v} NR>1{print $1, $2, 0.0001 < abs($3-$4) } ' deriv_delta +} diff --git a/plugins/pycv/regtest/pycvfunc/rt-derivative/deriv_delta.reference b/plugins/pycv/regtest/pycvfunc/rt-derivative/deriv_delta.reference new file mode 100644 index 0000000000..a44a09dd13 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-derivative/deriv_delta.reference @@ -0,0 +1,15 @@ +#! FIELDS time parameter mul-fPY +0.000000 0 0 +0.000000 1 0 +1.000000 0 0 +1.000000 1 0 +2.000000 0 0 +2.000000 1 0 +3.000000 0 0 +3.000000 1 0 +4.000000 0 0 +4.000000 1 0 +5.000000 0 0 +5.000000 1 0 +6.000000 0 0 +6.000000 1 0 diff --git a/plugins/pycv/regtest/pycvfunc/rt-derivative/plumed.dat b/plugins/pycv/regtest/pycvfunc/rt-derivative/plumed.dat new file mode 100644 index 0000000000..2f134563ab --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-derivative/plumed.dat @@ -0,0 +1,18 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +d1: DISTANCE ATOMS=1,2 +d2: DISTANCE ATOMS=1,3 + + +fPY: ... +PYFUNCTION +IMPORT=unitTest +INIT=initForF +CALCULATE=function +ARG=d1,d2 +... + +mul: CUSTOM ARG=d1,d2 FUNC=x*y PERIODIC=NO + +PRINT FILE=colvar.out ARG=* +DUMPDERIVATIVES ARG=mul,fPY FILE=deriv \ No newline at end of file diff --git a/plugins/pycv/regtest/pycvfunc/rt-derivative/traj.xyz b/plugins/pycv/regtest/pycvfunc/rt-derivative/traj.xyz new file mode 100644 index 0000000000..7df2fa7133 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-derivative/traj.xyz @@ -0,0 +1,35 @@ +3 +100 100 100 +X 0 0 0 +X 99 0 0 +X 0 0 50 +3 +100 100 100 +X 0 0 0 +X 0 99 0 +X 0 0 50 +3 +100 100 100 +X 0 0 0 +X 0 0 99 +X 0 0 50 +3 +100 100 100 +X 0 0 0 +X 0 99 99 +X 25 0 0 +3 +100 100 100 +X 0 0 0 +X 99 0 99 +X 0 0 25 +3 +100 100 100 +X 0 0 0 +X 0 98 0 +X 12.5 0 0 +3 +100 100 100 +X 0 0 0 +X 99 99 99 +X 0 0 50 diff --git a/plugins/pycv/regtest/pycvfunc/rt-derivative/unitTest.py b/plugins/pycv/regtest/pycvfunc/rt-derivative/unitTest.py new file mode 100644 index 0000000000..a0c386bfe1 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-derivative/unitTest.py @@ -0,0 +1,14 @@ +import plumedCommunications as PLMD +import numpy + +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + +initForF={"Value": PLMD.defaults.COMPONENT} + +def function(action: PLMD.PythonFunction): + arg = [action.argument(0), + action.argument(1) + ] + return arg[0]*arg[1], [arg[1], arg[0]] diff --git a/plugins/pycv/regtest/pycvfunc/rt-doc/Makefile b/plugins/pycv/regtest/pycvfunc/rt-doc/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-doc/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvfunc/rt-doc/config b/plugins/pycv/regtest/pycvfunc/rt-doc/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-doc/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvfunc/rt-doc/config.reference b/plugins/pycv/regtest/pycvfunc/rt-doc/config.reference new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-doc/config.reference @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvfunc/rt-doc/plumed.dat b/plugins/pycv/regtest/pycvfunc/rt-doc/plumed.dat new file mode 100644 index 0000000000..04c3517f44 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-doc/plumed.dat @@ -0,0 +1,5 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvdist: PYFUNCTION IMPORT=pyhelp + +#PRINT FILE=colvar.out ARG=* diff --git a/plugins/pycv/regtest/pycvfunc/rt-doc/pyhelp.py b/plugins/pycv/regtest/pycvfunc/rt-doc/pyhelp.py new file mode 100644 index 0000000000..bc2754d199 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-doc/pyhelp.py @@ -0,0 +1,24 @@ +# This is only a partial example. This function does not compute the +# gradient, so it is useless for biasing. See the other regression +# tests for how to auto-grad. + +# And, of course, one should not call slow functions (such as print) +# in the CV calculation. + +import plumedCommunications +import pydoc + +def plumedInit(_): + with open('PythonCVInterface.help.txt', 'w') as f: + h = pydoc.Helper(output=f) + h(plumedCommunications.PythonCVInterface) + with open('plumedCommunications.help.txt', 'w') as f: + h = pydoc.Helper(output=f) + h(plumedCommunications) + with open('plumedCommunications.defaults.help.txt', 'w') as f: + h = pydoc.Helper(output=f) + h(plumedCommunications.defaults) + return {"Value":plumedCommunications.defaults.COMPONENT_NODEV, "ATOMS":"1"} + +def plumedCalculate(_): + return 0 diff --git a/plugins/pycv/regtest/pycvfunc/rt-doc/traj.xyz b/plugins/pycv/regtest/pycvfunc/rt-doc/traj.xyz new file mode 100644 index 0000000000..b3b4e45d61 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-doc/traj.xyz @@ -0,0 +1,24 @@ +4 +100 100 100 +X 5 5 5 +X 4 5 5 +X 5 4 5 +X 5 5 4 +4 +100 100 100 +X 5 5 5 +X 3 5 5 +X 5 3 5 +X 5 5 3 +4 +100 100 100 +X 5 5 5 +X 2 5 5 +X 5 2 5 +X 5 5 2 +4 +100 100 100 +X 5 5 5 +X 1 5 5 +X 5 1 5 +X 5 5 1 diff --git a/plugins/pycv/regtest/pycvfunc/rt-withPYCV/Makefile b/plugins/pycv/regtest/pycvfunc/rt-withPYCV/Makefile new file mode 100644 index 0000000000..3703b27cea --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-withPYCV/Makefile @@ -0,0 +1 @@ +include ../../scripts/test.make diff --git a/plugins/pycv/regtest/pycvfunc/rt-withPYCV/colvar.out.reference b/plugins/pycv/regtest/pycvfunc/rt-withPYCV/colvar.out.reference new file mode 100644 index 0000000000..e7a436fa00 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-withPYCV/colvar.out.reference @@ -0,0 +1,10 @@ +#! FIELDS time cvPY fPY +#! SET min_cvPY 0 +#! SET max_cvPY 1.3 + 0.000000 0.000000 0.000000 + 1.000000 1.000000 1.000000 + 2.000000 0.700000 0.490000 + 3.000000 0.400000 0.160000 + 4.000000 0.100000 0.010000 + 5.000000 1.100000 1.210000 + 6.000000 0.800000 0.640000 diff --git a/plugins/pycv/regtest/pycvfunc/rt-withPYCV/config b/plugins/pycv/regtest/pycvfunc/rt-withPYCV/config new file mode 100644 index 0000000000..010fed6f0a --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-withPYCV/config @@ -0,0 +1,3 @@ + +type=driver +arg="--plumed plumed.dat --ixyz traj.xyz" diff --git a/plugins/pycv/regtest/pycvfunc/rt-withPYCV/plumed.dat b/plugins/pycv/regtest/pycvfunc/rt-withPYCV/plumed.dat new file mode 100644 index 0000000000..9fd1278102 --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-withPYCV/plumed.dat @@ -0,0 +1,22 @@ +LOAD GLOBAL FILE=../../../../PythonCVInterface.so + +cvPY: ... +PYCVINTERFACE +ATOMS=1,2 +IMPORT=unitTest +INIT=init +CALCULATE=mypytest +... + +fPY: ... +PYFUNCTION +IMPORT=unitTest +INIT=initForF +CALCULATE=function +ARG=cvPY +... + +PRINT FILE=colvar.out ARG=* + + + diff --git a/plugins/pycv/regtest/pycvfunc/rt-withPYCV/traj.xyz b/plugins/pycv/regtest/pycvfunc/rt-withPYCV/traj.xyz new file mode 100644 index 0000000000..1ed965943b --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-withPYCV/traj.xyz @@ -0,0 +1,28 @@ +2 +100 100 100 +X 0 0 0 +X 99 0 0 +2 +100 100 100 +X 0 0 0 +X 0 99 0 +2 +100 100 100 +X 0 0 0 +X 0 0 99 +2 +100 100 100 +X 0 0 0 +X 0 99 99 +2 +100 100 100 +X 0 0 0 +X 99 0 99 +2 +100 100 100 +X 0 0 0 +X 99 99 0 +2 +100 100 100 +X 0 0 0 +X 99 99 99 diff --git a/plugins/pycv/regtest/pycvfunc/rt-withPYCV/unitTest.py b/plugins/pycv/regtest/pycvfunc/rt-withPYCV/unitTest.py new file mode 100644 index 0000000000..39d3de9c3a --- /dev/null +++ b/plugins/pycv/regtest/pycvfunc/rt-withPYCV/unitTest.py @@ -0,0 +1,22 @@ +import plumedCommunications as PLMD +import numpy + +log = open("pydist.log", "w") + +print("Imported my pydist+.", file=log) + + +def init(action: PLMD.PythonCVInterface): + return {"COMPONENTS": {"val":{"period": ["0", 1.3]}}} + +initForF={"Value": PLMD.defaults.COMPONENT_NODEV} + + +def mypytest(action: PLMD.PythonCVInterface): + ret = [action.getStep()] + + return ret + +def function(action: PLMD.PythonFunction): + arg = [action.argument(0)] + return arg[0]*arg[0] \ No newline at end of file diff --git a/plugins/pycv/requirements.txt b/plugins/pycv/requirements.txt new file mode 100644 index 0000000000..52671c7685 --- /dev/null +++ b/plugins/pycv/requirements.txt @@ -0,0 +1,2 @@ +pybind11 +numpy diff --git a/plugins/pycv/standaloneCompile.sh b/plugins/pycv/standaloneCompile.sh new file mode 100755 index 0000000000..b4266f1722 --- /dev/null +++ b/plugins/pycv/standaloneCompile.sh @@ -0,0 +1,23 @@ +# Note that in conda libpython3xx is not found in the path returned by ldflags. IMHO it is a bug. +# The workaround is to -L appropriately. Will be fixed here. + +conda_fixup=${CONDA_PREFIX+-L$CONDA_PREFIX/lib} +if [ ! -z "$conda_fixup" ]; then + echo "CONDA_PREFIX is set. Assuming conda and enabling a workaround for missing -L in python3-config --ldflags --embed" +fi + +if ! python3-config --embed >/dev/null 2>/dev/null; then + #TODO: verify that this does not give problems with conda + echo "PyCV needs python to be built to be embedable" + echo "(compiling python with --enable-shared should be enough)" + exit 1 +fi + +export PLUMED_MKLIB_CFLAGS="$(python3-config --cflags --embed) $(python -m pybind11 --includes)" + +export PLUMED_MKLIB_LDFLAGS="$(python3-config --ldflags --embed) $conda_fixup" + +echo PLUMED_MKLIB_CFLAGS=$PLUMED_MKLIB_CFLAGS +echo PLUMED_MKLIB_LDFLAGS=$PLUMED_MKLIB_LDFLAGS + +plumed mklib PythonCVInterface.cpp ActionWithPython.cpp PlumedPythonEmbeddedModule.cpp