diff --git a/doc/src/PDF/colvars-refman-lammps.pdf b/doc/src/PDF/colvars-refman-lammps.pdf index 4f85537b35..b8f049ce01 100644 Binary files a/doc/src/PDF/colvars-refman-lammps.pdf and b/doc/src/PDF/colvars-refman-lammps.pdf differ diff --git a/lib/colvars/Makefile.common b/lib/colvars/Makefile.common index 9e7be12958..f0282b8caf 100644 --- a/lib/colvars/Makefile.common +++ b/lib/colvars/Makefile.common @@ -25,6 +25,7 @@ COLVARS_INCFLAGS = -DCOLVARS_LAMMPS $(COLVARS_DEBUG_INCFLAGS) $(COLVARS_PYTHON_I COLVARS_SRCS = \ colvaratoms.cpp \ colvarbias_abf.cpp \ + colvarbias_abmd.cpp \ colvarbias_alb.cpp \ colvarbias.cpp \ colvarbias_histogram.cpp \ @@ -59,6 +60,7 @@ COLVARS_SRCS = \ colvarscript_commands.cpp \ colvarscript_commands_bias.cpp \ colvarscript_commands_colvar.cpp \ + colvars_memstream.cpp \ colvartypes.cpp \ colvarvalue.cpp \ colvar_neuralnetworkcompute.cpp diff --git a/lib/colvars/Makefile.deps b/lib/colvars/Makefile.deps index 6bf5171710..61f0b1a335 100644 --- a/lib/colvars/Makefile.deps +++ b/lib/colvars/Makefile.deps @@ -1,201 +1,230 @@ -$(COLVARS_OBJ_DIR)colvaratoms.o: colvaratoms.cpp colvarmodule.h \ - colvars_version.h colvarproxy.h colvartypes.h colvarvalue.h \ +$(COLVARS_OBJ_DIR)colvaratoms.o: colvaratoms.cpp colvardeps.h \ + colvarmodule.h colvars_version.h colvarparse.h colvarvalue.h \ + colvartypes.h ../../src/math_eigen_impl.h colvarparams.h colvarproxy.h \ colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvarparse.h colvarparams.h colvaratoms.h \ - colvardeps.h + colvarproxy_volmaps.h colvaratoms.h colvar_rotation_derivative.h $(COLVARS_OBJ_DIR)colvarbias_abf.o: colvarbias_abf.cpp colvarmodule.h \ - colvars_version.h colvar.h colvarvalue.h colvartypes.h colvarparse.h \ - colvarparams.h colvardeps.h colvarbias_abf.h colvarproxy.h \ - colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvarbias.h colvargrid.h colvar_UIestimator.h + colvars_version.h colvar.h colvarvalue.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarparse.h colvarparams.h colvardeps.h \ + colvarbias_abf.h colvarproxy.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h colvarbias.h colvargrid.h \ + colvar_UIestimator.h colvars_memstream.h +$(COLVARS_OBJ_DIR)colvarbias_abmd.o: colvarbias_abmd.cpp \ + colvarbias_abmd.h colvarbias_restraint.h colvarbias.h colvar.h \ + colvarmodule.h colvars_version.h colvarvalue.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarparse.h colvarparams.h colvardeps.h \ + colvarproxy.h colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ + colvarproxy_volmaps.h $(COLVARS_OBJ_DIR)colvarbias_alb.o: colvarbias_alb.cpp colvarmodule.h \ - colvars_version.h colvarbias.h colvar.h colvarvalue.h colvartypes.h \ - colvarparse.h colvarparams.h colvardeps.h colvarbias_alb.h + colvars_version.h colvarproxy.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h colvarbias.h colvar.h \ + colvarvalue.h colvarparse.h colvarparams.h colvardeps.h colvarbias_alb.h $(COLVARS_OBJ_DIR)colvarbias.o: colvarbias.cpp colvarmodule.h \ - colvars_version.h colvarproxy.h colvartypes.h colvarvalue.h \ - colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvarbias.h colvar.h colvarparse.h colvarparams.h \ - colvardeps.h colvargrid.h + colvars_version.h colvarproxy.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h colvarvalue.h colvarbias.h \ + colvar.h colvarparse.h colvarparams.h colvardeps.h colvargrid.h \ + colvars_memstream.h $(COLVARS_OBJ_DIR)colvarbias_histogram.o: colvarbias_histogram.cpp \ colvarmodule.h colvars_version.h colvarproxy.h colvartypes.h \ - colvarvalue.h colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvar.h colvarparse.h colvarparams.h colvardeps.h \ - colvarbias_histogram.h colvarbias.h colvargrid.h + ../../src/math_eigen_impl.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h colvar.h colvarvalue.h \ + colvarparse.h colvarparams.h colvardeps.h colvarbias_histogram.h \ + colvarbias.h colvargrid.h colvars_memstream.h $(COLVARS_OBJ_DIR)colvarbias_histogram_reweight_amd.o: \ colvarbias_histogram_reweight_amd.cpp \ colvarbias_histogram_reweight_amd.h colvarbias_histogram.h colvarbias.h \ colvar.h colvarmodule.h colvars_version.h colvarvalue.h colvartypes.h \ - colvarparse.h colvarparams.h colvardeps.h colvargrid.h colvarproxy.h \ - colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h + ../../src/math_eigen_impl.h colvarparse.h colvarparams.h colvardeps.h \ + colvargrid.h colvarproxy.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h colvars_memstream.h $(COLVARS_OBJ_DIR)colvarbias_meta.o: colvarbias_meta.cpp colvarmodule.h \ - colvars_version.h colvarproxy.h colvartypes.h colvarvalue.h \ - colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvar.h colvarparse.h colvarparams.h colvardeps.h \ - colvarbias_meta.h colvarbias.h colvargrid.h + colvars_version.h colvarproxy.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h colvar.h colvarvalue.h \ + colvarparse.h colvarparams.h colvardeps.h colvarbias_meta.h colvarbias.h \ + colvargrid.h colvars_memstream.h $(COLVARS_OBJ_DIR)colvarbias_restraint.o: colvarbias_restraint.cpp \ colvarmodule.h colvars_version.h colvarproxy.h colvartypes.h \ - colvarvalue.h colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvarbias_restraint.h colvarbias.h colvar.h \ - colvarparse.h colvarparams.h colvardeps.h + ../../src/math_eigen_impl.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h colvarvalue.h \ + colvarbias_restraint.h colvarbias.h colvar.h colvarparse.h \ + colvarparams.h colvardeps.h $(COLVARS_OBJ_DIR)colvarcomp_alchlambda.o: colvarcomp_alchlambda.cpp \ colvarmodule.h colvars_version.h colvarvalue.h colvartypes.h \ - colvarparse.h colvarparams.h colvar.h colvardeps.h colvarcomp.h \ - colvaratoms.h colvarproxy.h colvarproxy_io.h colvarproxy_system.h \ - colvarproxy_tcl.h colvarproxy_volmaps.h colvar_arithmeticpath.h \ + ../../src/math_eigen_impl.h colvar.h colvarparse.h colvarparams.h \ + colvardeps.h colvarcomp.h colvaratoms.h colvarproxy.h colvarproxy_io.h \ + colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ colvar_geometricpath.h $(COLVARS_OBJ_DIR)colvarcomp_angles.o: colvarcomp_angles.cpp \ colvarmodule.h colvars_version.h colvar.h colvarvalue.h colvartypes.h \ - colvarparse.h colvarparams.h colvardeps.h colvarcomp.h colvaratoms.h \ - colvarproxy.h colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvar_arithmeticpath.h colvar_geometricpath.h -$(COLVARS_OBJ_DIR)colvarcomp_apath.o: colvarcomp_apath.cpp colvarmodule.h \ - colvars_version.h colvarvalue.h colvartypes.h colvarparse.h \ - colvarparams.h colvar.h colvardeps.h colvarcomp.h colvaratoms.h \ - colvarproxy.h colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvar_arithmeticpath.h colvar_geometricpath.h -$(COLVARS_OBJ_DIR)colvarcomp_coordnums.o: colvarcomp_coordnums.cpp \ - colvarmodule.h colvars_version.h colvarparse.h colvarvalue.h \ - colvartypes.h colvarparams.h colvaratoms.h colvarproxy.h \ - colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvardeps.h colvar.h colvarcomp.h \ - colvar_arithmeticpath.h colvar_geometricpath.h -$(COLVARS_OBJ_DIR)colvarcomp.o: colvarcomp.cpp colvarmodule.h \ - colvars_version.h colvarvalue.h colvartypes.h colvar.h colvarparse.h \ - colvarparams.h colvardeps.h colvarcomp.h colvaratoms.h colvarproxy.h \ - colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvar_arithmeticpath.h colvar_geometricpath.h -$(COLVARS_OBJ_DIR)colvarcomp_distances.o: colvarcomp_distances.cpp \ - colvarmodule.h colvars_version.h colvarvalue.h colvartypes.h \ - colvarparse.h colvarparams.h colvar.h colvardeps.h colvarcomp.h \ - colvaratoms.h colvarproxy.h colvarproxy_io.h colvarproxy_system.h \ - colvarproxy_tcl.h colvarproxy_volmaps.h colvar_arithmeticpath.h \ + ../../src/math_eigen_impl.h colvarparse.h colvarparams.h colvardeps.h \ + colvarcomp.h colvaratoms.h colvarproxy.h colvarproxy_io.h \ + colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ colvar_geometricpath.h -$(COLVARS_OBJ_DIR)colvarcomp_gpath.o: colvarcomp_gpath.cpp colvarmodule.h \ - colvars_version.h colvarvalue.h colvartypes.h colvarparse.h \ - colvarparams.h colvar.h colvardeps.h colvarcomp.h colvaratoms.h \ - colvarproxy.h colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvar_arithmeticpath.h colvar_geometricpath.h -$(COLVARS_OBJ_DIR)colvarcomp_neuralnetwork.o: \ - colvarcomp_neuralnetwork.cpp colvarmodule.h colvars_version.h \ - colvarvalue.h colvartypes.h colvarparse.h colvarparams.h colvar.h \ +$(COLVARS_OBJ_DIR)colvarcomp_apath.o: colvarcomp_apath.cpp colvarvalue.h \ + colvarmodule.h colvars_version.h colvartypes.h \ + ../../src/math_eigen_impl.h colvar.h colvarparse.h colvarparams.h \ colvardeps.h colvarcomp.h colvaratoms.h colvarproxy.h colvarproxy_io.h \ colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ - colvar_arithmeticpath.h colvar_geometricpath.h \ + colvar_geometricpath.h colvar_arithmeticpath.h +$(COLVARS_OBJ_DIR)colvarcomp_coordnums.o: colvarcomp_coordnums.cpp \ + colvarmodule.h colvars_version.h colvaratoms.h colvarproxy.h \ + colvartypes.h ../../src/math_eigen_impl.h colvarproxy_io.h \ + colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ + colvarparse.h colvarvalue.h colvarparams.h colvardeps.h colvar.h \ + colvarcomp.h colvar_geometricpath.h +$(COLVARS_OBJ_DIR)colvarcomp.o: colvarcomp.cpp colvarmodule.h \ + colvars_version.h colvarvalue.h colvartypes.h \ + ../../src/math_eigen_impl.h colvar.h colvarparse.h colvarparams.h \ + colvardeps.h colvarcomp.h colvaratoms.h colvarproxy.h colvarproxy_io.h \ + colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ + colvar_geometricpath.h +$(COLVARS_OBJ_DIR)colvarcomp_distances.o: colvarcomp_distances.cpp \ + colvarmodule.h colvars_version.h colvarvalue.h colvartypes.h \ + ../../src/math_eigen_impl.h colvar.h colvarparse.h colvarparams.h \ + colvardeps.h colvarcomp.h colvaratoms.h colvarproxy.h colvarproxy_io.h \ + colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ + colvar_geometricpath.h colvar_rotation_derivative.h +$(COLVARS_OBJ_DIR)colvarcomp_gpath.o: colvarcomp_gpath.cpp colvarmodule.h \ + colvars_version.h colvarvalue.h colvartypes.h \ + ../../src/math_eigen_impl.h colvar.h colvarparse.h colvarparams.h \ + colvardeps.h colvarcomp.h colvaratoms.h colvarproxy.h colvarproxy_io.h \ + colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ + colvar_geometricpath.h +$(COLVARS_OBJ_DIR)colvarcomp_neuralnetwork.o: \ + colvarcomp_neuralnetwork.cpp colvarmodule.h colvars_version.h \ + colvarvalue.h colvartypes.h ../../src/math_eigen_impl.h colvar.h \ + colvarparse.h colvarparams.h colvardeps.h colvarcomp.h colvaratoms.h \ + colvarproxy.h colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ + colvarproxy_volmaps.h colvar_geometricpath.h \ colvar_neuralnetworkcompute.h $(COLVARS_OBJ_DIR)colvarcomp_combination.o: colvarcomp_combination.cpp \ - colvarcomp.h colvarmodule.h colvars_version.h colvar.h colvarvalue.h \ - colvartypes.h colvarparse.h colvarparams.h colvardeps.h colvaratoms.h \ - colvarproxy.h colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvar_arithmeticpath.h colvar_geometricpath.h + colvarcomp.h colvarmodule.h colvars_version.h colvaratoms.h \ + colvarproxy.h colvartypes.h ../../src/math_eigen_impl.h colvarproxy_io.h \ + colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ + colvarparse.h colvarvalue.h colvarparams.h colvardeps.h colvar.h \ + colvar_geometricpath.h $(COLVARS_OBJ_DIR)colvarcomp_protein.o: colvarcomp_protein.cpp \ colvarmodule.h colvars_version.h colvarvalue.h colvartypes.h \ - colvarparse.h colvarparams.h colvar.h colvardeps.h colvarcomp.h \ - colvaratoms.h colvarproxy.h colvarproxy_io.h colvarproxy_system.h \ - colvarproxy_tcl.h colvarproxy_volmaps.h colvar_arithmeticpath.h \ + ../../src/math_eigen_impl.h colvar.h colvarparse.h colvarparams.h \ + colvardeps.h colvarcomp.h colvaratoms.h colvarproxy.h colvarproxy_io.h \ + colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ colvar_geometricpath.h $(COLVARS_OBJ_DIR)colvarcomp_rotations.o: colvarcomp_rotations.cpp \ colvarmodule.h colvars_version.h colvarvalue.h colvartypes.h \ - colvarparse.h colvarparams.h colvar.h colvardeps.h colvarcomp.h \ - colvaratoms.h colvarproxy.h colvarproxy_io.h colvarproxy_system.h \ - colvarproxy_tcl.h colvarproxy_volmaps.h colvar_arithmeticpath.h \ - colvar_geometricpath.h -$(COLVARS_OBJ_DIR)colvarcomp_volmaps.o: colvarcomp_volmaps.cpp \ - colvarmodule.h colvars_version.h colvarvalue.h colvartypes.h \ - colvarparse.h colvarparams.h colvar.h colvardeps.h colvarcomp.h \ - colvaratoms.h colvarproxy.h colvarproxy_io.h colvarproxy_system.h \ - colvarproxy_tcl.h colvarproxy_volmaps.h colvar_arithmeticpath.h \ - colvar_geometricpath.h -$(COLVARS_OBJ_DIR)colvar.o: colvar.cpp colvarmodule.h colvars_version.h \ - colvarvalue.h colvartypes.h colvarparse.h colvarparams.h colvar.h \ + ../../src/math_eigen_impl.h colvar.h colvarparse.h colvarparams.h \ colvardeps.h colvarcomp.h colvaratoms.h colvarproxy.h colvarproxy_io.h \ colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ - colvar_arithmeticpath.h colvar_geometricpath.h colvarscript.h \ - colvarbias.h colvarscript_commands.h colvarscript_commands_colvar.h \ - colvarscript_commands_bias.h + colvar_geometricpath.h colvar_rotation_derivative.h +$(COLVARS_OBJ_DIR)colvarcomp_volmaps.o: colvarcomp_volmaps.cpp \ + colvarmodule.h colvars_version.h colvarvalue.h colvartypes.h \ + ../../src/math_eigen_impl.h colvar.h colvarparse.h colvarparams.h \ + colvardeps.h colvarcomp.h colvaratoms.h colvarproxy.h colvarproxy_io.h \ + colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ + colvar_geometricpath.h +$(COLVARS_OBJ_DIR)colvar.o: colvar.cpp colvarmodule.h colvars_version.h \ + colvarvalue.h colvartypes.h ../../src/math_eigen_impl.h colvarparse.h \ + colvarparams.h colvarcomp.h colvaratoms.h colvarproxy.h colvarproxy_io.h \ + colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ + colvardeps.h colvar.h colvar_geometricpath.h colvarbias.h \ + colvars_memstream.h $(COLVARS_OBJ_DIR)colvardeps.o: colvardeps.cpp colvarmodule.h \ - colvars_version.h colvarproxy.h colvartypes.h colvarvalue.h \ - colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvardeps.h colvarparse.h colvarparams.h + colvars_version.h colvarproxy.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h colvardeps.h colvarparse.h \ + colvarvalue.h colvarparams.h $(COLVARS_OBJ_DIR)colvargrid.o: colvargrid.cpp colvarmodule.h \ - colvars_version.h colvarvalue.h colvartypes.h colvarparse.h \ - colvarparams.h colvar.h colvardeps.h colvarcomp.h colvaratoms.h \ - colvarproxy.h colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvar_arithmeticpath.h colvar_geometricpath.h \ - colvargrid.h colvargrid_def.h + colvars_version.h colvarvalue.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarparse.h colvarparams.h colvar.h \ + colvardeps.h colvargrid.h colvargrid_def.h colvarproxy.h \ + colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ + colvarproxy_volmaps.h colvars_memstream.h $(COLVARS_OBJ_DIR)colvarmodule.o: colvarmodule.cpp colvarmodule.h \ colvars_version.h colvarparse.h colvarvalue.h colvartypes.h \ - colvarparams.h colvarproxy.h colvarproxy_io.h colvarproxy_system.h \ - colvarproxy_tcl.h colvarproxy_volmaps.h colvar.h colvardeps.h \ - colvarbias.h colvarbias_abf.h colvargrid.h colvar_UIestimator.h \ - colvarbias_alb.h colvarbias_histogram.h \ - colvarbias_histogram_reweight_amd.h colvarbias_meta.h \ - colvarbias_restraint.h colvarscript.h colvarscript_commands.h \ - colvarscript_commands_colvar.h colvarscript_commands_bias.h \ - colvaratoms.h colvarcomp.h colvar_arithmeticpath.h \ - colvar_geometricpath.h colvarmodule_refs.h -$(COLVARS_OBJ_DIR)colvarparams.o: colvarparams.cpp colvarmodule.h \ - colvars_version.h colvarvalue.h colvartypes.h colvarparams.h -$(COLVARS_OBJ_DIR)colvarparse.o: colvarparse.cpp colvarmodule.h \ - colvars_version.h colvarvalue.h colvartypes.h colvarparse.h \ - colvarparams.h -$(COLVARS_OBJ_DIR)colvarproxy.o: colvarproxy.cpp colvarmodule.h \ - colvars_version.h colvarproxy.h colvartypes.h colvarvalue.h \ + ../../src/math_eigen_impl.h colvarparams.h colvarproxy.h \ colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvarscript.h colvarbias.h colvar.h colvarparse.h \ - colvarparams.h colvardeps.h colvarscript_commands.h \ - colvarscript_commands_colvar.h colvarscript_commands_bias.h \ - colvaratoms.h colvarmodule_utils.h + colvarproxy_volmaps.h colvar.h colvardeps.h colvarbias.h \ + colvarbias_abf.h colvargrid.h colvar_UIestimator.h colvarbias_abmd.h \ + colvarbias_restraint.h colvarbias_alb.h colvarbias_histogram.h \ + colvarbias_histogram_reweight_amd.h colvarbias_meta.h colvarscript.h \ + colvarscript_commands.h colvarscript_commands_colvar.h \ + colvarscript_commands_bias.h colvaratoms.h colvarcomp.h \ + colvar_geometricpath.h colvars_memstream.h colvarmodule_refs.h +$(COLVARS_OBJ_DIR)colvarparams.o: colvarparams.cpp colvarmodule.h \ + colvars_version.h colvarvalue.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarparams.h +$(COLVARS_OBJ_DIR)colvarparse.o: colvarparse.cpp colvarmodule.h \ + colvars_version.h colvarvalue.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarparse.h colvarparams.h \ + colvars_memstream.h +$(COLVARS_OBJ_DIR)colvarproxy.o: colvarproxy.cpp colvarmodule.h \ + colvars_version.h colvarproxy.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h colvar.h colvarvalue.h \ + colvarparse.h colvarparams.h colvardeps.h colvarbias.h colvarscript.h \ + colvarscript_commands.h colvarscript_commands_colvar.h \ + colvarscript_commands_bias.h colvarmodule_utils.h $(COLVARS_OBJ_DIR)colvarproxy_io.o: colvarproxy_io.cpp colvarmodule.h \ colvars_version.h colvarproxy_io.h $(COLVARS_OBJ_DIR)colvarproxy_replicas.o: colvarproxy_replicas.cpp \ colvarmodule.h colvars_version.h colvarproxy.h colvartypes.h \ - colvarvalue.h colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h + ../../src/math_eigen_impl.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h $(COLVARS_OBJ_DIR)colvarproxy_system.o: colvarproxy_system.cpp \ - colvarmodule.h colvars_version.h colvartypes.h colvarproxy_system.h + colvarmodule.h colvars_version.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarproxy_system.h $(COLVARS_OBJ_DIR)colvarproxy_tcl.o: colvarproxy_tcl.cpp colvarmodule.h \ - colvars_version.h colvarproxy.h colvartypes.h colvarvalue.h \ - colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvaratoms.h colvarparse.h colvarparams.h \ - colvardeps.h + colvars_version.h colvarproxy.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h colvaratoms.h colvarparse.h \ + colvarvalue.h colvarparams.h colvardeps.h $(COLVARS_OBJ_DIR)colvarproxy_volmaps.o: colvarproxy_volmaps.cpp \ colvarmodule.h colvars_version.h colvarproxy_volmaps.h \ colvarmodule_utils.h $(COLVARS_OBJ_DIR)colvarscript.o: colvarscript.cpp colvarproxy.h \ - colvarmodule.h colvars_version.h colvartypes.h colvarvalue.h \ - colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvardeps.h colvarparse.h colvarparams.h \ - colvarscript.h colvarbias.h colvar.h colvarscript_commands.h \ + colvarmodule.h colvars_version.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h colvardeps.h colvarparse.h \ + colvarvalue.h colvarparams.h colvarscript.h colvarscript_commands.h \ colvarscript_commands_colvar.h colvarscript_commands_bias.h $(COLVARS_OBJ_DIR)colvarscript_commands.o: colvarscript_commands.cpp \ - colvarproxy.h colvarmodule.h colvars_version.h colvartypes.h \ - colvarvalue.h colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ - colvarproxy_volmaps.h colvardeps.h colvarparse.h colvarparams.h \ - colvarscript.h colvarbias.h colvar.h colvarscript_commands.h \ - colvarscript_commands_colvar.h colvarscript_commands_bias.h + colvar.h colvarmodule.h colvars_version.h colvarvalue.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarparse.h colvarparams.h colvardeps.h \ + colvarbias.h colvarproxy.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h colvarscript.h \ + colvarscript_commands.h colvarscript_commands_colvar.h \ + colvarscript_commands_bias.h $(COLVARS_OBJ_DIR)colvarscript_commands_bias.o: \ colvarscript_commands_bias.cpp colvarproxy.h colvarmodule.h \ - colvars_version.h colvartypes.h colvarvalue.h colvarproxy_io.h \ - colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ - colvardeps.h colvarparse.h colvarparams.h colvarscript.h colvarbias.h \ - colvar.h colvarscript_commands.h colvarscript_commands_colvar.h \ - colvarscript_commands_bias.h + colvars_version.h colvartypes.h ../../src/math_eigen_impl.h \ + colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ + colvarproxy_volmaps.h colvarbias.h colvar.h colvarvalue.h colvarparse.h \ + colvarparams.h colvardeps.h colvarscript.h colvarscript_commands.h \ + colvarscript_commands_colvar.h colvarscript_commands_bias.h $(COLVARS_OBJ_DIR)colvarscript_commands_colvar.o: \ - colvarscript_commands_colvar.cpp colvarproxy.h colvarmodule.h \ - colvars_version.h colvartypes.h colvarvalue.h colvarproxy_io.h \ - colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h \ - colvardeps.h colvarparse.h colvarparams.h colvarscript.h colvarbias.h \ - colvar.h colvarscript_commands.h colvarscript_commands_colvar.h \ - colvarscript_commands_bias.h + colvarscript_commands_colvar.cpp colvar.h colvarmodule.h \ + colvars_version.h colvarvalue.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarparse.h colvarparams.h colvardeps.h \ + colvarproxy.h colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ + colvarproxy_volmaps.h colvarscript.h colvarscript_commands.h \ + colvarscript_commands_colvar.h colvarscript_commands_bias.h +$(COLVARS_OBJ_DIR)colvars_memstream.o: colvars_memstream.cpp \ + colvarmodule.h colvars_version.h colvartypes.h \ + ../../src/math_eigen_impl.h colvarvalue.h colvars_memstream.h $(COLVARS_OBJ_DIR)colvartypes.o: colvartypes.cpp colvarmodule.h \ - colvars_version.h colvartypes.h colvarparse.h colvarvalue.h \ - colvarparams.h ../../src/math_eigen_impl.h + colvars_version.h colvartypes.h ../../src/math_eigen_impl.h \ + colvaratoms.h colvarproxy.h colvarproxy_io.h colvarproxy_system.h \ + colvarproxy_tcl.h colvarproxy_volmaps.h colvarparse.h colvarvalue.h \ + colvarparams.h colvardeps.h colvar_rotation_derivative.h $(COLVARS_OBJ_DIR)colvarvalue.o: colvarvalue.cpp colvarmodule.h \ - colvars_version.h colvarvalue.h colvartypes.h + colvars_version.h colvarvalue.h colvartypes.h \ + ../../src/math_eigen_impl.h colvars_memstream.h $(COLVARS_OBJ_DIR)colvar_neuralnetworkcompute.o: \ colvar_neuralnetworkcompute.cpp colvar_neuralnetworkcompute.h \ colvarparse.h colvarmodule.h colvars_version.h colvarvalue.h \ - colvartypes.h colvarparams.h colvarproxy.h colvarproxy_io.h \ - colvarproxy_system.h colvarproxy_tcl.h colvarproxy_volmaps.h + colvartypes.h ../../src/math_eigen_impl.h colvarparams.h colvarproxy.h \ + colvarproxy_io.h colvarproxy_system.h colvarproxy_tcl.h \ + colvarproxy_volmaps.h diff --git a/lib/colvars/colvar.cpp b/lib/colvars/colvar.cpp index 0cb5c1ebdb..58eb87fd0e 100644 --- a/lib/colvars/colvar.cpp +++ b/lib/colvars/colvar.cpp @@ -16,13 +16,18 @@ #include "colvarmodule.h" #include "colvarvalue.h" #include "colvarparse.h" -#include "colvar.h" #include "colvarcomp.h" -#include "colvarscript.h" +#include "colvar.h" +#include "colvarbias.h" +#include "colvars_memstream.h" + + +std::map> colvar::global_cvc_map = + std::map>(); + +std::map colvar::global_cvc_desc_map = + std::map(); -#if (__cplusplus >= 201103L) -std::map> colvar::global_cvc_map = std::map>(); -#endif colvar::colvar() { @@ -36,6 +41,8 @@ colvar::colvar() dev_null = 0.0; #endif + matching_state = false; + expand_boundaries = false; description = "uninitialized colvar"; @@ -131,7 +138,14 @@ int colvar::init(std::string const &conf) // Sort array of cvcs based on their names // Note: default CVC names are in input order for same type of CVC - std::sort(cvcs.begin(), cvcs.end(), colvar::compare_cvc); + std::sort(cvcs.begin(), cvcs.end(), + [](std::shared_ptr const &cvc1, + std::shared_ptr const &cvc2) -> bool { + if (cvc1 && cvc2) { + return cvc1->name < cvc2->name; + } + return false; + }); if(cvcs.size() > 1) { cvm::log("Sorted list of components for this scripted colvar:\n"); @@ -186,9 +200,9 @@ int colvar::init(std::string const &conf) if ((cvcs[i])->sup_np < 0) { cvm::log("Warning: you chose a negative exponent in the combination; " - "if you apply forces, the simulation may become unstable " - "when the component \""+ - (cvcs[i])->function_type+"\" approaches zero.\n"); + "if you apply forces, the simulation may become unstable " + "when the component \""+ + (cvcs[i])->function_type()+"\" approaches zero.\n"); } } } @@ -295,7 +309,7 @@ int colvar::init(std::string const &conf) error_code |= init_grid_parameters(conf); // Detect if we have a single component that is an alchemical lambda - if (is_enabled(f_cv_single_cvc) && cvcs[0]->function_type == "alchLambda") { + if (is_enabled(f_cv_single_cvc) && cvcs[0]->function_type() == "alchLambda") { enable(f_cv_external); } @@ -468,13 +482,6 @@ int colvar::init_custom_function(std::string const &conf) size_t pos = 0; if (key_lookup(conf, "customFunction", &expr, &pos)) { std::string msg("Error: customFunction requires the Lepton library."); -#if (__cplusplus < 201103L) - // NOTE: this is not ideal; testing for the Lepton library's version would - // be more accurate, but also less portable - msg += - std::string(" Note also that recent versions of Lepton require C++11: " - "please see https://colvars.github.io/README-c++11.html."); -#endif return cvm::error(msg, COLVARS_NOT_IMPLEMENTED); } @@ -697,9 +704,14 @@ int colvar::init_extended_Lagrangian(std::string const &conf) } if (ext_gamma != 0.0) { enable(f_cv_Langevin); + cvm::main()->cite_feature("BAOA integrator"); ext_gamma *= 1.0e-3; // correct as long as input is required in ps-1 and cvm::dt() is in fs // Adjust Langevin sigma for slow time step if time_step_factor != 1 - ext_sigma = cvm::sqrt(2.0 * proxy->boltzmann() * temp * ext_gamma * ext_mass / (cvm::dt() * cvm::real(time_step_factor))); + // Eq. (6a) in https://doi.org/10.1021/acs.jctc.2c00585 + ext_sigma = cvm::sqrt((1.0 - cvm::exp(-2.0 * ext_gamma * cvm::dt() * cvm::real(time_step_factor))) + * ext_mass * proxy->boltzmann() * temp); + } else { + ext_sigma = 0.0; } get_keyval_feature(this, conf, "reflectingLowerBoundary", f_cv_reflecting_lower_boundary, false); @@ -744,75 +756,56 @@ int colvar::init_output_flags(std::string const &conf) return COLVARS_OK; } -#if (__cplusplus >= 201103L) -// C++11 -template int colvar::init_components_type(std::string const &, - char const * /* def_desc */, - char const *def_config_key) { - // global_cvc_map is only supported in the C++11 case - global_cvc_map[def_config_key] = [](const std::string& cvc_conf){return new def_class_name(cvc_conf);}; - // TODO: maybe it is better to do more check to avoid duplication in the map? - return COLVARS_OK; + +template +void colvar::add_component_type(char const *def_description, char const *def_config_key) +{ + if (global_cvc_map.count(def_config_key) == 0) { + global_cvc_map[def_config_key] = []() { + return new def_class_name(); + }; + global_cvc_desc_map[def_config_key] = std::string(def_description); + } } -int colvar::init_components_type_from_global_map(const std::string& conf, - const char* def_config_key) { -#else -template int colvar::init_components_type(std::string const & conf, - char const * /* def_desc */, - char const *def_config_key) { -#endif + +int colvar::init_components_type(const std::string& conf, const char* def_config_key) { size_t def_count = 0; std::string def_conf = ""; size_t pos = 0; + int error_code = COLVARS_OK; while ( this->key_lookup(conf, def_config_key, &def_conf, &pos) ) { - if (!def_conf.size()) continue; + cvm::log("Initializing " "a new \""+std::string(def_config_key)+"\" component"+ (cvm::debug() ? ", with configuration:\n"+def_conf : ".\n")); + cvc *cvcp = global_cvc_map[def_config_key](); + if (!cvcp) { + return cvm::error("Error: in creating object of type \"" + std::string(def_config_key) + + "\".\n", + COLVARS_MEMORY_ERROR); + } + cvcs.push_back(std::shared_ptr(cvcp)); + cvm::increase_depth(); - // only the following line is different from init_components_type - // in the non-C++11 case -#if (__cplusplus >= 201103L) - cvc *cvcp = global_cvc_map.at(def_config_key)(def_conf); -#else - cvc *cvcp = new def_class_name(def_conf); -#endif - if (cvcp != NULL) { - cvcs.push_back(cvcp); - cvcp->check_keywords(def_conf, def_config_key); - cvcp->set_function_type(def_config_key); - if (cvm::get_error()) { - cvm::error("Error: in setting up component \""+ - std::string(def_config_key)+"\".\n", COLVARS_INPUT_ERROR); - return COLVARS_INPUT_ERROR; - } - cvm::decrease_depth(); - } else { - cvm::decrease_depth(); - cvm::error("Error: in allocating component \""+ - std::string(def_config_key)+"\".\n", - COLVARS_MEMORY_ERROR); - return COLVARS_MEMORY_ERROR; - } - - if ( (cvcp->period != 0.0) || (cvcp->wrap_center != 0.0) ) { - if (! cvcp->is_enabled(f_cvc_periodic)) { - cvm::error("Error: invalid use of period and/or " - "wrapAround in a \""+ - std::string(def_config_key)+ - "\" component.\n"+ - "Period: "+cvm::to_str(cvcp->period) + - " wrapAround: "+cvm::to_str(cvcp->wrap_center), - COLVARS_INPUT_ERROR); - return COLVARS_INPUT_ERROR; - } + int error_code_this = cvcp->init(def_conf); + if (error_code_this == COLVARS_OK) { + // Checking for invalid keywords only if the parsing was successful, otherwise any + // early-returns due to errors would raise false positives + error_code_this |= cvcp->check_keywords(def_conf, def_config_key); + } + cvm::decrease_depth(); + if (error_code_this != COLVARS_OK) { + error_code |= + cvm::error("Error: in setting up component \"" + std::string(def_config_key) + "\".\n", + COLVARS_INPUT_ERROR); } + // Set default name if it doesn't have one if ( ! cvcs.back()->name.size()) { std::ostringstream s; s << def_config_key << std::setfill('0') << std::setw(4) << ++def_count; @@ -822,135 +815,138 @@ template int colvar::init_components_type(std::string c cvcs.back()->setup(); if (cvm::debug()) { - cvm::log("Done initializing a \""+ - std::string(def_config_key)+ - "\" component"+ - (cvm::debug() ? - ", named \""+cvcs.back()->name+"\"" - : "")+".\n"); + cvm::log("Done initializing a \"" + std::string(def_config_key) + "\" component" + + (cvm::debug() ? ", named \"" + cvcs.back()->name + "\"" : "") + ".\n"); } + def_conf = ""; if (cvm::debug()) { - cvm::log("Parsed "+cvm::to_str(cvcs.size())+ - " components at this time.\n"); + cvm::log("Parsed " + cvm::to_str(cvcs.size()) + " components at this time.\n"); } } - return COLVARS_OK; + return error_code; } + +void colvar::define_component_types() +{ + colvarproxy *proxy = cvm::main()->proxy; + + add_component_type("distance", "distance"); + add_component_type("distance vector", "distanceVec"); + add_component_type("Cartesian coordinates", "cartesian"); + add_component_type("distance vector direction", "distanceDir"); + add_component_type("distance projection on an axis", "distanceZ"); + add_component_type("distance projection on a plane", "distanceXY"); + add_component_type("spherical polar angle theta", "polarTheta"); + add_component_type("spherical azimuthal angle phi", "polarPhi"); + add_component_type("average distance weighted by inverse power", "distanceInv"); + add_component_type("N1xN2-long vector of pairwise distances", "distancePairs"); + add_component_type("dipole magnitude", "dipoleMagnitude"); + add_component_type("coordination number", "coordNum"); + add_component_type("self-coordination number", "selfCoordNum"); + add_component_type("group-coordination number", "groupCoord"); + add_component_type("angle", "angle"); + add_component_type("dipole angle", "dipoleAngle"); + add_component_type("dihedral", "dihedral"); + add_component_type("hydrogen bond", "hBond"); + + if (proxy->check_atom_name_selections_available() == COLVARS_OK) { + add_component_type("alpha helix", "alpha"); + add_component_type("dihedral principal component", "dihedralPC"); + } + + add_component_type("orientation", "orientation"); + add_component_type("orientation angle", "orientationAngle"); + add_component_type("orientation projection", "orientationProj"); + add_component_type("tilt", "tilt"); + add_component_type("spin angle", "spinAngle"); + add_component_type("RMSD", "rmsd"); + add_component_type("radius of gyration", "gyration"); + add_component_type("moment of inertia", "inertia"); + add_component_type("moment of inertia around an axis", "inertiaZ"); + add_component_type("eigenvector", "eigenvector"); + add_component_type("alchemical coupling parameter", "alchLambda"); + add_component_type("force on alchemical coupling parameter", "alchFLambda"); + add_component_type("arithmetic path collective variables (s)", "aspath"); + add_component_type("arithmetic path collective variables (z)", "azpath"); + add_component_type("geometrical path collective variables (s)", "gspath"); + add_component_type("geometrical path collective variables (z)", "gzpath"); + add_component_type("linear combination of other collective variables", "linearCombination"); + add_component_type("geometrical path collective variables (s) for other CVs", "gspathCV"); + add_component_type("geometrical path collective variables (z) for other CVs", "gzpathCV"); + add_component_type("arithmetic path collective variables (s) for other CVs", "aspathCV"); + add_component_type("arithmetic path collective variables (s) for other CVs", "azpathCV"); + add_component_type("euler phi angle of the optimal orientation", "eulerPhi"); + add_component_type("euler psi angle of the optimal orientation", "eulerPsi"); + add_component_type("euler theta angle of the optimal orientation", "eulerTheta"); + +#ifdef LEPTON + add_component_type("CV with support of the Lepton custom function", "customColvar"); +#endif + + add_component_type("neural network CV for other CVs", "neuralNetwork"); + + if (proxy->check_volmaps_available() == COLVARS_OK) { + add_component_type("total value of atomic map", "mapTotal"); + } +} + + int colvar::init_components(std::string const &conf) { int error_code = COLVARS_OK; size_t i = 0, j = 0; - // in the non-C++11 case, the components are initialized directly by init_components_type; - // in the C++11 case, the components are stored in the global_cvc_map at first - // by init_components_type, and then the map is iterated to initialize all components. - error_code |= init_components_type(conf, "distance", "distance"); - error_code |= init_components_type(conf, "distance vector", "distanceVec"); - error_code |= init_components_type(conf, "Cartesian coordinates", "cartesian"); - error_code |= init_components_type(conf, "distance vector " - "direction", "distanceDir"); - error_code |= init_components_type(conf, "distance projection " - "on an axis", "distanceZ"); - error_code |= init_components_type(conf, "distance projection " - "on a plane", "distanceXY"); - error_code |= init_components_type(conf, "spherical polar angle theta", - "polarTheta"); - error_code |= init_components_type(conf, "spherical azimuthal angle phi", - "polarPhi"); - error_code |= init_components_type(conf, "average distance " - "weighted by inverse power", "distanceInv"); - error_code |= init_components_type(conf, "N1xN2-long vector " - "of pairwise distances", "distancePairs"); - error_code |= init_components_type(conf, "dipole magnitude", - "dipoleMagnitude"); - error_code |= init_components_type(conf, "coordination " - "number", "coordNum"); - error_code |= init_components_type(conf, "self-coordination " - "number", "selfCoordNum"); - error_code |= init_components_type(conf, "group-coordination " - "number", "groupCoord"); - error_code |= init_components_type(conf, "angle", "angle"); - error_code |= init_components_type(conf, "dipole angle", "dipoleAngle"); - error_code |= init_components_type(conf, "dihedral", "dihedral"); - error_code |= init_components_type(conf, "hydrogen bond", "hBond"); - error_code |= init_components_type(conf, "alpha helix", "alpha"); - error_code |= init_components_type(conf, "dihedral " - "principal component", "dihedralPC"); - error_code |= init_components_type(conf, "orientation", "orientation"); - error_code |= init_components_type(conf, "orientation " - "angle", "orientationAngle"); - error_code |= init_components_type(conf, "orientation " - "projection", "orientationProj"); - error_code |= init_components_type(conf, "tilt", "tilt"); - error_code |= init_components_type(conf, "spin angle", "spinAngle"); - error_code |= init_components_type(conf, "RMSD", "rmsd"); - error_code |= init_components_type(conf, "radius of " - "gyration", "gyration"); - error_code |= init_components_type(conf, "moment of " - "inertia", "inertia"); - error_code |= init_components_type(conf, "moment of inertia around an axis", "inertiaZ"); - error_code |= init_components_type(conf, "eigenvector", "eigenvector"); - error_code |= init_components_type(conf, "alchemical coupling parameter", "alchLambda"); - error_code |= init_components_type(conf, "force on alchemical coupling parameter", "alchFLambda"); - error_code |= init_components_type(conf, "geometrical path collective variables (s)", "gspath"); - error_code |= init_components_type(conf, "geometrical path collective variables (z)", "gzpath"); - error_code |= init_components_type(conf, "linear combination of other collective variables", "linearCombination"); - error_code |= init_components_type(conf, "geometrical path collective variables (s) for other CVs", "gspathCV"); - error_code |= init_components_type(conf, "geometrical path collective variables (z) for other CVs", "gzpathCV"); - error_code |= init_components_type(conf, "arithmetic path collective variables (s) for other CVs", "aspathCV"); - error_code |= init_components_type(conf, "arithmetic path collective variables (s) for other CVs", "azpathCV"); - error_code |= init_components_type(conf, "euler phi angle of the optimal orientation", "eulerPhi"); - error_code |= init_components_type(conf, "euler psi angle of the optimal orientation", "eulerPsi"); - error_code |= init_components_type(conf, "euler theta angle of the optimal orientation", "eulerTheta"); -#ifdef LEPTON - error_code |= init_components_type(conf, "CV with support of the lepton custom function", "customColvar"); -#endif - error_code |= init_components_type(conf, "neural network CV for other CVs", "NeuralNetwork"); + if (global_cvc_map.empty()) { + define_component_types(); + } - error_code |= init_components_type(conf, "total value of atomic map", "mapTotal"); -#if (__cplusplus >= 201103L) // iterate over all available CVC in the map for (auto it = global_cvc_map.begin(); it != global_cvc_map.end(); ++it) { - error_code |= init_components_type_from_global_map(conf, it->first.c_str()); + error_code |= init_components_type(conf, it->first.c_str()); // TODO: is it better to check the error code here? if (error_code != COLVARS_OK) { cvm::log("Failed to initialize " + it->first + " with the following configuration:\n"); cvm::log(conf); // TODO: should it stop here? + break; } } -#endif - if (!cvcs.size() || (error_code != COLVARS_OK)) { - cvm::error("Error: no valid components were provided " - "for this collective variable.\n", - COLVARS_INPUT_ERROR); - return COLVARS_INPUT_ERROR; + + if (!cvcs.size()) { + std::string msg("Error: no valid components were provided for this collective variable.\n"); + msg += "Currently available component types are: \n"; + for (auto it = global_cvc_desc_map.begin(); it != global_cvc_desc_map.end(); ++it) { + msg += " " + it->first + " -- " + it->second + "\n"; + } + msg += "\nPlease note that some of the above types may still be unavailable, irrespective of this error.\n"; + error_code |= cvm::error(msg, COLVARS_INPUT_ERROR); } // Check for uniqueness of CVC names (esp. if user-provided) for (i = 0; i < cvcs.size(); i++) { - for (j = i+1; j < cvcs.size(); j++) { + for (j = i + 1; j < cvcs.size(); j++) { if (cvcs[i]->name == cvcs[j]->name) { - cvm::error("Components " + cvm::to_str(i) + " and " + cvm::to_str(j) +\ - " cannot have the same name \"" + cvcs[i]->name+ "\".\n", COLVARS_INPUT_ERROR); - return COLVARS_INPUT_ERROR; + error_code |= cvm::error("Components " + cvm::to_str(i) + " and " + cvm::to_str(j) + + " cannot have the same name \"" + cvcs[i]->name + "\".\n", + COLVARS_INPUT_ERROR); } } } - n_active_cvcs = cvcs.size(); - - // Store list of children cvcs for dependency checking purposes - for (i = 0; i < cvcs.size(); i++) { - add_child(cvcs[i]); + if (error_code == COLVARS_OK) { + // Store list of children cvcs for dependency checking purposes + for (i = 0; i < cvcs.size(); i++) { + add_child(cvcs[i].get()); + } + // By default all CVCs are active at the start + n_active_cvcs = cvcs.size(); + cvm::log("All components initialized.\n"); } - cvm::log("All components initialized.\n"); - - return COLVARS_OK; + return error_code; } @@ -1220,7 +1216,7 @@ int colvar::init_dependencies() { // Initialize feature_states for each instance feature_states.reserve(f_cv_ntot); - for (i = 0; i < f_cv_ntot; i++) { + for (i = feature_states.size(); i < f_cv_ntot; i++) { feature_states.push_back(feature_state(true, false)); // Most features are available, so we set them so // and list exceptions below @@ -1283,14 +1279,10 @@ colvar::~colvar() // for dependency purposes remove_all_children(); - for (std::vector::reverse_iterator ci = cvcs.rbegin(); - ci != cvcs.rend(); - ++ci) { - // clear all children of this cvc (i.e. its atom groups) - // because the cvc base class destructor can't do it early enough - // and we don't want to have each cvc derived class do it separately + for (auto ci = cvcs.rbegin(); ci != cvcs.rend(); ++ci) { + // Clear all children of this cvc (i.e. its atom groups), because the cvc base class destructor + // can't do it early enough and we don't want to have each cvc derived class do it separately (*ci)->remove_all_children(); - delete *ci; } cvcs.clear(); @@ -1512,6 +1504,7 @@ int colvar::collect_cvc_values() cvm::to_str(x, cvm::cv_width, cvm::cv_prec)+".\n"); if (after_restart) { + x_old = x_restart; if (cvm::proxy->simulation_running()) { cvm::real const jump2 = dist2(x, x_restart) / (width*width); if (jump2 > 0.25) { @@ -1555,12 +1548,12 @@ int colvar::calc_cvc_gradients(int first_cvc, size_t num_cvcs) (cvcs[i])->debug_gradients(); } - cvm::decrease_depth(); - if (cvm::debug()) cvm::log("Done calculating gradients of colvar \""+this->name+"\".\n"); } + cvm::decrease_depth(); + return COLVARS_OK; } @@ -1706,12 +1699,13 @@ int colvar::calc_colvar_properties() // Do the same if no simulation is running (eg. VMD postprocessing) if ((cvm::step_relative() == 0 && !after_restart) || x_ext.type() == colvarvalue::type_notset || !cvm::proxy->simulation_running()) { x_ext = x; + cvm::log("Initializing extended coordinate to colvar value.\n"); if (is_enabled(f_cv_reflecting_lower_boundary) && x_ext < lower_boundary) { - cvm::log("Warning: initializing extended coordinate to reflective lower boundary, as colvar value is below."); + cvm::log("Warning: initializing extended coordinate to reflective lower boundary, as colvar value is below.\n"); x_ext = lower_boundary; } if (is_enabled(f_cv_reflecting_upper_boundary) && x_ext > upper_boundary) { - cvm::log("Warning: initializing extended coordinate to reflective upper boundary, as colvar value is above."); + cvm::log("Warning: initializing extended coordinate to reflective upper boundary, as colvar value is above.\n"); x_ext = upper_boundary; } @@ -1721,8 +1715,18 @@ int colvar::calc_colvar_properties() // Special case of a repeated timestep (eg. multiple NAMD "run" statements) // revert values of the extended coordinate and velocity prior to latest integration if (cvm::proxy->simulation_running() && cvm::step_relative() == prev_timestep) { - x_ext = prev_x_ext; - v_ext = prev_v_ext; + // Detect jumps due to discrete changes in coordinates (eg. in replica exchange schemes) + cvm::real const jump2 = dist2(x, x_old) / (width*width); + if (jump2 > 0.25) { + cvm::log("Detected discrete jump in colvar value from " + + cvm::to_str(x_old) + " to " + cvm::to_str(x) + ".\n"); + cvm::log("Reinitializing extended coordinate to colvar value.\n"); + x_ext = x; + } else { + cvm::log("Reinitializing extended coordinate to last value.\n"); + x_ext = prev_x_ext; + v_ext = prev_v_ext; + } } // report the restraint center as "value" // These position and velocities come from integration at the _previous timestep_ in update_forces_energy() @@ -1830,9 +1834,11 @@ void colvar::update_extended_Lagrangian() f += fb_actual; } - fr = f; - // External force has been scaled for a 1-timestep impulse, scale it back because we will - // integrate it with the colvar's own timestep factor + // fr: bias force on extended variable (without harmonic spring), for output in trajectory + fr = f; + + // External force has been scaled for an inner-timestep impulse (for the back-end integrator) + // here we scale it back because this integrator uses only the outer (long) timestep f_ext = f / cvm::real(time_step_factor); colvarvalue f_system(fr.type()); // force exterted by the system on the extended DOF @@ -1845,15 +1851,14 @@ void colvar::update_extended_Lagrangian() } else { // the total force is applied to the fictitious mass, while the // atoms only feel the harmonic force + wall force - // fr: bias force on extended variable (without harmonic spring), for output in trajectory // f_ext: total force on extended variable (including harmonic spring) // f: - initially, external biasing force // - after this code block, colvar force to be applied to atomic coordinates // ie. spring force (fb_actual will be added just below) f_system = (-0.5 * ext_force_k) * this->dist2_lgrad(x_ext, x); f = -1.0 * f_system; - // Coupling force is a slow force, to be applied to atomic coords impulse-style - // over a single MD timestep + // Coupling force will be applied to atomic coords impulse-style + // over an inner timestep of the back-end integrator f *= cvm::real(time_step_factor); } f_ext += f_system; @@ -1873,34 +1878,57 @@ void colvar::update_extended_Lagrangian() prev_x_ext = x_ext; prev_v_ext = v_ext; - // leapfrog: starting from x_i, f_i, v_(i-1/2) - v_ext += (0.5 * dt) * f_ext / ext_mass; - // Because of leapfrog, kinetic energy at time i is approximate + // BAOA (GSD) integrator as formulated in https://doi.org/10.1021/acs.jctc.2c00585 + // starting from x_t, f_t, v_(t-1/2) + // Variation: the velocity step is split in two to estimate the kinetic energy at time t + // so this is more of a "BBAOA" scheme: a rearranged BAOAB where the second B is deferred + // to the next time step for implementation reasons (waiting for the force calculation) + + // [B] Eq. (10a) split into two half-steps + // would reduce to leapfrog when gamma = 0 if this was the reported velocity + v_ext += 0.5 * dt * f_ext / ext_mass; + + // Kinetic energy at t kinetic_energy = 0.5 * ext_mass * v_ext * v_ext; + + // Potential energy at t potential_energy = 0.5 * ext_force_k * this->dist2(x_ext, x); - // leap to v_(i+1/2) + + // Total energy will lag behind position by one timestep + // (current kinetic energy is not accessible before the next force calculation) + + v_ext += 0.5 * dt * f_ext / ext_mass; + // Final v_ext lags behind x_ext by half a timestep + + // [A] Half step in position (10b) + x_ext += dt * v_ext / 2.0; + + // [O] leap to v_(i+1/2) (10c) if (is_enabled(f_cv_Langevin)) { - v_ext -= dt * ext_gamma * v_ext; colvarvalue rnd(x); rnd.set_random(); - v_ext += dt * ext_sigma * rnd / ext_mass; + // ext_sigma has been computed at init time according to (10c) + v_ext = cvm::exp(- 1.0 * dt * ext_gamma) * v_ext + ext_sigma * rnd / ext_mass; } - v_ext += (0.5 * dt) * f_ext / ext_mass; - x_ext += dt * v_ext; + // [A] Second half step in position (10d) + x_ext += dt * v_ext / 2.0; cvm::real delta = 0; // Length of overshoot past either reflecting boundary if ((is_enabled(f_cv_reflecting_lower_boundary) && (delta = x_ext - lower_boundary) < 0) || (is_enabled(f_cv_reflecting_upper_boundary) && (delta = x_ext - upper_boundary) > 0)) { + // Reflect arrival position x_ext -= 2.0 * delta; - v_ext *= -1.0; - if ((is_enabled(f_cv_reflecting_lower_boundary) && (delta = x_ext - lower_boundary) < 0) || - (is_enabled(f_cv_reflecting_upper_boundary) && (delta = x_ext - upper_boundary) > 0)) { + // Bounce happened on average at t+1/2 -> reflect velocity at t+1/2 + v_ext = -0.5 * (prev_v_ext + v_ext); + if ((is_enabled(f_cv_reflecting_lower_boundary) && (x_ext - lower_boundary) < 0.0) || + (is_enabled(f_cv_reflecting_upper_boundary) && (x_ext - upper_boundary) > 0.0)) { cvm::error("Error: extended coordinate value " + cvm::to_str(x_ext) + " is still outside boundaries after reflection.\n"); } } x_ext.apply_constraints(); this->wrap(x_ext); + if (is_enabled(f_cv_external)) { // Colvar value is constrained to the extended value x = x_ext; @@ -1914,9 +1942,8 @@ int colvar::end_of_step() if (cvm::debug()) cvm::log("End of step for colvar \""+this->name+"\".\n"); - if (is_enabled(f_cv_fdiff_velocity)) { - x_old = x; - } + // Used for fdiff_velocity and for detecting jumps for extended Lagrangian colvars + x_old = x; if (is_enabled(f_cv_subtract_applied_force)) { f_old = f; @@ -2256,44 +2283,65 @@ void colvar::wrap(colvarvalue &x_unwrapped) const std::istream & colvar::read_state(std::istream &is) { - std::streampos const start_pos = is.tellg(); + auto const start_pos = is.tellg(); std::string conf; - if ( !(is >> colvarparse::read_block("colvar", &conf)) ) { + if ( !(is >> colvarparse::read_block("colvar", &conf)) || + (check_matching_state(conf) != COLVARS_OK) ) { // this is not a colvar block is.clear(); - is.seekg(start_pos, std::ios::beg); + is.seekg(start_pos); is.setstate(std::ios::failbit); return is; } - { - std::string check_name = ""; - get_keyval(conf, "name", check_name, - std::string(""), colvarparse::parse_silent); - if (check_name.size() == 0) { - cvm::error("Error: Collective variable in the " - "restart file without any identifier.\n", COLVARS_INPUT_ERROR); - is.clear(); - is.seekg(start_pos, std::ios::beg); - is.setstate(std::ios::failbit); - return is; - } - - if (check_name != name) { - if (cvm::debug()) { - cvm::log("Ignoring state of colvar \""+check_name+ - "\": this colvar is named \""+name+"\".\n"); - } - is.seekg(start_pos, std::ios::beg); - return is; - } + if (!matching_state) { + // No errors reading, but this state is not for this colvar; rewind + is.seekg(start_pos); + return is; } + if (set_state_params(conf) != COLVARS_OK) { + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + } + + return is; +} + + +int colvar::check_matching_state(std::string const &conf) +{ + std::string check_name = ""; + get_keyval(conf, "name", check_name, std::string(""), colvarparse::parse_silent); + + if (check_name.size() == 0) { + return cvm::error("Error: Collective variable in the " + "state file without any identifier.\n", COLVARS_INPUT_ERROR); + } + + if (check_name != name) { + if (cvm::debug()) { + cvm::log("Ignoring state of colvar \""+check_name+ + "\": this colvar is named \""+name+"\".\n"); + } + matching_state = false; + } else { + matching_state = true; + } + + return COLVARS_OK; +} + + +int colvar::set_state_params(std::string const &conf) +{ + int error_code = COLVARS_OK; if ( !(get_keyval(conf, "x", x, x, colvarparse::parse_silent)) ) { - cvm::log("Error: restart file does not contain " - "the value of the colvar \""+ - name+"\" .\n"); + error_code |= cvm::error("Error: restart file does not contain " + "the value of the colvar \""+ + name+"\" .\n", COLVARS_INPUT_ERROR); } else { cvm::log("Restarting collective variable \""+name+"\" from value: "+ cvm::to_str(x)+"\n"); @@ -2306,9 +2354,10 @@ std::istream & colvar::read_state(std::istream &is) colvarvalue(x.type()), colvarparse::parse_silent)) || !(get_keyval(conf, "extended_v", v_ext, colvarvalue(x.type()), colvarparse::parse_silent)) ) { - cvm::log("Error: restart file does not contain " - "\"extended_x\" or \"extended_v\" for the colvar \""+ - name+"\", but you requested \"extendedLagrangian\".\n"); + error_code |= cvm::error("Error: restart file does not contain " + "\"extended_x\" or \"extended_v\" for the colvar \""+ + name+"\", but you requested \"extendedLagrangian\".\n", + COLVARS_INPUT_ERROR); } x_reported = x_ext; } else { @@ -2319,9 +2368,10 @@ std::istream & colvar::read_state(std::istream &is) if ( !(get_keyval(conf, "v", v_fdiff, colvarvalue(x.type()), colvarparse::parse_silent)) ) { - cvm::log("Error: restart file does not contain " - "the velocity for the colvar \""+ - name+"\", but you requested \"outputVelocity\".\n"); + error_code |= cvm::error("Error: restart file does not contain " + "the velocity for the colvar \""+ + name+"\", but you requested \"outputVelocity\".\n", + COLVARS_INPUT_ERROR); } if (is_enabled(f_cv_extended_Lagrangian)) { @@ -2331,6 +2381,41 @@ std::istream & colvar::read_state(std::istream &is) } } + return error_code; +} + + +cvm::memory_stream &colvar::read_state(cvm::memory_stream &is) +{ + auto const start_pos = is.tellg(); + std::string key, data; + if (is >> key) { + if (key == "colvar") { + // Read a formatted config string, then read the state parameters from it + if (is >> data) { + if (set_state_params(data) == COLVARS_OK) { + return is; + } + } + } + } + + auto const error_pos = is.tellg(); + + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + + std::string error_msg("Error: in reading state data for colvar \"" + name + " at position " + + cvm::to_str(error_pos) + " in unformatted stream.\n"); + if (key.size() && key != "colvar") { + error_msg += "; the keyword read was \"" + key + "\", but \"colvar\" was expected"; + } + if (data.size()) { + error_msg += "; the configuration string read was not recognized"; + } + error_msg += ".\n"; + cvm::error(error_msg, COLVARS_INPUT_ERROR); return is; } @@ -2345,7 +2430,7 @@ std::istream & colvar::read_traj(std::istream &is) cvm::log("Error: in reading the value of colvar \""+ this->name+"\" from trajectory.\n"); is.clear(); - is.seekg(start_pos, std::ios::beg); + is.seekg(start_pos); is.setstate(std::ios::failbit); return is; } @@ -2385,10 +2470,23 @@ std::istream & colvar::read_traj(std::istream &is) // ******************** OUTPUT FUNCTIONS ******************** -std::ostream & colvar::write_state(std::ostream &os) { +std::ostream & colvar::write_state(std::ostream &os) const +{ + os << "colvar {\n" << get_state_params() << "}\n\n"; - os << "colvar {\n" - << " name " << name << "\n" + if (runave_outfile.size() > 0) { + cvm::main()->proxy->flush_output_stream(runave_outfile); + } + + return os; +} + + +std::string const colvar::get_state_params() const +{ + std::ostringstream os; + + os << " name " << name << "\n" << " x " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) @@ -2412,7 +2510,13 @@ std::ostream & colvar::write_state(std::ostream &os) { << v_reported << "\n"; } - os << "}\n\n"; + return os.str(); +} + + +cvm::memory_stream & colvar::write_state(cvm::memory_stream &os) const +{ + os << std::string("colvar") << get_state_params(); if (runave_outfile.size() > 0) { cvm::main()->proxy->flush_output_stream(runave_outfile); @@ -2875,14 +2979,11 @@ int colvar::calc_runave() runave_variance *= 1.0 / cvm::real(runave_length-1); if (runave_outfile.size() > 0) { - std::ostream &runave_os = proxy->output_stream(runave_outfile); - runave_os << std::setw(cvm::it_width) << cvm::step_relative() - << " " - << std::setprecision(cvm::cv_prec) - << std::setw(cvm::cv_width) - << runave << " " - << std::setprecision(cvm::cv_prec) - << std::setw(cvm::cv_width) + std::ostream &runave_os = + proxy->output_stream(runave_outfile, "running average output file"); + runave_os << std::setw(cvm::it_width) << cvm::step_relative() << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << runave << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << cvm::sqrt(runave_variance) << "\n"; } } diff --git a/lib/colvars/colvar.h b/lib/colvars/colvar.h index 9af26dedd3..443e1e4bdd 100644 --- a/lib/colvars/colvar.h +++ b/lib/colvars/colvar.h @@ -10,12 +10,11 @@ #ifndef COLVAR_H #define COLVAR_H -#include - -#if (__cplusplus >= 201103L) -#include #include -#endif +#include +#include +#include +#include #include "colvarmodule.h" #include "colvarvalue.h" @@ -91,7 +90,7 @@ public: /// calculations, \link colvarbias_abf \endlink, it is used to /// calculate the grid spacing in the direction of this collective /// variable. - cvm::real width; + cvm::real width = 1.0; /// \brief Implementation of the feature list for colvar static std::vector cv_features; @@ -184,13 +183,13 @@ protected: /// Previous velocity of the restraint center colvarvalue prev_v_ext; /// Mass of the restraint center - cvm::real ext_mass; + cvm::real ext_mass = 0.0; /// Restraint force constant - cvm::real ext_force_k; + cvm::real ext_force_k = 0.0; /// Friction coefficient for Langevin extended dynamics - cvm::real ext_gamma; + cvm::real ext_gamma = 0.0; /// Amplitude of Gaussian white noise for Langevin extended dynamics - cvm::real ext_sigma; + cvm::real ext_sigma = 0.0; /// \brief Applied force on extended DOF, for output (unscaled if using MTS) colvarvalue fr; @@ -224,14 +223,14 @@ public: colvarvalue ft; /// Period, if this variable is periodic - cvm::real period; + cvm::real period = 0.0; /// Center of wrapping, if this variable is periodic - cvm::real wrap_center; + cvm::real wrap_center = 0.0; /// \brief Expand the boundaries of multiples of width, to keep the /// value always within range - bool expand_boundaries; + bool expand_boundaries = false; /// \brief Location of the lower boundary colvarvalue lower_boundary; @@ -252,6 +251,9 @@ public: /// Main init function int init(std::string const &conf); + /// Populate the map of available CVC types + void define_component_types(); + /// Parse the CVC configuration and allocate their data int init_components(std::string const &conf); @@ -271,17 +273,13 @@ public: virtual int init_dependencies(); private: - /// Parse the CVC configuration for all components of a certain type - template int init_components_type(std::string const & conf, - char const *def_desc, - char const *def_config_key); -#if (__cplusplus >= 201103L) - /// For the C++11 case, the names of all available components are - /// registered in the global map at first, and then the CVC configuration - /// is parsed by this function - int init_components_type_from_global_map(const std::string& conf, - const char* def_config_key); -#endif + + /// Declare an available CVC type and its description, register them in the global map + template + void add_component_type(char const *description, char const *config_key); + + /// Initialize any CVC objects matching the given key + int init_components_type(const std::string &conf, const char *config_key); public: @@ -387,10 +385,10 @@ public: protected: /// \brief Number of CVC objects with an active flag - size_t n_active_cvcs; + size_t n_active_cvcs = 0; /// Sum of square coefficients for active cvcs - cvm::real active_cvc_square_norm; + cvm::real active_cvc_square_norm = 0.0; /// Update the sum of square coefficients for active cvcs void update_active_cvc_square_norm(); @@ -460,16 +458,35 @@ public: /// Write a label to the trajectory file (comment line) std::ostream & write_traj_label(std::ostream &os); - /// Read the collective variable from a restart file + /// Read the colvar's state from a formatted input stream std::istream & read_state(std::istream &is); - /// Write the collective variable to a restart file - std::ostream & write_state(std::ostream &os); + + /// Read the colvar's state from an unformatted input stream + cvm::memory_stream & read_state(cvm::memory_stream &is); + + /// Check the name of the bias vs. the given string, set the matching_state flag accordingly + int check_matching_state(std::string const &state_conf); + + /// Read the values of colvar mutable data from a string (used by both versions of read_state()) + int set_state_params(std::string const &state_conf); + + /// Write the state information of this colvar in a block of text, suitable for later parsing + std::string const get_state_params() const; + + /// Write the colvar's state to a formatted output stream + std::ostream & write_state(std::ostream &os) const; + + /// Write the colvar's state to an unformatted output stream + cvm::memory_stream & write_state(cvm::memory_stream &os) const; /// Write output files (if defined, e.g. in analysis mode) int write_output_files(); protected: + /// Flag used to tell if the state string being read is for this colvar + bool matching_state; + /// Previous value (to calculate velocities during analysis) colvarvalue x_old; @@ -554,15 +571,15 @@ protected: /// Current value of the running average colvarvalue runave; /// Current value of the square deviation from the running average - cvm::real runave_variance; + cvm::real runave_variance = 0.0; /// Calculate the running average and its standard deviation int calc_runave(); /// If extended Lagrangian active: colvar kinetic energy - cvm::real kinetic_energy; + cvm::real kinetic_energy = 0.0; /// If extended Lagrangian active: colvar harmonic potential - cvm::real potential_energy; + cvm::real potential_energy = 0.0; public: @@ -601,8 +618,9 @@ public: class dihedPC; class alch_lambda; class alch_Flambda; - class componentDisabled; class CartesianBasedPath; + class aspath; + class azpath; class gspath; class gzpath; class linearCombination; @@ -626,21 +644,19 @@ public: // components that do not handle any atoms directly class map_total; - /// getter of the global cvc map -#if (__cplusplus >= 201103L) /// A global mapping of cvc names to the cvc constructors - static const std::map>& get_global_cvc_map() { - return global_cvc_map; + static const std::map> &get_global_cvc_map() + { + return global_cvc_map; } -#endif /// \brief function for sorting cvcs by their names static bool compare_cvc(const colvar::cvc* const i, const colvar::cvc* const j); protected: - /// \brief Array of \link colvar::cvc \endlink objects - std::vector cvcs; + /// Array of components objects + std::vector> cvcs; /// \brief Flags to enable or disable cvcs at next colvar evaluation std::vector cvc_flags; @@ -671,10 +687,11 @@ protected: double dev_null; #endif -#if (__cplusplus >= 201103L) /// A global mapping of cvc names to the cvc constructors - static std::map> global_cvc_map; -#endif + static std::map> global_cvc_map; + + /// A global mapping of cvc names to the corresponding descriptions + static std::map global_cvc_desc_map; /// Volmap numeric IDs, one for each CVC (-1 if not available) std::vector volmap_ids_; @@ -762,4 +779,3 @@ inline void colvar::reset_bias_force() { } #endif - diff --git a/lib/colvars/colvar_arithmeticpath.h b/lib/colvars/colvar_arithmeticpath.h index bea86a1014..ee97390dfc 100644 --- a/lib/colvars/colvar_arithmeticpath.h +++ b/lib/colvars/colvar_arithmeticpath.h @@ -7,126 +7,84 @@ #include #include #include +#include namespace ArithmeticPathCV { using std::vector; -enum path_sz {S, Z}; - -template +template class ArithmeticPathBase { public: ArithmeticPathBase() {} - virtual ~ArithmeticPathBase() {} - virtual void initialize(size_t p_num_elements, size_t p_total_frames, double p_lambda, const vector& p_element, const vector& p_weights); - virtual void updateDistanceToReferenceFrames() = 0; - virtual void computeValue(); - virtual void computeDerivatives(); - virtual void compute(); - virtual void reComputeLambda(const vector& rmsd_between_refs); + ~ArithmeticPathBase() {} + void initialize(size_t p_num_elements, size_t p_total_frames, scalar_type p_lambda, const vector& p_weights); + void reComputeLambda(const vector& rmsd_between_refs); + template + void computeValue(const vector>& frame_element_distances, scalar_type *s = nullptr, scalar_type *z = nullptr); + // can only be called after computeValue() for element-wise derivatives and store derivatives of i-th frame to dsdx and dzdx + template + void computeDerivatives(const vector>& frame_element_distances, vector> *dsdx = nullptr, vector> *dzdx = nullptr); protected: scalar_type lambda; - vector weights; + vector squared_weights; size_t num_elements; size_t total_frames; - vector< vector > frame_element_distances; - scalar_type s; - scalar_type z; - vector dsdx; - vector dzdx; -private: - // intermediate variables - vector s_numerator_frame; - vector s_denominator_frame; - scalar_type numerator_s; - scalar_type denominator_s; + vector exponents; + scalar_type max_exponent; + scalar_type saved_exponent_sum; scalar_type normalization_factor; + scalar_type saved_s; }; -template -void ArithmeticPathBase::initialize(size_t p_num_elements, size_t p_total_frames, double p_lambda, const vector& p_element, const vector& p_weights) { +template +void ArithmeticPathBase::initialize(size_t p_num_elements, size_t p_total_frames, scalar_type p_lambda, const vector& p_weights) { lambda = p_lambda; - weights = p_weights; + for (size_t i = 0; i < p_weights.size(); ++i) squared_weights.push_back(p_weights[i] * p_weights[i]); num_elements = p_num_elements; total_frames = p_total_frames; - frame_element_distances.resize(total_frames, p_element); - for (size_t i_frame = 0; i_frame < frame_element_distances.size(); ++i_frame) { - for (size_t j_elem = 0; j_elem < num_elements; ++j_elem) { - frame_element_distances[i_frame][j_elem].reset(); - } - } - s = scalar_type(0); - z = scalar_type(0); - dsdx = p_element; - dzdx = p_element; - s_numerator_frame.resize(total_frames, scalar_type(0)); - s_denominator_frame.resize(total_frames, scalar_type(0)); - numerator_s = scalar_type(0); - denominator_s = scalar_type(0); + exponents.resize(total_frames); normalization_factor = 1.0 / static_cast(total_frames - 1); + saved_s = scalar_type(); + saved_exponent_sum = scalar_type(); + max_exponent = scalar_type(); } -template -void ArithmeticPathBase::computeValue() { - updateDistanceToReferenceFrames(); - numerator_s = scalar_type(0); - denominator_s = scalar_type(0); - for (size_t i_frame = 0; i_frame < frame_element_distances.size(); ++i_frame) { - scalar_type exponent_tmp = scalar_type(0); +template +template +void ArithmeticPathBase::computeValue( + const vector>& frame_element_distances, + scalar_type *s, scalar_type *z) +{ + for (size_t i_frame = 0; i_frame < total_frames; ++i_frame) { + scalar_type exponent_tmp = scalar_type(); for (size_t j_elem = 0; j_elem < num_elements; ++j_elem) { - exponent_tmp += weights[j_elem] * frame_element_distances[i_frame][j_elem] * weights[j_elem] * frame_element_distances[i_frame][j_elem]; + exponent_tmp += squared_weights[j_elem] * frame_element_distances[i_frame][j_elem] * frame_element_distances[i_frame][j_elem]; } - exponent_tmp = exponent_tmp * -1.0 * lambda; - // prevent underflow if the argument of cvm::exp is less than -708.4 - if (exponent_tmp > -708.4) { - exponent_tmp = cvm::exp(exponent_tmp); - } else { - exponent_tmp = 0; - } - numerator_s += static_cast(i_frame) * exponent_tmp; - denominator_s += exponent_tmp; - s_numerator_frame[i_frame] = static_cast(i_frame) * exponent_tmp; - s_denominator_frame[i_frame] = exponent_tmp; + exponents[i_frame] = exponent_tmp * -1.0 * lambda; + if (i_frame == 0 || exponents[i_frame] > max_exponent) max_exponent = exponents[i_frame]; } - s = numerator_s / denominator_s * normalization_factor; - z = -1.0 / lambda * cvm::logn(denominator_s); -} - -template -void ArithmeticPathBase::compute() { - computeValue(); - computeDerivatives(); -} - -template -void ArithmeticPathBase::computeDerivatives() { - for (size_t j_elem = 0; j_elem < num_elements; ++j_elem) { - element_type dsdxj_numerator_part1(dsdx[j_elem]); - element_type dsdxj_numerator_part2(dsdx[j_elem]); - element_type dzdxj_numerator(dsdx[j_elem]); - dsdxj_numerator_part1.reset(); - dsdxj_numerator_part2.reset(); - dzdxj_numerator.reset(); - for (size_t i_frame = 0; i_frame < frame_element_distances.size(); ++i_frame) { - element_type derivative_tmp = -2.0 * lambda * weights[j_elem] * weights[j_elem] * frame_element_distances[i_frame][j_elem]; - dsdxj_numerator_part1 += s_numerator_frame[i_frame] * derivative_tmp; - dsdxj_numerator_part2 += s_denominator_frame[i_frame] * derivative_tmp; - dzdxj_numerator += s_denominator_frame[i_frame] * derivative_tmp; - } - dsdxj_numerator_part1 *= denominator_s; - dsdxj_numerator_part2 *= numerator_s; - if ((dsdxj_numerator_part1 - dsdxj_numerator_part2).norm() < std::numeric_limits::min()) { - dsdx[j_elem] = 0; - } else { - dsdx[j_elem] = (dsdxj_numerator_part1 - dsdxj_numerator_part2) / (denominator_s * denominator_s) * normalization_factor; - } - dzdx[j_elem] = -1.0 / lambda * dzdxj_numerator / denominator_s; + scalar_type log_sum_exp_0 = scalar_type(); + scalar_type log_sum_exp_1 = scalar_type(); + for (size_t i_frame = 0; i_frame < total_frames; ++i_frame) { + exponents[i_frame] = cvm::exp(exponents[i_frame] - max_exponent); + log_sum_exp_0 += exponents[i_frame]; + log_sum_exp_1 += i_frame * exponents[i_frame]; + } + saved_exponent_sum = log_sum_exp_0; + log_sum_exp_0 = max_exponent + cvm::logn(log_sum_exp_0); + log_sum_exp_1 = max_exponent + cvm::logn(log_sum_exp_1); + saved_s = normalization_factor * cvm::exp(log_sum_exp_1 - log_sum_exp_0); + if (s != nullptr) { + *s = saved_s; + } + if (z != nullptr) { + *z = -1.0 / lambda * log_sum_exp_0; } } -template -void ArithmeticPathBase::reComputeLambda(const vector& rmsd_between_refs) { +template +void ArithmeticPathBase::reComputeLambda(const vector& rmsd_between_refs) { scalar_type mean_square_displacements = 0.0; for (size_t i_frame = 1; i_frame < total_frames; ++i_frame) { cvm::log(std::string("Distance between frame ") + cvm::to_str(i_frame) + " and " + cvm::to_str(i_frame + 1) + " is " + cvm::to_str(rmsd_between_refs[i_frame - 1]) + std::string("\n")); @@ -135,6 +93,45 @@ void ArithmeticPathBase::reComputeLambda(c mean_square_displacements /= scalar_type(total_frames - 1); lambda = 1.0 / mean_square_displacements; } + +// frame-wise derivatives for frames using optimal rotation +template +template +void ArithmeticPathBase::computeDerivatives( + const vector>& frame_element_distances, + vector> *dsdx, + vector> *dzdx) +{ + vector softmax_out, tmps; + softmax_out.reserve(total_frames); + tmps.reserve(total_frames); + for (size_t i_frame = 0; i_frame < total_frames; ++i_frame) { + softmax_out.push_back(exponents[i_frame] / saved_exponent_sum); + tmps.push_back( + (static_cast(i_frame) - + static_cast(total_frames - 1) * saved_s) * + normalization_factor); + } + if (dsdx != nullptr) { + for (size_t i_frame = 0; i_frame < total_frames; ++i_frame) { + for (size_t j_elem = 0; j_elem < num_elements; ++j_elem) { + (*dsdx)[i_frame][j_elem] = + -2.0 * squared_weights[j_elem] * lambda * + frame_element_distances[i_frame][j_elem] * + softmax_out[i_frame] * tmps[i_frame]; + } + } + } + if (dzdx != nullptr) { + for (size_t i_frame = 0; i_frame < total_frames; ++i_frame) { + for (size_t j_elem = 0; j_elem < num_elements; ++j_elem) { + (*dzdx)[i_frame][j_elem] = + 2.0 * squared_weights[j_elem] * softmax_out[i_frame] * + frame_element_distances[i_frame][j_elem]; + } + } + } +} } #endif // ARITHMETICPATHCV_H diff --git a/lib/colvars/colvar_geometricpath.h b/lib/colvars/colvar_geometricpath.h index 9ff78261f2..51f97bb675 100644 --- a/lib/colvars/colvar_geometricpath.h +++ b/lib/colvars/colvar_geometricpath.h @@ -8,12 +8,13 @@ // Colvars repository at GitHub. +#include +#include +#include +#include + #include "colvarmodule.h" -#include -#include -#include -#include namespace GeometricPathCV { @@ -171,10 +172,14 @@ void GeometricPathBase::determineClosestFr sign = -1; } if (cvm::fabs(static_cast(frame_index[0]) - static_cast(frame_index[1])) > 1) { - std::cout << "Warning: Geometrical pathCV relies on the assumption that the second closest frame is the neighbouring frame\n"; - std::cout << " Please check your configuration or increase restraint on z(σ)\n"; + std::string message( + "Warning: Geometrical pathCV relies on the assumption that the second closest frame is " + "the neighbouring frame\n" + " Please check your configuration or increase restraint on z(σ)\n"); for (size_t i_frame = 0; i_frame < frame_index.size(); ++i_frame) { - std::cout << "Frame index: " << frame_index[i_frame] << " ; optimal RMSD = " << frame_distances[frame_index[i_frame]] << "\n"; + message += "Frame index: " + cvm::to_str(frame_index[i_frame]) + + " ; optimal RMSD = " + cvm::to_str(frame_distances[frame_index[i_frame]]) + + "\n"; } } min_frame_index_1 = frame_index[0]; // s_m diff --git a/lib/colvars/colvar_neuralnetworkcompute.cpp b/lib/colvars/colvar_neuralnetworkcompute.cpp index a1ad717946..b77db0cfa3 100644 --- a/lib/colvars/colvar_neuralnetworkcompute.cpp +++ b/lib/colvars/colvar_neuralnetworkcompute.cpp @@ -10,7 +10,6 @@ #include #include -#if (__cplusplus >= 201103L) #include "colvar_neuralnetworkcompute.h" #include "colvarparse.h" #include "colvarproxy.h" @@ -272,9 +271,12 @@ std::vector> neuralNetworkCompute::multiply_matrix(const std const size_t t = B[0].size(); std::vector> C(m, std::vector(t, 0.0)); for (size_t i = 0; i < m; ++i) { - for (size_t j = 0; j < t; ++j) { - for (size_t k = 0; k < n; ++k) { - C[i][j] += A[i][k] * B[k][j]; + for (size_t k = 0; k < n; ++k) { + const auto tmp = A[i][k]; + auto& C_i = C[i]; + auto& B_k = B[k]; + for (size_t j = 0; j < t; ++j) { + C_i[j] += tmp * B_k[j]; } } } @@ -306,5 +308,3 @@ void neuralNetworkCompute::compute() { } } } - -#endif diff --git a/lib/colvars/colvar_neuralnetworkcompute.h b/lib/colvars/colvar_neuralnetworkcompute.h index 5a56887431..575ce3b40b 100644 --- a/lib/colvars/colvar_neuralnetworkcompute.h +++ b/lib/colvars/colvar_neuralnetworkcompute.h @@ -7,7 +7,6 @@ // If you wish to distribute your changes, please submit them to the // Colvars repository at GitHub. -#if (__cplusplus >= 201103L) #ifndef NEURALNETWORKCOMPUTE_H #define NEURALNETWORKCOMPUTE_H @@ -145,4 +144,3 @@ public: } #endif -#endif diff --git a/lib/colvars/colvar_rotation_derivative.h b/lib/colvars/colvar_rotation_derivative.h new file mode 100644 index 0000000000..50f4f1aa97 --- /dev/null +++ b/lib/colvars/colvar_rotation_derivative.h @@ -0,0 +1,627 @@ +#ifndef COLVAR_ROTATION_DERIVATIVE +#define COLVAR_ROTATION_DERIVATIVE + +#include "colvartypes.h" +#include +#include + +/// \brief Helper function for loading the ia-th atom in the vector pos to x, y and z (C++11 SFINAE is used) +template ::value, bool>::type = true> +inline void read_atom_coord( + size_t ia, const std::vector& pos, + cvm::real* x, cvm::real* y, cvm::real* z) { + *x = pos[ia].x; + *y = pos[ia].y; + *z = pos[ia].z; +} + +template ::value, bool>::type = true> +inline void read_atom_coord( + size_t ia, const std::vector& pos, + cvm::real* x, cvm::real* y, cvm::real* z) { + *x = pos[ia].pos.x; + *y = pos[ia].pos.y; + *z = pos[ia].pos.z; +} + +/// \brief Helper enum class for specifying options in rotation_derivative::prepare_derivative +enum class rotation_derivative_dldq { + /// Require the derivative of the leading eigenvalue with respect to the atom coordinats + use_dl = 1 << 0, + /// Require the derivative of the leading eigenvector with respect to the atom coordinats + use_dq = 1 << 1 +}; + +inline constexpr rotation_derivative_dldq operator|(rotation_derivative_dldq Lhs, rotation_derivative_dldq Rhs) { + return static_cast( + static_cast::type>(Lhs) | + static_cast::type>(Rhs)); +} + +inline constexpr bool operator&(rotation_derivative_dldq Lhs, rotation_derivative_dldq Rhs) +{ + return (static_cast::type>(Lhs) & + static_cast::type>(Rhs)); +} + +/// \brief Helper class for calculating the derivative of rotation +template +struct rotation_derivative { + static_assert(std::is_same::value || std::is_same::value, + "class template rotation_derivative only supports cvm::atom_pos or cvm::atom types."); + static_assert(std::is_same::value || std::is_same::value, + "class template rotation_derivative only supports cvm::atom_pos or cvm::atom types."); + /// \brief Reference to the rotation + const cvm::rotation &m_rot; + /// \brief Reference to the atom positions of group 1 + const std::vector &m_pos1; + /// \brief Reference to the atom positions of group 2 + const std::vector &m_pos2; + /// \brief Temporary variable that will be updated if prepare_derivative called + cvm::real tmp_Q0Q0[4][4]; + cvm::real tmp_Q0Q0_L[4][4][4]; + /*! @brief Constructor of the cvm::rotation::derivative class + * @param[in] rot The cvm::rotation object (must have called + * `calc_optimal_rotation` before calling + * `calc_derivative_wrt_group1` and + * `calc_derivative_wrt_group2`) + * @param[in] pos1 The atom positions of group 1 + * @param[in] pos2 The atom positions of group 2 + */ + rotation_derivative( + const cvm::rotation &rot, + const std::vector &pos1, + const std::vector &pos2): + m_rot(rot), m_pos1(pos1), m_pos2(pos2) {}; + /*! @brief This function must be called before `calc_derivative_wrt_group1` + * and `calc_derivative_wrt_group2` in order to prepare the tmp_Q0Q0 + * and tmp_Q0Q0_L. + * @param[in] require_dl_dq Require the calculation of the derivatives of L or/and Q + * with respect to atoms. + */ + void prepare_derivative(rotation_derivative_dldq require_dl_dq) { + if (require_dl_dq & rotation_derivative_dldq::use_dl) { + const auto &Q0 = m_rot.S_eigvec[0]; + tmp_Q0Q0[0][0] = Q0[0] * Q0[0]; + tmp_Q0Q0[0][1] = Q0[0] * Q0[1]; + tmp_Q0Q0[0][2] = Q0[0] * Q0[2]; + tmp_Q0Q0[0][3] = Q0[0] * Q0[3]; + tmp_Q0Q0[1][0] = Q0[1] * Q0[0]; + tmp_Q0Q0[1][1] = Q0[1] * Q0[1]; + tmp_Q0Q0[1][2] = Q0[1] * Q0[2]; + tmp_Q0Q0[1][3] = Q0[1] * Q0[3]; + tmp_Q0Q0[2][0] = Q0[2] * Q0[0]; + tmp_Q0Q0[2][1] = Q0[2] * Q0[1]; + tmp_Q0Q0[2][2] = Q0[2] * Q0[2]; + tmp_Q0Q0[2][3] = Q0[2] * Q0[3]; + tmp_Q0Q0[3][0] = Q0[3] * Q0[0]; + tmp_Q0Q0[3][1] = Q0[3] * Q0[1]; + tmp_Q0Q0[3][2] = Q0[3] * Q0[2]; + tmp_Q0Q0[3][3] = Q0[3] * Q0[3]; + } + if (require_dl_dq & rotation_derivative_dldq::use_dq) { + const auto &Q0 = m_rot.S_eigvec[0]; + const auto &Q1 = m_rot.S_eigvec[1]; + const auto &Q2 = m_rot.S_eigvec[2]; + const auto &Q3 = m_rot.S_eigvec[3]; + cvm::real const L0 = m_rot.S_eigval[0]; + cvm::real const L1 = m_rot.S_eigval[1]; + cvm::real const L2 = m_rot.S_eigval[2]; + cvm::real const L3 = m_rot.S_eigval[3]; + + tmp_Q0Q0_L[0][0][0] = (Q1[0] * Q0[0]) / (L0-L1) * Q1[0] + + (Q2[0] * Q0[0]) / (L0-L2) * Q2[0] + + (Q3[0] * Q0[0]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][0][0] = (Q1[0] * Q0[0]) / (L0-L1) * Q1[1] + + (Q2[0] * Q0[0]) / (L0-L2) * Q2[1] + + (Q3[0] * Q0[0]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][0][0] = (Q1[0] * Q0[0]) / (L0-L1) * Q1[2] + + (Q2[0] * Q0[0]) / (L0-L2) * Q2[2] + + (Q3[0] * Q0[0]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][0][0] = (Q1[0] * Q0[0]) / (L0-L1) * Q1[3] + + (Q2[0] * Q0[0]) / (L0-L2) * Q2[3] + + (Q3[0] * Q0[0]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][0][1] = (Q1[0] * Q0[1]) / (L0-L1) * Q1[0] + + (Q2[0] * Q0[1]) / (L0-L2) * Q2[0] + + (Q3[0] * Q0[1]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][0][1] = (Q1[0] * Q0[1]) / (L0-L1) * Q1[1] + + (Q2[0] * Q0[1]) / (L0-L2) * Q2[1] + + (Q3[0] * Q0[1]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][0][1] = (Q1[0] * Q0[1]) / (L0-L1) * Q1[2] + + (Q2[0] * Q0[1]) / (L0-L2) * Q2[2] + + (Q3[0] * Q0[1]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][0][1] = (Q1[0] * Q0[1]) / (L0-L1) * Q1[3] + + (Q2[0] * Q0[1]) / (L0-L2) * Q2[3] + + (Q3[0] * Q0[1]) / (L0-L3) * Q3[3]; + + + tmp_Q0Q0_L[0][0][2] = (Q1[0] * Q0[2]) / (L0-L1) * Q1[0] + + (Q2[0] * Q0[2]) / (L0-L2) * Q2[0] + + (Q3[0] * Q0[2]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][0][2] = (Q1[0] * Q0[2]) / (L0-L1) * Q1[1] + + (Q2[0] * Q0[2]) / (L0-L2) * Q2[1] + + (Q3[0] * Q0[2]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][0][2] = (Q1[0] * Q0[2]) / (L0-L1) * Q1[2] + + (Q2[0] * Q0[2]) / (L0-L2) * Q2[2] + + (Q3[0] * Q0[2]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][0][2] = (Q1[0] * Q0[2]) / (L0-L1) * Q1[3] + + (Q2[0] * Q0[2]) / (L0-L2) * Q2[3] + + (Q3[0] * Q0[2]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][0][3] = (Q1[0] * Q0[3]) / (L0-L1) * Q1[0] + + (Q2[0] * Q0[3]) / (L0-L2) * Q2[0] + + (Q3[0] * Q0[3]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][0][3] = (Q1[0] * Q0[3]) / (L0-L1) * Q1[1] + + (Q2[0] * Q0[3]) / (L0-L2) * Q2[1] + + (Q3[0] * Q0[3]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][0][3] = (Q1[0] * Q0[3]) / (L0-L1) * Q1[2] + + (Q2[0] * Q0[3]) / (L0-L2) * Q2[2] + + (Q3[0] * Q0[3]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][0][3] = (Q1[0] * Q0[3]) / (L0-L1) * Q1[3] + + (Q2[0] * Q0[3]) / (L0-L2) * Q2[3] + + (Q3[0] * Q0[3]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][1][0] = (Q1[1] * Q0[0]) / (L0-L1) * Q1[0] + + (Q2[1] * Q0[0]) / (L0-L2) * Q2[0] + + (Q3[1] * Q0[0]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][1][0] = (Q1[1] * Q0[0]) / (L0-L1) * Q1[1] + + (Q2[1] * Q0[0]) / (L0-L2) * Q2[1] + + (Q3[1] * Q0[0]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][1][0] = (Q1[1] * Q0[0]) / (L0-L1) * Q1[2] + + (Q2[1] * Q0[0]) / (L0-L2) * Q2[2] + + (Q3[1] * Q0[0]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][1][0] = (Q1[1] * Q0[0]) / (L0-L1) * Q1[3] + + (Q2[1] * Q0[0]) / (L0-L2) * Q2[3] + + (Q3[1] * Q0[0]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][1][1] = (Q1[1] * Q0[1]) / (L0-L1) * Q1[0] + + (Q2[1] * Q0[1]) / (L0-L2) * Q2[0] + + (Q3[1] * Q0[1]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][1][1] = (Q1[1] * Q0[1]) / (L0-L1) * Q1[1] + + (Q2[1] * Q0[1]) / (L0-L2) * Q2[1] + + (Q3[1] * Q0[1]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][1][1] = (Q1[1] * Q0[1]) / (L0-L1) * Q1[2] + + (Q2[1] * Q0[1]) / (L0-L2) * Q2[2] + + (Q3[1] * Q0[1]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][1][1] = (Q1[1] * Q0[1]) / (L0-L1) * Q1[3] + + (Q2[1] * Q0[1]) / (L0-L2) * Q2[3] + + (Q3[1] * Q0[1]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][1][2] = (Q1[1] * Q0[2]) / (L0-L1) * Q1[0] + + (Q2[1] * Q0[2]) / (L0-L2) * Q2[0] + + (Q3[1] * Q0[2]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][1][2] = (Q1[1] * Q0[2]) / (L0-L1) * Q1[1] + + (Q2[1] * Q0[2]) / (L0-L2) * Q2[1] + + (Q3[1] * Q0[2]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][1][2] = (Q1[1] * Q0[2]) / (L0-L1) * Q1[2] + + (Q2[1] * Q0[2]) / (L0-L2) * Q2[2] + + (Q3[1] * Q0[2]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][1][2] = (Q1[1] * Q0[2]) / (L0-L1) * Q1[3] + + (Q2[1] * Q0[2]) / (L0-L2) * Q2[3] + + (Q3[1] * Q0[2]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][1][3] = (Q1[1] * Q0[3]) / (L0-L1) * Q1[0] + + (Q2[1] * Q0[3]) / (L0-L2) * Q2[0] + + (Q3[1] * Q0[3]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][1][3] = (Q1[1] * Q0[3]) / (L0-L1) * Q1[1] + + (Q2[1] * Q0[3]) / (L0-L2) * Q2[1] + + (Q3[1] * Q0[3]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][1][3] = (Q1[1] * Q0[3]) / (L0-L1) * Q1[2] + + (Q2[1] * Q0[3]) / (L0-L2) * Q2[2] + + (Q3[1] * Q0[3]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][1][3] = (Q1[1] * Q0[3]) / (L0-L1) * Q1[3] + + (Q2[1] * Q0[3]) / (L0-L2) * Q2[3] + + (Q3[1] * Q0[3]) / (L0-L3) * Q3[3]; + + + tmp_Q0Q0_L[0][2][0] = (Q1[2] * Q0[0]) / (L0-L1) * Q1[0] + + (Q2[2] * Q0[0]) / (L0-L2) * Q2[0] + + (Q3[2] * Q0[0]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][2][0] = (Q1[2] * Q0[0]) / (L0-L1) * Q1[1] + + (Q2[2] * Q0[0]) / (L0-L2) * Q2[1] + + (Q3[2] * Q0[0]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][2][0] = (Q1[2] * Q0[0]) / (L0-L1) * Q1[2] + + (Q2[2] * Q0[0]) / (L0-L2) * Q2[2] + + (Q3[2] * Q0[0]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][2][0] = (Q1[2] * Q0[0]) / (L0-L1) * Q1[3] + + (Q2[2] * Q0[0]) / (L0-L2) * Q2[3] + + (Q3[2] * Q0[0]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][2][1] = (Q1[2] * Q0[1]) / (L0-L1) * Q1[0] + + (Q2[2] * Q0[1]) / (L0-L2) * Q2[0] + + (Q3[2] * Q0[1]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][2][1] = (Q1[2] * Q0[1]) / (L0-L1) * Q1[1] + + (Q2[2] * Q0[1]) / (L0-L2) * Q2[1] + + (Q3[2] * Q0[1]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][2][1] = (Q1[2] * Q0[1]) / (L0-L1) * Q1[2] + + (Q2[2] * Q0[1]) / (L0-L2) * Q2[2] + + (Q3[2] * Q0[1]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][2][1] = (Q1[2] * Q0[1]) / (L0-L1) * Q1[3] + + (Q2[2] * Q0[1]) / (L0-L2) * Q2[3] + + (Q3[2] * Q0[1]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][2][2] = (Q1[2] * Q0[2]) / (L0-L1) * Q1[0] + + (Q2[2] * Q0[2]) / (L0-L2) * Q2[0] + + (Q3[2] * Q0[2]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][2][2] = (Q1[2] * Q0[2]) / (L0-L1) * Q1[1] + + (Q2[2] * Q0[2]) / (L0-L2) * Q2[1] + + (Q3[2] * Q0[2]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][2][2] = (Q1[2] * Q0[2]) / (L0-L1) * Q1[2] + + (Q2[2] * Q0[2]) / (L0-L2) * Q2[2] + + (Q3[2] * Q0[2]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][2][2] = (Q1[2] * Q0[2]) / (L0-L1) * Q1[3] + + (Q2[2] * Q0[2]) / (L0-L2) * Q2[3] + + (Q3[2] * Q0[2]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][2][3] = (Q1[2] * Q0[3]) / (L0-L1) * Q1[0] + + (Q2[2] * Q0[3]) / (L0-L2) * Q2[0] + + (Q3[2] * Q0[3]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][2][3] = (Q1[2] * Q0[3]) / (L0-L1) * Q1[1] + + (Q2[2] * Q0[3]) / (L0-L2) * Q2[1] + + (Q3[2] * Q0[3]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][2][3] = (Q1[2] * Q0[3]) / (L0-L1) * Q1[2] + + (Q2[2] * Q0[3]) / (L0-L2) * Q2[2] + + (Q3[2] * Q0[3]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][2][3] = (Q1[2] * Q0[3]) / (L0-L1) * Q1[3] + + (Q2[2] * Q0[3]) / (L0-L2) * Q2[3] + + (Q3[2] * Q0[3]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][3][0] = (Q1[3] * Q0[0]) / (L0-L1) * Q1[0] + + (Q2[3] * Q0[0]) / (L0-L2) * Q2[0] + + (Q3[3] * Q0[0]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][3][0] = (Q1[3] * Q0[0]) / (L0-L1) * Q1[1] + + (Q2[3] * Q0[0]) / (L0-L2) * Q2[1] + + (Q3[3] * Q0[0]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][3][0] = (Q1[3] * Q0[0]) / (L0-L1) * Q1[2] + + (Q2[3] * Q0[0]) / (L0-L2) * Q2[2] + + (Q3[3] * Q0[0]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][3][0] = (Q1[3] * Q0[0]) / (L0-L1) * Q1[3] + + (Q2[3] * Q0[0]) / (L0-L2) * Q2[3] + + (Q3[3] * Q0[0]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][3][1] = (Q1[3] * Q0[1]) / (L0-L1) * Q1[0] + + (Q2[3] * Q0[1]) / (L0-L2) * Q2[0] + + (Q3[3] * Q0[1]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][3][1] = (Q1[3] * Q0[1]) / (L0-L1) * Q1[1] + + (Q2[3] * Q0[1]) / (L0-L2) * Q2[1] + + (Q3[3] * Q0[1]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][3][1] = (Q1[3] * Q0[1]) / (L0-L1) * Q1[2] + + (Q2[3] * Q0[1]) / (L0-L2) * Q2[2] + + (Q3[3] * Q0[1]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][3][1] = (Q1[3] * Q0[1]) / (L0-L1) * Q1[3] + + (Q2[3] * Q0[1]) / (L0-L2) * Q2[3] + + (Q3[3] * Q0[1]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][3][2] = (Q1[3] * Q0[2]) / (L0-L1) * Q1[0] + + (Q2[3] * Q0[2]) / (L0-L2) * Q2[0] + + (Q3[3] * Q0[2]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][3][2] = (Q1[3] * Q0[2]) / (L0-L1) * Q1[1] + + (Q2[3] * Q0[2]) / (L0-L2) * Q2[1] + + (Q3[3] * Q0[2]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][3][2] = (Q1[3] * Q0[2]) / (L0-L1) * Q1[2] + + (Q2[3] * Q0[2]) / (L0-L2) * Q2[2] + + (Q3[3] * Q0[2]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][3][2] = (Q1[3] * Q0[2]) / (L0-L1) * Q1[3] + + (Q2[3] * Q0[2]) / (L0-L2) * Q2[3] + + (Q3[3] * Q0[2]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][3][3] = (Q1[3] * Q0[3]) / (L0-L1) * Q1[0] + + (Q2[3] * Q0[3]) / (L0-L2) * Q2[0] + + (Q3[3] * Q0[3]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][3][3] = (Q1[3] * Q0[3]) / (L0-L1) * Q1[1] + + (Q2[3] * Q0[3]) / (L0-L2) * Q2[1] + + (Q3[3] * Q0[3]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][3][3] = (Q1[3] * Q0[3]) / (L0-L1) * Q1[2] + + (Q2[3] * Q0[3]) / (L0-L2) * Q2[2] + + (Q3[3] * Q0[3]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][3][3] = (Q1[3] * Q0[3]) / (L0-L1) * Q1[3] + + (Q2[3] * Q0[3]) / (L0-L2) * Q2[3] + + (Q3[3] * Q0[3]) / (L0-L3) * Q3[3]; + } + } + /*! @brief Actual implementation of the derivative calculation + * @param[in] ds The derivative of matrix S with respect to an atom of + * either group 1 or group 2 + * @param[out] dl0_out The output of derivative of L + * @param[out] dq0_out The output of derivative of Q + * @param[out] ds_out The output of derivative of overlap matrix S + */ + void calc_derivative_impl( + const cvm::rvector (&ds)[4][4], + cvm::rvector* const dl0_out, + cvm::vector1d* const dq0_out, + cvm::matrix2d* const ds_out) const { + if (ds_out != nullptr) { + // this code path is for debug_gradients, so not necessary to unroll the loop + *ds_out = cvm::matrix2d(4, 4); + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + (*ds_out)[i][j] = ds[i][j]; + } + } + } + if (dl0_out != nullptr) { + /* manually loop unrolling of the following loop: + dl0_1.reset(); + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < 4; j++) { + dl0_1 += Q0[i] * ds_1[i][j] * Q0[j]; + } + } + */ + *dl0_out = tmp_Q0Q0[0][0] * ds[0][0] + + tmp_Q0Q0[0][1] * ds[0][1] + + tmp_Q0Q0[0][2] * ds[0][2] + + tmp_Q0Q0[0][3] * ds[0][3] + + tmp_Q0Q0[1][0] * ds[1][0] + + tmp_Q0Q0[1][1] * ds[1][1] + + tmp_Q0Q0[1][2] * ds[1][2] + + tmp_Q0Q0[1][3] * ds[1][3] + + tmp_Q0Q0[2][0] * ds[2][0] + + tmp_Q0Q0[2][1] * ds[2][1] + + tmp_Q0Q0[2][2] * ds[2][2] + + tmp_Q0Q0[2][3] * ds[2][3] + + tmp_Q0Q0[3][0] * ds[3][0] + + tmp_Q0Q0[3][1] * ds[3][1] + + tmp_Q0Q0[3][2] * ds[3][2] + + tmp_Q0Q0[3][3] * ds[3][3]; + } + if (dq0_out != nullptr) { + // we can skip this check if a fixed-size array is used + if (dq0_out->size() != 4) dq0_out->resize(4); + /* manually loop unrolling of the following loop: + dq0_1.reset(); + for (size_t p = 0; p < 4; p++) { + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < 4; j++) { + dq0_1[p] += + (Q1[i] * ds_1[i][j] * Q0[j]) / (L0-L1) * Q1[p] + + (Q2[i] * ds_1[i][j] * Q0[j]) / (L0-L2) * Q2[p] + + (Q3[i] * ds_1[i][j] * Q0[j]) / (L0-L3) * Q3[p]; + } + } + } + */ + (*dq0_out)[0] = tmp_Q0Q0_L[0][0][0] * ds[0][0] + + tmp_Q0Q0_L[0][0][1] * ds[0][1] + + tmp_Q0Q0_L[0][0][2] * ds[0][2] + + tmp_Q0Q0_L[0][0][3] * ds[0][3] + + tmp_Q0Q0_L[0][1][0] * ds[1][0] + + tmp_Q0Q0_L[0][1][1] * ds[1][1] + + tmp_Q0Q0_L[0][1][2] * ds[1][2] + + tmp_Q0Q0_L[0][1][3] * ds[1][3] + + tmp_Q0Q0_L[0][2][0] * ds[2][0] + + tmp_Q0Q0_L[0][2][1] * ds[2][1] + + tmp_Q0Q0_L[0][2][2] * ds[2][2] + + tmp_Q0Q0_L[0][2][3] * ds[2][3] + + tmp_Q0Q0_L[0][3][0] * ds[3][0] + + tmp_Q0Q0_L[0][3][1] * ds[3][1] + + tmp_Q0Q0_L[0][3][2] * ds[3][2] + + tmp_Q0Q0_L[0][3][3] * ds[3][3]; + + (*dq0_out)[1] = tmp_Q0Q0_L[1][0][0] * ds[0][0] + + tmp_Q0Q0_L[1][0][1] * ds[0][1] + + tmp_Q0Q0_L[1][0][2] * ds[0][2] + + tmp_Q0Q0_L[1][0][3] * ds[0][3] + + tmp_Q0Q0_L[1][1][0] * ds[1][0] + + tmp_Q0Q0_L[1][1][1] * ds[1][1] + + tmp_Q0Q0_L[1][1][2] * ds[1][2] + + tmp_Q0Q0_L[1][1][3] * ds[1][3] + + tmp_Q0Q0_L[1][2][0] * ds[2][0] + + tmp_Q0Q0_L[1][2][1] * ds[2][1] + + tmp_Q0Q0_L[1][2][2] * ds[2][2] + + tmp_Q0Q0_L[1][2][3] * ds[2][3] + + tmp_Q0Q0_L[1][3][0] * ds[3][0] + + tmp_Q0Q0_L[1][3][1] * ds[3][1] + + tmp_Q0Q0_L[1][3][2] * ds[3][2] + + tmp_Q0Q0_L[1][3][3] * ds[3][3]; + + (*dq0_out)[2] = tmp_Q0Q0_L[2][0][0] * ds[0][0] + + tmp_Q0Q0_L[2][0][1] * ds[0][1] + + tmp_Q0Q0_L[2][0][2] * ds[0][2] + + tmp_Q0Q0_L[2][0][3] * ds[0][3] + + tmp_Q0Q0_L[2][1][0] * ds[1][0] + + tmp_Q0Q0_L[2][1][1] * ds[1][1] + + tmp_Q0Q0_L[2][1][2] * ds[1][2] + + tmp_Q0Q0_L[2][1][3] * ds[1][3] + + tmp_Q0Q0_L[2][2][0] * ds[2][0] + + tmp_Q0Q0_L[2][2][1] * ds[2][1] + + tmp_Q0Q0_L[2][2][2] * ds[2][2] + + tmp_Q0Q0_L[2][2][3] * ds[2][3] + + tmp_Q0Q0_L[2][3][0] * ds[3][0] + + tmp_Q0Q0_L[2][3][1] * ds[3][1] + + tmp_Q0Q0_L[2][3][2] * ds[3][2] + + tmp_Q0Q0_L[2][3][3] * ds[3][3]; + + (*dq0_out)[3] = tmp_Q0Q0_L[3][0][0] * ds[0][0] + + tmp_Q0Q0_L[3][0][1] * ds[0][1] + + tmp_Q0Q0_L[3][0][2] * ds[0][2] + + tmp_Q0Q0_L[3][0][3] * ds[0][3] + + tmp_Q0Q0_L[3][1][0] * ds[1][0] + + tmp_Q0Q0_L[3][1][1] * ds[1][1] + + tmp_Q0Q0_L[3][1][2] * ds[1][2] + + tmp_Q0Q0_L[3][1][3] * ds[1][3] + + tmp_Q0Q0_L[3][2][0] * ds[2][0] + + tmp_Q0Q0_L[3][2][1] * ds[2][1] + + tmp_Q0Q0_L[3][2][2] * ds[2][2] + + tmp_Q0Q0_L[3][2][3] * ds[2][3] + + tmp_Q0Q0_L[3][3][0] * ds[3][0] + + tmp_Q0Q0_L[3][3][1] * ds[3][1] + + tmp_Q0Q0_L[3][3][2] * ds[3][2] + + tmp_Q0Q0_L[3][3][3] * ds[3][3]; + } + } + /*! @brief Calculate the derivatives of S, the leading eigenvalue L and + * the leading eigenvector Q with respect to `m_pos1` + * @param[in] ia The index the of atom + * @param[out] dl0_1_out The output of derivative of L with respect to + * ia-th atom of group 1 + * @param[out] dq0_1_out The output of derivative of Q with respect to + * ia-th atom of group 1 + * @param[out] ds_1_out The output of derivative of overlap matrix S with + * respect to ia-th atom of group 1 + */ + void calc_derivative_wrt_group1( + size_t ia, cvm::rvector* const dl0_1_out = nullptr, + cvm::vector1d* const dq0_1_out = nullptr, + cvm::matrix2d* const ds_1_out = nullptr) const { + if (dl0_1_out == nullptr && dq0_1_out == nullptr) return; + cvm::real a2x, a2y, a2z; + // we can get rid of the helper function read_atom_coord if C++17 (constexpr) is available + read_atom_coord(ia, m_pos2, &a2x, &a2y, &a2z); + cvm::rvector ds_1[4][4]; + ds_1[0][0].set( a2x, a2y, a2z); + ds_1[1][0].set( 0.0, a2z, -a2y); + ds_1[0][1] = ds_1[1][0]; + ds_1[2][0].set(-a2z, 0.0, a2x); + ds_1[0][2] = ds_1[2][0]; + ds_1[3][0].set( a2y, -a2x, 0.0); + ds_1[0][3] = ds_1[3][0]; + ds_1[1][1].set( a2x, -a2y, -a2z); + ds_1[2][1].set( a2y, a2x, 0.0); + ds_1[1][2] = ds_1[2][1]; + ds_1[3][1].set( a2z, 0.0, a2x); + ds_1[1][3] = ds_1[3][1]; + ds_1[2][2].set(-a2x, a2y, -a2z); + ds_1[3][2].set( 0.0, a2z, a2y); + ds_1[2][3] = ds_1[3][2]; + ds_1[3][3].set(-a2x, -a2y, a2z); + calc_derivative_impl(ds_1, dl0_1_out, dq0_1_out, ds_1_out); + } + /*! @brief Calculate the derivatives of S, the leading eigenvalue L and + * the leading eigenvector Q with respect to `m_pos2` + * @param[in] ia The index the of atom + * @param[out] dl0_2_out The output of derivative of L with respect to + * ia-th atom of group 2 + * @param[out] dq0_2_out The output of derivative of Q with respect to + * ia-th atom of group 2 + * @param[out] ds_2_out The output of derivative of overlap matrix S with + * respect to ia-th atom of group 2 + */ + void calc_derivative_wrt_group2( + size_t ia, cvm::rvector* const dl0_2_out = nullptr, + cvm::vector1d* const dq0_2_out = nullptr, + cvm::matrix2d* const ds_2_out = nullptr) const { + if (dl0_2_out == nullptr && dq0_2_out == nullptr) return; + cvm::real a1x, a1y, a1z; + // we can get rid of the helper function read_atom_coord if C++17 (constexpr) is available + read_atom_coord(ia, m_pos1, &a1x, &a1y, &a1z); + cvm::rvector ds_2[4][4]; + ds_2[0][0].set( a1x, a1y, a1z); + ds_2[1][0].set( 0.0, -a1z, a1y); + ds_2[0][1] = ds_2[1][0]; + ds_2[2][0].set( a1z, 0.0, -a1x); + ds_2[0][2] = ds_2[2][0]; + ds_2[3][0].set(-a1y, a1x, 0.0); + ds_2[0][3] = ds_2[3][0]; + ds_2[1][1].set( a1x, -a1y, -a1z); + ds_2[2][1].set( a1y, a1x, 0.0); + ds_2[1][2] = ds_2[2][1]; + ds_2[3][1].set( a1z, 0.0, a1x); + ds_2[1][3] = ds_2[3][1]; + ds_2[2][2].set(-a1x, a1y, -a1z); + ds_2[3][2].set( 0.0, a1z, a1y); + ds_2[2][3] = ds_2[3][2]; + ds_2[3][3].set(-a1x, -a1y, a1z); + calc_derivative_impl(ds_2, dl0_2_out, dq0_2_out, ds_2_out); + } +}; + +/*! @brief Function for debugging gradients (allow using either + * std::vector or std::vector for + * pos1 and pos2) + * @param[in] pos1 Atom positions of group 1 + * @param[in] pos2 Atom positions of group 2 + */ +template +void debug_gradients( + cvm::rotation &rot, + const std::vector &pos1, + const std::vector &pos2) { + static_assert(std::is_same::value || std::is_same::value, ""); + static_assert(std::is_same::value || std::is_same::value, ""); + // eigenvalues and eigenvectors + cvm::real const L0 = rot.S_eigval[0]; + cvm::real const L1 = rot.S_eigval[1]; + cvm::real const L2 = rot.S_eigval[2]; + cvm::real const L3 = rot.S_eigval[3]; + cvm::quaternion const Q0(rot.S_eigvec[0]); + cvm::quaternion const Q1(rot.S_eigvec[1]); + cvm::quaternion const Q2(rot.S_eigvec[2]); + cvm::quaternion const Q3(rot.S_eigvec[3]); + + cvm::log("L0 = "+cvm::to_str(L0, cvm::cv_width, cvm::cv_prec)+ + ", Q0 = "+cvm::to_str(Q0, cvm::cv_width, cvm::cv_prec)+ + ", Q0*Q0 = "+cvm::to_str(Q0.inner(Q0), cvm::cv_width, cvm::cv_prec)+ + "\n"); + cvm::log("L1 = "+cvm::to_str(L1, cvm::cv_width, cvm::cv_prec)+ + ", Q1 = "+cvm::to_str(Q1, cvm::cv_width, cvm::cv_prec)+ + ", Q0*Q1 = "+cvm::to_str(Q0.inner(Q1), cvm::cv_width, cvm::cv_prec)+ + "\n"); + cvm::log("L2 = "+cvm::to_str(L2, cvm::cv_width, cvm::cv_prec)+ + ", Q2 = "+cvm::to_str(Q2, cvm::cv_width, cvm::cv_prec)+ + ", Q0*Q2 = "+cvm::to_str(Q0.inner(Q2), cvm::cv_width, cvm::cv_prec)+ + "\n"); + cvm::log("L3 = "+cvm::to_str(L3, cvm::cv_width, cvm::cv_prec)+ + ", Q3 = "+cvm::to_str(Q3, cvm::cv_width, cvm::cv_prec)+ + ", Q0*Q3 = "+cvm::to_str(Q0.inner(Q3), cvm::cv_width, cvm::cv_prec)+ + "\n"); + + rotation_derivative deriv(rot, pos1, pos2); + cvm::rvector dl0_2; + cvm::vector1d dq0_2(4); + cvm::matrix2d ds_2; +#ifdef COLVARS_LAMMPS + MathEigen::Jacobi *ecalc = + reinterpret_cast *>(rot.jacobi); +#endif + deriv.prepare_derivative(rotation_derivative_dldq::use_dl | rotation_derivative_dldq::use_dq); + cvm::real S_new[4][4]; + cvm::real S_new_eigval[4]; + cvm::real S_new_eigvec[4][4]; + for (size_t ia = 0; ia < pos2.size(); ++ia) { + // cvm::real const &a1x = pos1[ia].x; + // cvm::real const &a1y = pos1[ia].y; + // cvm::real const &a1z = pos1[ia].z; + deriv.calc_derivative_wrt_group2(ia, &dl0_2, &dq0_2, &ds_2); + // make an infitesimal move along each cartesian coordinate of + // this atom, and solve again the eigenvector problem + for (size_t comp = 0; comp < 3; comp++) { + std::memcpy(S_new, rot.S_backup, sizeof(cvm::real) * 4 * 4); + std::memset(S_new_eigval, 0, sizeof(cvm::real) * 4); + std::memset(S_new_eigvec, 0, sizeof(cvm::real) * 4 * 4); + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < 4; j++) { + S_new[i][j] += + colvarmodule::debug_gradients_step_size * ds_2[i][j][comp]; + } + } +#ifdef COLVARS_LAMMPS + ecalc->Diagonalize(S_new, S_new_eigval, S_new_eigvec); +#else + NR::diagonalize_matrix(S_new, S_new_eigval, S_new_eigvec); +#endif + cvm::real const &L0_new = S_new_eigval[0]; + cvm::quaternion const Q0_new(S_new_eigvec[0]); + + cvm::real const DL0 = (dl0_2[comp]) * colvarmodule::debug_gradients_step_size; + cvm::quaternion const DQ0(dq0_2[0][comp] * colvarmodule::debug_gradients_step_size, + dq0_2[1][comp] * colvarmodule::debug_gradients_step_size, + dq0_2[2][comp] * colvarmodule::debug_gradients_step_size, + dq0_2[3][comp] * colvarmodule::debug_gradients_step_size); + + cvm::log( "|(l_0+dl_0) - l_0^new|/l_0 = "+ + cvm::to_str(cvm::fabs(L0+DL0 - L0_new)/L0, cvm::cv_width, cvm::cv_prec)+ + ", |(q_0+dq_0) - q_0^new| = "+ + cvm::to_str((Q0+DQ0 - Q0_new).norm(), cvm::cv_width, cvm::cv_prec)+ + "\n"); + } + } +} + +#endif // COLVAR_ROTATION_DERIVATIVE diff --git a/lib/colvars/colvaratoms.cpp b/lib/colvars/colvaratoms.cpp index e31041ea6b..e15b9301a1 100644 --- a/lib/colvars/colvaratoms.cpp +++ b/lib/colvars/colvaratoms.cpp @@ -13,10 +13,12 @@ #include #include +#include "colvardeps.h" #include "colvarmodule.h" #include "colvarproxy.h" #include "colvarparse.h" #include "colvaratoms.h" +#include "colvar_rotation_derivative.h" cvm::atom::atom() @@ -118,6 +120,11 @@ cvm::atom_group::~atom_group() fitting_group = NULL; } + if (rot_deriv != nullptr) { + delete rot_deriv; + rot_deriv = nullptr; + } + cvm::main()->unregister_named_atom_group(this); } @@ -226,6 +233,7 @@ int cvm::atom_group::init() b_dummy = false; b_user_defined_fit = false; fitting_group = NULL; + rot_deriv = nullptr; noforce = false; @@ -278,7 +286,7 @@ int cvm::atom_group::init_dependencies() { // Initialize feature_states for each instance // default as unavailable, not enabled feature_states.reserve(f_ag_ntot); - for (i = 0; i < colvardeps::f_ag_ntot; i++) { + for (i = feature_states.size(); i < colvardeps::f_ag_ntot; i++) { feature_states.push_back(feature_state(false, false)); } @@ -317,6 +325,13 @@ int cvm::atom_group::setup() return COLVARS_OK; } +void cvm::atom_group::setup_rotation_derivative() { + if (rot_deriv != nullptr) delete rot_deriv; + rot_deriv = new rotation_derivative( + rot, fitting_group ? fitting_group->atoms : this->atoms, ref_pos + ); +} + void cvm::atom_group::update_total_mass() { @@ -383,7 +398,7 @@ int cvm::atom_group::parse(std::string const &group_conf) // } // colvarparse::Parse_Mode mode = parse_normal; - int parse_error = COLVARS_OK; + int error_code = COLVARS_OK; // Optional group name will let other groups reuse atom definition if (get_keyval(group_conf, "name", name)) { @@ -433,7 +448,7 @@ int cvm::atom_group::parse(std::string const &group_conf) cvm::error("Error: cannot find atom group with name " + atoms_of + ".\n"); return COLVARS_ERROR; } - parse_error |= add_atoms_of_group(ag); + error_code |= add_atoms_of_group(ag); } } @@ -447,7 +462,7 @@ int cvm::atom_group::parse(std::string const &group_conf) std::string numbers_conf = ""; size_t pos = 0; while (key_lookup(group_conf, "atomNumbers", &numbers_conf, &pos)) { - parse_error |= add_atom_numbers(numbers_conf); + error_code |= add_atom_numbers(numbers_conf); numbers_conf = ""; } } @@ -456,7 +471,7 @@ int cvm::atom_group::parse(std::string const &group_conf) std::string index_group_name; if (get_keyval(group_conf, "indexGroup", index_group_name)) { // use an index group from the index file read globally - parse_error |= add_index_group(index_group_name); + error_code |= add_index_group(index_group_name); } } @@ -465,7 +480,7 @@ int cvm::atom_group::parse(std::string const &group_conf) size_t pos = 0; while (key_lookup(group_conf, "atomNumbersRange", &range_conf, &pos)) { - parse_error |= add_atom_numbers_range(range_conf); + error_code |= add_atom_numbers_range(range_conf); range_conf = ""; } } @@ -492,7 +507,7 @@ int cvm::atom_group::parse(std::string const &group_conf) cvm::error("Error: more instances of \"atomNameResidueRange\" than " "values of \"psfSegID\".\n", COLVARS_INPUT_ERROR); } else { - parse_error |= add_atom_name_residue_range(psf_segids.size() ? + error_code |= add_atom_name_residue_range(psf_segids.size() ? *psii : std::string(""), range_conf); if (psf_segids.size()) psii++; } @@ -517,26 +532,26 @@ int cvm::atom_group::parse(std::string const &group_conf) cvm::error("Error: atomsColValue, if provided, must be non-zero.\n", COLVARS_INPUT_ERROR); } - // NOTE: calls to add_atom() and/or add_atom_id() are in the proxy-implemented function - parse_error |= cvm::load_atoms(atoms_file_name.c_str(), *this, atoms_col, atoms_col_value); + error_code |= cvm::main()->proxy->load_atoms_pdb(atoms_file_name.c_str(), *this, atoms_col, + atoms_col_value); } } // Catch any errors from all the initialization steps above - if (parse_error || cvm::get_error()) return (parse_error || cvm::get_error()); + if (error_code || cvm::get_error()) return (error_code || cvm::get_error()); // checks of doubly-counted atoms have been handled by add_atom() already if (get_keyval(group_conf, "dummyAtom", dummy_atom_pos, cvm::atom_pos())) { - parse_error |= set_dummy(); - parse_error |= set_dummy_pos(dummy_atom_pos); + error_code |= set_dummy(); + error_code |= set_dummy_pos(dummy_atom_pos); } else { if (!(atoms_ids.size())) { - parse_error |= cvm::error("Error: no atoms defined for atom group \""+ - key+"\".\n", COLVARS_INPUT_ERROR); + error_code |= cvm::error("Error: no atoms defined for atom group \"" + key + "\".\n", + COLVARS_INPUT_ERROR); } // whether these atoms will ever receive forces or not @@ -546,7 +561,7 @@ int cvm::atom_group::parse(std::string const &group_conf) } // Now that atoms are defined we can parse the detailed fitting options - parse_error |= parse_fitting_options(group_conf); + error_code |= parse_fitting_options(group_conf); if (is_enabled(f_ag_scalable) && !b_dummy) { cvm::log("Enabling scalable calculation for group \""+this->key+"\".\n"); @@ -583,7 +598,9 @@ int cvm::atom_group::parse(std::string const &group_conf) cvm::log(print_atom_ids()); } - return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); + if (is_enabled(f_ag_rotate)) setup_rotation_derivative(); + + return error_code; } @@ -883,8 +900,6 @@ int cvm::atom_group::parse_fitting_options(std::string const &group_conf) "to its radius of gyration), the optimal rotation and its gradients may become discontinuous. " "If that happens, use fittingGroup (or a different definition for it if already defined) " "to align the coordinates.\n"); - // initialize rot member data - rot.request_group1_gradients(group_for_fit->size()); } } @@ -912,7 +927,6 @@ void cvm::atom_group::do_feature_side_effects(int id) if (is_enabled(f_ag_center) || is_enabled(f_ag_rotate)) { atom_group *group_for_fit = fitting_group ? fitting_group : this; group_for_fit->fit_gradients.assign(group_for_fit->size(), cvm::atom_pos(0.0, 0.0, 0.0)); - rot.request_group1_gradients(group_for_fit->size()); } break; } @@ -1045,17 +1059,18 @@ void cvm::atom_group::calc_apply_roto_translation() // rotate the group (around the center of geometry if f_ag_center is // enabled, around the origin otherwise) rot.calc_optimal_rotation(fitting_group ? - fitting_group->positions() : - this->positions(), + fitting_group->atoms: + this->atoms, ref_pos); + const auto rot_mat = rot.matrix(); cvm::atom_iter ai; for (ai = this->begin(); ai != this->end(); ai++) { - ai->pos = rot.rotate(ai->pos); + ai->pos = rot_mat * ai->pos; } if (fitting_group) { for (ai = fitting_group->begin(); ai != fitting_group->end(); ai++) { - ai->pos = rot.rotate(ai->pos); + ai->pos = rot_mat * ai->pos; } } } @@ -1095,9 +1110,10 @@ void cvm::atom_group::read_velocities() if (is_enabled(f_ag_rotate)) { + const auto rot_mat = rot.matrix(); for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { ai->read_velocity(); - ai->vel = rot.rotate(ai->vel); + ai->vel = rot_mat * ai->vel; } } else { @@ -1116,9 +1132,10 @@ void cvm::atom_group::read_total_forces() if (is_enabled(f_ag_rotate)) { + const auto rot_mat = rot.matrix(); for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { ai->read_total_force(); - ai->total_force = rot.rotate(ai->total_force); + ai->total_force = rot_mat * ai->total_force; } } else { @@ -1200,52 +1217,71 @@ void cvm::atom_group::calc_fit_gradients() if (cvm::debug()) cvm::log("Calculating fit gradients.\n"); - cvm::atom_group *group_for_fit = fitting_group ? fitting_group : this; - - if (is_enabled(f_ag_center)) { - // add the center of geometry contribution to the gradients - cvm::rvector atom_grad; - - for (size_t i = 0; i < this->size(); i++) { - atom_grad += atoms[i].grad; - } - if (is_enabled(f_ag_rotate)) atom_grad = (rot.inverse()).rotate(atom_grad); - atom_grad *= (-1.0)/(cvm::real(group_for_fit->size())); - - for (size_t j = 0; j < group_for_fit->size(); j++) { - group_for_fit->fit_gradients[j] = atom_grad; - } - } - - if (is_enabled(f_ag_rotate)) { - - // add the rotation matrix contribution to the gradients - cvm::rotation const rot_inv = rot.inverse(); - - for (size_t i = 0; i < this->size(); i++) { - - // compute centered, unrotated position - cvm::atom_pos const pos_orig = - rot_inv.rotate((is_enabled(f_ag_center) ? (atoms[i].pos - ref_pos_cog) : (atoms[i].pos))); - - // calculate \partial(R(q) \vec{x}_i)/\partial q) \cdot \partial\xi/\partial\vec{x}_i - cvm::quaternion const dxdq = - rot.q.position_derivative_inner(pos_orig, atoms[i].grad); - - for (size_t j = 0; j < group_for_fit->size(); j++) { - // multiply by {\partial q}/\partial\vec{x}_j and add it to the fit gradients - for (size_t iq = 0; iq < 4; iq++) { - group_for_fit->fit_gradients[j] += dxdq[iq] * rot.dQ0_1[j][iq]; - } - } - } - } + if (is_enabled(f_ag_center) && is_enabled(f_ag_rotate)) + calc_fit_gradients_impl(); + if (is_enabled(f_ag_center) && !is_enabled(f_ag_rotate)) + calc_fit_gradients_impl(); + if (!is_enabled(f_ag_center) && is_enabled(f_ag_rotate)) + calc_fit_gradients_impl(); + if (!is_enabled(f_ag_center) && !is_enabled(f_ag_rotate)) + calc_fit_gradients_impl(); if (cvm::debug()) cvm::log("Done calculating fit gradients.\n"); } +template +void cvm::atom_group::calc_fit_gradients_impl() { + cvm::atom_group *group_for_fit = fitting_group ? fitting_group : this; + // the center of geometry contribution to the gradients + cvm::rvector atom_grad; + // the rotation matrix contribution to the gradients + const auto rot_inv = rot.inverse().matrix(); + // temporary variables for computing and summing derivatives + cvm::real sum_dxdq[4] = {0, 0, 0, 0}; + cvm::vector1d dq0_1(4); + // loop 1: iterate over the current atom group + for (size_t i = 0; i < size(); i++) { + cvm::atom_pos pos_orig; + if (B_ag_center) { + atom_grad += atoms[i].grad; + if (B_ag_rotate) pos_orig = rot_inv * (atoms[i].pos - ref_pos_cog); + } else { + if (B_ag_rotate) pos_orig = atoms[i].pos; + } + if (B_ag_rotate) { + // calculate \partial(R(q) \vec{x}_i)/\partial q) \cdot \partial\xi/\partial\vec{x}_i + cvm::quaternion const dxdq = + rot.q.position_derivative_inner(pos_orig, atoms[i].grad); + sum_dxdq[0] += dxdq[0]; + sum_dxdq[1] += dxdq[1]; + sum_dxdq[2] += dxdq[2]; + sum_dxdq[3] += dxdq[3]; + } + } + if (B_ag_center) { + if (B_ag_rotate) atom_grad = rot.inverse().matrix() * atom_grad; + atom_grad *= (-1.0)/(cvm::real(group_for_fit->size())); + } + // loop 2: iterate over the fitting group + if (B_ag_rotate) rot_deriv->prepare_derivative(rotation_derivative_dldq::use_dq); + for (size_t j = 0; j < group_for_fit->size(); j++) { + if (B_ag_center) { + group_for_fit->fit_gradients[j] = atom_grad; + } + if (B_ag_rotate) { + rot_deriv->calc_derivative_wrt_group1(j, nullptr, &dq0_1); + // multiply by {\partial q}/\partial\vec{x}_j and add it to the fit gradients + group_for_fit->fit_gradients[j] += sum_dxdq[0] * dq0_1[0] + + sum_dxdq[1] * dq0_1[1] + + sum_dxdq[2] * dq0_1[2] + + sum_dxdq[3] * dq0_1[3]; + } + } +} + + std::vector cvm::atom_group::positions() const { if (b_dummy) { @@ -1373,9 +1409,9 @@ void cvm::atom_group::apply_colvar_force(cvm::real const &force) if (is_enabled(f_ag_rotate)) { // rotate forces back to the original frame - cvm::rotation const rot_inv = rot.inverse(); + const auto rot_inv = rot.inverse().matrix(); for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { - ai->apply_force(rot_inv.rotate(force * ai->grad)); + ai->apply_force(rot_inv * (force * ai->grad)); } } else { @@ -1418,9 +1454,9 @@ void cvm::atom_group::apply_force(cvm::rvector const &force) if (is_enabled(f_ag_rotate)) { - cvm::rotation const rot_inv = rot.inverse(); + const auto rot_inv = rot.inverse().matrix(); for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { - ai->apply_force(rot_inv.rotate((ai->mass/total_mass) * force)); + ai->apply_force(rot_inv * ((ai->mass/total_mass) * force)); } } else { diff --git a/lib/colvars/colvaratoms.h b/lib/colvars/colvaratoms.h index 84c5ee137e..d16ca7bd56 100644 --- a/lib/colvars/colvaratoms.h +++ b/lib/colvars/colvaratoms.h @@ -15,6 +15,9 @@ #include "colvarparse.h" #include "colvardeps.h" +template +struct rotation_derivative; + /// \brief Stores numeric id, mass and all mutable data for an atom, /// mostly used by a \link colvar::cvc \endlink @@ -167,7 +170,7 @@ public: atom_group(std::vector const &atoms_in); /// \brief Destructor - ~atom_group(); + ~atom_group() override; /// \brief Optional name to reuse properties of this in other groups std::string name; @@ -180,7 +183,7 @@ public: int init(); /// \brief Initialize dependency tree - virtual int init_dependencies(); + int init_dependencies() override; /// \brief Update data required to calculate cvc's int setup(); @@ -221,16 +224,13 @@ public: static std::vector ag_features; /// \brief Implementation of the feature list accessor for atom group - virtual const std::vector &features() const + const std::vector &features() const override { return ag_features; } + + std::vector &modify_features() override { return ag_features; } + + static void delete_features() { - return ag_features; - } - virtual std::vector &modify_features() - { - return ag_features; - } - static void delete_features() { - for (size_t i=0; i < ag_features.size(); i++) { + for (size_t i = 0; i < ag_features.size(); i++) { delete ag_features[i]; } ag_features.clear(); @@ -330,6 +330,9 @@ public: /// The rotation calculated automatically if f_ag_rotate is defined cvm::rotation rot; + /// Rotation derivative; + rotation_derivative* rot_deriv; + /// \brief Indicates that the user has explicitly set centerToReference or /// rotateReference, and the corresponding reference: /// cvc's (eg rmsd, eigenvector) will not override the user's choice @@ -369,6 +372,8 @@ public: /// \brief (Re)calculate the optimal roto-translation void calc_apply_roto_translation(); + void setup_rotation_derivative(); + /// \brief Save aside the center of geometry of the reference positions, /// then subtract it from them /// @@ -492,6 +497,16 @@ public: /// \brief Calculate the derivatives of the fitting transformation void calc_fit_gradients(); +/*! @brief Actual implementation of `calc_fit_gradients`. The template is + * used to avoid branching inside the loops in case that the CPU + * branch prediction is broken (or further migration to GPU code). + * @tparam B_ag_center Centered the reference to origin? This should follow + * the value of `is_enabled(f_ag_center)`. + * @tparam B_ag_rotate Calculate the optimal rotation? This should follow + * the value of `is_enabled(f_ag_rotate)`. + */ + template void calc_fit_gradients_impl(); + /// \brief Derivatives of the fitting transformation std::vector fit_gradients; @@ -525,7 +540,7 @@ public: /// Implements possible actions to be carried out /// when a given feature is enabled /// This overloads the base function in colvardeps - void do_feature_side_effects(int id); + void do_feature_side_effects(int id) override; }; diff --git a/lib/colvars/colvarbias.cpp b/lib/colvars/colvarbias.cpp index 18969fc531..fdffdc1794 100644 --- a/lib/colvars/colvarbias.cpp +++ b/lib/colvars/colvarbias.cpp @@ -15,6 +15,7 @@ #include "colvarvalue.h" #include "colvarbias.h" #include "colvargrid.h" +#include "colvars_memstream.h" colvarbias::colvarbias(char const *key) @@ -166,6 +167,10 @@ int colvarbias::init_dependencies() { init_feature(f_cvb_get_total_force, "obtain_total_force", f_type_dynamic); require_feature_children(f_cvb_get_total_force, f_cv_total_force); + // Depending on back-end, we may not obtain total force at step 0 + if (!cvm::main()->proxy->total_forces_same_step()) { + exclude_feature_self(f_cvb_get_total_force, f_cvb_step_zero_data); + } init_feature(f_cvb_output_acc_work, "output_accumulated_work", f_type_user); require_feature_self(f_cvb_output_acc_work, f_cvb_apply_force); @@ -192,6 +197,8 @@ int colvarbias::init_dependencies() { init_feature(f_cvb_scale_biasing_force, "scale_biasing_force", f_type_user); require_feature_children(f_cvb_scale_biasing_force, f_cv_grid); + init_feature(f_cvb_extended, "Bias on extended-Lagrangian variables", f_type_static); + // check that everything is initialized for (i = 0; i < colvardeps::f_cvb_ntot; i++) { if (is_not_set(i)) { @@ -202,7 +209,7 @@ int colvarbias::init_dependencies() { // Initialize feature_states for each instance feature_states.reserve(f_cvb_ntot); - for (i = 0; i < f_cvb_ntot; i++) { + for (i = feature_states.size(); i < f_cvb_ntot; i++) { feature_states.push_back(feature_state(true, false)); // Most features are available, so we set them so // and list exceptions below @@ -436,43 +443,55 @@ int colvarbias::bin_num() cvm::error("Error: bin_num() not implemented.\n"); return COLVARS_NOT_IMPLEMENTED; } + int colvarbias::current_bin() { cvm::error("Error: current_bin() not implemented.\n"); return COLVARS_NOT_IMPLEMENTED; } + int colvarbias::bin_count(int /* bin_index */) { cvm::error("Error: bin_count() not implemented.\n"); return COLVARS_NOT_IMPLEMENTED; } + +int colvarbias::local_sample_count(int /* radius */) +{ + cvm::error("Error: local_sample_count() not implemented.\n"); + return COLVARS_NOT_IMPLEMENTED; +} + int colvarbias::replica_share() { cvm::error("Error: replica_share() not implemented.\n"); return COLVARS_NOT_IMPLEMENTED; } +size_t colvarbias::replica_share_freq() const +{ + return 0; +} + std::string const colvarbias::get_state_params() const { std::ostringstream os; - os << "step " << cvm::step_absolute() << "\n" - << "name " << this->name << "\n"; + os << " step " << cvm::step_absolute() << "\n" + << " name " << this->name << "\n"; return os.str(); } -int colvarbias::set_state_params(std::string const &conf) +int colvarbias::check_matching_state(std::string const &conf) { - matching_state = false; - std::string check_name = ""; colvarparse::get_keyval(conf, "name", check_name, std::string(""), colvarparse::parse_silent); if (check_name.size() == 0) { - cvm::error("Error: \""+bias_type+"\" block within the restart file " - "has no identifiers.\n", COLVARS_INPUT_ERROR); + return cvm::error("Error: \""+bias_type+"\" block within the state file " + "has no identifiers.\n", COLVARS_INPUT_ERROR); } if (check_name != this->name) { @@ -480,11 +499,17 @@ int colvarbias::set_state_params(std::string const &conf) cvm::log("Ignoring state of bias \""+check_name+ "\": this bias is named \""+name+"\".\n"); } - return COLVARS_OK; + matching_state = false; + } else { + matching_state = true; } - matching_state = true; + return COLVARS_OK; +} + +int colvarbias::set_state_params(std::string const &conf) +{ colvarparse::get_keyval(conf, "step", state_file_step, cvm::step_absolute(), colvarparse::parse_silent); @@ -495,76 +520,119 @@ int colvarbias::set_state_params(std::string const &conf) std::ostream & colvarbias::write_state(std::ostream &os) { if (cvm::debug()) { - cvm::log("Writing state file for bias \""+name+"\"\n"); + cvm::log("Writing formatted state for bias \""+name+"\"\n"); } os.setf(std::ios::scientific, std::ios::floatfield); os.precision(cvm::cv_prec); os << state_keyword << " {\n" - << " configuration {\n"; - std::istringstream is(get_state_params()); - std::string line; - while (std::getline(is, line)) { - os << " " << line << "\n"; - } - os << " }\n"; + << " configuration {\n" + << get_state_params() + << " }\n"; write_state_data(os); os << "}\n\n"; return os; } -std::istream & colvarbias::read_state(std::istream &is) +cvm::memory_stream & colvarbias::write_state(cvm::memory_stream &os) { - std::streampos const start_pos = is.tellg(); + if (cvm::debug()) { + cvm::log("Writing unformatted state for bias \""+name+"\"\n"); + } + os << state_keyword << std::string("configuration") << get_state_params(); + write_state_data(os); + return os; +} + + +template +void raise_error_rewind(IST &is, SPT start_pos, std::string const &bias_type, + std::string const &bias_name, std::string const added_msg = "") +{ + auto state = is.rdstate(); + is.clear(); + is.seekg(start_pos); + is.setstate(state | std::ios::failbit); + cvm::error("Error: in reading state for \"" + bias_type + "\" bias \"" + bias_name + + "\" at position " + cvm::to_str(static_cast(is.tellg())) + " in stream." + + added_msg + "\n", + COLVARS_INPUT_ERROR); +} + + +template IST & colvarbias::read_state_template_(IST &is) +{ + auto const start_pos = is.tellg(); std::string key, brace, conf; - if ( !(is >> key) || !(key == state_keyword || key == bias_type) || - !(is >> brace) || !(brace == "{") || - !(is >> colvarparse::read_block("configuration", &conf)) || - (set_state_params(conf) != COLVARS_OK) ) { - cvm::error("Error: in reading state configuration for \""+bias_type+ - "\" bias \""+ - this->name+"\" at position "+ - cvm::to_str(static_cast(is.tellg()))+ - " in stream.\n", COLVARS_INPUT_ERROR); - is.clear(); - is.seekg(start_pos, std::ios::beg); - is.setstate(std::ios::failbit); + if (is >> key) { + if (key == state_keyword || key == bias_type) { + + if (! std::is_same::value) { + // Formatted input only + if (!(is >> brace) || !(brace == "{") ) { + raise_error_rewind(is, start_pos, bias_type, name); + return is; + } + } + + if (!(is >> colvarparse::read_block("configuration", &conf)) || + (check_matching_state(conf) != COLVARS_OK)) { + raise_error_rewind(is, start_pos, bias_type, name); + return is; + } + + } else { + // Not a match for this bias type, rewind without error + is.seekg(start_pos); + return is; + } + + } else { + raise_error_rewind(is, start_pos, bias_type, name); return is; } - if (matching_state == false) { - // This state is not for this bias - is.seekg(start_pos, std::ios::beg); + if (!matching_state) { + // No errors, but not a match for this bias instance; rewind + is.seekg(start_pos); return is; } - cvm::log("Restarting "+bias_type+" bias \""+name+"\" from step number "+ - cvm::to_str(state_file_step)+".\n"); - - if (!read_state_data(is)) { - cvm::error("Error: in reading state data for \""+bias_type+"\" bias \""+ - this->name+"\" at position "+ - cvm::to_str(static_cast(is.tellg()))+ - " in stream.\n", COLVARS_INPUT_ERROR); - is.clear(); - is.seekg(start_pos, std::ios::beg); - is.setstate(std::ios::failbit); + if ((set_state_params(conf) != COLVARS_OK) || !read_state_data(is)) { + raise_error_rewind(is, start_pos, bias_type, name); } - is >> brace; - if (brace != "}") { - cvm::error("Error: corrupt restart information for \""+bias_type+"\" bias \""+ - this->name+"\": no matching brace at position "+ - cvm::to_str(static_cast(is.tellg()))+ - " in stream.\n"); - is.setstate(std::ios::failbit); + if (! std::is_same::value) { + is >> brace; + if (brace != "}") { + cvm::error("Error: corrupt restart information for \""+bias_type+"\" bias \""+ + this->name+"\": no matching brace at position "+ + cvm::to_str(static_cast(is.tellg()))+ + " in stream.\n"); + raise_error_rewind(is, start_pos, bias_type, name); + } } + cvm::log("Restarted " + bias_type + " bias \"" + name + "\" with step number " + + cvm::to_str(state_file_step) + ".\n"); + return is; } +std::istream &colvarbias::read_state(std::istream &is) +{ + return read_state_template_(is); +} + + +cvm::memory_stream &colvarbias::read_state(cvm::memory_stream &is) +{ + return read_state_template_(is); +} + + int colvarbias::write_state_prefix(std::string const &prefix) { std::string const filename = @@ -635,25 +703,51 @@ int colvarbias::read_state_string(char const *buffer) } -std::istream & colvarbias::read_state_data_key(std::istream &is, char const *key) +std::ostream &colvarbias::write_state_data_key(std::ostream &os, std::string const &key, + bool header) { - std::streampos const start_pos = is.tellg(); + os << (header ? "\n" : "") << key << (header ? "\n" : " "); + return os; +} + + +cvm::memory_stream &colvarbias::write_state_data_key(cvm::memory_stream &os, std::string const &key, + bool /* header */) +{ + os << std::string(key); + return os; +} + + +template +IST &colvarbias::read_state_data_key_template_(IST &is, std::string const &key) +{ + auto const start_pos = is.tellg(); std::string key_in; - if ( !(is >> key_in) || - !(to_lower_cppstr(key_in) == to_lower_cppstr(std::string(key))) ) { - cvm::error("Error: in reading restart configuration for "+ - bias_type+" bias \""+this->name+"\" at position "+ - cvm::to_str(static_cast(is.tellg()))+ - " in stream.\n", COLVARS_INPUT_ERROR); - is.clear(); - is.seekg(start_pos, std::ios::beg); - is.setstate(std::ios::failbit); - return is; + if (is >> key_in) { + if (key_in != key) { + raise_error_rewind(is, start_pos, bias_type, name, + " Expected keyword \"" + std::string(key) + "\", found \"" + key_in + + "\"."); + } + } else { + raise_error_rewind(is, start_pos, bias_type, name); } return is; } +std::istream & colvarbias::read_state_data_key(std::istream &is, std::string const &key) +{ + return read_state_data_key_template_(is, key); +} + + +cvm::memory_stream & colvarbias::read_state_data_key(cvm::memory_stream &is, std::string const &key) +{ + return read_state_data_key_template_(is, key); +} + std::ostream & colvarbias::write_traj_label(std::ostream &os) { @@ -686,28 +780,11 @@ colvarbias_ti::colvarbias_ti(char const *key) // Samples at step zero can not be collected feature_states[f_cvb_step_zero_data].available = false; } - ti_avg_forces = NULL; - ti_count = NULL; } colvarbias_ti::~colvarbias_ti() { - colvarbias_ti::clear_state_data(); -} - - -int colvarbias_ti::clear_state_data() -{ - if (ti_avg_forces != NULL) { - delete ti_avg_forces; - ti_avg_forces = NULL; - } - if (ti_count != NULL) { - delete ti_count; - ti_count = NULL; - } - return COLVARS_OK; } @@ -765,7 +842,7 @@ int colvarbias_ti::init(std::string const &conf) int colvarbias_ti::init_grids() { if (is_enabled(f_cvb_calc_ti_samples)) { - if (ti_avg_forces == NULL) { + if (!ti_avg_forces) { ti_bin.resize(num_variables()); ti_system_forces.resize(num_variables()); for (size_t icv = 0; icv < num_variables(); icv++) { @@ -773,8 +850,8 @@ int colvarbias_ti::init_grids() ti_system_forces[icv].is_derivative(); ti_system_forces[icv].reset(); } - ti_avg_forces = new colvar_grid_gradient(colvars); - ti_count = new colvar_grid_count(colvars); + ti_avg_forces.reset(new colvar_grid_gradient(colvars)); + ti_count.reset(new colvar_grid_count(colvars)); ti_avg_forces->samples = ti_count; ti_count->has_parent_data = true; } @@ -860,9 +937,22 @@ std::ostream & colvarbias_ti::write_state_data(std::ostream &os) if (! is_enabled(f_cvb_calc_ti_samples)) { return os; } - os << "\nhistogram\n"; + write_state_data_key(os, "histogram"); ti_count->write_raw(os); - os << "\nsystem_forces\n"; + write_state_data_key(os, "system_forces"); + ti_avg_forces->write_raw(os); + return os; +} + + +cvm::memory_stream & colvarbias_ti::write_state_data(cvm::memory_stream &os) +{ + if (! is_enabled(f_cvb_calc_ti_samples)) { + return os; + } + write_state_data_key(os, "histogram"); + ti_count->write_raw(os); + write_state_data_key(os, "system_forces"); ti_avg_forces->write_raw(os); return os; } @@ -895,6 +985,33 @@ std::istream & colvarbias_ti::read_state_data(std::istream &is) } +cvm::memory_stream & colvarbias_ti::read_state_data(cvm::memory_stream &is) +{ + if (! is_enabled(f_cvb_calc_ti_samples)) { + return is; + } + if (cvm::debug()) { + cvm::log("Reading state data for the TI estimator.\n"); + } + if (! read_state_data_key(is, "histogram")) { + return is; + } + if (! ti_count->read_raw(is)) { + return is; + } + if (! read_state_data_key(is, "system_forces")) { + return is; + } + if (! ti_avg_forces->read_raw(is)) { + return is; + } + if (cvm::debug()) { + cvm::log("Done reading state data for the TI estimator.\n"); + } + return is; +} + + int colvarbias_ti::write_output_files() { int error_code = COLVARS_OK; diff --git a/lib/colvars/colvarbias.h b/lib/colvars/colvarbias.h index e865902be6..03f93f4315 100644 --- a/lib/colvars/colvarbias.h +++ b/lib/colvars/colvarbias.h @@ -10,6 +10,8 @@ #ifndef COLVARBIAS_H #define COLVARBIAS_H +#include + #include "colvar.h" #include "colvarparse.h" #include "colvardeps.h" @@ -91,11 +93,16 @@ public: // FIXME this is currently 1D only virtual int current_bin(); //// Give the count at a given bin index. - // FIXME this is currently 1D only virtual int bin_count(int bin_index); + /// Return the average number of samples in a given "radius" around current bin + virtual int local_sample_count(int radius); + //// Share information between replicas, whatever it may be. virtual int replica_share(); + /// Report the frequency at which this bias needs to communicate with replicas + virtual size_t replica_share_freq() const; + /// Perform analysis tasks virtual void analyze() {} @@ -133,32 +140,87 @@ public: /// Write the values of specific mutable properties to a string virtual std::string const get_state_params() const; + /// Check the name of the bias vs. the given string, set the matching_state flag accordingly + int check_matching_state(std::string const &conf); + /// Read the values of specific mutable properties from a string virtual int set_state_params(std::string const &state_conf); - /// Write all mutable data not already written by get_state_params() + /// Write all mutable data not already written by get_state_params() to a formatted stream virtual std::ostream & write_state_data(std::ostream &os) { return os; } - /// Read all mutable data not already set by set_state_params() + /// Write all mutable data not already written by get_state_params() to an unformatted stream + virtual cvm::memory_stream & write_state_data(cvm::memory_stream &os) + { + return os; + } + + /// Read all mutable data not already set by set_state_params() from a formatted stream virtual std::istream & read_state_data(std::istream &is) { return is; } - /// Read a keyword from the state data (typically a header) - /// \param Input stream - /// \param Keyword labeling the header block - std::istream & read_state_data_key(std::istream &is, char const *key); + /// Read all mutable data not already set by set_state_params() from an unformatted stream + virtual cvm::memory_stream & read_state_data(cvm::memory_stream &is) + { + return is; + } - /// Write the bias configuration to a state file or other stream - std::ostream & write_state(std::ostream &os); + /// Write a keyword header for a data sequence to a formatted stream + /// \param[in,out] os Output stream + /// \param[in] key Keyword labeling the header block + /// \param[in] header Whether this is the header of a multi-line segment vs a single line + std::ostream &write_state_data_key(std::ostream &os, std::string const &key, bool header = true); - /// Read the bias configuration from a restart file or other stream + /// Write a keyword header for a data sequence to an unformatted stream + /// \param[in,out] os Output stream + /// \param[in] key Keyword labeling the header block + /// \param[in] header Ignored + cvm::memory_stream &write_state_data_key(cvm::memory_stream &os, std::string const &key, + bool header = true); + +private: + + /// Read a keyword header for a data sequence from a stream + /// \param[in,out] Input stream + /// \param[in] Keyword labeling the header block; an error will be raised if not matching + template IST &read_state_data_key_template_(IST &is, std::string const &key); + +public: + + /// Read a keyword header for a data sequence from a formatted stream + /// \param[in,out] Input stream + /// \param[in] Keyword labeling the header block; an error will be raised if not matching + std::istream & read_state_data_key(std::istream &is, std::string const &key); + + /// Read a keyword header for a data sequence from an unformatted stream + /// \param[in,out] Input stream + /// \param[in] Keyword labeling the header block; an error will be raised if not matching + cvm::memory_stream & read_state_data_key(cvm::memory_stream &is, std::string const &key); + +private: + + /// Generic stream reading function (formatted and not) + template IST & read_state_template_(IST &is); + +public: + + /// Write the bias configuration to a formatted stream + std::ostream &write_state(std::ostream &os); + + /// Write the bias configuration to an unformatted stream + cvm::memory_stream & write_state(cvm::memory_stream &os); + + /// Read the bias configuration from a formatted stream std::istream & read_state(std::istream &is); + /// Read the bias configuration from an unformatted stream + cvm::memory_stream & read_state(cvm::memory_stream &is); + /// Write the bias state to a file with the given prefix int write_state_prefix(std::string const &prefix); @@ -274,8 +336,6 @@ public: colvarbias_ti(char const *key); virtual ~colvarbias_ti(); - virtual int clear_state_data(); - virtual int init(std::string const &conf); virtual int init_grids(); virtual int update(); @@ -288,7 +348,9 @@ public: virtual std::string const get_state_params() const; virtual int set_state_params(std::string const &state_conf); virtual std::ostream & write_state_data(std::ostream &os); + virtual cvm::memory_stream & write_state_data(cvm::memory_stream &os); virtual std::istream & read_state_data(std::istream &is); + virtual cvm::memory_stream & read_state_data(cvm::memory_stream &is); virtual int write_output_files(); protected: @@ -297,10 +359,10 @@ protected: std::vector ti_system_forces; /// Averaged system forces - colvar_grid_gradient *ti_avg_forces; + std::shared_ptr ti_avg_forces; /// Histogram of sampled data - colvar_grid_count *ti_count; + std::shared_ptr ti_count; /// Because total forces may be from the last simulation step, /// store the index of the variables then diff --git a/lib/colvars/colvarbias_abf.cpp b/lib/colvars/colvarbias_abf.cpp index 7d6b7b7fef..6327650863 100644 --- a/lib/colvars/colvarbias_abf.cpp +++ b/lib/colvars/colvarbias_abf.cpp @@ -12,29 +12,15 @@ #include "colvarmodule.h" #include "colvar.h" #include "colvarbias_abf.h" - +#include "colvars_memstream.h" colvarbias_abf::colvarbias_abf(char const *key) : colvarbias(key), b_UI_estimator(false), b_CZAR_estimator(false), pabf_freq(0), - system_force(NULL), - gradients(NULL), - samples(NULL), - pmf(NULL), - z_gradients(NULL), - z_samples(NULL), - czar_gradients(NULL), - czar_pmf(NULL), - last_gradients(NULL), - last_samples(NULL) + system_force(NULL) { - colvarproxy *proxy = cvm::main()->proxy; - if (!proxy->total_forces_same_step()) { - // Samples at step zero can not be collected - feature_states[f_cvb_step_zero_data].available = false; - } } @@ -42,7 +28,10 @@ int colvarbias_abf::init(std::string const &conf) { colvarproxy *proxy = cvm::main()->proxy; - colvarbias::init(conf); + int err = colvarbias::init(conf); + if (err != COLVARS_OK) { + return err; + } cvm::main()->cite_feature("ABF colvar bias implementation"); enable(f_cvb_scalar_variables); @@ -54,18 +43,11 @@ int colvarbias_abf::init(std::string const &conf) // ************* parsing general ABF options *********************** - get_keyval_feature((colvarparse *)this, conf, "applyBias", f_cvb_apply_force, true); + get_keyval_feature(this, conf, "applyBias", f_cvb_apply_force, true); if (!is_enabled(f_cvb_apply_force)){ cvm::log("WARNING: ABF biases will *not* be applied!\n"); } - get_keyval(conf, "updateBias", update_bias, true); - if (update_bias) { - enable(f_cvb_history_dependent); - } else { - cvm::log("WARNING: ABF biases will *not* be updated!\n"); - } - get_keyval(conf, "hideJacobian", hide_Jacobian, false); if (hide_Jacobian) { cvm::log("Jacobian (geometric) forces will be handled internally.\n"); @@ -73,13 +55,21 @@ int colvarbias_abf::init(std::string const &conf) cvm::log("Jacobian (geometric) forces will be included in reported free energy gradients.\n"); } - get_keyval(conf, "fullSamples", full_samples, 200); - if ( full_samples <= 1 ) full_samples = 1; - min_samples = full_samples / 2; - // full_samples - min_samples >= 1 is guaranteed + full_samples = 200; + get_keyval(conf, "fullSamples", full_samples, full_samples); + get_keyval(conf, "minSamples", min_samples, full_samples / 2); + + if (full_samples <= 1 ) { + full_samples = 1; + min_samples = 0; + } + if (min_samples >= full_samples) { + return cvm::error("Error: minSamples must be lower than fullSamples\n"); + } get_keyval(conf, "inputPrefix", input_prefix, std::vector()); + history_last_step = -1; get_keyval(conf, "historyFreq", history_freq, 0); if (history_freq != 0) { if (output_freq == 0) { @@ -92,7 +82,6 @@ int colvarbias_abf::init(std::string const &conf) } } } - b_history_files = (history_freq > 0); // shared ABF get_keyval(conf, "shared", shared_on, false); @@ -105,30 +94,25 @@ int colvarbias_abf::init(std::string const &conf) } cvm::log("shared ABF will be applied among "+ cvm::to_str(proxy->num_replicas()) + " replicas.\n"); - if (cvm::proxy->smp_enabled() == COLVARS_OK) { - cvm::error("Error: shared ABF is currently not available with SMP parallelism; " - "please set \"SMP off\" at the top of the Colvars configuration file.\n", - COLVARS_NOT_IMPLEMENTED); - return COLVARS_NOT_IMPLEMENTED; - } // If shared_freq is not set, we default to output_freq get_keyval(conf, "sharedFreq", shared_freq, output_freq); + if ( shared_freq && output_freq % shared_freq ) { + return cvm::error("Error: outputFreq must be a multiple of sharedFreq.\n"); + } + + // Allocate these at init time if possible + local_samples.reset(new colvar_grid_count(colvars)); + local_gradients.reset(new colvar_grid_gradient(colvars, local_samples)); + local_pmf.reset(new integrate_potential(colvars, local_gradients)); } // ************* checking the associated colvars ******************* if (num_variables() == 0) { - cvm::error("Error: no collective variables specified for the ABF bias.\n"); - return COLVARS_ERROR; + return cvm::error("Error: no collective variables specified for the ABF bias.\n"); } - if (update_bias) { - // Request calculation of total force - if(enable(f_cvb_get_total_force)) return cvm::get_error(); - } - - bool b_extended = false; size_t i; for (i = 0; i < num_variables(); i++) { @@ -140,11 +124,10 @@ int colvarbias_abf::init(std::string const &conf) colvars[i]->enable(f_cv_hide_Jacobian); } - // If any colvar is extended-system (restrained style, not external with constraint), we are running eABF - if (colvars[i]->is_enabled(f_cv_extended_Lagrangian) - && !colvars[i]->is_enabled(f_cv_external)) { - b_extended = true; - } + // If any colvar is extended-system, we need to collect the extended + // system gradient + if (colvars[i]->is_enabled(f_cv_extended_Lagrangian)) + enable(f_cvb_extended); // Cannot mix and match coarse time steps with ABF because it gives // wrong total force averages - total force needs to be averaged over @@ -160,7 +143,15 @@ int colvarbias_abf::init(std::string const &conf) // and make it just a warning if some parameter is set? } - if (b_extended) { + get_keyval(conf, "updateBias", update_bias, true); + if (update_bias) { + enable(f_cvb_history_dependent); + enable(f_cvb_get_total_force); + } else { + cvm::log("WARNING: ABF biases will *not* be updated!\n"); + } + + if (is_enabled(f_cvb_extended)) { cvm::main()->cite_feature("eABF implementation"); } else { cvm::main()->cite_feature("Internal-forces free energy estimator"); @@ -190,13 +181,14 @@ int colvarbias_abf::init(std::string const &conf) cvm::log("Allocating count and free energy gradient grids.\n"); } - samples = new colvar_grid_count(colvars); - gradients = new colvar_grid_gradient(colvars); - gradients->samples = samples; - samples->has_parent_data = true; + samples.reset(new colvar_grid_count(colvars)); + gradients.reset(new colvar_grid_gradient(colvars, samples)); - // Data for eAB F z-based estimator - if ( b_extended ) { + gradients->full_samples = full_samples; + gradients->min_samples = min_samples; + + // Data for eABF z-based estimator + if (is_enabled(f_cvb_extended)) { get_keyval(conf, "CZARestimator", b_CZAR_estimator, true); if ( b_CZAR_estimator ) { cvm::main()->cite_feature("CZAR eABF estimator"); @@ -206,13 +198,11 @@ int colvarbias_abf::init(std::string const &conf) colvarparse::parse_silent); z_bin.assign(num_variables(), 0); - z_samples = new colvar_grid_count(colvars); + z_samples.reset(new colvar_grid_count(colvars)); z_samples->request_actual_value(); - z_gradients = new colvar_grid_gradient(colvars); + z_gradients.reset(new colvar_grid_gradient(colvars, z_samples)); z_gradients->request_actual_value(); - z_gradients->samples = z_samples; - z_samples->has_parent_data = true; - czar_gradients = new colvar_grid_gradient(colvars); + czar_gradients.reset(new colvar_grid_gradient(colvars)); } get_keyval(conf, "integrate", b_integrate, num_variables() <= 3); // Integrate for output if d<=3 @@ -222,9 +212,9 @@ int colvarbias_abf::init(std::string const &conf) cvm::error("Error: cannot integrate free energy in dimension > 3.\n"); return COLVARS_ERROR; } - pmf = new integrate_potential(colvars, gradients); - if ( b_CZAR_estimator ) { - czar_pmf = new integrate_potential(colvars, czar_gradients); + pmf.reset(new integrate_potential(colvars, gradients)); + if (b_CZAR_estimator) { + czar_pmf.reset(new integrate_potential(colvars, czar_gradients)); } // Parameters for integrating initial (and final) gradient data get_keyval(conf, "integrateMaxIterations", integrate_iterations, 10000, colvarparse::parse_silent); @@ -235,16 +225,34 @@ int colvarbias_abf::init(std::string const &conf) get_keyval(conf, "pABFintegrateTol", pabf_integrate_tol, 1e-4, colvarparse::parse_silent); } + if (b_CZAR_estimator && shared_on && cvm::main()->proxy->replica_index() == 0) { + // The pointers below are used for outputting CZAR data + // Allocate grids for collected global data, on replica 0 only + global_z_samples.reset(new colvar_grid_count(colvars)); + global_z_gradients.reset(new colvar_grid_gradient(colvars, global_z_samples)); + global_czar_gradients.reset(new colvar_grid_gradient(colvars)); + global_czar_pmf.reset(new integrate_potential(colvars, global_czar_gradients)); + } else { + // otherwise they are just aliases for the local CZAR grids + global_z_samples = z_samples; + global_z_gradients = z_gradients; + global_czar_gradients = czar_gradients; + global_czar_pmf = czar_pmf; + } + // For shared ABF, we store a second set of grids. // This used to be only if "shared" was defined, // but now we allow calling share externally (e.g. from Tcl). - last_samples = new colvar_grid_count(colvars); - last_gradients = new colvar_grid_gradient(colvars); - last_gradients->samples = last_samples; - last_samples->has_parent_data = true; - shared_last_step = -1; + if (b_CZAR_estimator) { + z_samples_in.reset(new colvar_grid_count(colvars)); + z_gradients_in.reset(new colvar_grid_gradient(colvars, z_samples_in)); + } + last_samples.reset(new colvar_grid_count(colvars)); + last_gradients.reset(new colvar_grid_gradient(colvars, last_samples)); + // Any data collected after now is new for shared ABF purposes + shared_last_step = cvm::step_absolute(); - // If custom grids are provided, read them + // Read any custom input ABF data if ( input_prefix.size() > 0 ) { read_gradients_samples(); // Update divergence to account for input data @@ -252,11 +260,15 @@ int colvarbias_abf::init(std::string const &conf) } // if extendedLangrangian is on, then call UI estimator - if (b_extended) { + if (is_enabled(f_cvb_extended)) { get_keyval(conf, "UIestimator", b_UI_estimator, false); if (b_UI_estimator) { - + if (shared_on) { + cvm::error("Error: UI estimator is not available for multiple-walker (shared) ABF.\n"); + b_UI_estimator = false; + return COLVARS_ERROR; + } cvm::main()->cite_feature("Umbrella-integration eABF estimator"); std::vector UI_lowerboundary; std::vector UI_upperboundary; @@ -290,63 +302,11 @@ int colvarbias_abf::init(std::string const &conf) /// Destructor colvarbias_abf::~colvarbias_abf() { - if (samples) { - delete samples; - samples = NULL; - } - - if (gradients) { - delete gradients; - gradients = NULL; - } - - if (pmf) { - delete pmf; - pmf = NULL; - } - - if (z_samples) { - delete z_samples; - z_samples = NULL; - } - - if (z_gradients) { - delete z_gradients; - z_gradients = NULL; - } - - if (czar_gradients) { - delete czar_gradients; - czar_gradients = NULL; - } - - if (czar_pmf) { - delete czar_pmf; - czar_pmf = NULL; - } - - // shared ABF - // We used to only do this if "shared" was defined, - // but now we can call shared externally - if (last_samples) { - delete last_samples; - last_samples = NULL; - } - - if (last_gradients) { - delete last_gradients; - last_gradients = NULL; - } - - if (system_force) { - delete [] system_force; - system_force = NULL; - } + if (system_force) delete[] system_force; } /// Update the FE gradient, compute and apply biasing force -/// also output data to disk if needed int colvarbias_abf::update() { @@ -356,68 +316,79 @@ int colvarbias_abf::update() for (i = 0; i < num_variables(); i++) { bin[i] = samples->current_bin_scalar(i); } + + + // *********************************************************** + // ****** ABF Part I: update the FE gradient estimate ****** + // *********************************************************** + + if (cvm::proxy->total_forces_same_step()) { // e.g. in LAMMPS, total forces are current force_bin = bin; } - if (cvm::step_relative() > 0 || is_enabled(f_cvb_step_zero_data)) { + if (can_accumulate_data() && is_enabled(f_cvb_history_dependent)) { - if (update_bias) { -// if (b_adiabatic_reweighting) { -// // Update gradients non-locally based on conditional distribution of -// // fictitious variable TODO -// -// } else + if (cvm::step_relative() > 0 || cvm::proxy->total_forces_same_step()) { if (samples->index_ok(force_bin)) { // Only if requested and within bounds of the grid... - for (i = 0; i < num_variables(); i++) { - // get total forces (lagging by 1 timestep) from colvars - // and subtract previous ABF force if necessary - update_system_force(i); - } - gradients->acc_force(force_bin, system_force); - if ( b_integrate ) { - pmf->update_div_neighbors(force_bin); - } - } - } + // get total forces (lagging by 1 timestep) from colvars + // and subtract previous ABF force if necessary + update_system_force(); - if ( z_gradients && update_bias ) { - for (i = 0; i < num_variables(); i++) { - z_bin[i] = z_samples->current_bin_scalar(i); + gradients->acc_force(force_bin, system_force); + if ( b_integrate ) { + pmf->update_div_neighbors(force_bin); + } } - if ( z_samples->index_ok(z_bin) ) { + + if ( z_gradients ) { for (i = 0; i < num_variables(); i++) { - // If we are outside the range of xi, the force has not been obtained above + z_bin[i] = z_samples->current_bin_scalar(i); + } + if ( z_samples->index_ok(z_bin) ) { + // If we are outside the range of z, the force has not been obtained above // the function is just an accessor, so cheap to call again anyway - update_system_force(i); + update_system_force(); + z_gradients->acc_force(z_bin, system_force); } - z_gradients->acc_force(z_bin, system_force); } - } - if ( b_integrate ) { if ( pabf_freq && cvm::step_relative() % pabf_freq == 0 ) { cvm::real err; - int iter = pmf->integrate(pabf_integrate_iterations, pabf_integrate_tol, err); - if ( iter == pabf_integrate_iterations ) { - cvm::log("Warning: PMF integration did not converge to " + cvm::to_str(pabf_integrate_tol) - + " in " + cvm::to_str(pabf_integrate_iterations) + int iter = pmf->integrate(integrate_iterations, integrate_tol, err); + if ( iter == integrate_iterations ) { + cvm::log("Warning: PMF integration did not converge to " + cvm::to_str(integrate_tol) + + " in " + cvm::to_str(integrate_iterations) + " steps. Residual error: " + cvm::to_str(err)); } - pmf->set_zero_minimum(); // TODO: do this only when necessary } } } - if (!cvm::proxy->total_forces_same_step()) { + if (!(cvm::proxy->total_forces_same_step())) { // e.g. in NAMD, total forces will be available for next timestep // hence we store the current colvar bin force_bin = bin; } + // Share data after force sample is collected for this time step + // shared_on can be true with shared_freq 0 if we are sharing via script + if (shared_on && shared_freq && + cvm::step_absolute() > shared_last_step && // time has passed since the last sharing timestep + // (avoid re-sharing at last and first ts of successive run statements) + cvm::step_absolute() % shared_freq == 0) { + // Share gradients and samples for shared ABF. + replica_share(); + } + + // ****************************************************************** + // ****** ABF Part II: calculate and apply the biasing force ****** + // ****************************************************************** + + // Reset biasing forces from previous timestep for (i = 0; i < num_variables(); i++) { colvar_forces[i].reset(); @@ -426,51 +397,20 @@ int colvarbias_abf::update() // Compute and apply the new bias, if applicable if (is_enabled(f_cvb_apply_force) && samples->index_ok(bin)) { - cvm::real count = cvm::real(samples->value(bin)); - cvm::real fact = 1.0; + std::vector force(num_variables()); + calc_biasing_force(force); - // Factor that ensures smooth introduction of the force - if ( count < full_samples ) { - fact = (count < min_samples) ? 0.0 : - (cvm::real(count - min_samples)) / (cvm::real(full_samples - min_samples)); - } - - std::vector grad(num_variables()); - - if ( pabf_freq ) { - // In projected ABF, the force is the PMF gradient estimate - pmf->vector_gradient_finite_diff(bin, grad); - } else { - // Normal ABF - gradients->vector_value(bin, grad); - } - -// if ( b_adiabatic_reweighting) { -// // Average of force according to conditional distribution of fictitious variable -// // need freshly integrated PMF, gradient TODO -// } else - if ( fact != 0.0 ) { - if ( (num_variables() == 1) && colvars[0]->periodic_boundaries() ) { - // Enforce a zero-mean bias on periodic, 1D coordinates - // in other words: boundary condition is that the biasing potential is periodic - // This is enforced naturally if using integrated PMF - colvar_forces[0].real_value = fact * (grad[0] - gradients->average ()); - } else { - for (i = 0; i < num_variables(); i++) { - // subtracting the mean force (opposite of the FE gradient) means adding the gradient - colvar_forces[i].real_value = fact * grad[i]; - } - } - if (cap_force) { - for (i = 0; i < num_variables(); i++) { - if ( colvar_forces[i].real_value * colvar_forces[i].real_value > max_force[i] * max_force[i] ) { - colvar_forces[i].real_value = (colvar_forces[i].real_value > 0 ? max_force[i] : -1.0 * max_force[i]); - } - } - } + for (size_t i = 0; i < num_variables(); i++) { + colvar_forces[i].real_value = force[i]; } } + + // ********************************* + // ****** End of ABF proper ****** + // ********************************* + + // update the output prefix; TODO: move later to setup_output() function if (cvm::main()->num_biases_feature(colvardeps::f_cvb_calc_pmf) == 1) { // This is the only bias computing PMFs @@ -479,19 +419,6 @@ int colvarbias_abf::update() output_prefix = cvm::output_prefix() + "." + this->name; } - if (shared_on && shared_last_step >= 0 && cvm::step_absolute() % shared_freq == 0) { - // Share gradients and samples for shared ABF. - replica_share(); - } - - // Prepare for the first sharing. - if (shared_last_step < 0) { - // Copy the current gradient and count values into last. - last_gradients->copy_grid(*gradients); - last_samples->copy_grid(*samples); - shared_last_step = cvm::step_absolute(); - cvm::log("Prepared sample and gradient buffers at step "+cvm::to_str(cvm::step_absolute())+".\n"); - } // update UI estimator every step if (b_UI_estimator) @@ -514,6 +441,86 @@ int colvarbias_abf::update() } + // **************************************** + // ****** Helper functions for ABF ****** + // **************************************** + + +int colvarbias_abf::update_system_force() +{ + size_t i; + // System force from atomic forces (or extended Lagrangian if applicable) + + for (i = 0; i < num_variables(); i++) { + if (colvars[i]->is_enabled(f_cv_subtract_applied_force)) { + // this colvar is already subtracting the ABF force + system_force[i] = colvars[i]->total_force().real_value; + } else { + system_force[i] = colvars[i]->total_force().real_value + - colvar_forces[i].real_value; + } + } + return COLVARS_OK; +} + + +cvm::real colvarbias_abf::smoothing_factor(cvm::real weight) +{ + cvm::real fact = 1.0; + if ( weight < full_samples ) { + if ( weight < min_samples) { + fact = 0.0; + } else { + fact = (weight - min_samples) / cvm::real(full_samples - min_samples); + } + } + return fact; +} + + +int colvarbias_abf::calc_biasing_force(std::vector &force) +{ + size_t i; + + // Pick between different types of biasing force + if ( pabf_freq ) { + // In projected ABF, the force is the PMF gradient estimate + pmf->vector_gradient_finite_diff(bin, force); + // Calculate ramp factor that ensures smooth introduction of the force + const cvm::real count = samples->value(bin); + const cvm::real fact = smoothing_factor(count); + for (i = 0; i < num_variables(); i++) { + force[i] *= fact; + } + } else { + // Normal ABF or eABF: use accumulated gradient average + gradients->vector_value_smoothed(bin, &force[0], true); + if ( (num_variables() == 1) && gradients->periodic[0] ) { + // Enforce a zero-mean bias on periodic, 1D coordinates + // in other words: boundary condition is that the biasing potential is periodic + // Only plain ABF needs this + force[0] = force[0] - gradients->average(); + } + } + + if (cap_force) { + for (i = 0; i < num_variables(); i++) { + if ( force[i] * force[i] > max_force[i] * max_force[i] ) { + force[i] = (force[i] > 0 ? max_force[i] : -1.0 * max_force[i]); + } + } + } + + return COLVARS_OK; +} + + + // ************************************ + // ****** Shared ABF functions ****** + // ************************************ + + + int colvarbias_abf::replica_share() { colvarproxy *proxy = cvm::main()->proxy; @@ -527,53 +534,82 @@ int colvarbias_abf::replica_share() { cvm::error("Error: shared ABF: Tried to apply shared ABF before any sampling had occurred.\n"); return COLVARS_ERROR; } + shared_on = true; // If called by a script, inform the rest of the code that we're sharing, eg. CZAR // Share gradients for shared ABF. cvm::log("shared ABF: Sharing gradient and samples among replicas at step "+cvm::to_str(cvm::step_absolute()) ); + if (!local_samples) { + // We arrive here if sharing has just been enabled by a script + // in which case local arrays have not been initialized yet + local_samples.reset(new colvar_grid_count(colvars)); + local_gradients.reset(new colvar_grid_gradient(colvars, local_samples)); + local_pmf.reset(new integrate_potential(colvars, local_gradients)); + } + // Calculate the delta gradient and count for the local replica + last_gradients->delta_grid(*gradients); + // Add the delta gradient and count to the accumulated local data + local_gradients->add_grid(*last_gradients); + + last_samples->delta_grid(*samples); + local_samples->add_grid(*last_samples); + + // Count of data items. - size_t data_n = gradients->raw_data_num(); - size_t samp_start = data_n*sizeof(cvm::real); - size_t msg_total = data_n*sizeof(size_t) + samp_start; + size_t samples_n = samples->raw_data_num(); + size_t gradients_n = gradients->raw_data_num(); + + size_t samp_start = gradients_n * sizeof(cvm::real); + int msg_total = samples_n * sizeof(size_t) + samp_start; char* msg_data = new char[msg_total]; - if (proxy->replica_index() == 0) { + if (cvm::main()->proxy->replica_index() == 0) { int p; // Replica 0 collects the delta gradient and count from the others. for (p = 1; p < proxy->num_replicas(); p++) { // Receive the deltas. - proxy->replica_comm_recv(msg_data, msg_total, p); + if (proxy->replica_comm_recv(msg_data, msg_total, p) != msg_total) { + cvm::error("Error getting shared ABF data from replica."); + return COLVARS_ERROR; + } // Map the deltas from the others into the grids. + // Re-use last_gradients as temp array, erasing its contents each time last_gradients->raw_data_in((cvm::real*)(&msg_data[0])); - last_samples->raw_data_in((size_t*)(&msg_data[samp_start])); - // Combine the delta gradient and count of the other replicas // with Replica 0's current state (including its delta). - gradients->add_grid( *last_gradients ); - samples->add_grid( *last_samples ); + gradients->add_grid(*last_gradients); + + last_samples->raw_data_in((size_t*)(&msg_data[samp_start])); + samples->add_grid(*last_samples); } // Now we must send the combined gradient to the other replicas. gradients->raw_data_out((cvm::real*)(&msg_data[0])); samples->raw_data_out((size_t*)(&msg_data[samp_start])); + for (p = 1; p < proxy->num_replicas(); p++) { - proxy->replica_comm_send(msg_data, msg_total, p); + if (proxy->replica_comm_send(msg_data, msg_total, p) != msg_total) { + cvm::error("Error sending shared ABF data to replica."); + return COLVARS_ERROR; + } } } else { // All other replicas send their delta gradient and count. - // Calculate the delta gradient and count. - last_gradients->delta_grid(*gradients); - last_samples->delta_grid(*samples); - // Cast the raw char data to the gradient and samples. last_gradients->raw_data_out((cvm::real*)(&msg_data[0])); last_samples->raw_data_out((size_t*)(&msg_data[samp_start])); - proxy->replica_comm_send(msg_data, msg_total, 0); + if (proxy->replica_comm_send(msg_data, msg_total, 0) != msg_total) { + cvm::error("Error sending shared ABF data to replica."); + return COLVARS_ERROR; + } // We now receive the combined gradient from Replica 0. - proxy->replica_comm_recv(msg_data, msg_total, 0); + if (proxy->replica_comm_recv(msg_data, msg_total, 0) != msg_total) { + cvm::error("Error getting shared ABF data from replica 0."); + return COLVARS_ERROR; + } // We sync to the combined gradient computed by Replica 0. gradients->raw_data_in((cvm::real*)(&msg_data[0])); samples->raw_data_in((size_t*)(&msg_data[samp_start])); @@ -590,18 +626,105 @@ int colvarbias_abf::replica_share() { last_samples->copy_grid(*samples); shared_last_step = cvm::step_absolute(); + cvm::log("RMSD btw. local and global ABF gradients: " + cvm::to_str(gradients->grid_rmsd(*local_gradients))); + if (b_integrate) { - // Update divergence to account for newly shared gradients + cvm::real err; + + // Update whole divergence field to account for newly shared gradients pmf->set_div(); + pmf->integrate(integrate_iterations, integrate_tol, err); + pmf->set_zero_minimum(); + local_pmf->set_div(); + local_pmf->integrate(integrate_iterations, integrate_tol, err); + local_pmf->set_zero_minimum(); + cvm::log("RMSD btw. local and global ABF FES: " + cvm::to_str(pmf->grid_rmsd(*local_pmf))); } return COLVARS_OK; } +int colvarbias_abf::replica_share_CZAR() { + colvarproxy *proxy = cvm::main()->proxy; + + cvm::log("shared eABF: Gathering CZAR gradient and samples from replicas at step "+cvm::to_str(cvm::step_absolute()) ); + + // Count of data items. + size_t samples_n = z_samples->raw_data_num(); + size_t gradients_n = z_gradients->raw_data_num(); + + size_t samp_start = gradients_n*sizeof(cvm::real); + int msg_total = samples_n*sizeof(size_t) + samp_start; + char* msg_data = new char[msg_total]; + + if (cvm::main()->proxy->replica_index() == 0) { + if (!global_z_samples) { + // We arrive here if sharing has just been enabled by a script + // Allocate grids for collective data, on replica 0 only + // overriding CZAR grids that are equal to local ones by default + global_z_samples.reset(new colvar_grid_count(colvars)); + global_z_gradients.reset(new colvar_grid_gradient(colvars, global_z_samples)); + global_czar_gradients.reset(new colvar_grid_gradient(colvars)); + global_czar_pmf.reset(new integrate_potential(colvars, global_czar_gradients)); + } + + // Start with data from replica 0 + global_z_gradients->copy_grid(*z_gradients); + global_z_samples->copy_grid(*z_samples); + + int p; + // Replica 0 collects the gradient and count from the others. + for (p = 1; p < proxy->num_replicas(); p++) { + if (proxy->replica_comm_recv(msg_data, msg_total, p) != msg_total) { + cvm::error("Error getting shared ABF data from replica."); + return COLVARS_ERROR; + } + + // Map the deltas from the others into the grids. + // Re-use z_gradients_in, erasing its contents each time + z_gradients_in->raw_data_in((cvm::real*)(&msg_data[0])); + z_samples_in->raw_data_in((size_t*)(&msg_data[samp_start])); + + // Combine the new gradient and count of the other replicas + // with Replica 0's current state + global_z_gradients->add_grid(*z_gradients_in); + global_z_samples->add_grid(*z_samples_in); + } + } else { + // All other replicas send their current z gradient and z count. + z_gradients->raw_data_out((cvm::real*)(&msg_data[0])); + z_samples->raw_data_out((size_t*)(&msg_data[samp_start])); + if (proxy->replica_comm_send(msg_data, msg_total, 0) != msg_total) { + cvm::error("Error sending shared ABF data to replica."); + return COLVARS_ERROR; + } + } + + // Without a barrier it's possible that one replica starts + // share 2 when other replicas haven't finished share 1. + proxy->replica_comm_barrier(); + // Done syncing the replicas. + delete[] msg_data; + + return COLVARS_OK; +} + + + // ***************************** + // ****** I/O functions ****** + // ***************************** + + +size_t colvarbias_abf::replica_share_freq() const +{ + return shared_freq; +} + + template int colvarbias_abf::write_grid_to_file(T const *grid, std::string const &filename, bool close) { - std::ostream &os = cvm::proxy->output_stream(filename); + std::ostream &os = cvm::proxy->output_stream(filename, "multicolumn grid file"); if (!os) { return cvm::error("Error opening file " + filename + " for writing.\n", COLVARS_ERROR | COLVARS_FILE_ERROR); } @@ -619,7 +742,7 @@ template int colvarbias_abf::write_grid_to_file(T const *grid, // (could be implemented as multiple dx files) if (num_variables() > 2 && close) { std::string dx = filename + ".dx"; - std::ostream &dx_os = cvm::proxy->output_stream(dx); + std::ostream &dx_os = cvm::proxy->output_stream(dx, "OpenDX grid file"); if (!dx_os) { return cvm::error("Error opening file " + dx + " for writing.\n", COLVARS_ERROR | COLVARS_FILE_ERROR); } @@ -637,45 +760,77 @@ template int colvarbias_abf::write_grid_to_file(T const *grid, } -void colvarbias_abf::write_gradients_samples(const std::string &prefix, bool close) +void colvarbias_abf::write_gradients_samples(const std::string &prefix, bool close, bool local) { colvarproxy *proxy = cvm::main()->proxy; - write_grid_to_file(samples, prefix + ".count", close); - write_grid_to_file(gradients, prefix + ".grad", close); + // The following are local aliases for the class' unique pointers + colvar_grid_count *samples_out, *z_samples_out; + colvar_grid_gradient *gradients_out, *z_gradients_out, *czar_gradients_out; + integrate_potential *pmf_out, *czar_pmf_out; + + // In shared ABF, write grids containing local data only if requested + if (local) { + samples_out = local_samples.get(); + gradients_out = local_gradients.get(); + pmf_out = local_pmf.get(); + // Update the divergence before integrating the local PMF below + // only needs to happen here, just before output + local_pmf->set_div(); + z_samples_out = z_samples.get(); + z_gradients_out = z_gradients.get(); + czar_gradients_out = czar_gradients.get(); + czar_pmf_out = czar_pmf.get(); + } else { + samples_out = samples.get(); + gradients_out = gradients.get(); + pmf_out = pmf.get(); + // Note: outside of shared ABF, "global" CZAR grids are just the local ones + z_samples_out = global_z_samples.get(); + z_gradients_out = global_z_gradients.get(); + czar_gradients_out = global_czar_gradients.get(); + czar_pmf_out = global_czar_pmf.get(); + } + + write_grid_to_file(samples_out, prefix + ".count", close); + write_grid_to_file(gradients_out, prefix + ".grad", close); if (b_integrate) { // Do numerical integration (to high precision) and output a PMF cvm::real err; - pmf->integrate(integrate_iterations, integrate_tol, err); - pmf->set_zero_minimum(); - write_grid_to_file(pmf, prefix + ".pmf", close); + // Divergence has already been updated on the fly for the global PMF (member data 'pmf') + pmf_out->integrate(integrate_iterations, integrate_tol, err); + pmf_out->set_zero_minimum(); + write_grid_to_file(pmf_out, prefix + ".pmf", close); } if (b_CZAR_estimator) { // Write eABF CZAR-related quantities - write_grid_to_file(z_samples, prefix + ".zcount", close); + write_grid_to_file(z_samples_out, prefix + ".zcount", close); if (b_czar_window_file) { - write_grid_to_file(z_gradients, prefix + ".zgrad", close); + write_grid_to_file(z_gradients_out, prefix + ".zgrad", close); } - // Calculate CZAR estimator of gradients - for (std::vector ix = czar_gradients->new_index(); - czar_gradients->index_ok(ix); czar_gradients->incr(ix)) { - for (size_t n = 0; n < czar_gradients->multiplicity(); n++) { - czar_gradients->set_value(ix, z_gradients->value_output(ix, n) - - proxy->target_temperature() * proxy->boltzmann() * z_samples->log_gradient_finite_diff(ix, n), n); + // Update the CZAR estimator of gradients, except at step 0 + // in which case we preserve any existing data (e.g. read via inputPrefix, used to join strata in stratified eABF) + if (cvm::step_relative() > 0) { + for (std::vector iz_bin = czar_gradients_out->new_index(); + czar_gradients_out->index_ok(iz_bin); czar_gradients_out->incr(iz_bin)) { + for (size_t n = 0; n < czar_gradients_out->multiplicity(); n++) { + czar_gradients_out->set_value(iz_bin, z_gradients_out->value_output(iz_bin, n) + - proxy->target_temperature() * proxy->boltzmann() * z_samples_out->log_gradient_finite_diff(iz_bin, n), n); + } } } - write_grid_to_file(czar_gradients, prefix + ".czar.grad", close); + write_grid_to_file(czar_gradients_out, prefix + ".czar.grad", close); if (b_integrate) { // Do numerical integration (to high precision) and output a PMF cvm::real err; - czar_pmf->set_div(); - czar_pmf->integrate(integrate_iterations, integrate_tol, err); - czar_pmf->set_zero_minimum(); - write_grid_to_file(czar_pmf, prefix + ".czar.pmf", close); + czar_pmf_out->set_div(); + czar_pmf_out->integrate(integrate_iterations, integrate_tol, err); + czar_pmf_out->set_zero_minimum(); + write_grid_to_file(czar_pmf_out, prefix + ".czar.pmf", close); } } return; @@ -685,80 +840,98 @@ void colvarbias_abf::write_gradients_samples(const std::string &prefix, bool clo // For Tcl implementation of selection rules. /// Give the total number of bins for a given bias. int colvarbias_abf::bin_num() { - return samples->number_of_points(0); + return samples->number_of_points(); } + /// Calculate the bin index for a given bias. int colvarbias_abf::current_bin() { - return samples->current_bin_scalar(0); + return samples->current_bin_flat_bound(); } + /// Give the count at a given bin index. int colvarbias_abf::bin_count(int bin_index) { if (bin_index < 0 || bin_index >= bin_num()) { cvm::error("Error: Tried to get bin count from invalid bin index "+cvm::to_str(bin_index)); return -1; } - std::vector ix(1,(int)bin_index); - return samples->value(ix); + return int(samples->get_value(bin_index)); } +// Return the average number of samples in a given "radius" around current bin +int colvarbias_abf::colvarbias_abf::local_sample_count(int radius) { + return samples->local_sample_count(radius); +} int colvarbias_abf::read_gradients_samples() { - int error_code = COLVARS_OK; + int err = COLVARS_OK; + // Reading the CZAR gradients is necessary for joining strata in stratified eABF + std::unique_ptr czar_gradients_in; - std::string samples_in_name, gradients_in_name, z_samples_in_name, z_gradients_in_name; + if (b_CZAR_estimator) { + // CZAR gradients are usually computed as needed from z-gradients and z_samples + // Therefore the czar_gradients grid is not linked to a sampling grid + // Here we define a temporary czar_gradients grid linked to z_samples, + // to correctly average input gradients if overlapping + czar_gradients_in.reset(new colvar_grid_gradient(colvars, z_samples)); + } for ( size_t i = 0; i < input_prefix.size(); i++ ) { - samples_in_name = input_prefix[i] + ".count"; - gradients_in_name = input_prefix[i] + ".grad"; - z_samples_in_name = input_prefix[i] + ".zcount"; - z_gradients_in_name = input_prefix[i] + ".zgrad"; + std::string prefix = input_prefix[i]; + // For user-provided files, the per-bias naming scheme may not apply - cvm::log("Reading sample count from " + samples_in_name + - " and gradient from " + gradients_in_name); - - error_code |= samples->read_multicol(samples_in_name, - "ABF samples file", - true); - - error_code |= gradients->read_multicol(gradients_in_name, - "ABF gradient file", - true); + err |= samples->read_multicol(prefix + ".count", "ABF samples file", true); + err |= gradients->read_multicol(prefix + ".grad", "ABF gradient file", true); + if (shared_on) { + last_gradients->copy_grid(*gradients); + last_samples->copy_grid(*samples); + } if (b_CZAR_estimator) { // Read eABF z-averaged data for CZAR - cvm::log("Reading z-histogram from " + z_samples_in_name + " and z-gradient from " + z_gradients_in_name); - error_code |= z_samples->read_multicol(z_samples_in_name, - "eABF z-histogram file", - true); - error_code |= z_gradients->read_multicol(z_gradients_in_name, - "eABF z-gradient file", - true); + err |= z_samples->read_multicol(prefix + ".zcount", "eABF z-histogram file", true); + err |= z_gradients->read_multicol(prefix + ".zgrad", "eABF z-gradient file", true); + err |= czar_gradients_in->read_multicol(prefix + ".czar.grad", "eABF CZAR gradient file", true); } } - return error_code; + if (b_CZAR_estimator) { + // Now copy real CZAR gradients (divided by total count) to the final grid + for (std::vector ix = czar_gradients->new_index(); + czar_gradients->index_ok(ix); czar_gradients->incr(ix)) { + for (size_t n = 0; n < czar_gradients->multiplicity(); n++) { + czar_gradients->set_value(ix, czar_gradients_in->value_output(ix, n), n); + } + } + } + return err; } -std::ostream & colvarbias_abf::write_state_data(std::ostream& os) +template OST & colvarbias_abf::write_state_data_template_(OST &os) { - std::ios::fmtflags flags(os.flags()); + auto flags = os.flags(); os.setf(std::ios::fmtflags(0), std::ios::floatfield); // default floating-point format - os << "\nsamples\n"; - samples->write_raw(os, 8); - os.flags(flags); - os << "\ngradient\n"; + write_state_data_key(os, "samples"); + samples->write_raw(os, 8); + + write_state_data_key(os, "gradient"); gradients->write_raw(os, 8); + if (shared_on) { + write_state_data_key(os, "local_samples"); + local_samples->write_raw(os, 8); + write_state_data_key(os, "local_gradient"); + local_gradients->write_raw(os, 8); + } + if (b_CZAR_estimator) { os.setf(std::ios::fmtflags(0), std::ios::floatfield); // default floating-point format - os << "\nz_samples\n"; + write_state_data_key(os, "z_samples"); z_samples->write_raw(os, 8); - os.flags(flags); - os << "\nz_gradient\n"; + write_state_data_key(os, "z_gradient"); z_gradients->write_raw(os, 8); } @@ -767,7 +940,19 @@ std::ostream & colvarbias_abf::write_state_data(std::ostream& os) } -std::istream & colvarbias_abf::read_state_data(std::istream& is) +std::ostream & colvarbias_abf::write_state_data(std::ostream& os) +{ + return write_state_data_template_(os); +} + + +cvm::memory_stream & colvarbias_abf::write_state_data(cvm::memory_stream& os) +{ + return write_state_data_template_(os); +} + + +template IST &colvarbias_abf::read_state_data_template_(IST &is) { if ( input_prefix.size() > 0 ) { cvm::error("ERROR: cannot provide both inputPrefix and a colvars state file.\n", COLVARS_INPUT_ERROR); @@ -791,6 +976,21 @@ std::istream & colvarbias_abf::read_state_data(std::istream& is) pmf->set_div(); } + if (shared_on) { + if (! read_state_data_key(is, "local_samples")) { + return is; + } + if (! local_samples->read_raw(is)) { + return is; + } + if (! read_state_data_key(is, "local_gradient")) { + return is; + } + if (! local_gradients->read_raw(is)) { + return is; + } + } + if (b_CZAR_estimator) { if (! read_state_data_key(is, "z_samples")) { @@ -808,29 +1008,64 @@ std::istream & colvarbias_abf::read_state_data(std::istream& is) } } + // Last samples / gradients must be updated after restart + // reproducing the state after the last sharing step of previous run + if (shared_on) { + last_gradients->copy_grid(*gradients); + last_samples->copy_grid(*samples); + shared_last_step = cvm::step_absolute(); + } + return is; } +std::istream & colvarbias_abf::read_state_data(std::istream& is) +{ + return read_state_data_template_(is); +} + + +cvm::memory_stream & colvarbias_abf::read_state_data(cvm::memory_stream& is) +{ + return read_state_data_template_(is); +} + + int colvarbias_abf::write_output_files() { if (cvm::debug()) { cvm::log("ABF bias trying to write gradients and samples to disk"); } - if (shared_on && cvm::main()->proxy->replica_index() > 0 - && ! (b_CZAR_estimator || b_UI_estimator) ) { - // No need to report the same data as replica 0, let it do the I/O job - // except if using an eABF FE estimator - return COLVARS_OK; + // In shared eABF/CZAR, the communication routine needs to run on all ranks + if (shared_on) { + if (b_CZAR_estimator) replica_share_CZAR(); } - write_gradients_samples(output_prefix); - if (b_history_files) { - if ((cvm::step_absolute() % history_freq) == 0) { - write_gradients_samples(output_prefix + ".hist", false); + // In shared setting, output local data for all replicas + if (shared_on) { + // Write local data on all replicas + write_gradients_samples(output_prefix, true, true); + + if (cvm::main()->proxy->replica_index() > 0) { + // No need to report the same data as replica 0, let it do the I/O job + return COLVARS_OK; } } + // In shared ABF, only replica 0 reaches this + // filename prefix for master replica + // used in mwABF to distinguish local from complete data + std::string master_prefix = (shared_on ? output_prefix + ".all" : output_prefix); + write_gradients_samples(master_prefix); + + if ((history_freq > 0) && + (!shared_on || cvm::main()->proxy->replica_index() == 0) && // if shared, only on replica 0 + (cvm::step_absolute() % history_freq == 0) && // at requested frequency + (cvm::step_absolute() != history_last_step)) { // not twice the same timestep + write_gradients_samples(master_prefix + ".hist", false); + history_last_step = cvm::step_absolute(); + } if (b_UI_estimator) { eabf_UI.calc_pmf(); @@ -848,14 +1083,13 @@ int colvarbias_abf::calc_energy(std::vector const *values) if (num_variables() > 1 || values != NULL) { // Use simple estimate: neglect effect of fullSamples, // return value at center of bin - if (pmf != NULL) { - std::vector const curr_bin = values ? + if (pmf) { + std::vector curr_bin = values ? pmf->get_colvars_index(*values) : pmf->get_colvars_index(); - - if (pmf->index_ok(curr_bin)) { - bias_energy = pmf->value(curr_bin); - } + pmf->set_zero_minimum(); + pmf->wrap_to_edge(curr_bin, curr_bin); // Closest edge if outside of grid + bias_energy = pmf->value(curr_bin); } return COLVARS_OK; } @@ -870,28 +1104,14 @@ int colvarbias_abf::calc_energy(std::vector const *values) cvm::real sum = 0.0; for (int i = 0; i < home; i++) { std::vector ix(1,i); - - // Include the full_samples factor if necessary. - unsigned int count = samples->value(ix); - cvm::real fact = 1.0; - if ( count < full_samples ) { - fact = (count < min_samples) ? 0.0 : - (cvm::real(count - min_samples)) / (cvm::real(full_samples - min_samples)); - } - if (count > 0) sum += fact*gradients->value(ix)/count*gradients->widths[0]; + // Include the smoothing factor if necessary. + sum += gradients->value_output_smoothed(ix, true) * gradients->widths[0]; } // Integrate the gradient up to the current position in the home interval, a fractional portion of a bin. std::vector ix(1,home); cvm::real frac = gradients->current_bin_scalar_fraction(0); - unsigned int count = samples->value(ix); - cvm::real fact = 1.0; - if ( count < full_samples ) { - fact = (count < min_samples) ? 0.0 : - (cvm::real(count - min_samples)) / (cvm::real(full_samples - min_samples)); - } - if (count > 0) - sum += fact*gradients->value(ix)/count*gradients->widths[0]*frac; + sum += gradients->value_output_smoothed(ix, true) * gradients->widths[0] * frac; // The applied potential is the negative integral of force samples. bias_energy = -sum; diff --git a/lib/colvars/colvarbias_abf.h b/lib/colvars/colvarbias_abf.h index f5d5bd267f..4426a8a3f4 100644 --- a/lib/colvars/colvarbias_abf.h +++ b/lib/colvars/colvarbias_abf.h @@ -14,13 +14,14 @@ #include #include #include +#include #include "colvarproxy.h" #include "colvarbias.h" #include "colvargrid.h" #include "colvar_UIestimator.h" -typedef cvm::real* gradient_t; +typedef cvm::real *gradient_t; /// ABF bias @@ -31,17 +32,14 @@ public: /// Constructor for ABF bias colvarbias_abf(char const *key); /// Initializer for ABF bias - virtual int init(std::string const &conf); + int init(std::string const &conf) override; /// Default destructor for ABF bias - virtual ~colvarbias_abf(); + ~colvarbias_abf() override; /// Per-timestep update of ABF bias - virtual int update(); + int update() override; private: - /// Filename prefix for human-readable gradient/sample count output - std::string output_prefix; - /// Base filename(s) for reading previous gradient data (replaces data from restart file) std::vector input_prefix; @@ -57,8 +55,8 @@ private: size_t full_samples; /// Number of samples per bin before applying a scaled-down biasing force size_t min_samples; - /// Write combined files with a history of all output data? - bool b_history_files; + /// Latest absolute time step at which history files were written + cvm::step_number history_last_step; /// Write CZAR output file for stratified eABF (.zgrad) bool b_czar_window_file; /// Number of timesteps between recording data in history files (if non-zero) @@ -99,75 +97,104 @@ private: gradient_t system_force; /// n-dim grid of free energy gradients - colvar_grid_gradient *gradients; + std::shared_ptr gradients; /// n-dim grid of number of samples - colvar_grid_count *samples; + std::shared_ptr samples; /// n-dim grid of pmf (dimension 1 to 3) - integrate_potential *pmf; + std::shared_ptr pmf; /// n-dim grid: average force on "real" coordinate for eABF z-based estimator - colvar_grid_gradient *z_gradients; + std::shared_ptr z_gradients; /// n-dim grid of number of samples on "real" coordinate for eABF z-based estimator - colvar_grid_count *z_samples; - /// n-dim grid containing CZAR estimator of "real" free energy gradients - colvar_grid_gradient *czar_gradients; + std::shared_ptr z_samples; + /// n-dim grid containing CZAR estimatr of "real" free energy gradients + std::shared_ptr czar_gradients; /// n-dim grid of CZAR pmf (dimension 1 to 3) - integrate_potential *czar_pmf; + std::shared_ptr czar_pmf; - inline int update_system_force(size_t i) - { - if (colvars[i]->is_enabled(f_cv_subtract_applied_force)) { - // this colvar is already subtracting the ABF force - system_force[i] = colvars[i]->total_force().real_value; - } else { - system_force[i] = colvars[i]->total_force().real_value - - colvar_forces[i].real_value; - // If hideJacobian is active then total_force has an extra term of -fj - // which is the Jacobian-compensating force at the colvar level - } - if (cvm::debug()) - cvm::log("ABF System force calc: cv " + cvm::to_str(i) + - " fs " + cvm::to_str(system_force[i]) + - " = ft " + cvm::to_str(colvars[i]->total_force().real_value) + - " - fa " + cvm::to_str(colvar_forces[i].real_value)); - return COLVARS_OK; - } + /// Calculate system force for all colvars + int update_system_force(); + + /// Calulate the biasing force for the current bin + int calc_biasing_force(std::vector &force); + + /// Calulate the smoothing factor to apply to biasing forces for given local count + cvm::real smoothing_factor(cvm::real weight); // shared ABF bool shared_on; size_t shared_freq; cvm::step_number shared_last_step; - // Share between replicas -- may be called independently of update - virtual int replica_share(); - // Store the last set for shared ABF - colvar_grid_gradient *last_gradients; - colvar_grid_count *last_samples; + // Share between replicas -- may be called independently of update + int replica_share() override; + + // Share data needed for CZAR between replicas - called before output only + int replica_share_CZAR(); + + /// Report the frequency at which this bias needs to communicate with replicas + size_t replica_share_freq() const override; + + // Data just after the last share (start of cycle) in shared ABF + std::unique_ptr last_gradients; + std::shared_ptr last_samples; + // eABF/CZAR local data last shared + std::unique_ptr z_gradients_in; + std::shared_ptr z_samples_in; + // ABF data from local replica only in shared ABF + std::shared_ptr local_gradients; + std::shared_ptr local_samples; + std::unique_ptr local_pmf; + // eABF/CZAR data collected from all replicas in shared eABF on replica 0 + // if non-shared, aliases of regular CZAR grids, for output purposes + std::shared_ptr global_z_gradients; + std::shared_ptr global_z_samples; + std::shared_ptr global_czar_gradients; + std::shared_ptr global_czar_pmf; + // For Tcl implementation of selection rules. /// Give the total number of bins for a given bias. - virtual int bin_num(); + int bin_num() override; /// Calculate the bin index for a given bias. - virtual int current_bin(); + int current_bin() override; //// Give the count at a given bin index. - virtual int bin_count(int bin_index); + int bin_count(int bin_index) override; + /// Return the average number of samples in a given "radius" around current bin + int local_sample_count(int radius) override; /// Write human-readable FE gradients and sample count, and DX file in dim > 2 - void write_gradients_samples(const std::string &prefix, bool close = true); + /// \param local write grids contining replica-local data in shared ABF + void write_gradients_samples(const std::string &prefix, bool close = true, bool local = false); /// Read human-readable FE gradients and sample count (if not using restart) int read_gradients_samples(); - /// Template used in write_gradient_samples() + /// Shorthand template used in write_gradient_samples() template int write_grid_to_file(T const *grid, std::string const &name, bool close); - virtual std::istream& read_state_data(std::istream&); - virtual std::ostream& write_state_data(std::ostream&); - virtual int write_output_files(); +private: + + /// Generic stream writing function (formatted and not) + template OST &write_state_data_template_(OST &os); + + /// Generic stream readingx function (formatted and not) + template IST &read_state_data_template_(IST &is); + +public: + + std::ostream &write_state_data(std::ostream &os) override; + + cvm::memory_stream &write_state_data(cvm::memory_stream &os) override; + + std::istream &read_state_data(std::istream &is) override; + + cvm::memory_stream &read_state_data(cvm::memory_stream &is) override; + + int write_output_files() override; /// Calculate the bias energy for 1D ABF - virtual int calc_energy(std::vector const *values); + int calc_energy(std::vector const *values) override; }; - #endif diff --git a/lib/colvars/colvarbias_abmd.cpp b/lib/colvars/colvarbias_abmd.cpp new file mode 100644 index 0000000000..4825c73be4 --- /dev/null +++ b/lib/colvars/colvarbias_abmd.cpp @@ -0,0 +1,136 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + + +#include "colvarbias_abmd.h" +#include "colvarproxy.h" +#include + + +colvarbias_abmd::colvarbias_abmd(char const *key) + : colvarbias(key), + colvarbias_ti(key) +{ +} + + +int colvarbias_abmd::init(std::string const &conf) +{ + cvm::main()->cite_feature("ABMD bias"); + + int err = colvarbias::init(conf); + err |= colvarbias_ti::init(conf); + if (err != COLVARS_OK) return err; + + enable(f_cvb_apply_force); + + if (num_variables() != 1) { + return cvm::error("ABMD requires exactly one collective variable.\n", COLVARS_INPUT_ERROR); + } + + if ( ! (variables(0))->is_enabled(f_cv_scalar) ) { + return cvm::error("ABMD colvar must be scalar.\n", COLVARS_INPUT_ERROR); + } + + get_keyval(conf, "forceConstant", k); + get_keyval(conf, "decreasing", decreasing, decreasing); + get_keyval(conf, "stoppingValue", stopping_val); + + return COLVARS_OK; +} + + +int colvarbias_abmd::update() +{ + if (!cvm::main()->proxy->simulation_running()) { + return COLVARS_OK; + } + + colvar const *cv = variables(0); + cvm::real const val = cv->value().real_value; + + if (!ref_initialized) { + ref_val = val; + ref_initialized = true; + } + + // Compute sign factor to unify increasing and decreasing cases below + // less conditionals, more arithmetic + cvm::real const sign = decreasing ? -1. : 1.; + cvm::real const diff = (val - ref_val) * sign; + + if ( diff > 0. ) { + colvar_forces[0] = 0.; + bias_energy = 0.; + if ( (ref_val-stopping_val) * sign <= 0. ) ref_val = val; + } else { + colvar_forces[0] = - sign * k * diff; + bias_energy = 0.5 * k * diff * diff;; + } + return COLVARS_OK; +} + + +std::string const colvarbias_abmd::get_state_params() const +{ + std::ostringstream os; + os.setf(std::ios::scientific, std::ios::floatfield); + + os << " refValue " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << ref_val << "\n"; + os << " stoppingValue " << stopping_val << "\n"; + os << " forceConstant " << k << "\n"; + os << " decreasing " << (decreasing ? "on" : "off") << "\n"; + + return (colvarbias::get_state_params() + os.str()); +} + + +int colvarbias_abmd::set_state_params(std::string const &conf) +{ + int error_code = colvarbias::set_state_params(conf); + + if (error_code != COLVARS_OK) { + return error_code; + } + + get_keyval(conf, "refValue", ref_val, ref_val, + colvarparse::parse_restart | colvarparse::parse_required); + ref_initialized = true; + + get_keyval(conf, "forceConstant", k, k, + colvarparse::parse_restart | colvarparse::parse_required); + get_keyval(conf, "decreasing", decreasing, decreasing, + colvarparse::parse_restart | colvarparse::parse_required); + get_keyval(conf, "stoppingValue", stopping_val, stopping_val, + colvarparse::parse_restart | colvarparse::parse_required); + + return COLVARS_OK; +} + + +std::ostream & colvarbias_abmd::write_traj_label(std::ostream &os) +{ + size_t const this_cv_width = (variables(0)->value()).output_width(cvm::cv_width); + os << " ref_" + << cvm::wrap_string(variables(0)->name, this_cv_width-4); + + return os; +} + + +std::ostream & colvarbias_abmd::write_traj(std::ostream &os) +{ + os << " " + << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) + << ref_val; + + return os; +} diff --git a/lib/colvars/colvarbias_abmd.h b/lib/colvars/colvarbias_abmd.h new file mode 100644 index 0000000000..96278c2de8 --- /dev/null +++ b/lib/colvars/colvarbias_abmd.h @@ -0,0 +1,49 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARBIAS_ABMD_H +#define COLVARBIAS_ABMD_H + +#include "colvarbias_restraint.h" + + +/// \brief Adiabatic Bias MD +class colvarbias_abmd + : public colvarbias_ti +{ +public: + + colvarbias_abmd(char const *key); + virtual int init(std::string const &conf); + virtual int update(); + virtual std::string const get_state_params() const; + virtual int set_state_params(std::string const &conf); + virtual std::ostream & write_traj_label(std::ostream &os); + virtual std::ostream & write_traj(std::ostream &os); + +protected: + + /// \brief Location of the moving wall + cvm::real ref_val = 0.; + + /// \brief Has ref_val already been set? + bool ref_initialized = false; + + /// \brief Value of the reference where it stops moving + cvm::real stopping_val = 0.; + + /// \brief Is the target moving down? + bool decreasing = false; + + /// \brief Restraint force constant + cvm::real k = 0.; +}; + + +#endif diff --git a/lib/colvars/colvarbias_alb.cpp b/lib/colvars/colvarbias_alb.cpp index b432659bf4..5b664b8c0b 100644 --- a/lib/colvars/colvarbias_alb.cpp +++ b/lib/colvars/colvarbias_alb.cpp @@ -9,7 +9,6 @@ #include #include -#include #include "colvarmodule.h" #include "colvarproxy.h" @@ -40,7 +39,10 @@ colvarbias_alb::colvarbias_alb(char const *key) int colvarbias_alb::init(std::string const &conf) { colvarproxy *proxy = cvm::main()->proxy; - colvarbias::init(conf); + int err = colvarbias::init(conf); + if (err != COLVARS_OK) { + return err; + } cvm::main()->cite_feature("ALB colvar bias implementation"); enable(f_cvb_scalar_variables); diff --git a/lib/colvars/colvarbias_histogram.cpp b/lib/colvars/colvarbias_histogram.cpp index 84f1a5bdee..98de275304 100644 --- a/lib/colvars/colvarbias_histogram.cpp +++ b/lib/colvars/colvarbias_histogram.cpp @@ -7,10 +7,13 @@ // If you wish to distribute your changes, please submit them to the // Colvars repository at GitHub. +#include + #include "colvarmodule.h" #include "colvarproxy.h" #include "colvar.h" #include "colvarbias_histogram.h" +#include "colvars_memstream.h" colvarbias_histogram::colvarbias_histogram(char const *key) @@ -23,7 +26,10 @@ colvarbias_histogram::colvarbias_histogram(char const *key) int colvarbias_histogram::init(std::string const &conf) { - colvarbias::init(conf); + int err = colvarbias::init(conf); + if (err != COLVARS_OK) { + return err; + } cvm::main()->cite_feature("Histogram colvar bias implementation"); enable(f_cvb_scalar_variables); @@ -125,22 +131,6 @@ int colvarbias_histogram::update() // assign a valid bin size bin.assign(num_variables(), 0); - if (out_name.size() == 0) { - // At the first timestep, we need to assign out_name since - // output_prefix is unset during the constructor - if (cvm::step_relative() == 0) { - out_name = cvm::output_prefix() + "." + this->name + ".dat"; - cvm::log("Histogram " + this->name + " will be written to file \"" + out_name + "\"\n"); - } - } - - if (out_name_dx.size() == 0) { - if (cvm::step_relative() == 0) { - out_name_dx = cvm::output_prefix() + "." + this->name + ".dx"; - cvm::log("Histogram " + this->name + " will be written to file \"" + out_name_dx + "\"\n"); - } - } - if (colvar_array_size == 0) { // update indices for scalar values size_t i; @@ -181,6 +171,16 @@ int colvarbias_histogram::write_output_files() int error_code = COLVARS_OK; + // Set default filenames, if none have been provided + if (!cvm::output_prefix().empty()) { + if (out_name.empty()) { + out_name = cvm::output_prefix() + "." + this->name + ".dat"; + } + if (out_name_dx.empty()) { + out_name_dx = cvm::output_prefix() + "." + this->name + ".dx"; + } + } + if (out_name.size() && out_name != "none") { cvm::log("Writing the histogram file \""+out_name+"\".\n"); error_code |= grid->write_multicol(out_name, "histogram output file"); @@ -197,13 +197,18 @@ int colvarbias_histogram::write_output_files() std::istream & colvarbias_histogram::read_state_data(std::istream& is) { - if (! read_state_data_key(is, "grid")) { - return is; - } - if (! grid->read_raw(is)) { - return is; + if (read_state_data_key(is, "grid")) { + grid->read_raw(is); } + return is; +} + +cvm::memory_stream & colvarbias_histogram::read_state_data(cvm::memory_stream& is) +{ + if (read_state_data_key(is, "grid")) { + grid->read_raw(is); + } return is; } @@ -212,8 +217,16 @@ std::ostream & colvarbias_histogram::write_state_data(std::ostream& os) { std::ios::fmtflags flags(os.flags()); os.setf(std::ios::fmtflags(0), std::ios::floatfield); - os << "grid\n"; + write_state_data_key(os, "grid"); grid->write_raw(os, 8); os.flags(flags); return os; } + + +cvm::memory_stream & colvarbias_histogram::write_state_data(cvm::memory_stream& os) +{ + write_state_data_key(os, "grid"); + grid->write_raw(os); + return os; +} diff --git a/lib/colvars/colvarbias_histogram.h b/lib/colvars/colvarbias_histogram.h index 6044fa189e..2c6ee84d1f 100644 --- a/lib/colvars/colvarbias_histogram.h +++ b/lib/colvars/colvarbias_histogram.h @@ -24,11 +24,16 @@ class colvarbias_histogram : public colvarbias { public: colvarbias_histogram(char const *key); - ~colvarbias_histogram(); + virtual ~colvarbias_histogram(); virtual int init(std::string const &conf); virtual int update(); virtual int write_output_files(); + virtual std::ostream & write_state_data(std::ostream &os); + virtual cvm::memory_stream & write_state_data(cvm::memory_stream &os); + virtual std::istream & read_state_data(std::istream &is); + virtual cvm::memory_stream & read_state_data(cvm::memory_stream &is); + protected: /// n-dim histogram @@ -40,9 +45,6 @@ protected: size_t colvar_array_size; /// If colvar_array_size is larger than 1, weigh each one by this number before accumulating the histogram std::vector weights; - - virtual std::istream & read_state_data(std::istream &is); - virtual std::ostream & write_state_data(std::ostream &os); }; #endif diff --git a/lib/colvars/colvarbias_histogram_reweight_amd.cpp b/lib/colvars/colvarbias_histogram_reweight_amd.cpp index 85f1bde35c..de2f6d9b8a 100644 --- a/lib/colvars/colvarbias_histogram_reweight_amd.cpp +++ b/lib/colvars/colvarbias_histogram_reweight_amd.cpp @@ -9,6 +9,7 @@ #include "colvarbias_histogram_reweight_amd.h" #include "colvarproxy.h" +#include "colvars_memstream.h" colvarbias_reweightaMD::colvarbias_reweightaMD(char const *key) : colvarbias_histogram(key), grid_count(NULL), grid_dV(NULL), @@ -343,23 +344,37 @@ void colvarbias_reweightaMD::compute_cumulant_expansion_factor( } } -std::ostream & colvarbias_reweightaMD::write_state_data(std::ostream& os) + +template OST & colvarbias_reweightaMD::write_state_data_template_(OST& os) { std::ios::fmtflags flags(os.flags()); os.setf(std::ios::fmtflags(0), std::ios::floatfield); - os << "grid\n"; + write_state_data_key(os, "grid"); grid->write_raw(os, 8); - os << "grid_count\n"; + write_state_data_key(os, "grid_count"); grid_count->write_raw(os, 8); - os << "grid_dV\n"; + write_state_data_key(os, "grid_dV"); grid_dV->write_raw(os, 8); - os << "grid_dV_square\n"; + write_state_data_key(os, "grid_dV_square"); grid_dV_square->write_raw(os, 8); os.flags(flags); return os; } -std::istream & colvarbias_reweightaMD::read_state_data(std::istream& is) + +std::ostream & colvarbias_reweightaMD::write_state_data(std::ostream& os) +{ + return write_state_data_template_(os); +} + + +cvm::memory_stream & colvarbias_reweightaMD::write_state_data(cvm::memory_stream& os) +{ + return write_state_data_template_(os); +} + + +template IST & colvarbias_reweightaMD::read_state_data_template_(IST& is) { if (! read_state_data_key(is, "grid")) { return is; @@ -387,3 +402,15 @@ std::istream & colvarbias_reweightaMD::read_state_data(std::istream& is) } return is; } + + +std::istream & colvarbias_reweightaMD::read_state_data(std::istream& is) +{ + return read_state_data_template_(is); +} + + +cvm::memory_stream & colvarbias_reweightaMD::read_state_data(cvm::memory_stream& is) +{ + return read_state_data_template_(is); +} diff --git a/lib/colvars/colvarbias_histogram_reweight_amd.h b/lib/colvars/colvarbias_histogram_reweight_amd.h index f126738305..43759b3bde 100644 --- a/lib/colvars/colvarbias_histogram_reweight_amd.h +++ b/lib/colvars/colvarbias_histogram_reweight_amd.h @@ -18,15 +18,9 @@ class colvarbias_reweightaMD : public colvarbias_histogram { public: colvarbias_reweightaMD(char const *key); virtual ~colvarbias_reweightaMD(); -#if (__cplusplus >= 201103L) virtual int init(std::string const &conf) override; virtual int update() override; virtual int write_output_files() override; -#else - virtual int init(std::string const &conf); - virtual int update(); - virtual int write_output_files(); -#endif /// @brief convert histogram to PMF by taking logarithm and multiplying /// it with -1/beta @@ -85,14 +79,15 @@ protected: /// Write gradients of the PMF? bool b_write_gradients; + template OST & write_state_data_template_(OST& os); + template IST & read_state_data_template_(IST& is); + /// save and restore -#if (__cplusplus >= 201103L) virtual std::istream & read_state_data(std::istream &is) override; + virtual cvm::memory_stream & read_state_data(cvm::memory_stream &is) override; virtual std::ostream & write_state_data(std::ostream &os) override; -#else - virtual std::istream & read_state_data(std::istream &is); - virtual std::ostream & write_state_data(std::ostream &os); -#endif + virtual cvm::memory_stream & write_state_data(cvm::memory_stream &os) override; + private: /// temporary grids for evaluating PMFs colvar_grid_scalar *pmf_grid_exp_avg; diff --git a/lib/colvars/colvarbias_meta.cpp b/lib/colvars/colvarbias_meta.cpp index 90497bf150..905cd17883 100644 --- a/lib/colvars/colvarbias_meta.cpp +++ b/lib/colvars/colvarbias_meta.cpp @@ -7,29 +7,33 @@ // If you wish to distribute your changes, please submit them to the // Colvars repository at GitHub. -#include -#include #include #include #include -// used to set the absolute path of a replica file +// Define function to get the absolute path of a replica file #if defined(_WIN32) && !defined(__CYGWIN__) #include -#define CHDIR ::_chdir -#define GETCWD ::_getcwd +#define GETCWD(BUF, SIZE) ::_getcwd(BUF, SIZE) #define PATHSEP "\\" #else #include -#define CHDIR ::chdir -#define GETCWD ::getcwd +#define GETCWD(BUF, SIZE) ::getcwd(BUF, SIZE) #define PATHSEP "/" #endif +#ifdef __cpp_lib_filesystem +// When std::filesystem is available, use it +#include +#undef GETCWD +#define GETCWD(BUF, SIZE) (std::filesystem::current_path().string().c_str()) +#endif + #include "colvarmodule.h" #include "colvarproxy.h" #include "colvar.h" #include "colvarbias_meta.h" +#include "colvars_memstream.h" colvarbias_meta::colvarbias_meta(char const *key) @@ -58,7 +62,6 @@ colvarbias_meta::colvarbias_meta(char const *key) ebmeta_equil_steps = 0L; - replica_update_freq = 0; replica_id.clear(); } @@ -392,11 +395,8 @@ colvarbias_meta::add_hill(colvarbias_meta::hill const &h) // output to trajectory (if specified) if (b_hills_traj) { - // Open trajectory file or access the one already open - std::ostream &hills_traj_os = - cvm::proxy->output_stream(hills_traj_file_name()); - hills_traj_os << (hills.back()).output_traj(); - cvm::proxy->flush_output_stream(hills_traj_file_name()); + // Save the current hill to a buffer for further traj output + hills_traj_os_buf << (hills.back()).output_traj(); } has_data = true; @@ -427,13 +427,10 @@ colvarbias_meta::delete_hill(hill_iter &h) } if (b_hills_traj) { - // output to the trajectory - std::ostream &hills_traj_os = - cvm::proxy->output_stream(hills_traj_file_name()); - hills_traj_os << "# DELETED this hill: " - << (hills.back()).output_traj() - << "\n"; - cvm::proxy->flush_output_stream(hills_traj_file_name()); + // Save the current hill to a buffer for further traj output + hills_traj_os_buf << "# DELETED this hill: " + << (hills.back()).output_traj() + << "\n"; } return hills.erase(h); @@ -624,9 +621,9 @@ int colvarbias_meta::update_bias() add_hill(hill(cvm::step_absolute(), hill_weight*hills_scale, colvar_values, colvar_sigmas, replica_id)); std::ostream &replica_hills_os = - cvm::proxy->output_stream(replica_hills_file); + cvm::proxy->output_stream(replica_hills_file, "replica hills file"); if (replica_hills_os) { - replica_hills_os << hills.back(); + write_hill(replica_hills_os, hills.back()); } else { return cvm::error("Error: in metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ @@ -985,9 +982,9 @@ void colvarbias_meta::recount_hills_off_grid(colvarbias_meta::hill_iter h_first int colvarbias_meta::replica_share() { int error_code = COLVARS_OK; - colvarproxy *proxy = cvm::proxy; // sync with the other replicas (if needed) if (comm == multiple_replicas) { + colvarproxy *proxy = cvm::main()->proxy; // reread the replicas registry error_code |= update_replicas_registry(); // empty the output buffer @@ -998,6 +995,12 @@ int colvarbias_meta::replica_share() } +size_t colvarbias_meta::replica_share_freq() const +{ + return replica_update_freq; +} + + int colvarbias_meta::update_replicas_registry() { int error_code = COLVARS_OK; @@ -1299,21 +1302,40 @@ int colvarbias_meta::set_state_params(std::string const &state_conf) } -std::istream & colvarbias_meta::read_state_data(std::istream& is) +template +IST & colvarbias_meta::read_grid_data_template_(IST& is, std::string const &key, + GT *grid, GT *backup_grid) +{ + auto const start_pos = is.tellg(); + std::string key_in; + if (is >> key_in) { + if ((key != key_in) || !(grid->read_restart(is))) { + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + if (!rebin_grids) { + if ((backup_grid == nullptr) || (comm == single_replica)) { + cvm::error("Error: couldn't read grid data for metadynamics bias \""+ + this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + "; if useGrids was off when the state file was written, " + "try enabling rebinGrids now to regenerate the grids.\n", COLVARS_INPUT_ERROR); + } + } + } + } else { + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + } + return is; +} + + +template IST &colvarbias_meta::read_state_data_template_(IST &is) { if (use_grids) { - if (expand_grids) { - // the boundaries of the colvars may have been changed; TODO: - // this reallocation is only for backward-compatibility, and may - // be deleted when grid_parameters (i.e. colvargrid's own - // internal reallocation) has kicked in - delete hills_energy; - delete hills_energy_gradients; - hills_energy = new colvar_grid_scalar(colvars); - hills_energy_gradients = new colvar_grid_gradient(colvars); - } - colvar_grid_scalar *hills_energy_backup = NULL; colvar_grid_gradient *hills_energy_gradients_backup = NULL; @@ -1328,95 +1350,26 @@ std::istream & colvarbias_meta::read_state_data(std::istream& is) hills_energy_gradients = new colvar_grid_gradient(colvars); } - std::streampos const hills_energy_pos = is.tellg(); - std::string key; - if (!(is >> key)) { - if (hills_energy_backup != NULL) { - delete hills_energy; - delete hills_energy_gradients; - hills_energy = hills_energy_backup; - hills_energy_gradients = hills_energy_gradients_backup; + read_grid_data_template_(is, "hills_energy", hills_energy, + hills_energy_backup); + + read_grid_data_template_( + is, "hills_energy_gradients", hills_energy_gradients, hills_energy_gradients_backup); + + if (is) { + cvm::log(" successfully read the biasing potential and its gradients from grids.\n"); + if (hills_energy_backup != nullptr) { + // Now that we have successfully updated the grids, delete the backup copies + delete hills_energy_backup; + delete hills_energy_gradients_backup; } - is.clear(); - is.seekg(hills_energy_pos, std::ios::beg); - is.setstate(std::ios::failbit); + } else { return is; - } else if (!(key == std::string("hills_energy")) || - !(hills_energy->read_restart(is))) { - is.clear(); - is.seekg(hills_energy_pos, std::ios::beg); - if (!rebin_grids) { - if ((hills_energy_backup == NULL) || (comm == single_replica)) { - cvm::error("Error: couldn't read the energy grid for metadynamics bias \""+ - this->name+"\""+ - ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ - "; if useGrids was off when the state file was written, " - "enable rebinGrids now to regenerate the grids.\n"); - } else { - delete hills_energy; - delete hills_energy_gradients; - hills_energy = hills_energy_backup; - hills_energy_gradients = hills_energy_gradients_backup; - is.setstate(std::ios::failbit); - return is; - } - } - } - - std::streampos const hills_energy_gradients_pos = is.tellg(); - if (!(is >> key)) { - if (hills_energy_backup != NULL) { - delete hills_energy; - delete hills_energy_gradients; - hills_energy = hills_energy_backup; - hills_energy_gradients = hills_energy_gradients_backup; - } - is.clear(); - is.seekg(hills_energy_gradients_pos, std::ios::beg); - is.setstate(std::ios::failbit); - return is; - } else if (!(key == std::string("hills_energy_gradients")) || - !(hills_energy_gradients->read_restart(is))) { - is.clear(); - is.seekg(hills_energy_gradients_pos, std::ios::beg); - if (!rebin_grids) { - if ((hills_energy_backup == NULL) || (comm == single_replica)) { - cvm::error("Error: couldn't read the gradients grid for metadynamics bias \""+ - this->name+"\""+ - ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ - "; if useGrids was off when the state file was written, " - "enable rebinGrids now to regenerate the grids.\n"); - } else { - delete hills_energy; - delete hills_energy_gradients; - hills_energy = hills_energy_backup; - hills_energy_gradients = hills_energy_gradients_backup; - is.setstate(std::ios::failbit); - return is; - } - } - } - - if (cvm::debug()) - cvm::log("Successfully read new grids for bias \""+ - this->name+"\""+ - ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+"\n"); - - cvm::log(" read biasing energy and forces from grids.\n"); - - if (hills_energy_backup != NULL) { - // now that we have successfully updated the grids, delete the - // backup copies - if (cvm::debug()) - cvm::log("Deallocating the older grids.\n"); - - delete hills_energy_backup; - delete hills_energy_gradients_backup; } } - // Save references to the end of the list of existing hills, so that it can - // be cleared if hills are read successfully state + // Save references to the end of the list of existing hills, so that they can + // be cleared if hills are read successfully from the stream bool const existing_hills = !hills.empty(); size_t const old_hills_size = hills.size(); hill_iter old_hills_end = hills.end(); @@ -1430,17 +1383,20 @@ std::istream & colvarbias_meta::read_state_data(std::istream& is) while (read_hill(is)) { if (cvm::debug()) { cvm::log("Read a previously saved hill under the " - "metadynamics bias \""+ - this->name+"\", created at step "+ - cvm::to_str((hills.back()).it)+".\n"); + "metadynamics bias \"" + + this->name + "\", created at step " + cvm::to_str((hills.back()).it) + + "; position in stream is " + cvm::to_str(is.tellg()) + ".\n"); } } + is.clear(); + new_hills_begin = hills.end(); - cvm::log(" read "+cvm::to_str(hills.size() - old_hills_size)+ - " additional explicit hills.\n"); + cvm::log(" successfully read "+cvm::to_str(hills.size() - old_hills_size)+ + " explicit hills from state.\n"); if (existing_hills) { + // Prune any hills that pre-existed those just read hills.erase(hills.begin(), old_hills_end); hills_off_grid.erase(hills_off_grid.begin(), old_hills_off_grid_end); if (cvm::debug()) { @@ -1449,6 +1405,46 @@ std::istream & colvarbias_meta::read_state_data(std::istream& is) } } + // If rebinGrids is set, rebin the grids based on the current information + rebin_grids_after_restart(); + + if (use_grids) { + if (!hills_off_grid.empty()) { + cvm::log(cvm::to_str(hills_off_grid.size())+" hills are near the " + "grid boundaries: they will be computed analytically " + "and saved to the state files.\n"); + } + } + + colvarbias_ti::read_state_data(is); + + if (cvm::debug()) + cvm::log("colvarbias_meta::read_restart() done\n"); + + has_data = true; + + if (comm == multiple_replicas) { + read_replica_files(); + } + + return is; +} + + +std::istream & colvarbias_meta::read_state_data(std::istream& is) +{ + return read_state_data_template_(is); +} + + +cvm::memory_stream &colvarbias_meta::read_state_data(cvm::memory_stream &is) +{ + return read_state_data_template_(is); +} + + +void colvarbias_meta::rebin_grids_after_restart() +{ if (rebin_grids) { // allocate new grids (based on the new boundaries and widths just @@ -1463,9 +1459,9 @@ std::istream & colvarbias_meta::read_state_data(std::istream& is) if (cvm::debug()) { std::ostringstream tmp_os; tmp_os << "hills_energy parameters:\n"; - hills_energy->write_params(tmp_os); + tmp_os << hills_energy->get_state_params(); tmp_os << "new_hills_energy parameters:\n"; - new_hills_energy->write_params(tmp_os); + tmp_os << new_hills_energy->get_state_params(); cvm::log(tmp_os.str()); } @@ -1495,114 +1491,182 @@ std::istream & colvarbias_meta::read_state_data(std::istream& is) if (!hills.empty()) recount_hills_off_grid(hills.begin(), hills.end(), hills_energy); } +} - if (use_grids) { - if (!hills_off_grid.empty()) { - cvm::log(cvm::to_str(hills_off_grid.size())+" hills are near the " - "grid boundaries: they will be computed analytically " - "and saved to the state files.\n"); + +template +OST &colvarbias_meta::write_hill_template_(OST &os, colvarbias_meta::hill const &h) +{ + bool const formatted = !std::is_same::value; + + if (formatted) { + os.setf(std::ios::scientific, std::ios::floatfield); + } + + write_state_data_key(os, "hill", false); + + if (formatted) + os << "{\n"; + + write_state_data_key(os, "step", false); + if (formatted) + os << std::setw(cvm::it_width); + os << h.it; + if (formatted) + os << "\n"; + + write_state_data_key(os, "weight", false); + if (formatted) + os << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width); + os << h.W; + if (formatted) + os << "\n"; + + size_t i; + write_state_data_key(os, "centers", false); + for (i = 0; i < (h.centers).size(); i++) { + if (formatted) + os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width); + os << h.centers[i]; + } + if (formatted) + os << "\n"; + + // For backward compatibility, write the widths instead of the sigmas + write_state_data_key(os, "widths", false); + for (i = 0; i < (h.sigmas).size(); i++) { + if (formatted) + os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width); + os << 2.0 * h.sigmas[i]; + } + if (formatted) + os << "\n"; + + if (h.replica.size()) { + write_state_data_key(os, "replicaID", false); + os << h.replica; + if (formatted) + os << "\n"; + } + + if (formatted) + os << "}\n"; + + return os; +} + + +std::ostream &colvarbias_meta::write_hill(std::ostream &os, colvarbias_meta::hill const &h) +{ + return write_hill_template_(os, h); +} + + +cvm::memory_stream &colvarbias_meta::write_hill(cvm::memory_stream &os, + colvarbias_meta::hill const &h) +{ + return write_hill_template_(os, h); +} + + +template IST &hill_stream_error(IST &is, size_t start_pos, std::string const &key) +{ + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + cvm::error("Error: in reading data for keyword \"" + key + "\" from stream.\n", + COLVARS_INPUT_ERROR); + return is; +} + + +template IST &colvarbias_meta::read_hill_template_(IST &is) +{ + if (!is) + return is; // do nothing if failbit is set + + bool const formatted = !std::is_same::value; + + auto const start_pos = is.tellg(); + + std::string key; + if (!(is >> key) || (key != "hill")) { + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + return is; + } + + if (formatted) { + std::string brace; + if (!(is >> brace) || (brace != "{")) { + return hill_stream_error(is, start_pos, "hill"); } } - colvarbias_ti::read_state_data(is); - - if (cvm::debug()) - cvm::log("colvarbias_meta::read_restart() done\n"); - - has_data = true; - - if (comm != single_replica) { - read_replica_files(); - } - - return is; -} - - -inline std::istream & reset_istream(std::istream &is, size_t start_pos) -{ - is.clear(); - is.seekg(start_pos, std::ios::beg); - is.setstate(std::ios::failbit); - return is; -} - - -std::istream & colvarbias_meta::read_hill(std::istream &is) -{ - if (!is) return is; // do nothing if failbit is set - - std::streampos const start_pos = is.tellg(); - size_t i = 0; - - std::string data; - if ( !(is >> read_block("hill", &data)) ) { - return reset_istream(is, start_pos); - } - - std::istringstream data_is(data); - cvm::step_number h_it = 0L; - cvm::real h_weight; + cvm::real h_weight = 0.0; std::vector h_centers(num_variables()); - for (i = 0; i < num_variables(); i++) { + for (size_t i = 0; i < num_variables(); i++) { h_centers[i].type(variables(i)->value()); } std::vector h_sigmas(num_variables()); std::string h_replica; - std::string keyword; - while (data_is >> keyword) { + if (!read_state_data_key(is, "step") || !(is >> h_it)) { + return hill_stream_error(is, start_pos, "step"); + } - if (keyword == "step") { - if ( !(data_is >> h_it)) { - return reset_istream(is, start_pos); - } - if ((h_it <= state_file_step) && !restart_keep_hills) { - if (cvm::debug()) - cvm::log("Skipping a hill older than the state file for metadynamics bias \""+ - this->name+"\""+ - ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+"\n"); - return is; + if (read_state_data_key(is, "weight")) { + if (!(is >> h_weight)) { + return hill_stream_error(is, start_pos, "weight"); + } + } + + if (read_state_data_key(is, "centers")) { + for (size_t i = 0; i < num_variables(); i++) { + if (!(is >> h_centers[i])) { + return hill_stream_error(is, start_pos, "centers"); } } + } - if (keyword == "weight") { - if ( !(data_is >> h_weight)) { - return reset_istream(is, start_pos); + if (read_state_data_key(is, "widths")) { + for (size_t i = 0; i < num_variables(); i++) { + if (!(is >> h_sigmas[i])) { + return hill_stream_error(is, start_pos, "widths"); + } + // For backward compatibility, read the widths instead of the sigmas + h_sigmas[i] /= 2.0; + } + } + + if (comm != single_replica) { + if (read_state_data_key(is, "replicaID")) { + if (!(is >> h_replica)) { + return hill_stream_error(is, start_pos, "replicaID"); + } + if (h_replica != replica_id) { + cvm::error("Error: trying to read a hill created by replica \"" + h_replica + + "\" for replica \"" + replica_id + "\"; did you swap output files?\n", + COLVARS_INPUT_ERROR); + return hill_stream_error(is, start_pos, "replicaID"); } } + } - if (keyword == "centers") { - for (i = 0; i < num_variables(); i++) { - if ( !(data_is >> h_centers[i])) { - return reset_istream(is, start_pos); - } - } + if (formatted) { + std::string brace; + if (!(is >> brace) || (brace != "}")) { + return hill_stream_error(is, start_pos, "hill"); } + } - if (keyword == "widths") { - for (i = 0; i < num_variables(); i++) { - if ( !(data_is >> h_sigmas[i])) { - return reset_istream(is, start_pos); - } - // For backward compatibility, read the widths instead of the sigmas - h_sigmas[i] /= 2.0; - } - } - - if (comm != single_replica) { - if (keyword == "replicaID") { - if ( !(data_is >> h_replica)) { - return reset_istream(is, start_pos); - } - if (h_replica != replica_id) { - cvm::error("Error: trying to read a hill created by replica \""+ - h_replica+"\" for replica \""+replica_id+ - "\"; did you swap output files?\n", COLVARS_INPUT_ERROR); - } - } - } + if ((h_it <= state_file_step) && !restart_keep_hills) { + if (cvm::debug()) + cvm::log("Skipping a hill older than the state file for metadynamics bias \"" + this->name + + "\"" + ((comm != single_replica) ? ", replica \"" + replica_id + "\"" : "") + "\n"); + return is; } hill_iter const hills_end = hills.end(); @@ -1617,7 +1681,7 @@ std::istream & colvarbias_meta::read_hill(std::istream &is) // add this also to the list of hills that are off-grid, which will // be computed analytically cvm::real const min_dist = - hills_energy->bin_distance_from_boundaries((hills.back()).centers, true); + hills_energy->bin_distance_from_boundaries((hills.back()).centers, true); if (min_dist < (3.0 * cvm::floor(hill_width)) + 1.0) { hills_off_grid.push_back(hills.back()); } @@ -1628,6 +1692,18 @@ std::istream & colvarbias_meta::read_hill(std::istream &is) } +std::istream &colvarbias_meta::read_hill(std::istream &is) +{ + return read_hill_template_(is); +} + + +cvm::memory_stream &colvarbias_meta::read_hill(cvm::memory_stream &is) +{ + return read_hill_template_(is); +} + + int colvarbias_meta::setup_output() { int error_code = COLVARS_OK; @@ -1644,7 +1720,10 @@ int colvarbias_meta::setup_output() // TODO: one may want to specify the path manually for intricated filesystems? char *pwd = new char[3001]; - if (GETCWD(pwd, 3000) == NULL) { + if (GETCWD(pwd, 3000) == nullptr) { + if (pwd != nullptr) { // + delete[] pwd; + } return cvm::error("Error: cannot get the path of the current working directory.\n", COLVARS_BUG_ERROR); } @@ -1701,7 +1780,7 @@ int colvarbias_meta::setup_output() // if we're running without grids, use a growing list of "hills" files // otherwise, just one state file and one "hills" file as buffer - std::ostream &list_os = cvm::proxy->output_stream(replica_list_file); + std::ostream &list_os = cvm::proxy->output_stream(replica_list_file, "replica list file"); if (list_os) { list_os << "stateFile " << replica_state_file << "\n"; list_os << "hillsFile " << replica_hills_file << "\n"; @@ -1723,7 +1802,7 @@ int colvarbias_meta::setup_output() if (b_hills_traj) { std::ostream &hills_traj_os = - cvm::proxy->output_stream(hills_traj_file_name()); + cvm::proxy->output_stream(hills_traj_file_name(), "hills trajectory file"); if (!hills_traj_os) { error_code |= COLVARS_FILE_ERROR; } @@ -1757,36 +1836,32 @@ std::string const colvarbias_meta::get_state_params() const } -std::ostream & colvarbias_meta::write_state_data(std::ostream& os) +template OST &colvarbias_meta::write_state_data_template_(OST &os) { if (use_grids) { // this is a very good time to project hills, if you haven't done // it already! - project_hills(new_hills_begin, hills.end(), - hills_energy, hills_energy_gradients); + project_hills(new_hills_begin, hills.end(), hills_energy, hills_energy_gradients); new_hills_begin = hills.end(); // write down the grids to the restart file - os << " hills_energy\n"; + write_state_data_key(os, "hills_energy"); hills_energy->write_restart(os); - os << " hills_energy_gradients\n"; + write_state_data_key(os, "hills_energy_gradients"); hills_energy_gradients->write_restart(os); } - if ( (!use_grids) || keep_hills ) { + if ((!use_grids) || keep_hills) { // write all hills currently in memory - for (std::list::const_iterator h = this->hills.begin(); - h != this->hills.end(); - h++) { - os << *h; + for (std::list::const_iterator h = this->hills.begin(); h != this->hills.end(); h++) { + write_hill(os, *h); } } else { // write just those that are near the grid boundaries for (std::list::const_iterator h = this->hills_off_grid.begin(); - h != this->hills_off_grid.end(); - h++) { - os << *h; + h != this->hills_off_grid.end(); h++) { + write_hill(os, *h); } } @@ -1795,6 +1870,18 @@ std::ostream & colvarbias_meta::write_state_data(std::ostream& os) } +std::ostream & colvarbias_meta::write_state_data(std::ostream& os) +{ + return write_state_data_template_(os); +} + + +cvm::memory_stream &colvarbias_meta::write_state_data(cvm::memory_stream &os) +{ + return write_state_data_template_(os); +} + + int colvarbias_meta::write_state_to_replicas() { int error_code = COLVARS_OK; @@ -1816,6 +1903,15 @@ int colvarbias_meta::write_output_files() if (dump_fes) { write_pmf(); } + if (b_hills_traj) { + std::ostream &hills_traj_os = + cvm::proxy->output_stream(hills_traj_file_name(), "hills trajectory file"); + hills_traj_os << hills_traj_os_buf.str(); + cvm::proxy->flush_output_stream(hills_traj_file_name()); + // clear the buffer + hills_traj_os_buf.str(""); + hills_traj_os_buf.clear(); + } return COLVARS_OK; } @@ -1915,7 +2011,7 @@ int colvarbias_meta::write_replica_state_file() // Write to temporary state file std::string const tmp_state_file(replica_state_file+".tmp"); error_code |= proxy->remove_file(tmp_state_file); - std::ostream &rep_state_os = cvm::proxy->output_stream(tmp_state_file); + std::ostream &rep_state_os = cvm::proxy->output_stream(tmp_state_file, "temporary state file"); if (rep_state_os) { if (!write_state(rep_state_os)) { error_code |= cvm::error("Error: in writing to temporary file \""+ @@ -1934,11 +2030,11 @@ int colvarbias_meta::reopen_replica_buffer_file() { int error_code = COLVARS_OK; colvarproxy *proxy = cvm::proxy; - if (proxy->output_stream(replica_hills_file)) { + if (proxy->output_stream(replica_hills_file, "replica hills file")) { error_code |= proxy->close_output_stream(replica_hills_file); } error_code |= proxy->remove_file(replica_hills_file); - std::ostream &replica_hills_os = proxy->output_stream(replica_hills_file); + std::ostream &replica_hills_os = proxy->output_stream(replica_hills_file, "replica hills file"); if (replica_hills_os) { replica_hills_os.setf(std::ios::scientific, std::ios::floatfield); } else { @@ -2037,43 +2133,3 @@ colvarbias_meta::hill::operator = (colvarbias_meta::hill const &h) colvarbias_meta::hill::~hill() {} - - -std::ostream & operator << (std::ostream &os, colvarbias_meta::hill const &h) -{ - os.setf(std::ios::scientific, std::ios::floatfield); - - os << "hill {\n"; - os << " step " << std::setw(cvm::it_width) << h.it << "\n"; - os << " weight " - << std::setprecision(cvm::en_prec) - << std::setw(cvm::en_width) - << h.W << "\n"; - - if (h.replica.size()) - os << " replicaID " << h.replica << "\n"; - - size_t i; - os << " centers "; - for (i = 0; i < (h.centers).size(); i++) { - os << " " - << std::setprecision(cvm::cv_prec) - << std::setw(cvm::cv_width) - << h.centers[i]; - } - os << "\n"; - - // For backward compatibility, write the widths instead of the sigmas - os << " widths "; - for (i = 0; i < (h.sigmas).size(); i++) { - os << " " - << std::setprecision(cvm::cv_prec) - << std::setw(cvm::cv_width) - << 2.0 * h.sigmas[i]; - } - os << "\n"; - - os << "}\n"; - - return os; -} diff --git a/lib/colvars/colvarbias_meta.h b/lib/colvars/colvarbias_meta.h index bac2ff74d4..a765a60c71 100644 --- a/lib/colvars/colvarbias_meta.h +++ b/lib/colvars/colvarbias_meta.h @@ -17,6 +17,7 @@ #include "colvarbias.h" #include "colvargrid.h" + /// Metadynamics bias (implementation of \link colvarbias \endlink) class colvarbias_meta : public virtual colvarbias, @@ -51,14 +52,32 @@ public: virtual int update_bias(); virtual int update_grid_data(); virtual int replica_share(); + virtual size_t replica_share_freq() const; virtual int calc_energy(std::vector const *values); virtual int calc_forces(std::vector const *values); virtual std::string const get_state_params() const; virtual int set_state_params(std::string const &state_conf); - virtual std::ostream & write_state_data(std::ostream &os); - virtual std::istream & read_state_data(std::istream &os); + + virtual std::ostream &write_state_data(std::ostream &os); + virtual cvm::memory_stream &write_state_data(cvm::memory_stream &os); + virtual std::istream &read_state_data(std::istream &is); + virtual cvm::memory_stream &read_state_data(cvm::memory_stream &is); + +private: + + template + IST &read_grid_data_template_(IST &is, std::string const &key, GT *grid, GT *backup_grid); + + template IST &read_state_data_template_(IST &is); + + template OST &write_state_data_template_(OST &os); + +public: + + /// Function called by read_state_data() to execute rebinning (if requested) + void rebin_grids_after_restart(); virtual int setup_output(); virtual int write_output_files(); @@ -105,11 +124,24 @@ protected: /// Regenerate the hills_off_grid list void recount_hills_off_grid(hill_iter h_first, hill_iter h_last, - colvar_grid_scalar *ge); + colvar_grid_scalar *ge); - /// Read a hill from a file + template OST &write_hill_template_(OST &os, colvarbias_meta::hill const &h); + + /// Write a hill to a formatted stream + std::ostream &write_hill(std::ostream &os, hill const &h); + + /// Write a hill to an unformatted stream + cvm::memory_stream &write_hill(cvm::memory_stream &os, hill const &h); + + template IST &read_hill_template_(IST &is); + + /// Read a new hill from a formatted stream std::istream & read_hill(std::istream &is); + /// Read a new hill from an unformatted stream + cvm::memory_stream & read_hill(cvm::memory_stream &is); + /// \brief Add a new hill; if a .hills trajectory is written, /// write it there; if there is more than one replica, communicate /// it to the others @@ -230,7 +262,7 @@ protected: std::vector replicas; /// \brief Frequency at which data the "mirror" biases are updated - size_t replica_update_freq; + size_t replica_update_freq = 0; /// List of replicas (and their output list files): contents are /// copied into replicas_registry for convenience @@ -258,6 +290,8 @@ protected: /// Position within replica_hills_file (when reading it) std::streampos replica_hills_file_pos; + /// Cache of the hills trajectory + std::ostringstream hills_traj_os_buf; }; @@ -399,9 +433,6 @@ public: /// Represent the hill ina string suitable for a trajectory file std::string output_traj(); - /// Write the hill to an output stream - friend std::ostream & operator << (std::ostream &os, hill const &h); - }; diff --git a/lib/colvars/colvarbias_restraint.cpp b/lib/colvars/colvarbias_restraint.cpp index a7963c4f8f..6ea00375e3 100644 --- a/lib/colvars/colvarbias_restraint.cpp +++ b/lib/colvars/colvarbias_restraint.cpp @@ -27,7 +27,10 @@ colvarbias_restraint::colvarbias_restraint(char const *key) int colvarbias_restraint::init(std::string const &conf) { - colvarbias::init(conf); + int err = colvarbias::init(conf); + if (err != COLVARS_OK) { + return err; + } enable(f_cvb_apply_force); colvarbias_ti::init(conf); @@ -202,6 +205,8 @@ int colvarbias_restraint_moving::init(std::string const &conf) first_step = cvm::step_absolute(); + cvm::log("Initial step for restraint change: " + cvm::to_str(first_step) + "\n"); + get_keyval(conf, "targetNumSteps", target_nsteps, target_nsteps); if (!target_nsteps) { cvm::error("Error: targetNumSteps must be non-zero.\n", COLVARS_INPUT_ERROR); @@ -232,10 +237,9 @@ std::string const colvarbias_restraint_moving::get_state_params() const std::ostringstream os; os.setf(std::ios::scientific, std::ios::floatfield); if (b_chg_centers || b_chg_force_k) { - // TODO move this + os << "firstStep " << std::setw(cvm::it_width) << first_step << "\n"; if (target_nstages) { - os << "stage " << std::setw(cvm::it_width) - << stage << "\n"; + os << "stage " << std::setw(cvm::it_width) << stage << "\n"; } } return os.str(); @@ -245,6 +249,12 @@ std::string const colvarbias_restraint_moving::get_state_params() const int colvarbias_restraint_moving::set_state_params(std::string const &conf) { if (b_chg_centers || b_chg_force_k) { + auto first_step_flags = colvarparse::parse_restart; + if (cvm::main()->restart_version_number() > 20230906) { + // Only require the first step when the code could produce it + first_step_flags = colvarparse::parse_restart | colvarparse::parse_required; + } + get_keyval(conf, "firstStep", first_step, first_step, first_step_flags); if (target_nstages) { get_keyval(conf, "stage", stage, stage, colvarparse::parse_restart | colvarparse::parse_required); @@ -837,18 +847,6 @@ int colvarbias_restraint_harmonic::set_state_params(std::string const &conf) } -std::ostream & colvarbias_restraint_harmonic::write_state_data(std::ostream &os) -{ - return colvarbias_ti::write_state_data(os); -} - - -std::istream & colvarbias_restraint_harmonic::read_state_data(std::istream &is) -{ - return colvarbias_ti::read_state_data(is); -} - - std::ostream & colvarbias_restraint_harmonic::write_traj_label(std::ostream &os) { colvarbias_restraint::write_traj_label(os); @@ -1136,18 +1134,6 @@ int colvarbias_restraint_harmonic_walls::set_state_params(std::string const &con } -std::ostream & colvarbias_restraint_harmonic_walls::write_state_data(std::ostream &os) -{ - return colvarbias_ti::write_state_data(os); -} - - -std::istream & colvarbias_restraint_harmonic_walls::read_state_data(std::istream &is) -{ - return colvarbias_ti::read_state_data(is); -} - - std::ostream & colvarbias_restraint_harmonic_walls::write_traj_label(std::ostream &os) { colvarbias_restraint::write_traj_label(os); @@ -1293,18 +1279,6 @@ int colvarbias_restraint_linear::set_state_params(std::string const &conf) } -std::ostream & colvarbias_restraint_linear::write_state_data(std::ostream &os) -{ - return colvarbias_ti::write_state_data(os); -} - - -std::istream & colvarbias_restraint_linear::read_state_data(std::istream &is) -{ - return colvarbias_ti::read_state_data(is); -} - - std::ostream & colvarbias_restraint_linear::write_traj_label(std::ostream &os) { colvarbias_restraint::write_traj_label(os); @@ -1338,7 +1312,10 @@ int colvarbias_restraint_histogram::init(std::string const &conf) { int error_code = COLVARS_OK; - colvarbias::init(conf); + int err = colvarbias::init(conf); + if (err != COLVARS_OK) { + return err; + } enable(f_cvb_apply_force); cvm::main()->cite_feature("histogramRestraint colvar bias implementation"); diff --git a/lib/colvars/colvarbias_restraint.h b/lib/colvars/colvarbias_restraint.h index 45a96d14f8..f030a5cad7 100644 --- a/lib/colvars/colvarbias_restraint.h +++ b/lib/colvars/colvarbias_restraint.h @@ -38,9 +38,6 @@ public: virtual std::string const get_state_params() const; virtual int set_state_params(std::string const &conf); - // virtual std::ostream & write_state_data(std::ostream &os); - // virtual std::istream & read_state_data(std::istream &os); - virtual std::ostream & write_traj_label(std::ostream &os); virtual std::ostream & write_traj(std::ostream &os); @@ -242,8 +239,6 @@ public: virtual int update(); virtual std::string const get_state_params() const; virtual int set_state_params(std::string const &conf); - virtual std::ostream & write_state_data(std::ostream &os); - virtual std::istream & read_state_data(std::istream &os); virtual std::ostream & write_traj_label(std::ostream &os); virtual std::ostream & write_traj(std::ostream &os); virtual int change_configuration(std::string const &conf); @@ -269,8 +264,6 @@ public: virtual int update(); virtual std::string const get_state_params() const; virtual int set_state_params(std::string const &conf); - virtual std::ostream & write_state_data(std::ostream &os); - virtual std::istream & read_state_data(std::istream &os); virtual std::ostream & write_traj_label(std::ostream &os); virtual std::ostream & write_traj(std::ostream &os); @@ -311,8 +304,6 @@ public: virtual std::string const get_state_params() const; virtual int set_state_params(std::string const &conf); - virtual std::ostream & write_state_data(std::ostream &os); - virtual std::istream & read_state_data(std::istream &os); virtual std::ostream & write_traj_label(std::ostream &os); virtual std::ostream & write_traj(std::ostream &os); diff --git a/lib/colvars/colvarcomp.cpp b/lib/colvars/colvarcomp.cpp index abe522a249..e6729f43a7 100644 --- a/lib/colvars/colvarcomp.cpp +++ b/lib/colvars/colvarcomp.cpp @@ -19,45 +19,40 @@ colvar::cvc::cvc() { description = "uninitialized colvar component"; - b_try_scalable = true; - sup_coeff = 1.0; - sup_np = 1; - period = 0.0; - wrap_center = 0.0; - width = 0.0; cvc::init_dependencies(); } -colvar::cvc::cvc(std::string const &conf) +int colvar::cvc::update_description() { - description = "uninitialized colvar component"; - b_try_scalable = true; - sup_coeff = 1.0; - sup_np = 1; - period = 0.0; - wrap_center = 0.0; - width = 0.0; - init_dependencies(); - colvar::cvc::init(conf); + if (name.size() > 0) { + description = "cvc \"" + name + "\""; + } else { + description = "unnamed cvc"; + } + description += " of type \"" + function_type() + "\""; + return COLVARS_OK; +} + + +std::string colvar::cvc::function_type() const +{ + if (function_types.empty()) { + return "unset"; + } + return function_types.back(); } int colvar::cvc::set_function_type(std::string const &type) { - function_type = type; - if (function_types.size() == 0) { - function_types.push_back(function_type); - } else { - if (function_types.back() != function_type) { - function_types.push_back(function_type); - } - } + function_types.push_back(type); + update_description(); + cvm::main()->cite_feature(function_types[0]+" colvar component"); for (size_t i = function_types.size()-1; i > 0; i--) { cvm::main()->cite_feature(function_types[i]+" colvar component"+ " (derived from "+function_types[i-1]+")"); } - cvm::main()->cite_feature(function_types[0]+" colvar component"); return COLVARS_OK; } @@ -67,6 +62,8 @@ int colvar::cvc::init(std::string const &conf) if (cvm::debug()) cvm::log("Initializing cvc base object.\n"); + int error_code = COLVARS_OK; + std::string const old_name(name); if (name.size() > 0) { @@ -74,18 +71,14 @@ int colvar::cvc::init(std::string const &conf) } if (get_keyval(conf, "name", name, name)) { - if (name.size() > 0) { - description = "cvc \"" + name + "\" of type " + function_type; - } else { - description = "unnamed cvc"; - } if ((name != old_name) && (old_name.size() > 0)) { - cvm::error("Error: cannot rename component \""+old_name+ - "\" after initialization (new name = \""+name+"\")", - COLVARS_INPUT_ERROR); + error_code |= cvm::error("Error: cannot rename component \"" + old_name + + "\" after initialization (new name = \"" + name + "\")", + COLVARS_INPUT_ERROR); name = old_name; } } + update_description(); get_keyval(conf, "componentCoeff", sup_coeff, sup_coeff); get_keyval(conf, "componentExp", sup_np, sup_np); @@ -102,6 +95,24 @@ int colvar::cvc::init(std::string const &conf) register_param("period", reinterpret_cast(&period)); register_param("wrapAround", reinterpret_cast(&wrap_center)); + if (period != 0.0) { + if (!is_available(f_cvc_periodic)) { + error_code |= + cvm::error("Error: invalid use of period and/or " + "wrapAround in a \"" + + function_type() + "\" component.\n" + "Period: " + cvm::to_str(period) + + " wrapAround: " + cvm::to_str(wrap_center), + COLVARS_INPUT_ERROR); + } else { + enable(f_cvc_periodic); + } + } + + if ((wrap_center != 0.0) && !is_enabled(f_cvc_periodic)) { + error_code |= cvm::error("Error: wrapAround was defined for a non-periodic component.\n", + COLVARS_INPUT_ERROR); + } + get_keyval_feature(this, conf, "debugGradients", f_cvc_debug_gradient, false, parse_silent); @@ -119,7 +130,7 @@ int colvar::cvc::init(std::string const &conf) if (cvm::debug()) cvm::log("Done initializing cvc base object.\n"); - return cvm::get_error(); + return error_code; } @@ -157,10 +168,13 @@ cvm::atom_group *colvar::cvc::parse_group(std::string const &conf, char const *group_key, bool optional) { - cvm::atom_group *group = NULL; + int error_code = COLVARS_OK; + + cvm::atom_group *group = nullptr; std::string group_conf; if (key_lookup(conf, group_key, &group_conf)) { + group = new cvm::atom_group(group_key); if (b_try_scalable) { @@ -177,31 +191,42 @@ cvm::atom_group *colvar::cvc::parse_group(std::string const &conf, // TODO check for other types of parallelism here } - if (group_conf.size() == 0) { - cvm::error("Error: atom group \""+group->key+ - "\" is set, but has no definition.\n", - COLVARS_INPUT_ERROR); + if (group_conf.empty()) { + error_code |= cvm::error("Error: atom group \"" + group->key + "\" has no definition.\n", + COLVARS_INPUT_ERROR); + delete group; + group = nullptr; + // Silence unused variable warning; TODO stop returning a pointer + (void) error_code; return group; } cvm::increase_depth(); - if (group->parse(group_conf) == COLVARS_OK) { + error_code |= group->parse(group_conf); + if (error_code != COLVARS_OK) { + error_code |= + cvm::error("Error: in definition of atom group \"" + std::string(group_key) + "\".", + COLVARS_INPUT_ERROR); + delete group; + group = nullptr; + } else { register_atom_group(group); - } - group->check_keywords(group_conf, group_key); - if (cvm::get_error()) { - cvm::error("Error parsing definition for atom group \""+ - std::string(group_key)+"\".", COLVARS_INPUT_ERROR); + error_code |= group->check_keywords(group_conf, group_key); } cvm::decrease_depth(); } else { - if (! optional) { - cvm::error("Error: definition for atom group \""+ - std::string(group_key)+"\" not found.\n"); + + if (!optional) { + error_code |= + cvm::error("Error: atom group \"" + std::string(group_key) + "\" is required.\n", + COLVARS_INPUT_ERROR); } } + // Silence unused variable warning; TODO stop returning a pointer + (void) error_code; + return group; } @@ -228,6 +253,8 @@ int colvar::cvc::init_dependencies() { init_feature(f_cvc_upper_boundary, "defined_upper_boundary", f_type_static); + init_feature(f_cvc_explicit_atom_groups, "explicit_atom_groups", f_type_static); + init_feature(f_cvc_gradient, "gradient", f_type_dynamic); init_feature(f_cvc_explicit_gradient, "explicit_gradient", f_type_static); @@ -264,6 +291,7 @@ int colvar::cvc::init_dependencies() { init_feature(f_cvc_collect_atom_ids, "collect_atom_ids", f_type_dynamic); require_feature_children(f_cvc_collect_atom_ids, f_ag_collect_atom_ids); + require_feature_self(f_cvc_collect_atom_ids, f_cvc_explicit_atom_groups); // TODO only enable this when f_ag_scalable can be turned on for a pre-initialized group // require_feature_children(f_cvc_scalable, f_ag_scalable); @@ -281,7 +309,7 @@ int colvar::cvc::init_dependencies() { // default as available, not enabled // except dynamic features which default as unavailable feature_states.reserve(f_cvc_ntot); - for (i = 0; i < colvardeps::f_cvc_ntot; i++) { + for (i = feature_states.size(); i < colvardeps::f_cvc_ntot; i++) { bool avail = is_dynamic(i) ? false : true; feature_states.push_back(feature_state(avail, false)); } @@ -292,6 +320,8 @@ int colvar::cvc::init_dependencies() { feature_states[f_cvc_gradient].available = true; feature_states[f_cvc_collect_atom_ids].available = true; + feature_states[f_cvc_periodic].available = false; + // CVCs are enabled from the start - get disabled based on flags enable(f_cvc_active); @@ -314,7 +344,7 @@ int colvar::cvc::init_dependencies() { int colvar::cvc::setup() { - description = "cvc " + name; + update_description(); return COLVARS_OK; } @@ -349,6 +379,7 @@ void colvar::cvc::init_as_angle() void colvar::cvc::init_as_periodic_angle() { x.type(colvarvalue::type_scalar); + provide(f_cvc_periodic); enable(f_cvc_periodic); period = 360.0; init_scalar_boundaries(-180.0, 180.0); @@ -372,6 +403,7 @@ void colvar::cvc::register_atom_group(cvm::atom_group *ag) { atom_groups.push_back(ag); add_child(ag); + enable(f_cvc_explicit_atom_groups); } @@ -411,29 +443,21 @@ int colvar::cvc::set_param(std::string const ¶m_name, void colvar::cvc::read_data() { - size_t ig; - for (ig = 0; ig < atom_groups.size(); ig++) { - cvm::atom_group &atoms = *(atom_groups[ig]); - atoms.reset_atoms_data(); - atoms.read_positions(); - atoms.calc_required_properties(); - // each atom group will take care of its own fitting_group, if defined + if (is_enabled(f_cvc_explicit_atom_groups)) { + for (auto agi = atom_groups.begin(); agi != atom_groups.end(); agi++) { + cvm::atom_group &atoms = *(*agi); + atoms.reset_atoms_data(); + atoms.read_positions(); + atoms.calc_required_properties(); + // each atom group will take care of its own fitting_group, if defined + } } - -//// Don't try to get atom velocities, as no back-end currently implements it -// if (tasks[task_output_velocity] && !tasks[task_fdiff_velocity]) { -// for (i = 0; i < cvcs.size(); i++) { -// for (ig = 0; ig < cvcs[i]->atom_groups.size(); ig++) { -// cvcs[i]->atom_groups[ig]->read_velocities(); -// } -// } -// } } -std::vector > colvar::cvc::get_atom_lists() +std::vector> colvar::cvc::get_atom_lists() { - std::vector > lists; + std::vector> lists; std::vector::iterator agi = atom_groups.begin(); for ( ; agi != atom_groups.end(); ++agi) { @@ -462,12 +486,12 @@ void colvar::cvc::collect_gradients(std::vector const &atom_ids, std::vecto // If necessary, apply inverse rotation to get atomic // gradient in the laboratory frame if (ag.is_enabled(f_ag_rotate)) { - cvm::rotation const rot_inv = ag.rot.inverse(); + const auto rot_inv = ag.rot.inverse().matrix(); for (size_t k = 0; k < ag.size(); k++) { size_t a = std::lower_bound(atom_ids.begin(), atom_ids.end(), ag[k].id) - atom_ids.begin(); - atomic_gradients[a] += coeff * rot_inv.rotate(ag[k].grad); + atomic_gradients[a] += coeff * (rot_inv * ag[k].grad); } } else { @@ -494,7 +518,7 @@ void colvar::cvc::collect_gradients(std::vector const &atom_ids, std::vecto void colvar::cvc::calc_force_invgrads() { cvm::error("Error: calculation of inverse gradients is not implemented " - "for colvar components of type \""+function_type+"\".\n", + "for colvar components of type \""+function_type()+"\".\n", COLVARS_NOT_IMPLEMENTED); } @@ -502,7 +526,7 @@ void colvar::cvc::calc_force_invgrads() void colvar::cvc::calc_Jacobian_derivative() { cvm::error("Error: calculation of inverse gradients is not implemented " - "for colvar components of type \""+function_type+"\".\n", + "for colvar components of type \""+function_type()+"\".\n", COLVARS_NOT_IMPLEMENTED); } @@ -515,6 +539,18 @@ void colvar::cvc::calc_fit_gradients() } +void colvar::cvc::apply_force(colvarvalue const &cvforce) +{ + if (is_enabled(f_cvc_explicit_atom_groups)) { + for (auto agi = atom_groups.begin(); agi != atom_groups.end(); agi++) { + if (!(*agi)->noforce) { + (*agi)->apply_colvar_force(cvforce); + } + } + } +} + + void colvar::cvc::debug_gradients() { // this function should work for any scalar cvc: @@ -528,8 +564,8 @@ void colvar::cvc::debug_gradients() cvm::atom_group *group = atom_groups[ig]; if (group->b_dummy) continue; - cvm::rotation const rot_0 = group->rot; - cvm::rotation const rot_inv = group->rot.inverse(); + const auto rot_0 = group->rot.matrix(); + const auto rot_inv = group->rot.inverse().matrix(); cvm::real x_0 = x.real_value; if ((x.type() == colvarvalue::type_vector) && (x.size() == 1)) x_0 = x[0]; @@ -550,7 +586,7 @@ void colvar::cvc::debug_gradients() cvm::log((group->fitting_group ? std::string("refPosGroup") : group->key) + "[" + cvm::to_str(j) + "] = " + (group->is_enabled(f_ag_rotate) ? - cvm::to_str(rot_0.rotate(group_for_fit->fit_gradients[j])) : + cvm::to_str(rot_0 * (group_for_fit->fit_gradients[j])) : cvm::to_str(group_for_fit->fit_gradients[j]))); } } @@ -561,7 +597,7 @@ void colvar::cvc::debug_gradients() // tests are best conducted in the unrotated (simulation) frame cvm::rvector const atom_grad = (group->is_enabled(f_ag_rotate) ? - rot_inv.rotate((*group)[ia].grad) : + rot_inv * ((*group)[ia].grad) : (*group)[ia].grad); gradient_sum += atom_grad; @@ -634,34 +670,43 @@ void colvar::cvc::debug_gradients() } -cvm::real colvar::cvc::dist2(colvarvalue const &x1, - colvarvalue const &x2) const +cvm::real colvar::cvc::dist2(colvarvalue const &x1, colvarvalue const &x2) const { - return x1.dist2(x2); + cvm::real diff = x1.real_value - x2.real_value; + if (is_enabled(f_cvc_periodic)) { + cvm::real const shift = cvm::floor(diff / period + 0.5); + diff -= shift * period; + } + return diff * diff; } -colvarvalue colvar::cvc::dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const +colvarvalue colvar::cvc::dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const { - return x1.dist2_grad(x2); + cvm::real diff = x1.real_value - x2.real_value; + if (is_enabled(f_cvc_periodic)) { + cvm::real const shift = cvm::floor(diff / period + 0.5); + diff -= shift * period; + } + return 2.0 * diff; } -colvarvalue colvar::cvc::dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const +colvarvalue colvar::cvc::dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const { - return x2.dist2_grad(x1); + return cvc::dist2_lgrad(x1, x2); } -void colvar::cvc::wrap(colvarvalue & /* x_unwrapped */) const +void colvar::cvc::wrap(colvarvalue &x_unwrapped) const { - return; + if (is_enabled(f_cvc_periodic)) { + cvm::real const shift = cvm::floor((x_unwrapped.real_value - wrap_center) / period + 0.5); + x_unwrapped.real_value -= shift * period; + } } - // Static members std::vector colvar::cvc::cvc_features; diff --git a/lib/colvars/colvarcomp.h b/lib/colvars/colvarcomp.h index 87d3e27a8f..334fdc1f6e 100644 --- a/lib/colvars/colvarcomp.h +++ b/lib/colvars/colvarcomp.h @@ -19,20 +19,12 @@ // this can be done straightforwardly by using the macro: // simple_scalar_dist_functions (derived_class) +#include #include "colvarmodule.h" -#include "colvar.h" #include "colvaratoms.h" -#include "colvar_arithmeticpath.h" - -#if (__cplusplus >= 201103L) -// C++11-only functions +#include "colvar.h" #include "colvar_geometricpath.h" -#include -#include -#endif - -#include /// \brief Colvar component (base class for collective variables) @@ -82,37 +74,29 @@ public: /// cvc instance when debugging) std::string name; - /// \brief Description of the type of collective variable - /// - /// Normally this string is set by the parent \link colvar \endlink - /// object within its constructor, when all \link colvar::cvc \endlink - /// objects are initialized; therefore the main "config string" - /// constructor does not need to define it. If a \link colvar::cvc - /// \endlink is initialized and/or a different constructor is used, - /// this variable definition should be set within the constructor. - std::string function_type; + /// String identifier for the type of collective variable + std::string function_type() const; /// Keyword used in the input to denote this CVC std::string config_key; /// \brief Coefficient in the polynomial combination (default: 1.0) - cvm::real sup_coeff; + cvm::real sup_coeff = 1.0; + /// \brief Exponent in the polynomial combination (default: 1) - int sup_np; + int sup_np = 1; /// \brief Period of the values of this CVC (default: 0.0, non periodic) - cvm::real period; + cvm::real period = 0.0; /// \brief If the component is periodic, wrap around this value (default: 0.0) - cvm::real wrap_center; + cvm::real wrap_center = 0.0; - /// \brief Constructor - /// - /// Calls the init() function of the class - cvc(std::string const &conf); + /// Constructor + cvc(); - /// Set the value of \link function_type \endlink and its dependencies - int set_function_type(std::string const &type); + /// Destructor + virtual ~cvc(); /// An init function should be defined for every class inheriting from cvc /// \param conf Contents of the configuration file pertaining to this \link @@ -122,38 +106,20 @@ public: /// \brief Initialize dependency tree virtual int init_dependencies(); - /// \brief Within the constructor, make a group parse its own - /// options from the provided configuration string - /// Returns reference to new group - cvm::atom_group *parse_group(std::string const &conf, - char const *group_key, - bool optional = false); - - /// \brief Parse options pertaining to total force calculation - virtual int init_total_force_params(std::string const &conf); - /// \brief After construction, set data related to dependency handling int setup(); - /// \brief Default constructor (used when \link colvar::cvc \endlink - /// objects are declared within other ones) - cvc(); - - /// Destructor - virtual ~cvc(); - - /// \brief Implementation of the feature list for colvar - static std::vector cvc_features; - /// \brief Implementation of the feature list accessor for colvar virtual const std::vector &features() const { return cvc_features; } + virtual std::vector &modify_features() { return cvc_features; } + static void delete_features() { for (size_t i=0; i < cvc_features.size(); i++) { delete cvc_features[i]; @@ -211,16 +177,12 @@ public: /// collective variable force, usually coming from the biases and /// eventually manipulated by the parent \link colvar \endlink /// object - virtual void apply_force(colvarvalue const &cvforce) = 0; + virtual void apply_force(colvarvalue const &cvforce); - /// \brief Square distance between x1 and x2 (can be redefined to - /// transparently implement constraints, symmetries and - /// periodicities) - /// - /// colvar::cvc::dist2() and the related functions are - /// declared as "const" functions, but not "static", because - /// additional parameters defining the metrics (e.g. the - /// periodicity) may be specific to each colvar::cvc object. + /// Square distance between x1 and x2 (can be redefined to transparently + /// implement metrics in multi-dimensional spaces with or without + /// constraints, symmetries and periodicities). The default implementation + /// assumes scalar numbers and no symmetries or periodicities. /// /// If symmetries or periodicities are present, the /// colvar::cvc::dist2() should be redefined to return the @@ -235,12 +197,6 @@ public: /// i.e. already deprived of its component normal to the constraint /// hypersurface. /// - /// Finally, another useful application, if you are performing very - /// many operations with these functions, could be to override the - /// \link colvarvalue \endlink member functions and access directly - /// its member data. For instance: to define dist2(x1,x2) as - /// (x2.real_value-x1.real_value)*(x2.real_value-x1.real_value) in - /// case of a scalar \link colvarvalue \endlink type. virtual cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const; @@ -273,7 +229,7 @@ public: virtual int set_param(std::string const ¶m_name, void const *new_value); /// \brief Whether or not this CVC will be computed in parallel whenever possible - bool b_try_scalable; + bool b_try_scalable = true; /// Forcibly set value of CVC - useful for driving an external coordinate, /// eg. lambda dynamics @@ -283,6 +239,22 @@ public: protected: + /// Set the value of \link function_type \endlink and its dependencies + int set_function_type(std::string const &type); + + /// Update the description string based on name and type + int update_description(); + + /// Parse a group definition + cvm::atom_group *parse_group(std::string const &conf, char const *group_key, + bool optional = false); + + /// \brief Parse options pertaining to total force calculation + virtual int init_total_force_params(std::string const &conf); + + /// \brief Implementation of the feature list for colvar + static std::vector cvc_features; + /// Record the type of this class as well as those it is derived from std::vector function_types; @@ -320,8 +292,8 @@ protected: /// \brief Location of the upper boundary (not defined by user choice) colvarvalue upper_boundary; - /// \brief CVC-specific default colvar width - cvm::real width; + /// \brief CVC-specific default colvar width (default: not provided) + cvm::real width = 0.0; }; @@ -352,26 +324,19 @@ class colvar::distance { protected: /// First atom group - cvm::atom_group *group1; + cvm::atom_group *group1 = nullptr; /// Second atom group - cvm::atom_group *group2; + cvm::atom_group *group2 = nullptr; /// Vector distance, cached to be recycled cvm::rvector dist_v; public: - distance(std::string const &conf); distance(); virtual ~distance() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void calc_force_invgrads(); virtual void calc_Jacobian_derivative(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -383,25 +348,22 @@ class colvar::distance_vec : public colvar::distance { public: - distance_vec(std::string const &conf); distance_vec(); virtual ~distance_vec() {} virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); - /// Redefined to handle the box periodicity - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to handle the box periodicity - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to handle the box periodicity - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual void wrap(colvarvalue &x_unwrapped) const; }; - /// \brief Colvar component: distance unit vector (direction) between /// centers of mass of two groups (colvarvalue::type_unit3vector type, /// range [-1:1]x[-1:1]x[-1:1]) @@ -409,21 +371,19 @@ class colvar::distance_dir : public colvar::distance { public: - distance_dir(std::string const &conf); distance_dir(); virtual ~distance_dir() {} virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); - /// Redefined to override the distance ones - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to override the distance ones - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to override the distance ones - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual void wrap(colvarvalue &x_unwrapped) const; }; @@ -435,11 +395,11 @@ class colvar::distance_z { protected: /// Main atom group - cvm::atom_group *main; + cvm::atom_group *main = nullptr; /// Reference atom group - cvm::atom_group *ref1; + cvm::atom_group *ref1 = nullptr; /// Optional, second ref atom group - cvm::atom_group *ref2; + cvm::atom_group *ref2 = nullptr; /// Vector on which the distance vector is projected cvm::rvector axis; /// Norm of the axis @@ -447,24 +407,15 @@ protected: /// Vector distance, cached to be recycled cvm::rvector dist_v; /// Flag: using a fixed axis vector? - bool fixed_axis; + bool fixed_axis = true; public: - distance_z(std::string const &conf); distance_z(); virtual ~distance_z() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void calc_force_invgrads(); virtual void calc_Jacobian_derivative(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - /// \brief Redefined to make use of the user-provided period - virtual void wrap(colvarvalue &x_unwrapped) const; }; @@ -480,20 +431,12 @@ protected: /// Vector distances cvm::rvector v12, v13; public: - distance_xy(std::string const &conf); distance_xy(); virtual ~distance_xy() {} virtual void calc_value(); virtual void calc_gradients(); virtual void calc_force_invgrads(); virtual void calc_Jacobian_derivative(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -502,28 +445,16 @@ public: class colvar::polar_phi : public colvar::cvc { +protected: + cvm::atom_group *atoms = nullptr; + cvm::real r, theta, phi; + public: - polar_phi(std::string const &conf); polar_phi(); virtual ~polar_phi() {} -protected: - cvm::atom_group *atoms; - cvm::real r, theta, phi; -public: + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - /// Redefined to handle the 2*PI periodicity - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to handle the 2*PI periodicity - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to handle the 2*PI periodicity - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to handle the 2*PI periodicity - virtual void wrap(colvarvalue &x_unwrapped) const; }; @@ -533,27 +464,18 @@ class colvar::polar_theta : public colvar::cvc { public: - polar_theta(std::string const &conf); polar_theta(); virtual ~polar_theta() {} + virtual int init(std::string const &conf); protected: - cvm::atom_group *atoms; + cvm::atom_group *atoms = nullptr; cvm::real r, theta, phi; public: virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - /// Redefined to override the distance ones - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to override the distance ones - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to override the distance ones - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; + /// \brief Colvar component: average distance between two groups of atoms, weighted as the sixth power, /// as in NMR refinements(colvarvalue::type_scalar type, range (0:*)) class colvar::distance_inv @@ -561,23 +483,17 @@ class colvar::distance_inv { protected: /// First atom group - cvm::atom_group *group1; + cvm::atom_group *group1 = nullptr; /// Second atom group - cvm::atom_group *group2; + cvm::atom_group *group2 = nullptr; /// Components of the distance vector orthogonal to the axis - int exponent; + int exponent = 6; public: - distance_inv(std::string const &conf); + distance_inv(); virtual ~distance_inv() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -589,45 +505,41 @@ class colvar::distance_pairs { protected: /// First atom group - cvm::atom_group *group1; + cvm::atom_group *group1 = nullptr; /// Second atom group - cvm::atom_group *group2; + cvm::atom_group *group2 = nullptr; public: - distance_pairs(std::string const &conf); distance_pairs(); virtual ~distance_pairs() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); + /// Redefined to deal with multiple dimensions + virtual cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual void wrap(colvarvalue &x_unwrapped) const; }; - /// \brief Colvar component: dipole magnitude of a molecule class colvar::dipole_magnitude : public colvar::cvc { protected: /// Dipole atom group - cvm::atom_group *atoms; + cvm::atom_group *atoms = nullptr; cvm::atom_pos dipoleV; public: - /// Initialize by parsing the configuration - dipole_magnitude (std::string const &conf); - dipole_magnitude (cvm::atom const &a1); dipole_magnitude(); virtual ~dipole_magnitude() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); - //virtual void calc_force_invgrads(); - //virtual void calc_Jacobian_derivative(); - virtual void apply_force (colvarvalue const &force); - virtual cvm::real dist2 (colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad (colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad (colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -639,21 +551,15 @@ class colvar::gyration { protected: /// Atoms involved - cvm::atom_group *atoms; + cvm::atom_group *atoms = nullptr; public: - gyration(std::string const &conf); + gyration(); virtual ~gyration() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void calc_force_invgrads(); virtual void calc_Jacobian_derivative(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -664,19 +570,10 @@ class colvar::inertia : public colvar::gyration { public: - /// Constructor - inertia(std::string const &conf); inertia(); virtual ~inertia() {} virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -690,19 +587,11 @@ protected: /// Vector on which the inertia tensor is projected cvm::rvector axis; public: - /// Constructor - inertia_z(std::string const &conf); inertia_z(); virtual ~inertia_z() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -715,7 +604,7 @@ class colvar::eigenvector protected: /// Atom group - cvm::atom_group * atoms; + cvm::atom_group * atoms = nullptr; /// Reference coordinates std::vector ref_pos; @@ -728,20 +617,13 @@ protected: public: - /// Constructor - eigenvector(std::string const &conf); + eigenvector(); virtual ~eigenvector() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void calc_force_invgrads(); virtual void calc_Jacobian_derivative(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -754,11 +636,11 @@ class colvar::angle protected: /// Atom group - cvm::atom_group *group1; + cvm::atom_group *group1 = nullptr; /// Atom group - cvm::atom_group *group2; + cvm::atom_group *group2 = nullptr; /// Atom group - cvm::atom_group *group3; + cvm::atom_group *group3 = nullptr; /// Inter site vectors cvm::rvector r21, r23; @@ -770,25 +652,18 @@ protected: /// Compute total force on first site only to avoid unwanted /// coupling to other colvars (see e.g. Ciccotti et al., 2005) /// (or to allow dummy atoms) - bool b_1site_force; + bool b_1site_force = false; public: - /// Initialize by parsing the configuration - angle(std::string const &conf); + angle(); /// \brief Initialize the three groups after three atoms angle(cvm::atom const &a1, cvm::atom const &a2, cvm::atom const &a3); virtual ~angle() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void calc_force_invgrads(); virtual void calc_Jacobian_derivative(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -801,11 +676,11 @@ class colvar::dipole_angle protected: /// Dipole atom group - cvm::atom_group *group1; + cvm::atom_group *group1 = nullptr; /// Atom group - cvm::atom_group *group2; + cvm::atom_group *group2 = nullptr; /// Atom group - cvm::atom_group *group3; + cvm::atom_group *group3 = nullptr; /// Inter site vectors cvm::rvector r21, r23; @@ -817,24 +692,14 @@ protected: /// Compute total force on first site only to avoid unwanted /// coupling to other colvars (see e.g. Ciccotti et al., 2005) /// (or to allow dummy atoms) - bool b_1site_force; + bool b_1site_force = false; public: - /// Initialize by parsing the configuration - dipole_angle (std::string const &conf); - /// \brief Initialize the three groups after three atoms - dipole_angle (cvm::atom const &a1, cvm::atom const &a2, cvm::atom const &a3); dipole_angle(); virtual ~dipole_angle() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force (colvarvalue const &force); - virtual cvm::real dist2 (colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad (colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad (colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -847,45 +712,31 @@ class colvar::dihedral protected: /// Atom group - cvm::atom_group *group1; + cvm::atom_group *group1 = nullptr; /// Atom group - cvm::atom_group *group2; + cvm::atom_group *group2 = nullptr; /// Atom group - cvm::atom_group *group3; + cvm::atom_group *group3 = nullptr; /// Atom group - cvm::atom_group *group4; + cvm::atom_group *group4 = nullptr; /// Inter site vectors cvm::rvector r12, r23, r34; /// \brief Compute total force on first site only to avoid unwanted /// coupling to other colvars (see e.g. Ciccotti et al., 2005) - bool b_1site_force; + bool b_1site_force = false; public: - /// Initialize by parsing the configuration - dihedral(std::string const &conf); /// \brief Initialize the four groups after four atoms dihedral(cvm::atom const &a1, cvm::atom const &a2, cvm::atom const &a3, cvm::atom const &a4); dihedral(); virtual ~dihedral() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void calc_force_invgrads(); virtual void calc_Jacobian_derivative(); - virtual void apply_force(colvarvalue const &force); - - /// Redefined to handle the 2*PI periodicity - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to handle the 2*PI periodicity - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to handle the 2*PI periodicity - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to handle the 2*PI periodicity - virtual void wrap(colvarvalue &x_unwrapped) const; }; @@ -897,46 +748,39 @@ class colvar::coordnum { protected: /// First atom group - cvm::atom_group *group1; + cvm::atom_group *group1 = nullptr; /// Second atom group - cvm::atom_group *group2; + cvm::atom_group *group2 = nullptr; /// \brief "Cutoff" for isotropic calculation (default) cvm::real r0; /// \brief "Cutoff vector" for anisotropic calculation cvm::rvector r0_vec; /// \brief Whether r/r0 or \vec{r}*\vec{1/r0_vec} should be used - bool b_anisotropic; + bool b_anisotropic = false; /// Integer exponent of the function numerator - int en; + int en = 6; /// Integer exponent of the function denominator - int ed; + int ed = 12; /// If true, group2 will be treated as a single atom - bool b_group2_center_only; + bool b_group2_center_only = false; /// Tolerance for the pair list - cvm::real tolerance; + cvm::real tolerance = 0.0; /// Frequency of update of the pair list - int pairlist_freq; + int pairlist_freq = 100; /// Pair list - bool *pairlist; + bool *pairlist = nullptr; public: - coordnum(std::string const &conf); - ~coordnum(); - + coordnum(); + virtual ~coordnum(); + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; enum { ef_null = 0, @@ -981,31 +825,25 @@ class colvar::selfcoordnum protected: /// Selected atoms - cvm::atom_group *group1; + cvm::atom_group *group1 = nullptr; /// \brief "Cutoff" for isotropic calculation (default) cvm::real r0; /// Integer exponent of the function numerator - int en; + int en = 6; /// Integer exponent of the function denominator - int ed; - cvm::real tolerance; - int pairlist_freq; - bool *pairlist; + int ed = 12; + cvm::real tolerance = 0.0; + int pairlist_freq = 100; + + bool *pairlist = nullptr; public: - selfcoordnum(std::string const &conf); + selfcoordnum(); ~selfcoordnum(); + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; /// Main workhorse function template int compute_selfcoordnum(); @@ -1025,25 +863,17 @@ protected: cvm::rvector r0_vec; /// \brief Wheter dist/r0 or \vec{dist}*\vec{1/r0_vec} should ne be /// used - bool b_anisotropic; + bool b_anisotropic = false; /// Integer exponent of the function numerator - int en; + int en = 6; /// Integer exponent of the function denominator - int ed; + int ed = 12; public: - /// Constructor - groupcoordnum(std::string const &conf); + groupcoordnum(); virtual ~groupcoordnum() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -1058,73 +888,22 @@ protected: /// \brief "Cutoff" distance between acceptor and donor cvm::real r0; /// Integer exponent of the function numerator - int en; + int en = 6; /// Integer exponent of the function denominator - int ed; + int ed = 8; public: - h_bond(std::string const &conf); /// Constructor for atoms already allocated h_bond(cvm::atom const &acceptor, cvm::atom const &donor, cvm::real r0, int en, int ed); h_bond(); virtual ~h_bond() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; - -/// \brief Colvar component: alpha helix content of a contiguous -/// segment of 5 or more residues, implemented as a sum of phi/psi -/// dihedral angles and hydrogen bonds (colvarvalue::type_scalar type, -/// range [0:1]) -// class colvar::alpha_dihedrals -// : public colvar::cvc -// { -// protected: - -// /// Alpha-helical reference phi value -// cvm::real phi_ref; - -// /// Alpha-helical reference psi value -// cvm::real psi_ref; - -// /// List of phi dihedral angles -// std::vector phi; - -// /// List of psi dihedral angles -// std::vector psi; - -// /// List of hydrogen bonds -// std::vector hb; - -// public: - -// alpha_dihedrals (std::string const &conf); -// alpha_dihedrals(); -// virtual ~alpha_dihedrals() {} -// virtual void calc_value(); -// virtual void calc_gradients(); -// virtual void apply_force (colvarvalue const &force); -// virtual cvm::real dist2 (colvarvalue const &x1, -// colvarvalue const &x2) const; -// virtual colvarvalue dist2_lgrad (colvarvalue const &x1, -// colvarvalue const &x2) const; -// virtual colvarvalue dist2_rgrad (colvarvalue const &x1, -// colvarvalue const &x2) const; -// }; - - - /// \brief Colvar component: alpha helix content of a contiguous /// segment of 5 or more residues, implemented as a sum of Ca-Ca-Ca /// angles and hydrogen bonds (colvarvalue::type_scalar type, range @@ -1135,10 +914,10 @@ class colvar::alpha_angles protected: /// Reference Calpha-Calpha angle (default: 88 degrees) - cvm::real theta_ref; + cvm::real theta_ref = 88.0; /// Tolerance on the Calpha-Calpha angle - cvm::real theta_tol; + cvm::real theta_tol = 15.0; /// List of Calpha-Calpha angles std::vector theta; @@ -1146,25 +925,28 @@ protected: /// List of hydrogen bonds std::vector hb; - /// Contribution of the hb terms - cvm::real hb_coeff; + /// Contribution of the HB terms + cvm::real hb_coeff = 0.5; + + /// Cutoff for HB + cvm::real r0; + + /// Integer exponent of the HB numerator + int en = 6; + + /// Integer exponent of the HB denominator + int ed = 8; public: - alpha_angles(std::string const &conf); alpha_angles(); virtual ~alpha_angles(); + virtual int init(std::string const &conf); void calc_value(); void calc_gradients(); /// Re-implementation of cvc::collect_gradients() to carry over atomic gradients of sub-cvcs void collect_gradients(std::vector const &atom_ids, std::vector &atomic_gradients); void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -1183,20 +965,14 @@ protected: public: - dihedPC(std::string const &conf); dihedPC(); - virtual ~dihedPC(); - void calc_value(); - void calc_gradients(); + virtual ~dihedPC(); + virtual int init(std::string const &conf); + virtual void calc_value(); + virtual void calc_gradients(); /// Re-implementation of cvc::collect_gradients() to carry over atomic gradients of sub-cvcs - void collect_gradients(std::vector const &atom_ids, std::vector &atomic_gradients); - void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; + virtual void collect_gradients(std::vector const &atom_ids, std::vector &atomic_gradients); + virtual void apply_force(colvarvalue const &force); }; @@ -1211,13 +987,16 @@ class colvar::orientation protected: /// Atom group - cvm::atom_group * atoms; + cvm::atom_group * atoms = nullptr; /// Center of geometry of the group cvm::atom_pos atoms_cog; /// Reference coordinates std::vector ref_pos; + /// Shifted atomic positions + std::vector shifted_pos; + /// Rotation object cvm::rotation rot; @@ -1225,45 +1004,50 @@ protected: /// quaternion, which may be annoying in the colvars trajectory cvm::quaternion ref_quat; + /// Rotation derivative + struct rotation_derivative_impl_; + std::unique_ptr rot_deriv_impl; + public: - orientation(std::string const &conf); orientation(); + virtual ~orientation(); virtual int init(std::string const &conf); - virtual ~orientation() {} virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; + /// Redefined to use quaternion metrics + virtual cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to use quaternion metrics + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to use quaternion metrics + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to use quaternion metrics + virtual void wrap(colvarvalue &x_unwrapped) const; }; - -/// \brief Colvar component: angle of rotation with respect to a set -/// of reference coordinates (colvarvalue::type_scalar type, range -/// [0:PI)) +/// Colvar component: angle of rotation with respect to a set of reference coordinates +/// (colvarvalue::type_scalar type, range [0:PI)) +/// This is also used to derive all other sub-rotation variables that return a scalar value class colvar::orientation_angle : public colvar::orientation { public: - orientation_angle(std::string const &conf); - virtual int init(std::string const &conf); + orientation_angle(); virtual ~orientation_angle() {} virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; + /// Redefined to use scalar metrics + virtual cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to use scalar metrics + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to use scalar metrics + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to use scalar metrics + virtual void wrap(colvarvalue &x_unwrapped) const; }; @@ -1272,23 +1056,14 @@ public: /// of reference coordinates (colvarvalue::type_scalar type, range /// [-1:1]) class colvar::orientation_proj - : public colvar::orientation + : public colvar::orientation_angle { public: - orientation_proj(std::string const &conf); orientation_proj(); - virtual int init(std::string const &conf); virtual ~orientation_proj() {} virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -1296,7 +1071,7 @@ public: /// \brief Colvar component: projection of the orientation vector onto /// a predefined axis (colvarvalue::type_scalar type, range [-1:1]) class colvar::tilt - : public colvar::orientation + : public colvar::orientation_proj { protected: @@ -1304,18 +1079,11 @@ protected: public: - tilt(std::string const &conf); - virtual int init(std::string const &conf); + tilt(); virtual ~tilt() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -1323,98 +1091,47 @@ public: /// \brief Colvar component: angle of rotation around a predefined /// axis (colvarvalue::type_scalar type, range [-PI:PI]) class colvar::spin_angle - : public colvar::orientation + : public colvar::tilt { -protected: - - cvm::rvector axis; - public: - spin_angle(std::string const &conf); spin_angle(); - virtual int init(std::string const &conf); virtual ~spin_angle() {} virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - /// Redefined to handle the 2*PI periodicity - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to handle the 2*PI periodicity - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to handle the 2*PI periodicity - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to handle the 2*PI periodicity - virtual void wrap(colvarvalue &x_unwrapped) const; }; class colvar::euler_phi - : public colvar::orientation + : public colvar::orientation_angle { public: - euler_phi(std::string const &conf); euler_phi(); - virtual int init(std::string const &conf); virtual ~euler_phi() {} virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to handle the 2*PI periodicity - virtual void wrap(colvarvalue &x_unwrapped) const; }; class colvar::euler_psi - : public colvar::orientation + : public colvar::orientation_angle { public: - euler_psi(std::string const &conf); euler_psi(); - virtual int init(std::string const &conf); virtual ~euler_psi() {} virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - /// Redefined to handle the 2*PI periodicity - virtual void wrap(colvarvalue &x_unwrapped) const; }; class colvar::euler_theta - : public colvar::orientation + : public colvar::orientation_angle { public: - euler_theta(std::string const &conf); euler_theta(); - virtual int init(std::string const &conf); virtual ~euler_theta() {} virtual void calc_value(); virtual void calc_gradients(); - virtual void apply_force(colvarvalue const &force); - // theta angle is a scalar variable and not periodic - // we need to override the virtual functions from orientation - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -1428,33 +1145,29 @@ class colvar::rmsd protected: /// Atom group - cvm::atom_group *atoms; + cvm::atom_group *atoms = nullptr; /// Reference coordinates (for RMSD calculation only) /// Includes sets with symmetry permutations (n_permutations * n_atoms) std::vector ref_pos; /// Number of permutations of symmetry-related atoms - size_t n_permutations; + size_t n_permutations = 1; /// Index of the permutation yielding the smallest RMSD (0 for identity) - size_t best_perm_index; -public: + size_t best_perm_index = 0; - /// Constructor - rmsd(std::string const &conf); + /// Permutation RMSD input parsing + int init_permutation(std::string const &conf); + +public: + rmsd(); virtual ~rmsd() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void calc_force_invgrads(); virtual void calc_Jacobian_derivative(); - virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -1466,16 +1179,27 @@ class colvar::cartesian { protected: /// Atom group - cvm::atom_group *atoms; + cvm::atom_group *atoms = nullptr; /// Which Cartesian coordinates to include std::vector axes; public: - cartesian(std::string const &conf); cartesian(); virtual ~cartesian() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); + /// Redefined to deal with multiple dimensions + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to deal with multiple dimensions + virtual void wrap(colvarvalue &x_unwrapped) const; }; @@ -1487,18 +1211,11 @@ class colvar::alch_lambda protected: // No atom groups needed public: - alch_lambda(std::string const &conf); alch_lambda(); virtual ~alch_lambda() {} virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; @@ -1510,58 +1227,38 @@ class colvar::alch_Flambda protected: // No atom groups needed public: - alch_Flambda(std::string const &conf); alch_Flambda(); virtual ~alch_Flambda() {} virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); - virtual cvm::real dist2(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const; - virtual colvarvalue dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const; }; -class colvar::componentDisabled - : public colvar::cvc -{ -public: - componentDisabled(std::string const & /* conf */) { - cvm::error("Error: this component is not enabled in the current build; please see https://colvars.github.io/README-c++11.html"); - } - virtual ~componentDisabled() {} - virtual void calc_value() {} - virtual void calc_gradients() {} - virtual void apply_force(colvarvalue const & /* force */) {} -}; - - - -#if (__cplusplus >= 201103L) class colvar::CartesianBasedPath : public colvar::cvc { protected: + virtual void computeDistanceBetweenReferenceFrames(std::vector& result); virtual void computeDistanceToReferenceFrames(std::vector& result); /// Selected atoms - cvm::atom_group *atoms; + cvm::atom_group *atoms = nullptr; /// Fitting options - bool has_user_defined_fitting; + bool has_user_defined_fitting = false; /// Reference frames std::vector> reference_frames; std::vector> reference_fitting_frames; /// Atom groups for RMSD calculation together with reference frames std::vector comp_atoms; /// Total number of reference frames - size_t total_reference_frames; + size_t total_reference_frames = 0; public: - CartesianBasedPath(std::string const &conf); + CartesianBasedPath(); virtual ~CartesianBasedPath(); + virtual int init(std::string const &conf); virtual void calc_value() = 0; - virtual void apply_force(colvarvalue const &force) = 0; + /// Redefined to raise error because this is an abstract type + virtual void apply_force(colvarvalue const &force); }; /// \brief Colvar component: alternative path collective variable using geometry, variable s @@ -1577,8 +1274,9 @@ protected: virtual void prepareVectors(); virtual void updateDistanceToReferenceFrames(); public: - gspath(std::string const &conf); + gspath(); virtual ~gspath() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); @@ -1599,8 +1297,9 @@ protected: virtual void prepareVectors(); virtual void updateDistanceToReferenceFrames(); public: - gzpath(std::string const &conf); + gzpath(); virtual ~gzpath() {} + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); @@ -1618,11 +1317,23 @@ protected: protected: cvm::real getPolynomialFactorOfCVGradient(size_t i_cv) const; public: - linearCombination(std::string const &conf); + linearCombination(); virtual ~linearCombination(); + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); + /// Redefined to allow arbitrary dimensions + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to allow arbitrary dimensions + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to allow arbitrary dimensions + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to allow arbitrary dimensions + virtual void wrap(colvarvalue &x_unwrapped) const; }; @@ -1631,7 +1342,7 @@ class colvar::customColvar : public colvar::linearCombination { protected: - bool use_custom_function; + bool use_custom_function = false; #ifdef LEPTON /// Vector of evaluators for custom functions using Lepton std::vector value_evaluators; @@ -1641,11 +1352,12 @@ protected: std::vector value_eval_var_refs; std::vector grad_eval_var_refs; /// Unused value that is written to when a variable simplifies out of a Lepton expression - double dev_null; + double dev_null = 0.0; #endif public: - customColvar(std::string const &conf); + customColvar(); virtual ~customColvar(); + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); @@ -1663,17 +1375,27 @@ protected: /// If all sub-cvs use explicit gradients then we also use it bool use_explicit_gradients; /// Total number of reference frames - size_t total_reference_frames; + size_t total_reference_frames = 0; protected: virtual void computeDistanceToReferenceFrames(std::vector& result); /// Helper function to determine the distance between reference frames virtual void computeDistanceBetweenReferenceFrames(std::vector& result) const; cvm::real getPolynomialFactorOfCVGradient(size_t i_cv) const; public: - CVBasedPath(std::string const &conf); + CVBasedPath(); virtual ~CVBasedPath(); + virtual int init(std::string const &conf); virtual void calc_value() = 0; - virtual void apply_force(colvarvalue const &force) = 0; + /// Redefined to raise error because this is an abstract type + virtual void apply_force(colvarvalue const &force); + /// Redefined to use the metric of the returned colvarvalue (defined at runtime) + virtual cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to use the metric of the returned colvarvalue (defined at runtime) + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to use the metric of the returned colvarvalue (defined at runtime) + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const; + /// Redefined to use the metric of the returned colvarvalue (defined at runtime) + virtual void wrap(colvarvalue &x_unwrapped) const; }; @@ -1688,8 +1410,9 @@ protected: virtual void updateDistanceToReferenceFrames(); virtual void prepareVectors(); public: - gspathCV(std::string const &conf); + gspathCV(); virtual ~gspathCV(); + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); @@ -1704,23 +1427,56 @@ protected: virtual void updateDistanceToReferenceFrames(); virtual void prepareVectors(); public: - gzpathCV(std::string const &conf); + gzpathCV(); virtual ~gzpathCV(); + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); }; +struct ArithmeticPathImpl; +class colvar::aspath + : public colvar::CartesianBasedPath +{ +private: + std::unique_ptr impl_; + friend struct ArithmeticPathImpl; +public: + aspath(); + virtual ~aspath(); + virtual int init(std::string const &conf); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + +class colvar::azpath + : public colvar::CartesianBasedPath +{ +private: + std::unique_ptr impl_; + friend struct ArithmeticPathImpl; +public: + azpath(); + virtual ~azpath(); + virtual int init(std::string const &conf); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; class colvar::aspathCV - : public colvar::CVBasedPath, public ArithmeticPathCV::ArithmeticPathBase + : public colvar::CVBasedPath { -protected: - virtual void updateDistanceToReferenceFrames(); +private: + std::unique_ptr impl_; + friend struct ArithmeticPathImpl; public: - aspathCV(std::string const &conf); + aspathCV(); virtual ~aspathCV(); + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); @@ -1728,13 +1484,15 @@ public: class colvar::azpathCV - : public colvar::CVBasedPath, public ArithmeticPathCV::ArithmeticPathBase + : public colvar::CVBasedPath { -protected: - virtual void updateDistanceToReferenceFrames(); +private: + std::unique_ptr impl_; + friend struct ArithmeticPathImpl; public: - azpathCV(std::string const &conf); + azpathCV(); virtual ~azpathCV(); + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); @@ -1745,6 +1503,7 @@ namespace neuralnetworkCV { class neuralNetworkCompute; } + class colvar::neuralNetwork : public linearCombination { @@ -1752,89 +1511,27 @@ protected: /// actual computation happens in neuralnetworkCV::neuralNetworkCompute std::unique_ptr nn; /// the index of nn output components - size_t m_output_index; + size_t m_output_index = 0; public: - neuralNetwork(std::string const &conf); + neuralNetwork(); virtual ~neuralNetwork(); + virtual int init(std::string const &conf); virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); + /// Redefined to allow arbitrary dimensions + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to allow arbitrary dimensions + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to allow arbitrary dimensions + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to allow arbitrary dimensions + virtual void wrap(colvarvalue &x_unwrapped) const; }; -#else // if the compiler doesn't support C++11 - -class colvar::linearCombination - : public colvar::componentDisabled -{ -public: - linearCombination(std::string const &conf) : componentDisabled(conf) {} -}; - -class colvar::CartesianBasedPath - : public colvar::componentDisabled -{ -public: - CartesianBasedPath(std::string const &conf) : componentDisabled(conf) {} -}; - -class colvar::CVBasedPath - : public colvar::componentDisabled -{ -public: - CVBasedPath(std::string const &conf) : componentDisabled(conf) {} -}; - -class colvar::gspath - : public colvar::componentDisabled -{ -public: - gspath(std::string const &conf) : componentDisabled(conf) {} -}; - -class colvar::gzpath - : public colvar::componentDisabled -{ -public: - gzpath(std::string const &conf) : componentDisabled(conf) {} -}; - -class colvar::gspathCV - : public colvar::componentDisabled -{ -public: - gspathCV(std::string const &conf) : componentDisabled(conf) {} -}; - -class colvar::gzpathCV - : public colvar::componentDisabled -{ -public: - gzpathCV(std::string const &conf) : componentDisabled(conf) {} -}; - -class colvar::aspathCV - : public colvar::componentDisabled -{ -public: - aspathCV(std::string const &conf) : componentDisabled(conf) {} -}; - -class colvar::azpathCV - : public colvar::componentDisabled -{ -public: - azpathCV(std::string const &conf) : componentDisabled(conf) {} -}; - -class colvar::neuralNetwork - : public colvar::componentDisabled -{ -public: - neuralNetwork(std::string const &conf) : componentDisabled(conf) {} -}; - -#endif // C++11 checking - // \brief Colvar component: total value of a scalar map // (usually implemented as a grid by the simulation engine) @@ -1844,7 +1541,6 @@ class colvar::map_total public: map_total(); - map_total(std::string const &conf); virtual ~map_total() {} virtual int init(std::string const &conf); virtual void calc_value(); @@ -1857,13 +1553,13 @@ protected: std::string volmap_name; /// Numeric identifier of the map object (as used by the simulation engine) - int volmap_id; + int volmap_id = -1; /// Index of the map objet in the proxy arrays - int volmap_index; + int volmap_index = -1; /// Group of atoms selected internally (optional) - cvm::atom_group *atoms; + cvm::atom_group *atoms = nullptr; /// Weights assigned to each atom (default: uniform weights) std::vector atom_weights; @@ -1871,36 +1567,4 @@ protected: -// metrics functions for cvc implementations - -// simple definitions of the distance functions; these are useful only -// for optimization (the type check performed in the default -// colvarcomp functions is skipped) - -// definitions assuming the scalar type - -#define simple_scalar_dist_functions(TYPE) \ - \ - \ - cvm::real colvar::TYPE::dist2(colvarvalue const &x1, \ - colvarvalue const &x2) const \ - { \ - return (x1.real_value - x2.real_value)*(x1.real_value - x2.real_value); \ - } \ - \ - \ - colvarvalue colvar::TYPE::dist2_lgrad(colvarvalue const &x1, \ - colvarvalue const &x2) const \ - { \ - return 2.0 * (x1.real_value - x2.real_value); \ - } \ - \ - \ - colvarvalue colvar::TYPE::dist2_rgrad(colvarvalue const &x1, \ - colvarvalue const &x2) const \ - { \ - return this->dist2_lgrad(x2, x1); \ - } \ - - #endif diff --git a/lib/colvars/colvarcomp_alchlambda.cpp b/lib/colvars/colvarcomp_alchlambda.cpp index 9f18bec776..a175ea43e8 100644 --- a/lib/colvars/colvarcomp_alchlambda.cpp +++ b/lib/colvars/colvarcomp_alchlambda.cpp @@ -12,13 +12,11 @@ #include "colvarmodule.h" #include "colvarvalue.h" -#include "colvarparse.h" #include "colvar.h" #include "colvarcomp.h" -colvar::alch_lambda::alch_lambda(std::string const &conf) - : cvc(conf) +colvar::alch_lambda::alch_lambda() { set_function_type("alchLambda"); @@ -56,12 +54,9 @@ void colvar::alch_lambda::apply_force(colvarvalue const & /* force */) cvm::proxy->set_alch_lambda(x.real_value); } -simple_scalar_dist_functions(alch_lambda) - -colvar::alch_Flambda::alch_Flambda(std::string const &conf) - : cvc(conf) +colvar::alch_Flambda::alch_Flambda() { set_function_type("alch_Flambda"); @@ -103,4 +98,3 @@ void colvar::alch_Flambda::apply_force(colvarvalue const &force) cvm::proxy->indirect_lambda_biasing_force += d2E_dlambda2 * f; } -simple_scalar_dist_functions(alch_Flambda) diff --git a/lib/colvars/colvarcomp_angles.cpp b/lib/colvars/colvarcomp_angles.cpp index d3f1bfd9aa..56894e9f5c 100644 --- a/lib/colvars/colvarcomp_angles.cpp +++ b/lib/colvars/colvarcomp_angles.cpp @@ -12,8 +12,7 @@ #include "colvarcomp.h" -colvar::angle::angle(std::string const &conf) - : cvc(conf) +colvar::angle::angle() { set_function_type("angle"); init_as_angle(); @@ -21,26 +20,25 @@ colvar::angle::angle(std::string const &conf) provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); enable(f_cvc_com_based); +} + + +int colvar::angle::init(std::string const &conf) +{ + int error_code = cvc::init(conf); group1 = parse_group(conf, "group1"); group2 = parse_group(conf, "group2"); group3 = parse_group(conf, "group3"); - init_total_force_params(conf); + error_code |= init_total_force_params(conf); + + return error_code; } -colvar::angle::angle(cvm::atom const &a1, - cvm::atom const &a2, - cvm::atom const &a3) +colvar::angle::angle(cvm::atom const &a1, cvm::atom const &a2, cvm::atom const &a3) : angle() { - set_function_type("angle"); - init_as_angle(); - - provide(f_cvc_inv_gradient); - provide(f_cvc_Jacobian); - enable(f_cvc_com_based); - group1 = new cvm::atom_group(std::vector(1, a1)); group2 = new cvm::atom_group(std::vector(1, a2)); group3 = new cvm::atom_group(std::vector(1, a3)); @@ -120,52 +118,6 @@ void colvar::angle::calc_Jacobian_derivative() } -void colvar::angle::apply_force(colvarvalue const &force) -{ - if (!group1->noforce) - group1->apply_colvar_force(force.real_value); - - if (!group2->noforce) - group2->apply_colvar_force(force.real_value); - - if (!group3->noforce) - group3->apply_colvar_force(force.real_value); -} - - -simple_scalar_dist_functions(angle) - - - -colvar::dipole_angle::dipole_angle(std::string const &conf) - : cvc(conf) -{ - set_function_type("dipoleAngle"); - init_as_angle(); - - group1 = parse_group(conf, "group1"); - group2 = parse_group(conf, "group2"); - group3 = parse_group(conf, "group3"); - - init_total_force_params(conf); -} - - -colvar::dipole_angle::dipole_angle(cvm::atom const &a1, - cvm::atom const &a2, - cvm::atom const &a3) -{ - set_function_type("dipoleAngle"); - init_as_angle(); - - group1 = new cvm::atom_group(std::vector(1, a1)); - group2 = new cvm::atom_group(std::vector(1, a2)); - group3 = new cvm::atom_group(std::vector(1, a3)); - register_atom_group(group1); - register_atom_group(group2); - register_atom_group(group3); -} - colvar::dipole_angle::dipole_angle() { @@ -174,6 +126,19 @@ colvar::dipole_angle::dipole_angle() } +int colvar::dipole_angle::init(std::string const &conf) +{ + int error_code = cvc::init(conf); + + group1 = parse_group(conf, "group1"); + group2 = parse_group(conf, "group2"); + group3 = parse_group(conf, "group3"); + + error_code |= init_total_force_params(conf); + return error_code; +} + + void colvar::dipole_angle::calc_value() { cvm::atom_pos const g1_pos = group1->center_of_mass(); @@ -229,52 +194,35 @@ void colvar::dipole_angle::calc_gradients() } -void colvar::dipole_angle::apply_force(colvarvalue const &force) -{ - if (!group1->noforce) - group1->apply_colvar_force(force.real_value); - if (!group2->noforce) - group2->apply_colvar_force(force.real_value); - - if (!group3->noforce) - group3->apply_colvar_force(force.real_value); -} - - -simple_scalar_dist_functions(dipole_angle) - - - -colvar::dihedral::dihedral(std::string const &conf) - : cvc(conf) +colvar::dihedral::dihedral() { set_function_type("dihedral"); init_as_periodic_angle(); provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); enable(f_cvc_com_based); +} + + +int colvar::dihedral::init(std::string const &conf) +{ + int error_code = cvc::init(conf); group1 = parse_group(conf, "group1"); group2 = parse_group(conf, "group2"); group3 = parse_group(conf, "group3"); group4 = parse_group(conf, "group4"); - init_total_force_params(conf); + error_code |= init_total_force_params(conf); + return error_code; } -colvar::dihedral::dihedral(cvm::atom const &a1, - cvm::atom const &a2, - cvm::atom const &a3, +colvar::dihedral::dihedral(cvm::atom const &a1, cvm::atom const &a2, cvm::atom const &a3, cvm::atom const &a4) + : dihedral() { - set_function_type("dihedral"); - init_as_periodic_angle(); - provide(f_cvc_inv_gradient); - provide(f_cvc_Jacobian); - enable(f_cvc_com_based); - b_1site_force = false; group1 = new cvm::atom_group(std::vector(1, a1)); @@ -288,16 +236,6 @@ colvar::dihedral::dihedral(cvm::atom const &a1, } -colvar::dihedral::dihedral() -{ - set_function_type("dihedral"); - init_as_periodic_angle(); - enable(f_cvc_periodic); - provide(f_cvc_inv_gradient); - provide(f_cvc_Jacobian); -} - - void colvar::dihedral::calc_value() { cvm::atom_pos const g1_pos = group1->center_of_mass(); @@ -323,7 +261,7 @@ void colvar::dihedral::calc_value() cvm::real const sin_phi = n1 * r34 * r23.norm(); x.real_value = (180.0/PI) * cvm::atan2(sin_phi, cos_phi); - this->wrap(x); + wrap(x); } @@ -349,7 +287,7 @@ void colvar::dihedral::calc_gradients() A *= rA; cvm::rvector const dcosdA = rA*(cos_phi*A-B); cvm::rvector const dcosdB = rB*(cos_phi*B-A); - rA = 1.0; + // rA = 1.0; cvm::real const K = (1.0/sin_phi) * (180.0/PI); @@ -363,7 +301,7 @@ void colvar::dihedral::calc_gradients() C *= rC; cvm::rvector const dsindC = rC*(sin_phi*C-B); cvm::rvector const dsindB = rB*(sin_phi*B-C); - rC = 1.0; + // rC = 1.0; cvm::real const K = (-1.0/cos_phi) * (180.0/PI); @@ -439,81 +377,22 @@ void colvar::dihedral::calc_Jacobian_derivative() } -void colvar::dihedral::apply_force(colvarvalue const &force) -{ - if (!group1->noforce) - group1->apply_colvar_force(force.real_value); - - if (!group2->noforce) - group2->apply_colvar_force(force.real_value); - - if (!group3->noforce) - group3->apply_colvar_force(force.real_value); - - if (!group4->noforce) - group4->apply_colvar_force(force.real_value); -} - - -// metrics functions for cvc implementations with a periodicity - -cvm::real colvar::dihedral::dist2(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return diff * diff; -} - - -colvarvalue colvar::dihedral::dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return 2.0 * diff; -} - - -colvarvalue colvar::dihedral::dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return (-2.0) * diff; -} - - -void colvar::dihedral::wrap(colvarvalue &x_unwrapped) const -{ - if ((x_unwrapped.real_value - wrap_center) >= 180.0) { - x_unwrapped.real_value -= 360.0; - return; - } - - if ((x_unwrapped.real_value - wrap_center) < -180.0) { - x_unwrapped.real_value += 360.0; - return; - } -} - - -colvar::polar_theta::polar_theta(std::string const &conf) - : cvc(conf) -{ - set_function_type("polarTheta"); - enable(f_cvc_com_based); - - atoms = parse_group(conf, "atoms"); - init_total_force_params(conf); - x.type(colvarvalue::type_scalar); -} - colvar::polar_theta::polar_theta() { + r = theta = phi = 0.0; set_function_type("polarTheta"); - x.type(colvarvalue::type_scalar); + enable(f_cvc_com_based); + init_as_angle(); +} + + +int colvar::polar_theta::init(std::string const &conf) +{ + int error_code = cvc::init(conf); + atoms = parse_group(conf, "atoms"); + error_code |= init_total_force_params(conf); + return error_code; } @@ -540,35 +419,25 @@ void colvar::polar_theta::calc_gradients() } -void colvar::polar_theta::apply_force(colvarvalue const &force) -{ - if (!atoms->noforce) - atoms->apply_colvar_force(force.real_value); -} - - -simple_scalar_dist_functions(polar_theta) - - -colvar::polar_phi::polar_phi(std::string const &conf) - : cvc(conf) -{ - set_function_type("polarPhi"); - init_as_periodic_angle(); - enable(f_cvc_com_based); - - atoms = parse_group(conf, "atoms"); - init_total_force_params(conf); -} - colvar::polar_phi::polar_phi() { + r = theta = phi = 0.0; set_function_type("polarPhi"); + enable(f_cvc_com_based); init_as_periodic_angle(); } +int colvar::polar_phi::init(std::string const &conf) +{ + int error_code = cvc::init(conf); + atoms = parse_group(conf, "atoms"); + error_code |= init_total_force_params(conf); + return error_code; +} + + void colvar::polar_phi::calc_value() { cvm::rvector pos = atoms->center_of_mass(); @@ -587,55 +456,3 @@ void colvar::polar_phi::calc_gradients() (180.0/PI) * cvm::cos(phi) / (r*cvm::sin(theta)), 0.)); } - - -void colvar::polar_phi::apply_force(colvarvalue const &force) -{ - if (!atoms->noforce) - atoms->apply_colvar_force(force.real_value); -} - - -// Same as dihedral, for polar_phi - -cvm::real colvar::polar_phi::dist2(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return diff * diff; -} - - -colvarvalue colvar::polar_phi::dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return 2.0 * diff; -} - - -colvarvalue colvar::polar_phi::dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return (-2.0) * diff; -} - - -void colvar::polar_phi::wrap(colvarvalue &x_unwrapped) const -{ - if ((x_unwrapped.real_value - wrap_center) >= 180.0) { - x_unwrapped.real_value -= 360.0; - return; - } - - if ((x_unwrapped.real_value - wrap_center) < -180.0) { - x_unwrapped.real_value += 360.0; - return; - } - - return; -} diff --git a/lib/colvars/colvarcomp_apath.cpp b/lib/colvars/colvarcomp_apath.cpp index ab759e9dea..9e8256957d 100644 --- a/lib/colvars/colvarcomp_apath.cpp +++ b/lib/colvars/colvarcomp_apath.cpp @@ -1,78 +1,312 @@ -#if (__cplusplus >= 201103L) +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. -#include #include #include -#include +#include #include +#include -#include "colvarmodule.h" #include "colvarvalue.h" -#include "colvarparse.h" #include "colvar.h" #include "colvarcomp.h" +#include "colvar_arithmeticpath.h" -colvar::aspathCV::aspathCV(std::string const &conf): CVBasedPath(conf) { + +struct ArithmeticPathImpl: public ArithmeticPathCV::ArithmeticPathBase { + std::vector> frame_element_distances; + std::vector> dsdx; + std::vector> dzdx; + template + void updateCartesianDistanceToReferenceFrames(T* obj) { + for (size_t i_frame = 0; i_frame < obj->reference_frames.size(); ++i_frame) { + for (size_t i_atom = 0; i_atom < obj->atoms->size(); ++i_atom) { + frame_element_distances[i_frame][i_atom] = (*(obj->comp_atoms[i_frame]))[i_atom].pos - obj->reference_frames[i_frame][i_atom]; + } + } + } + template + void updateCVDistanceToReferenceFrames(T* obj) { + for (size_t i_cv = 0; i_cv < obj->cv.size(); ++i_cv) { + obj->cv[i_cv]->calc_value(); + } + for (size_t i_frame = 0; i_frame < obj->ref_cv.size(); ++i_frame) { + for (size_t i_cv = 0; i_cv < obj->cv.size(); ++i_cv) { + colvarvalue ref_cv_value(obj->ref_cv[i_frame][i_cv]); + colvarvalue current_cv_value(obj->cv[i_cv]->value()); + if (current_cv_value.type() == colvarvalue::type_scalar) { + frame_element_distances[i_frame][i_cv] = 0.5 * obj->cv[i_cv]->dist2_lgrad(obj->cv[i_cv]->sup_coeff * (cvm::pow(current_cv_value.real_value, obj->cv[i_cv]->sup_np)), ref_cv_value.real_value); + } else { + frame_element_distances[i_frame][i_cv] = 0.5 * obj->cv[i_cv]->dist2_lgrad(obj->cv[i_cv]->sup_coeff * current_cv_value, ref_cv_value); + } + } + } + } + ArithmeticPathImpl(size_t p_num_elements, size_t p_total_frames, cvm::real p_lambda, const std::vector& p_weights) { + ArithmeticPathCV::ArithmeticPathBase::initialize(p_num_elements, p_total_frames, p_lambda, p_weights); + frame_element_distances.resize(p_total_frames, std::vector(p_num_elements, colvarvalue(colvarvalue::Type::type_notset))); + dsdx.resize(p_total_frames, std::vector(p_num_elements, colvarvalue(colvarvalue::Type::type_notset))); + dzdx.resize(p_total_frames, std::vector(p_num_elements, colvarvalue(colvarvalue::Type::type_notset))); + } + cvm::real get_lambda() const {return lambda;} + cvm::real compute_s() { + cvm::real s; + computeValue(frame_element_distances, &s, nullptr); + return s; + } + cvm::real compute_z() { + cvm::real z; + computeValue(frame_element_distances, nullptr, &z); + return z; + } + void compute_s_derivatives() { + computeDerivatives(frame_element_distances, &dsdx, nullptr); + } + void compute_z_derivatives() { + computeDerivatives(frame_element_distances, nullptr, &dzdx); + } + // for debug gradients of implicit sub CVs + template + colvarvalue compute_s_analytical_derivative_ij(size_t i, size_t j, cvm::real eps, T* obj) const { + ArithmeticPathImpl tmp_left(*this), tmp_right(*this); + const size_t value_size = frame_element_distances[i][j].size(); + colvarvalue result(frame_element_distances[i][j].type()); + colvarvalue ref_cv_value(obj->ref_cv[i][j]); + for (size_t k = 0; k < value_size; ++k) { + // get the current CV value + colvarvalue current_cv_value(obj->cv[j]->value()); + // save the old values in frame element distance matrices + const auto saved_left = tmp_left.frame_element_distances[i][j][k]; + const auto saved_right = tmp_right.frame_element_distances[i][j][k]; + // update frame element distance matrices + if (current_cv_value.type() == colvarvalue::type_scalar) { + tmp_left.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * (cvm::pow(current_cv_value.real_value - eps, obj->cv[j]->sup_np)), ref_cv_value.real_value); + tmp_right.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * (cvm::pow(current_cv_value.real_value + eps, obj->cv[j]->sup_np)), ref_cv_value.real_value); + } else { + current_cv_value[k] -= eps; + tmp_left.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * current_cv_value, ref_cv_value); + current_cv_value[k] += eps + eps; + tmp_right.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * current_cv_value, ref_cv_value); + } + const cvm::real s_left = tmp_left.compute_s(); + const cvm::real s_right = tmp_right.compute_s(); + result[k] = (s_right - s_left) / (2.0 * eps); + tmp_left.frame_element_distances[i][j][k] = saved_left; + tmp_right.frame_element_distances[i][j][k] = saved_right; + } + return result; + } + template + colvarvalue compute_z_analytical_derivative_ij(size_t i, size_t j, cvm::real eps, T* obj) const { + ArithmeticPathImpl tmp_left(*this), tmp_right(*this); + const size_t value_size = frame_element_distances[i][j].size(); + colvarvalue result(frame_element_distances[i][j].type()); + colvarvalue ref_cv_value(obj->ref_cv[i][j]); + for (size_t k = 0; k < value_size; ++k) { + // get the current CV value + colvarvalue current_cv_value(obj->cv[j]->value()); + // save the old values in frame element distance matrices + const auto saved_left = tmp_left.frame_element_distances[i][j][k]; + const auto saved_right = tmp_right.frame_element_distances[i][j][k]; + // update frame element distance matrices + if (current_cv_value.type() == colvarvalue::type_scalar) { + tmp_left.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * (cvm::pow(current_cv_value.real_value - eps, obj->cv[j]->sup_np)), ref_cv_value.real_value); + tmp_right.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * (cvm::pow(current_cv_value.real_value + eps, obj->cv[j]->sup_np)), ref_cv_value.real_value); + } else { + current_cv_value[k] -= eps; + tmp_left.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * current_cv_value, ref_cv_value); + current_cv_value[k] += eps + eps; + tmp_right.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * current_cv_value, ref_cv_value); + } + const cvm::real z_left = tmp_left.compute_z(); + const cvm::real z_right = tmp_right.compute_z(); + result[k] = (z_right - z_left) / (2.0 * eps); + tmp_left.frame_element_distances[i][j][k] = saved_left; + tmp_right.frame_element_distances[i][j][k] = saved_right; + } + return result; + } +}; + +colvar::aspath::aspath() +{ + set_function_type("aspath"); + x.type(colvarvalue::type_scalar); +} + + +int colvar::aspath::init(std::string const &conf) +{ + int error_code = CartesianBasedPath::init(conf); + if (error_code != COLVARS_OK) return error_code; + cvm::log(std::string("Total number of frames: ") + cvm::to_str(total_reference_frames) + std::string("\n")); + cvm::real p_lambda; + get_keyval(conf, "lambda", p_lambda, -1.0); + const size_t num_atoms = atoms->size(); + std::vector p_weights(num_atoms, std::sqrt(1.0 / num_atoms)); + // ArithmeticPathCV::ArithmeticPathBase::initialize(num_atoms, total_reference_frames, p_lambda, reference_frames[0], p_weights); + if (impl_) impl_.reset(); + impl_ = std::unique_ptr(new ArithmeticPathImpl(num_atoms, total_reference_frames, p_lambda, p_weights)); + cvm::log(std::string("Lambda is ") + cvm::to_str(impl_->get_lambda()) + std::string("\n")); + return error_code; +} + +colvar::aspath::~aspath() {} + +void colvar::aspath::calc_value() { + if (impl_->get_lambda() < 0) { + // this implies that the user may not set a valid lambda value + // so recompute it by the suggested value in Parrinello's paper + cvm::log("A non-positive value of lambda is detected, which implies that it may not set in the configuration.\n"); + cvm::log("This component (aspath) will recompute a value for lambda following the suggestion in the origin paper.\n"); + std::vector rmsd_between_refs(total_reference_frames - 1, 0.0); + computeDistanceBetweenReferenceFrames(rmsd_between_refs); + impl_->reComputeLambda(rmsd_between_refs); + cvm::log("Ok, the value of lambda is updated to " + cvm::to_str(impl_->get_lambda())); + } + impl_->updateCartesianDistanceToReferenceFrames(this); + x = impl_->compute_s(); +} + +void colvar::aspath::calc_gradients() { + impl_->compute_s_derivatives(); + for (size_t i_frame = 0; i_frame < reference_frames.size(); ++i_frame) { + for (size_t i_atom = 0; i_atom < atoms->size(); ++i_atom) { + (*(comp_atoms[i_frame]))[i_atom].grad += impl_->dsdx[i_frame][i_atom]; + } + } +} + +void colvar::aspath::apply_force(colvarvalue const &force) { + cvm::real const &F = force.real_value; + for (size_t i_frame = 0; i_frame < reference_frames.size(); ++i_frame) { + (*(comp_atoms[i_frame])).apply_colvar_force(F); + } +} + +colvar::azpath::azpath() +{ + set_function_type("azpath"); + x.type(colvarvalue::type_scalar); +} + +int colvar::azpath::init(std::string const &conf) +{ + int error_code = CartesianBasedPath::init(conf); + if (error_code != COLVARS_OK) return error_code; + cvm::log(std::string("Total number of frames: ") + cvm::to_str(total_reference_frames) + std::string("\n")); + x.type(colvarvalue::type_scalar); + cvm::real p_lambda; + get_keyval(conf, "lambda", p_lambda, -1.0); + const size_t num_atoms = atoms->size(); + std::vector p_weights(num_atoms, std::sqrt(1.0 / num_atoms)); + if (impl_) impl_.reset(); + impl_ = std::unique_ptr(new ArithmeticPathImpl(num_atoms, total_reference_frames, p_lambda, p_weights)); + cvm::log(std::string("Lambda is ") + cvm::to_str(impl_->get_lambda()) + std::string("\n")); + return error_code; +} + +colvar::azpath::~azpath() {} + +void colvar::azpath::calc_value() { + if (impl_->get_lambda() < 0) { + // this implies that the user may not set a valid lambda value + // so recompute it by the suggested value in Parrinello's paper + cvm::log("A non-positive value of lambda is detected, which implies that it may not set in the configuration.\n"); + cvm::log("This component (azpath) will recompute a value for lambda following the suggestion in the origin paper.\n"); + std::vector rmsd_between_refs(total_reference_frames - 1, 0.0); + computeDistanceBetweenReferenceFrames(rmsd_between_refs); + impl_->reComputeLambda(rmsd_between_refs); + cvm::log("Ok, the value of lambda is updated to " + cvm::to_str(impl_->get_lambda())); + } + impl_->updateCartesianDistanceToReferenceFrames(this); + x = impl_->compute_z(); +} + +void colvar::azpath::calc_gradients() { + impl_->compute_z_derivatives(); + for (size_t i_frame = 0; i_frame < reference_frames.size(); ++i_frame) { + for (size_t i_atom = 0; i_atom < atoms->size(); ++i_atom) { + (*(comp_atoms[i_frame]))[i_atom].grad += impl_->dzdx[i_frame][i_atom]; + } + } +} + +void colvar::azpath::apply_force(colvarvalue const &force) { + cvm::real const &F = force.real_value; + for (size_t i_frame = 0; i_frame < reference_frames.size(); ++i_frame) { + (*(comp_atoms[i_frame])).apply_colvar_force(F); + } +} + +colvar::aspathCV::aspathCV() +{ set_function_type("aspathCV"); + x.type(colvarvalue::type_scalar); +} + +int colvar::aspathCV::init(std::string const &conf) +{ + int error_code = CVBasedPath::init(conf); + if (error_code != COLVARS_OK) return error_code; cvm::log(std::string("Total number of frames: ") + cvm::to_str(total_reference_frames) + std::string("\n")); std::vector p_weights(cv.size(), 1.0); get_keyval(conf, "weights", p_weights, std::vector(cv.size(), 1.0)); - x.type(colvarvalue::type_scalar); use_explicit_gradients = true; cvm::real p_lambda; get_keyval(conf, "lambda", p_lambda, -1.0); - ArithmeticPathCV::ArithmeticPathBase::initialize(cv.size(), total_reference_frames, p_lambda, ref_cv[0], p_weights); - cvm::log(std::string("Lambda is ") + cvm::to_str(lambda) + std::string("\n")); + if (impl_) impl_.reset(); + impl_ = std::unique_ptr(new ArithmeticPathImpl(cv.size(), total_reference_frames, p_lambda, p_weights)); + cvm::log(std::string("Lambda is ") + cvm::to_str(impl_->get_lambda()) + std::string("\n")); for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { if (!cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { use_explicit_gradients = false; } - cvm::log(std::string("The weight of CV ") + cvm::to_str(i_cv) + std::string(" is ") + cvm::to_str(weights[i_cv]) + std::string("\n")); + cvm::log(std::string("The weight of CV ") + cvm::to_str(i_cv) + std::string(" is ") + cvm::to_str(p_weights[i_cv]) + std::string("\n")); } + return error_code; } -void colvar::aspathCV::updateDistanceToReferenceFrames() { - for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { - cv[i_cv]->calc_value(); - } - for (size_t i_frame = 0; i_frame < ref_cv.size(); ++i_frame) { - for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { - colvarvalue ref_cv_value(ref_cv[i_frame][i_cv]); - colvarvalue current_cv_value(cv[i_cv]->value()); - if (current_cv_value.type() == colvarvalue::type_scalar) { - frame_element_distances[i_frame][i_cv] = 0.5 * cv[i_cv]->dist2_lgrad(cv[i_cv]->sup_coeff * (cvm::pow(current_cv_value.real_value, cv[i_cv]->sup_np)), ref_cv_value.real_value); - } else { - frame_element_distances[i_frame][i_cv] = 0.5 * cv[i_cv]->dist2_lgrad(cv[i_cv]->sup_coeff * current_cv_value, ref_cv_value); - } - } - } -} +colvar::aspathCV::~aspathCV() {} void colvar::aspathCV::calc_value() { - if (lambda < 0) { + if (impl_->get_lambda() < 0) { // this implies that the user may not set a valid lambda value // so recompute it by the suggested value in Parrinello's paper cvm::log("A non-positive value of lambda is detected, which implies that it may not set in the configuration.\n"); cvm::log("This component (aspathCV) will recompute a value for lambda following the suggestion in the origin paper.\n"); std::vector rmsd_between_refs(total_reference_frames - 1, 0.0); computeDistanceBetweenReferenceFrames(rmsd_between_refs); - reComputeLambda(rmsd_between_refs); - cvm::log("Ok, the value of lambda is updated to " + cvm::to_str(lambda)); + impl_->reComputeLambda(rmsd_between_refs); + cvm::log("Ok, the value of lambda is updated to " + cvm::to_str(impl_->get_lambda())); } - computeValue(); - x = s; + impl_->updateCVDistanceToReferenceFrames(this); + x = impl_->compute_s(); } void colvar::aspathCV::calc_gradients() { - computeDerivatives(); + impl_->compute_s_derivatives(); for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { cv[i_cv]->calc_gradients(); if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + // compute the gradient (grad) with respect to the i-th CV + colvarvalue grad(cv[i_cv]->value().type()); + // sum up derivatives with respect to all frames + for (size_t m_frame = 0; m_frame < impl_->dsdx.size(); ++m_frame) { + // dsdx is the derivative of s with respect to the m-th frame + grad += impl_->dsdx[m_frame][i_cv]; + } for (size_t j_elem = 0; j_elem < cv[i_cv]->value().size(); ++j_elem) { for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { for (size_t l_atom = 0; l_atom < (cv[i_cv]->atom_groups)[k_ag]->size(); ++l_atom) { - (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad = dsdx[i_cv][j_elem] * factor_polynomial * (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad; + (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad = grad[j_elem] * factor_polynomial * (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad; } } } @@ -88,97 +322,137 @@ void colvar::aspathCV::apply_force(colvarvalue const &force) { } } else { cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); - colvarvalue cv_force = dsdx[i_cv] * force.real_value * factor_polynomial; - cv[i_cv]->apply_force(cv_force); - } - } -} - -colvar::aspathCV::~aspathCV() {} - -colvar::azpathCV::azpathCV(std::string const &conf): CVBasedPath(conf) { - set_function_type("azpathCV"); - cvm::log(std::string("Total number of frames: ") + cvm::to_str(total_reference_frames) + std::string("\n")); - std::vector p_weights(cv.size(), 1.0); - get_keyval(conf, "weights", p_weights, std::vector(cv.size(), 1.0)); - x.type(colvarvalue::type_scalar); - use_explicit_gradients = true; - cvm::real p_lambda; - get_keyval(conf, "lambda", p_lambda, -1.0); - ArithmeticPathCV::ArithmeticPathBase::initialize(cv.size(), total_reference_frames, p_lambda, ref_cv[0], p_weights); - cvm::log(std::string("Lambda is ") + cvm::to_str(lambda) + std::string("\n")); - for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { - if (!cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { - use_explicit_gradients = false; - } - cvm::log(std::string("The weight of CV ") + cvm::to_str(i_cv) + std::string(" is ") + cvm::to_str(weights[i_cv]) + std::string("\n")); - } -} - -void colvar::azpathCV::updateDistanceToReferenceFrames() { - for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { - cv[i_cv]->calc_value(); - } - for (size_t i_frame = 0; i_frame < ref_cv.size(); ++i_frame) { - for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { - colvarvalue ref_cv_value(ref_cv[i_frame][i_cv]); - colvarvalue current_cv_value(cv[i_cv]->value()); - if (current_cv_value.type() == colvarvalue::type_scalar) { - frame_element_distances[i_frame][i_cv] = 0.5 * cv[i_cv]->dist2_lgrad(cv[i_cv]->sup_coeff * (cvm::pow(current_cv_value.real_value, cv[i_cv]->sup_np)), ref_cv_value.real_value); - } else { - frame_element_distances[i_frame][i_cv] = 0.5 * cv[i_cv]->dist2_lgrad(cv[i_cv]->sup_coeff * current_cv_value, ref_cv_value); + // compute the gradient (grad) with respect to the i-th CV + colvarvalue grad(cv[i_cv]->value().type()); + for (size_t m_frame = 0; m_frame < impl_->dsdx.size(); ++m_frame) { + // dsdx is the derivative of s with respect to the m-th frame + grad += impl_->dsdx[m_frame][i_cv]; + } + grad *= factor_polynomial; + cv[i_cv]->apply_force(force.real_value * grad); + // try my best to debug gradients even if the sub-CVs do not have explicit gradients + if (is_enabled(f_cvc_debug_gradient)) { + cvm::log("Debugging gradients for " + description + + " with respect to sub-CV " + cv[i_cv]->description + + ", which has no explicit gradient with respect to its own input(s)"); + colvarvalue analytical_grad(cv[i_cv]->value().type()); + for (size_t m_frame = 0; m_frame < impl_->dsdx.size(); ++m_frame) { + analytical_grad += impl_->compute_s_analytical_derivative_ij( + m_frame, i_cv, cvm::debug_gradients_step_size, this); + } + cvm::log("dx(actual) = "+cvm::to_str(analytical_grad, 21, 14)+"\n"); + cvm::log("dx(interp) = "+cvm::to_str(grad, 21, 14)+"\n"); + cvm::log("|dx(actual) - dx(interp)|/|dx(actual)| = "+ + cvm::to_str((analytical_grad - grad).norm() / + (analytical_grad).norm(), 12, 5)+"\n"); } } } } +colvar::azpathCV::azpathCV() +{ + set_function_type("azpathCV"); + x.type(colvarvalue::type_scalar); +} + +int colvar::azpathCV::init(std::string const &conf) +{ + int error_code = CVBasedPath::init(conf); + if (error_code != COLVARS_OK) return error_code; + cvm::log(std::string("Total number of frames: ") + cvm::to_str(total_reference_frames) + std::string("\n")); + std::vector p_weights(cv.size(), 1.0); + get_keyval(conf, "weights", p_weights, std::vector(cv.size(), 1.0)); + use_explicit_gradients = true; + cvm::real p_lambda; + get_keyval(conf, "lambda", p_lambda, -1.0); + if (impl_) impl_.reset(); + impl_ = std::unique_ptr(new ArithmeticPathImpl(cv.size(), total_reference_frames, p_lambda, p_weights)); + cvm::log(std::string("Lambda is ") + cvm::to_str(impl_->get_lambda()) + std::string("\n")); + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + if (!cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + use_explicit_gradients = false; + } + cvm::log(std::string("The weight of CV ") + cvm::to_str(i_cv) + std::string(" is ") + cvm::to_str(p_weights[i_cv]) + std::string("\n")); + } + return error_code; +} + void colvar::azpathCV::calc_value() { - if (lambda < 0) { + if (impl_->get_lambda() < 0) { // this implies that the user may not set a valid lambda value // so recompute it by the suggested value in Parrinello's paper cvm::log("A non-positive value of lambda is detected, which implies that it may not set in the configuration.\n"); cvm::log("This component (azpathCV) will recompute a value for lambda following the suggestion in the origin paper.\n"); std::vector rmsd_between_refs(total_reference_frames - 1, 0.0); computeDistanceBetweenReferenceFrames(rmsd_between_refs); - reComputeLambda(rmsd_between_refs); - cvm::log("Ok, the value of lambda is updated to " + cvm::to_str(lambda)); + impl_->reComputeLambda(rmsd_between_refs); + cvm::log("Ok, the value of lambda is updated to " + cvm::to_str(impl_->get_lambda())); } - computeValue(); - x = z; + impl_->updateCVDistanceToReferenceFrames(this); + x = impl_->compute_z(); } void colvar::azpathCV::calc_gradients() { - computeDerivatives(); + impl_->compute_z_derivatives(); for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { cv[i_cv]->calc_gradients(); if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + // compute the gradient (grad) with respect to the i-th CV + colvarvalue grad(cv[i_cv]->value().type()); + // sum up derivatives with respect to all frames + for (size_t m_frame = 0; m_frame < impl_->dzdx.size(); ++m_frame) { + // dzdx is the derivative of z with respect to the m-th frame + grad += impl_->dzdx[m_frame][i_cv]; + } for (size_t j_elem = 0; j_elem < cv[i_cv]->value().size(); ++j_elem) { for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { for (size_t l_atom = 0; l_atom < (cv[i_cv]->atom_groups)[k_ag]->size(); ++l_atom) { - (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad = dzdx[i_cv][j_elem] * factor_polynomial * (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad; + (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad = grad[j_elem] * factor_polynomial * (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad; } } } - } } } void colvar::azpathCV::apply_force(colvarvalue const &force) { + // the PCV component itself is a scalar, so force should be scalar for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { (cv[i_cv]->atom_groups)[k_ag]->apply_colvar_force(force.real_value); } } else { - cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); - const colvarvalue cv_force = dzdx[i_cv] * force.real_value * factor_polynomial; - cv[i_cv]->apply_force(cv_force); + const cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + // compute the gradient (grad) with respect to the i-th CV + colvarvalue grad(cv[i_cv]->value().type()); + for (size_t m_frame = 0; m_frame < impl_->dzdx.size(); ++m_frame) { + // dzdx is the derivative of z with respect to the m-th frame + grad += impl_->dzdx[m_frame][i_cv]; + } + grad *= factor_polynomial; + cv[i_cv]->apply_force(force.real_value * grad); + // try my best to debug gradients even if the sub-CVs do not have explicit gradients + if (is_enabled(f_cvc_debug_gradient)) { + cvm::log("Debugging gradients for " + description + + " with respect to sub-CV " + cv[i_cv]->description + + ", which has no explicit gradient with respect to its own input(s)"); + colvarvalue analytical_grad(cv[i_cv]->value().type()); + for (size_t m_frame = 0; m_frame < impl_->dzdx.size(); ++m_frame) { + analytical_grad += impl_->compute_z_analytical_derivative_ij( + m_frame, i_cv, cvm::debug_gradients_step_size, this); + } + cvm::log("dx(actual) = "+cvm::to_str(analytical_grad, 21, 14)+"\n"); + cvm::log("dx(interp) = "+cvm::to_str(grad, 21, 14)+"\n"); + cvm::log("|dx(actual) - dx(interp)|/|dx(actual)| = "+ + cvm::to_str((analytical_grad - grad).norm() / + (analytical_grad).norm(), 12, 5)+"\n"); + } } } } colvar::azpathCV::~azpathCV() {} -#endif diff --git a/lib/colvars/colvarcomp_combination.cpp b/lib/colvars/colvarcomp_combination.cpp index 64745b6472..01c7ffc320 100644 --- a/lib/colvars/colvarcomp_combination.cpp +++ b/lib/colvars/colvarcomp_combination.cpp @@ -1,4 +1,4 @@ -#if (__cplusplus >= 201103L) +// -*- c++ -*- // This file is part of the Collective Variables module (Colvars). // The original version of Colvars and its updates are located at: @@ -9,14 +9,26 @@ #include "colvarcomp.h" -colvar::linearCombination::linearCombination(std::string const &conf): cvc(conf) { + +colvar::linearCombination::linearCombination() +{ + set_function_type("linearCombination"); +} + + +int colvar::linearCombination::init(std::string const &conf) +{ + int error_code = cvc::init(conf); + if (error_code != COLVARS_OK) return error_code; + // Lookup all available sub-cvcs for (auto it_cv_map = colvar::get_global_cvc_map().begin(); it_cv_map != colvar::get_global_cvc_map().end(); ++it_cv_map) { if (key_lookup(conf, it_cv_map->first.c_str())) { std::vector sub_cvc_confs; get_key_string_multi_value(conf, it_cv_map->first.c_str(), sub_cvc_confs); for (auto it_sub_cvc_conf = sub_cvc_confs.begin(); it_sub_cvc_conf != sub_cvc_confs.end(); ++it_sub_cvc_conf) { - cv.push_back((it_cv_map->second)(*(it_sub_cvc_conf))); + cv.push_back((it_cv_map->second)()); + cv.back()->init(*(it_sub_cvc_conf)); } } } @@ -29,26 +41,11 @@ colvar::linearCombination::linearCombination(std::string const &conf): cvc(conf) } // Show useful error messages and prevent crashes if no sub CVC is found if (cv.size() == 0) { - cvm::error("Error: the CV " + name + - " expects one or more nesting components.\n"); - return; + return cvm::error("Error: the CV " + name + " expects one or more nesting components.\n", + COLVARS_INPUT_ERROR); } else { - // TODO: Maybe we can add an option to allow mixing scalar and vector types, - // but that's a bit complicated so we just require a consistent type - // of nesting CVs. x.type(cv[0]->value()); x.reset(); - for (size_t i_cv = 1; i_cv < cv.size(); ++i_cv) { - const auto type_i = cv[i_cv]->value().type(); - if (type_i != x.type()) { - cvm::error("Error: the type of sub-CVC " + cv[i_cv]->name + - " is " + colvarvalue::type_desc(type_i) + ", which is " - "different to the type of the first sub-CVC. Currently " - "only sub-CVCs of the same type are supported to be " - "nested.\n"); - return; - } - } } use_explicit_gradients = true; for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { @@ -59,6 +56,7 @@ colvar::linearCombination::linearCombination(std::string const &conf): cvc(conf) if (!use_explicit_gradients) { disable(f_cvc_explicit_gradient); } + return error_code; } cvm::real colvar::linearCombination::getPolynomialFactorOfCVGradient(size_t i_cv) const { @@ -138,8 +136,42 @@ void colvar::linearCombination::apply_force(colvarvalue const &force) { } } -colvar::customColvar::customColvar(std::string const &conf): linearCombination(conf) { - use_custom_function = false; + +cvm::real colvar::linearCombination::dist2(colvarvalue const &x1, colvarvalue const &x2) const +{ + return x1.dist2(x2); +} + + +colvarvalue colvar::linearCombination::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x1.dist2_grad(x2); +} + + +colvarvalue colvar::linearCombination::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x2.dist2_grad(x1); +} + + +void colvar::linearCombination::wrap(colvarvalue & /* x_unwrapped */) const {} + + + +colvar::customColvar::customColvar() +{ + set_function_type("customColvar"); +} + + +int colvar::customColvar::init(std::string const &conf) +{ + int error_code = linearCombination::init(conf); + if (error_code != COLVARS_OK) return error_code; + // code swipe from colvar::init_custom_function std::string expr_in, expr; size_t pos = 0; // current position in config string @@ -160,7 +192,7 @@ colvar::customColvar::customColvar(std::string const &conf): linearCombination(c pexpr = Lepton::Parser::parse(expr); pexprs.push_back(pexpr); } catch (...) { - cvm::error("Error parsing expression \"" + expr + "\".\n", COLVARS_INPUT_ERROR); + return cvm::error("Error parsing expression \"" + expr + "\".\n", COLVARS_INPUT_ERROR); } try { value_evaluators.push_back(new Lepton::CompiledExpression(pexpr.createCompiledExpression())); @@ -178,7 +210,7 @@ colvar::customColvar::customColvar(std::string const &conf): linearCombination(c } } } catch (...) { - cvm::error("Error compiling expression \"" + expr + "\".\n", COLVARS_INPUT_ERROR); + return cvm::error("Error compiling expression \"" + expr + "\".\n", COLVARS_INPUT_ERROR); } } while (key_lookup(conf, "customFunction", &expr_in, &pos)); // Now define derivative with respect to each scalar sub-component @@ -203,7 +235,7 @@ colvar::customColvar::customColvar(std::string const &conf): linearCombination(c } } if (value_evaluators.size() == 0) { - cvm::error("Error: no custom function defined.\n", COLVARS_INPUT_ERROR); + return cvm::error("Error: no custom function defined.\n", COLVARS_INPUT_ERROR); } if (value_evaluators.size() != 1) { x.type(colvarvalue::type_vector); @@ -211,14 +243,17 @@ colvar::customColvar::customColvar(std::string const &conf): linearCombination(c x.type(colvarvalue::type_scalar); } #else - cvm::error("customFunction requires the Lepton library, but it is not enabled during compilation.\n" - "Please refer to the Compilation Notes section of the Colvars manual for more information.\n", - COLVARS_INPUT_ERROR); + return cvm::error( + "customFunction requires the Lepton library, but it is not enabled during compilation.\n" + "Please refer to the Compilation Notes section of the Colvars manual for more " + "information.\n", + COLVARS_NOT_IMPLEMENTED); #endif } else { cvm::log("Warning: no customFunction specified.\n"); cvm::log("Warning: use linear combination instead.\n"); } + return error_code; } colvar::customColvar::~customColvar() { @@ -317,7 +352,8 @@ void colvar::customColvar::apply_force(colvarvalue const &force) { } } else { const colvarvalue& current_cv_value = cv[i_cv]->value(); - colvarvalue cv_force(current_cv_value.type()); + colvarvalue cv_force(current_cv_value); + cv_force.reset(); const cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); for (size_t j_elem = 0; j_elem < current_cv_value.size(); ++j_elem) { for (size_t c = 0; c < x.size(); ++c) { @@ -340,5 +376,3 @@ void colvar::customColvar::apply_force(colvarvalue const &force) { #endif } } - -#endif // __cplusplus >= 201103L diff --git a/lib/colvars/colvarcomp_coordnums.cpp b/lib/colvars/colvarcomp_coordnums.cpp index 3d618ff805..63418050d0 100644 --- a/lib/colvars/colvarcomp_coordnums.cpp +++ b/lib/colvars/colvarcomp_coordnums.cpp @@ -8,7 +8,6 @@ // Colvars repository at GitHub. #include "colvarmodule.h" -#include "colvarparse.h" #include "colvaratoms.h" #include "colvarvalue.h" #include "colvar.h" @@ -90,48 +89,47 @@ cvm::real colvar::coordnum::switching_function(cvm::real const &r0, } -colvar::coordnum::coordnum(std::string const &conf) - : cvc(conf), b_anisotropic(false), pairlist(NULL) - +colvar::coordnum::coordnum() { set_function_type("coordNum"); x.type(colvarvalue::type_scalar); - colvarproxy *proxy = cvm::main()->proxy; + r0 = proxy->angstrom_to_internal(4.0); + r0_vec = cvm::rvector(proxy->angstrom_to_internal(4.0), + proxy->angstrom_to_internal(4.0), + proxy->angstrom_to_internal(4.0)); +} + + +int colvar::coordnum::init(std::string const &conf) +{ + int error_code = cvc::init(conf); group1 = parse_group(conf, "group1"); group2 = parse_group(conf, "group2"); - if (group1 == NULL || group2 == NULL) { - cvm::error("Error: failed to initialize atom groups.\n", - COLVARS_INPUT_ERROR); - return; + if (!group1 || !group2) { + return error_code | COLVARS_INPUT_ERROR; } if (int atom_number = cvm::atom_group::overlap(*group1, *group2)) { - cvm::error("Error: group1 and group2 share a common atom (number: " + - cvm::to_str(atom_number) + ")\n", COLVARS_INPUT_ERROR); - return; + error_code |= cvm::error( + "Error: group1 and group2 share a common atom (number: " + cvm::to_str(atom_number) + ")\n", + COLVARS_INPUT_ERROR); } if (group1->b_dummy) { - cvm::error("Error: only group2 is allowed to be a dummy atom\n", - COLVARS_INPUT_ERROR); - return; + error_code |= + cvm::error("Error: only group2 is allowed to be a dummy atom\n", COLVARS_INPUT_ERROR); } - bool const b_isotropic = get_keyval(conf, "cutoff", r0, - cvm::real(proxy->angstrom_to_internal(4.0))); + bool const b_isotropic = get_keyval(conf, "cutoff", r0, r0); - if (get_keyval(conf, "cutoff3", r0_vec, - cvm::rvector(proxy->angstrom_to_internal(4.0), - proxy->angstrom_to_internal(4.0), - proxy->angstrom_to_internal(4.0)))) { + if (get_keyval(conf, "cutoff3", r0_vec, r0_vec)) { if (b_isotropic) { - cvm::error("Error: cannot specify \"cutoff\" and \"cutoff3\" " - "at the same time.\n", - COLVARS_INPUT_ERROR); - return; + error_code |= cvm::error("Error: cannot specify \"cutoff\" and \"cutoff3\" " + "at the same time.\n", + COLVARS_INPUT_ERROR); } b_anisotropic = true; @@ -141,17 +139,17 @@ colvar::coordnum::coordnum(std::string const &conf) if (r0_vec.z < 0.0) r0_vec.z *= -1.0; } - get_keyval(conf, "expNumer", en, 6); - get_keyval(conf, "expDenom", ed, 12); + get_keyval(conf, "expNumer", en, en); + get_keyval(conf, "expDenom", ed, ed); if ( (en%2) || (ed%2) ) { - cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", - COLVARS_INPUT_ERROR); + error_code |= cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", + COLVARS_INPUT_ERROR); } if ( (en <= 0) || (ed <= 0) ) { - cvm::error("Error: negative exponent(s) provided.\n", - COLVARS_INPUT_ERROR); + error_code |= cvm::error("Error: negative exponent(s) provided.\n", + COLVARS_INPUT_ERROR); } if (!is_enabled(f_cvc_pbc_minimum_image)) { @@ -160,14 +158,14 @@ colvar::coordnum::coordnum(std::string const &conf) get_keyval(conf, "group2CenterOnly", b_group2_center_only, group2->b_dummy); - get_keyval(conf, "tolerance", tolerance, 0.0); + get_keyval(conf, "tolerance", tolerance, tolerance); if (tolerance > 0) { cvm::main()->cite_feature("coordNum pairlist"); - get_keyval(conf, "pairListFrequency", pairlist_freq, 100); + get_keyval(conf, "pairListFrequency", pairlist_freq, pairlist_freq); if ( ! (pairlist_freq > 0) ) { - cvm::error("Error: non-positive pairlistfrequency provided.\n", - COLVARS_INPUT_ERROR); - return; // and do not allocate the pairlists below + return cvm::error("Error: non-positive pairlistfrequency provided.\n", + COLVARS_INPUT_ERROR); + // return and do not allocate the pairlists below } if (b_group2_center_only) { pairlist = new bool[group1->size()]; @@ -181,12 +179,14 @@ colvar::coordnum::coordnum(std::string const &conf) static_cast(group1->size()) : static_cast(group1->size() * group2->size())); + + return error_code; } colvar::coordnum::~coordnum() { - if (pairlist != NULL) { + if (pairlist) { delete [] pairlist; } } @@ -285,25 +285,23 @@ void colvar::coordnum::calc_gradients() } -void colvar::coordnum::apply_force(colvarvalue const &force) -{ - if (!group1->noforce) - group1->apply_colvar_force(force.real_value); - - if (!group2->noforce) - group2->apply_colvar_force(force.real_value); -} - - -simple_scalar_dist_functions(coordnum) - - // h_bond member functions -colvar::h_bond::h_bond(std::string const &conf) -: cvc(conf) +colvar::h_bond::h_bond() { + colvarproxy *proxy = cvm::main()->proxy; + r0 = proxy->angstrom_to_internal(3.3); + set_function_type("hBond"); + x.type(colvarvalue::type_scalar); + init_scalar_boundaries(0.0, 1.0); +} + + +int colvar::h_bond::init(std::string const &conf) +{ + int error_code = cvc::init(conf); + if (cvm::debug()) cvm::log("Initializing h_bond object.\n"); @@ -311,15 +309,12 @@ colvar::h_bond::h_bond(std::string const &conf) x.type(colvarvalue::type_scalar); init_scalar_boundaries(0.0, 1.0); - colvarproxy *proxy = cvm::main()->proxy; - int a_num = -1, d_num = -1; get_keyval(conf, "acceptor", a_num, a_num); get_keyval(conf, "donor", d_num, a_num); if ( (a_num == -1) || (d_num == -1) ) { - cvm::error("Error: either acceptor or donor undefined.\n"); - return; + error_code |= cvm::error("Error: either acceptor or donor undefined.\n", COLVARS_INPUT_ERROR); } cvm::atom acceptor = cvm::atom(a_num); @@ -328,34 +323,34 @@ colvar::h_bond::h_bond(std::string const &conf) atom_groups[0]->add_atom(acceptor); atom_groups[0]->add_atom(donor); - get_keyval(conf, "cutoff", r0, proxy->angstrom_to_internal(3.3)); - get_keyval(conf, "expNumer", en, 6); - get_keyval(conf, "expDenom", ed, 8); + get_keyval(conf, "cutoff", r0, r0); + get_keyval(conf, "expNumer", en, en); + get_keyval(conf, "expDenom", ed, ed); - if ( (en%2) || (ed%2) ) { - cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", - COLVARS_INPUT_ERROR); + if ((en % 2) || (ed % 2)) { + error_code |= cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", + COLVARS_INPUT_ERROR); } - if ( (en <= 0) || (ed <= 0) ) { - cvm::error("Error: negative exponent(s) provided.\n", - COLVARS_INPUT_ERROR); + if ((en <= 0) || (ed <= 0)) { + error_code |= cvm::error("Error: negative exponent(s) provided.\n", COLVARS_INPUT_ERROR); } if (cvm::debug()) cvm::log("Done initializing h_bond object.\n"); + + return error_code; } colvar::h_bond::h_bond(cvm::atom const &acceptor, cvm::atom const &donor, cvm::real r0_i, int en_i, int ed_i) - : r0(r0_i), en(en_i), ed(ed_i) + : h_bond() { - set_function_type("hBond"); - x.type(colvarvalue::type_scalar); - init_scalar_boundaries(0.0, 1.0); - + r0 = r0_i; + en = en_i; + ed = ed_i; register_atom_group(new cvm::atom_group); atom_groups[0]->add_atom(acceptor); atom_groups[0]->add_atom(donor); @@ -385,64 +380,63 @@ void colvar::h_bond::calc_gradients() } -void colvar::h_bond::apply_force(colvarvalue const &force) -{ - (atom_groups[0])->apply_colvar_force(force); -} - -simple_scalar_dist_functions(h_bond) - - - -colvar::selfcoordnum::selfcoordnum(std::string const &conf) - : cvc(conf), pairlist(NULL) +colvar::selfcoordnum::selfcoordnum() { set_function_type("selfCoordNum"); x.type(colvarvalue::type_scalar); + r0 = cvm::main()->proxy->angstrom_to_internal(4.0); +} - colvarproxy *proxy = cvm::main()->proxy; + +int colvar::selfcoordnum::init(std::string const &conf) +{ + int error_code = cvc::init(conf); group1 = parse_group(conf, "group1"); - get_keyval(conf, "cutoff", r0, cvm::real(proxy->angstrom_to_internal(4.0))); - get_keyval(conf, "expNumer", en, 6); - get_keyval(conf, "expDenom", ed, 12); - - - if ( (en%2) || (ed%2) ) { - cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", - COLVARS_INPUT_ERROR); + if (!group1 || group1->size() == 0) { + return error_code | COLVARS_INPUT_ERROR; } - if ( (en <= 0) || (ed <= 0) ) { - cvm::error("Error: negative exponent(s) provided.\n", - COLVARS_INPUT_ERROR); + get_keyval(conf, "cutoff", r0, r0); + get_keyval(conf, "expNumer", en, en); + get_keyval(conf, "expDenom", ed, ed); + + + if ((en % 2) || (ed % 2)) { + error_code |= cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", + COLVARS_INPUT_ERROR); + } + + if ((en <= 0) || (ed <= 0)) { + error_code |= cvm::error("Error: negative exponent(s) provided.\n", COLVARS_INPUT_ERROR); } if (!is_enabled(f_cvc_pbc_minimum_image)) { cvm::log("Warning: only minimum-image distances are used by this variable.\n"); } - get_keyval(conf, "tolerance", tolerance, 0.0); + get_keyval(conf, "tolerance", tolerance, tolerance); if (tolerance > 0) { - get_keyval(conf, "pairListFrequency", pairlist_freq, 100); + get_keyval(conf, "pairListFrequency", pairlist_freq, pairlist_freq); if ( ! (pairlist_freq > 0) ) { - cvm::error("Error: non-positive pairlistfrequency provided.\n", - COLVARS_INPUT_ERROR); - return; + error_code |= cvm::error("Error: non-positive pairlistfrequency provided.\n", + COLVARS_INPUT_ERROR); } pairlist = new bool[(group1->size()-1) * (group1->size()-1)]; } init_scalar_boundaries(0.0, static_cast((group1->size()-1) * (group1->size()-1))); + + return error_code; } colvar::selfcoordnum::~selfcoordnum() { - if (pairlist != NULL) { + if (pairlist) { delete [] pairlist; } } @@ -527,44 +521,36 @@ void colvar::selfcoordnum::calc_gradients() } -void colvar::selfcoordnum::apply_force(colvarvalue const &force) -{ - if (!group1->noforce) { - group1->apply_colvar_force(force.real_value); - } -} - -simple_scalar_dist_functions(selfcoordnum) - - - -// groupcoordnum member functions -colvar::groupcoordnum::groupcoordnum(std::string const &conf) - : distance(conf), b_anisotropic(false) +colvar::groupcoordnum::groupcoordnum() { set_function_type("groupCoord"); x.type(colvarvalue::type_scalar); init_scalar_boundaries(0.0, 1.0); - colvarproxy *proxy = cvm::main()->proxy; + r0 = proxy->angstrom_to_internal(4.0); + r0_vec = cvm::rvector(proxy->angstrom_to_internal(4.0), + proxy->angstrom_to_internal(4.0), + proxy->angstrom_to_internal(4.0)); +} + + +int colvar::groupcoordnum::init(std::string const &conf) +{ + int error_code = distance::init(conf); // group1 and group2 are already initialized by distance() if (group1->b_dummy || group2->b_dummy) { - cvm::error("Error: neither group can be a dummy atom\n"); - return; + return cvm::error("Error: neither group can be a dummy atom\n", COLVARS_INPUT_ERROR); } - bool const b_scale = get_keyval(conf, "cutoff", r0, - cvm::real(proxy->angstrom_to_internal(4.0))); - - if (get_keyval(conf, "cutoff3", r0_vec, - cvm::rvector(4.0, 4.0, 4.0), parse_silent)) { + bool const b_scale = get_keyval(conf, "cutoff", r0, r0); + if (get_keyval(conf, "cutoff3", r0_vec, r0_vec)) { if (b_scale) { - cvm::error("Error: cannot specify \"scale\" and " - "\"scale3\" at the same time.\n"); - return; + error_code |= + cvm::error("Error: cannot specify \"cutoff\" and \"cutoff3\" at the same time.\n", + COLVARS_INPUT_ERROR); } b_anisotropic = true; // remove meaningless negative signs @@ -573,23 +559,23 @@ colvar::groupcoordnum::groupcoordnum(std::string const &conf) if (r0_vec.z < 0.0) r0_vec.z *= -1.0; } - get_keyval(conf, "expNumer", en, 6); - get_keyval(conf, "expDenom", ed, 12); + get_keyval(conf, "expNumer", en, en); + get_keyval(conf, "expDenom", ed, ed); - if ( (en%2) || (ed%2) ) { - cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", - COLVARS_INPUT_ERROR); + if ((en % 2) || (ed % 2)) { + error_code |= cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", + COLVARS_INPUT_ERROR); } - if ( (en <= 0) || (ed <= 0) ) { - cvm::error("Error: negative exponent(s) provided.\n", - COLVARS_INPUT_ERROR); + if ((en <= 0) || (ed <= 0)) { + error_code |= cvm::error("Error: negative exponent(s) provided.\n", COLVARS_INPUT_ERROR); } if (!is_enabled(f_cvc_pbc_minimum_image)) { cvm::log("Warning: only minimum-image distances are used by this variable.\n"); } + return error_code; } @@ -640,16 +626,3 @@ void colvar::groupcoordnum::calc_gradients() group1->set_weighted_gradient(group1_com_atom.grad); group2->set_weighted_gradient(group2_com_atom.grad); } - - -void colvar::groupcoordnum::apply_force(colvarvalue const &force) -{ - if (!group1->noforce) - group1->apply_colvar_force(force.real_value); - - if (!group2->noforce) - group2->apply_colvar_force(force.real_value); -} - - -simple_scalar_dist_functions(groupcoordnum) diff --git a/lib/colvars/colvarcomp_distances.cpp b/lib/colvars/colvarcomp_distances.cpp index d96cb33482..319190c385 100644 --- a/lib/colvars/colvarcomp_distances.cpp +++ b/lib/colvars/colvarcomp_distances.cpp @@ -11,14 +11,12 @@ #include "colvarmodule.h" #include "colvarvalue.h" -#include "colvarparse.h" #include "colvar.h" #include "colvarcomp.h" +#include "colvar_rotation_derivative.h" - -colvar::distance::distance(std::string const &conf) - : cvc(conf) +colvar::distance::distance() { set_function_type("distance"); init_as_distance(); @@ -26,23 +24,23 @@ colvar::distance::distance(std::string const &conf) provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); enable(f_cvc_com_based); +} + + +int colvar::distance::init(std::string const &conf) +{ + int error_code = cvc::init(conf); group1 = parse_group(conf, "group1"); group2 = parse_group(conf, "group2"); - init_total_force_params(conf); -} + if (!group1 || !group2) { + return error_code | COLVARS_INPUT_ERROR; + } + error_code |= init_total_force_params(conf); -colvar::distance::distance() - : cvc() -{ - set_function_type("distance"); - init_as_distance(); - - provide(f_cvc_inv_gradient); - provide(f_cvc_Jacobian); - enable(f_cvc_com_based); + return error_code; } @@ -84,35 +82,10 @@ void colvar::distance::calc_Jacobian_derivative() } -void colvar::distance::apply_force(colvarvalue const &force) -{ - if (!group1->noforce) - group1->apply_colvar_force(force.real_value); - - if (!group2->noforce) - group2->apply_colvar_force(force.real_value); -} - - -simple_scalar_dist_functions(distance) - - - -colvar::distance_vec::distance_vec(std::string const &conf) - : distance(conf) -{ - set_function_type("distanceVec"); - enable(f_cvc_com_based); - disable(f_cvc_explicit_gradient); - x.type(colvarvalue::type_3vector); -} - colvar::distance_vec::distance_vec() - : distance() { set_function_type("distanceVec"); - enable(f_cvc_com_based); disable(f_cvc_explicit_gradient); x.type(colvarvalue::type_3vector); } @@ -146,48 +119,47 @@ void colvar::distance_vec::apply_force(colvarvalue const &force) } -cvm::real colvar::distance_vec::dist2(colvarvalue const &x1, - colvarvalue const &x2) const +cvm::real colvar::distance_vec::dist2(colvarvalue const &x1, colvarvalue const &x2) const { - return (cvm::position_distance(x1.rvector_value, x2.rvector_value)).norm2(); + if (is_enabled(f_cvc_pbc_minimum_image)) { + return (cvm::position_distance(x1.rvector_value, x2.rvector_value)).norm2(); + } + return (x2.rvector_value - x1.rvector_value).norm2(); } -colvarvalue colvar::distance_vec::dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const +colvarvalue colvar::distance_vec::dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const { - return 2.0 * cvm::position_distance(x2.rvector_value, x1.rvector_value); + if (is_enabled(f_cvc_pbc_minimum_image)) { + return 2.0 * cvm::position_distance(x2.rvector_value, x1.rvector_value); + } + return 2.0 * (x2.rvector_value - x1.rvector_value); } -colvarvalue colvar::distance_vec::dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const +colvarvalue colvar::distance_vec::dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const { - return 2.0 * cvm::position_distance(x2.rvector_value, x1.rvector_value); + return distance_vec::dist2_lgrad(x2, x1); } +void colvar::distance_vec::wrap(colvarvalue & /* x_unwrapped */) const {} -colvar::distance_z::distance_z(std::string const &conf) - : cvc(conf) + +colvar::distance_z::distance_z() { set_function_type("distanceZ"); provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); enable(f_cvc_com_based); + provide(f_cvc_periodic); x.type(colvarvalue::type_scalar); +} - // TODO detect PBC from MD engine (in simple cases) - // and then update period in real time - if (period != 0.0) { - enable(f_cvc_periodic); - } - if ((wrap_center != 0.0) && !is_enabled(f_cvc_periodic)) { - cvm::error("Error: wrapAround was defined in a distanceZ component," - " but its period has not been set.\n"); - return; - } +int colvar::distance_z::init(std::string const &conf) +{ + int error_code = cvc::init(conf); main = parse_group(conf, "main"); ref1 = parse_group(conf, "ref"); @@ -202,8 +174,7 @@ colvar::distance_z::distance_z(std::string const &conf) } else { if (get_keyval(conf, "axis", axis, cvm::rvector(0.0, 0.0, 1.0))) { if (axis.norm2() == 0.0) { - cvm::error("Axis vector is zero!"); - return; + error_code |= cvm::error("Axis vector is zero!", COLVARS_INPUT_ERROR); } if (axis.norm2() != 1.0) { axis = axis.unit(); @@ -213,18 +184,9 @@ colvar::distance_z::distance_z(std::string const &conf) fixed_axis = true; } - init_total_force_params(conf); + error_code |= init_total_force_params(conf); -} - - -colvar::distance_z::distance_z() -{ - set_function_type("distanceZ"); - provide(f_cvc_inv_gradient); - provide(f_cvc_Jacobian); - enable(f_cvc_com_based); - x.type(colvarvalue::type_scalar); + return error_code; } @@ -254,7 +216,7 @@ void colvar::distance_z::calc_value() axis = axis.unit(); } x.real_value = axis * dist_v; - this->wrap(x); + wrap(x); } @@ -303,90 +265,12 @@ void colvar::distance_z::calc_Jacobian_derivative() } -void colvar::distance_z::apply_force(colvarvalue const &force) -{ - if (!ref1->noforce) - ref1->apply_colvar_force(force.real_value); - - if (ref2 && !ref2->noforce) - ref2->apply_colvar_force(force.real_value); - - if (!main->noforce) - main->apply_colvar_force(force.real_value); -} - - -// Differences should always be wrapped around 0 (ignoring wrap_center) -cvm::real colvar::distance_z::dist2(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - if (is_enabled(f_cvc_periodic)) { - cvm::real shift = cvm::floor(diff/period + 0.5); - diff -= shift * period; - } - return diff * diff; -} - - -colvarvalue colvar::distance_z::dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - if (is_enabled(f_cvc_periodic)) { - cvm::real shift = cvm::floor(diff/period + 0.5); - diff -= shift * period; - } - return 2.0 * diff; -} - - -colvarvalue colvar::distance_z::dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - if (is_enabled(f_cvc_periodic)) { - cvm::real shift = cvm::floor(diff/period + 0.5); - diff -= shift * period; - } - return (-2.0) * diff; -} - - -void colvar::distance_z::wrap(colvarvalue &x_unwrapped) const -{ - if (!is_enabled(f_cvc_periodic)) { - // don't wrap if the period has not been set - return; - } - cvm::real shift = - cvm::floor((x_unwrapped.real_value - wrap_center) / period + 0.5); - x_unwrapped.real_value -= shift * period; -} - - - -colvar::distance_xy::distance_xy(std::string const &conf) - : distance_z(conf) -{ - set_function_type("distanceXY"); - init_as_distance(); - - provide(f_cvc_inv_gradient); - provide(f_cvc_Jacobian); - enable(f_cvc_com_based); -} - colvar::distance_xy::distance_xy() - : distance_z() { set_function_type("distanceXY"); + provide(f_cvc_periodic, false); // Disable inherited distance_z flag init_as_distance(); - - provide(f_cvc_inv_gradient); - provide(f_cvc_Jacobian); - enable(f_cvc_com_based); } @@ -462,35 +346,8 @@ void colvar::distance_xy::calc_Jacobian_derivative() } -void colvar::distance_xy::apply_force(colvarvalue const &force) -{ - if (!ref1->noforce) - ref1->apply_colvar_force(force.real_value); - - if (ref2 && !ref2->noforce) - ref2->apply_colvar_force(force.real_value); - - if (!main->noforce) - main->apply_colvar_force(force.real_value); -} - - -simple_scalar_dist_functions(distance_xy) - - - -colvar::distance_dir::distance_dir(std::string const &conf) - : distance(conf) -{ - set_function_type("distanceDir"); - enable(f_cvc_com_based); - disable(f_cvc_explicit_gradient); - x.type(colvarvalue::type_unit3vector); -} - colvar::distance_dir::distance_dir() - : distance() { set_function_type("distanceDir"); enable(f_cvc_com_based); @@ -556,31 +413,39 @@ colvarvalue colvar::distance_dir::dist2_rgrad(colvarvalue const &x1, } +void colvar::distance_dir::wrap(colvarvalue & /* x_unwrapped */) const {} -colvar::distance_inv::distance_inv(std::string const &conf) - : cvc(conf) + + +colvar::distance_inv::distance_inv() { set_function_type("distanceInv"); init_as_distance(); +} + + +int colvar::distance_inv::init(std::string const &conf) +{ + int error_code = cvc::init(conf); group1 = parse_group(conf, "group1"); group2 = parse_group(conf, "group2"); - get_keyval(conf, "exponent", exponent, 6); - if (exponent%2) { - cvm::error("Error: odd exponent provided, can only use even ones.\n"); - return; + get_keyval(conf, "exponent", exponent, exponent); + if (exponent % 2) { + error_code |= + cvm::error("Error: odd exponent provided, can only use even ones.\n", COLVARS_INPUT_ERROR); } if (exponent <= 0) { - cvm::error("Error: negative or zero exponent provided.\n"); - return; + error_code |= cvm::error("Error: negative or zero exponent provided.\n", COLVARS_INPUT_ERROR); } for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) { for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { if (ai1->id == ai2->id) { - cvm::error("Error: group1 and group2 have some atoms in common: this is not allowed for distanceInv.\n"); - return; + error_code |= cvm::error("Error: group1 and group2 have some atoms in common: this is not " + "allowed for distanceInv.\n", + COLVARS_INPUT_ERROR); } } } @@ -590,6 +455,8 @@ colvar::distance_inv::distance_inv(std::string const &conf) "for distanceInv, because its value and gradients are computed " "simultaneously.\n"); } + + return error_code; } @@ -642,33 +509,6 @@ void colvar::distance_inv::calc_gradients() } -void colvar::distance_inv::apply_force(colvarvalue const &force) -{ - if (!group1->noforce) - group1->apply_colvar_force(force.real_value); - - if (!group2->noforce) - group2->apply_colvar_force(force.real_value); -} - - -simple_scalar_dist_functions(distance_inv) - - - -colvar::distance_pairs::distance_pairs(std::string const &conf) - : cvc(conf) -{ - set_function_type("distancePairs"); - - group1 = parse_group(conf, "group1"); - group2 = parse_group(conf, "group2"); - - x.type(colvarvalue::type_vector); - disable(f_cvc_explicit_gradient); - x.vector1d_value.resize(group1->size() * group2->size()); -} - colvar::distance_pairs::distance_pairs() { @@ -678,6 +518,18 @@ colvar::distance_pairs::distance_pairs() } +int colvar::distance_pairs::init(std::string const &conf) +{ + int error_code = cvc::init(conf); + + group1 = parse_group(conf, "group1"); + group2 = parse_group(conf, "group2"); + x.vector1d_value.resize(group1->size() * group2->size()); + + return error_code; +} + + void colvar::distance_pairs::calc_value() { x.vector1d_value.resize(group1->size() * group2->size()); @@ -740,26 +592,28 @@ void colvar::distance_pairs::apply_force(colvarvalue const &force) } - -colvar::dipole_magnitude::dipole_magnitude(std::string const &conf) - : cvc(conf) +cvm::real colvar::distance_pairs::dist2(colvarvalue const &x1, colvarvalue const &x2) const { - set_function_type("dipoleMagnitude"); - atoms = parse_group(conf, "atoms"); - init_total_force_params(conf); - x.type(colvarvalue::type_scalar); + return (x1.vector1d_value - x2.vector1d_value).norm2(); } -colvar::dipole_magnitude::dipole_magnitude(cvm::atom const &a1) +colvarvalue colvar::distance_pairs::dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const { - set_function_type("dipoleMagnitude"); - atoms = new cvm::atom_group(std::vector(1, a1)); - register_atom_group(atoms); - x.type(colvarvalue::type_scalar); + return 2.0 * (x1.vector1d_value - x2.vector1d_value); } +colvarvalue colvar::distance_pairs::dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const +{ + return distance_pairs::dist2_lgrad(x1, x2); +} + + +void colvar::distance_pairs::wrap(colvarvalue & /* x_unwrapped */) const {} + + + colvar::dipole_magnitude::dipole_magnitude() { set_function_type("dipoleMagnitude"); @@ -767,6 +621,15 @@ colvar::dipole_magnitude::dipole_magnitude() } +int colvar::dipole_magnitude::init(std::string const &conf) +{ + int error_code = cvc::init(conf); + atoms = parse_group(conf, "atoms"); + if (!atoms) error_code |= COLVARS_INPUT_ERROR; + return error_code; +} + + void colvar::dipole_magnitude::calc_value() { cvm::atom_pos const atomsCom = atoms->center_of_mass(); @@ -787,26 +650,20 @@ void colvar::dipole_magnitude::calc_gradients() } -void colvar::dipole_magnitude::apply_force(colvarvalue const &force) + +colvar::gyration::gyration() { - if (!atoms->noforce) { - atoms->apply_colvar_force(force.real_value); - } + set_function_type("gyration"); + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); + init_as_distance(); } -simple_scalar_dist_functions(dipole_magnitude) - - - -colvar::gyration::gyration(std::string const &conf) - : cvc(conf) +int colvar::gyration::init(std::string const &conf) { - set_function_type("gyration"); - init_as_distance(); + int error_code = cvc::init(conf); - provide(f_cvc_inv_gradient); - provide(f_cvc_Jacobian); atoms = parse_group(conf, "atoms"); if (atoms->b_user_defined_fit) { @@ -816,6 +673,8 @@ colvar::gyration::gyration(std::string const &conf) atoms->ref_pos.assign(1, cvm::atom_pos(0.0, 0.0, 0.0)); atoms->fit_gradients.assign(atoms->size(), cvm::rvector(0.0, 0.0, 0.0)); } + + return error_code; } @@ -857,22 +716,10 @@ void colvar::gyration::calc_Jacobian_derivative() } -void colvar::gyration::apply_force(colvarvalue const &force) -{ - if (!atoms->noforce) - atoms->apply_colvar_force(force.real_value); -} - -simple_scalar_dist_functions(gyration) - - - -colvar::inertia::inertia(std::string const &conf) - : gyration(conf) +colvar::inertia::inertia() { set_function_type("inertia"); - init_as_distance(); } @@ -893,32 +740,26 @@ void colvar::inertia::calc_gradients() } -void colvar::inertia::apply_force(colvarvalue const &force) + +colvar::inertia_z::inertia_z() { - if (!atoms->noforce) - atoms->apply_colvar_force(force.real_value); + set_function_type("inertiaZ"); } -simple_scalar_dist_functions(inertia_z) - - - -colvar::inertia_z::inertia_z(std::string const &conf) - : inertia(conf) +int colvar::inertia_z::init(std::string const &conf) { - set_function_type("inertiaZ"); - init_as_distance(); + int error_code = inertia::init(conf); if (get_keyval(conf, "axis", axis, cvm::rvector(0.0, 0.0, 1.0))) { if (axis.norm2() == 0.0) { - cvm::error("Axis vector is zero!", COLVARS_INPUT_ERROR); - return; + error_code |= cvm::error("Axis vector is zero!", COLVARS_INPUT_ERROR); } if (axis.norm2() != 1.0) { axis = axis.unit(); cvm::log("The normalized axis is: "+cvm::to_str(axis)+".\n"); } } + return error_code; } @@ -940,31 +781,23 @@ void colvar::inertia_z::calc_gradients() } -void colvar::inertia_z::apply_force(colvarvalue const &force) -{ - if (!atoms->noforce) - atoms->apply_colvar_force(force.real_value); -} - -simple_scalar_dist_functions(inertia) - - - - -colvar::rmsd::rmsd(std::string const &conf) - : cvc(conf) +colvar::rmsd::rmsd() { set_function_type("rmsd"); init_as_distance(); - provide(f_cvc_inv_gradient); +} + + +int colvar::rmsd::init(std::string const &conf) +{ + int error_code = cvc::init(conf); atoms = parse_group(conf, "atoms"); if (!atoms || atoms->size() == 0) { - cvm::error("Error: \"atoms\" must contain at least 1 atom to compute RMSD."); - return; + return error_code | COLVARS_INPUT_ERROR; } bool b_Jacobian_derivative = true; @@ -981,20 +814,20 @@ colvar::rmsd::rmsd(std::string const &conf) if (get_keyval(conf, "refPositions", ref_pos, ref_pos)) { cvm::log("Using reference positions from configuration file to calculate the variable.\n"); if (ref_pos.size() != atoms->size()) { - cvm::error("Error: the number of reference positions provided ("+ - cvm::to_str(ref_pos.size())+ - ") does not match the number of atoms of group \"atoms\" ("+ - cvm::to_str(atoms->size())+").\n"); - return; + error_code |= cvm::error("Error: the number of reference positions provided (" + + cvm::to_str(ref_pos.size()) + + ") does not match the number of atoms of group \"atoms\" (" + + cvm::to_str(atoms->size()) + ").\n", + COLVARS_INPUT_ERROR); } } else { // Only look for ref pos file if ref positions not already provided std::string ref_pos_file; if (get_keyval(conf, "refPositionsFile", ref_pos_file, std::string(""))) { if (ref_pos.size()) { - cvm::error("Error: cannot specify \"refPositionsFile\" and " - "\"refPositions\" at the same time.\n"); - return; + error_code |= cvm::error("Error: cannot specify \"refPositionsFile\" and " + "\"refPositions\" at the same time.\n", + COLVARS_INPUT_ERROR); } std::string ref_pos_col; @@ -1004,9 +837,9 @@ colvar::rmsd::rmsd(std::string const &conf) // if provided, use PDB column to select coordinates bool found = get_keyval(conf, "refPositionsColValue", ref_pos_col_value, 0.0); if (found && ref_pos_col_value==0.0) { - cvm::error("Error: refPositionsColValue, " - "if provided, must be non-zero.\n"); - return; + error_code |= cvm::error("Error: refPositionsColValue, " + "if provided, must be non-zero.\n", + COLVARS_INPUT_ERROR); } } @@ -1015,15 +848,17 @@ colvar::rmsd::rmsd(std::string const &conf) cvm::load_coords(ref_pos_file.c_str(), &ref_pos, atoms, ref_pos_col, ref_pos_col_value); } else { - cvm::error("Error: no reference positions for RMSD; use either refPositions of refPositionsFile."); - return; + error_code |= cvm::error( + "Error: no reference positions for RMSD; use either refPositions of refPositionsFile.", + COLVARS_INPUT_ERROR); } } if (ref_pos.size() != atoms->size()) { - cvm::error("Error: found " + cvm::to_str(ref_pos.size()) + - " reference positions for RMSD; expected " + cvm::to_str(atoms->size())); - return; + error_code |= + cvm::error("Error: found " + cvm::to_str(ref_pos.size()) + + " reference positions for RMSD; expected " + cvm::to_str(atoms->size()), + COLVARS_INPUT_ERROR); } if (atoms->b_user_defined_fit) { @@ -1042,14 +877,18 @@ colvar::rmsd::rmsd(std::string const &conf) cvm::log("This is a standard minimum RMSD, derivatives of the optimal rotation " "will not be computed as they cancel out in the gradients."); atoms->disable(f_ag_fit_gradients); - - // request the calculation of the derivatives of the rotation defined by the atom group - atoms->rot.request_group1_gradients(atoms->size()); - // request derivatives of optimal rotation wrt reference coordinates for Jacobian: - // this is only required for ABF, but we do both groups here for better caching - atoms->rot.request_group2_gradients(atoms->size()); } + atoms->setup_rotation_derivative(); + error_code |= init_permutation(conf); + + return error_code; +} + + +int colvar::rmsd::init_permutation(std::string const &conf) +{ + int error_code = COLVARS_OK; std::string perm_conf; size_t pos = 0; // current position in config string n_permutations = 1; @@ -1064,21 +903,22 @@ colvar::rmsd::rmsd(std::string const &conf) std::vector const &ids = atoms->ids(); size_t const ia = std::find(ids.begin(), ids.end(), index-1) - ids.begin(); if (ia == atoms->size()) { - cvm::error("Error: atom id " + cvm::to_str(index) + - " is not a member of group \"atoms\"."); - return; + error_code |= cvm::error("Error: atom id " + cvm::to_str(index) + + " is not a member of group \"atoms\".", + COLVARS_INPUT_ERROR); } if (std::find(perm.begin(), perm.end(), ia) != perm.end()) { - cvm::error("Error: atom id " + cvm::to_str(index) + - " is mentioned more than once in atomPermutation list."); - return; + error_code |= cvm::error("Error: atom id " + cvm::to_str(index) + + " is mentioned more than once in atomPermutation list.", + COLVARS_INPUT_ERROR); } perm.push_back(ia); } if (perm.size() != atoms->size()) { - cvm::error("Error: symmetry permutation in input contains " + cvm::to_str(perm.size()) + - " indices, but group \"atoms\" contains " + cvm::to_str(atoms->size()) + " atoms."); - return; + error_code |= cvm::error( + "Error: symmetry permutation in input contains " + cvm::to_str(perm.size()) + + " indices, but group \"atoms\" contains " + cvm::to_str(atoms->size()) + " atoms.", + COLVARS_INPUT_ERROR); } cvm::log("atomPermutation = " + cvm::to_str(perm)); n_permutations++; @@ -1088,6 +928,8 @@ colvar::rmsd::rmsd(std::string const &conf) } } } + + return error_code; } @@ -1132,13 +974,6 @@ void colvar::rmsd::calc_gradients() } -void colvar::rmsd::apply_force(colvarvalue const &force) -{ - if (!atoms->noforce) - atoms->apply_colvar_force(force.real_value); -} - - void colvar::rmsd::calc_force_invgrads() { atoms->read_total_forces(); @@ -1165,10 +1000,12 @@ void colvar::rmsd::calc_Jacobian_derivative() cvm::matrix2d grad_rot_mat(3, 3); // gradients of products of 2 quaternion components cvm::rvector g11, g22, g33, g01, g02, g03, g12, g13, g23; + cvm::vector1d dq; + atoms->rot_deriv->prepare_derivative(rotation_derivative_dldq::use_dq); for (size_t ia = 0; ia < atoms->size(); ia++) { // Gradient of optimal quaternion wrt current Cartesian position - cvm::vector1d &dq = atoms->rot.dQ0_1[ia]; + atoms->rot_deriv->calc_derivative_wrt_group1(ia, nullptr, &dq); g11 = 2.0 * (atoms->rot.q)[1]*dq[1]; g22 = 2.0 * (atoms->rot.q)[2]*dq[2]; @@ -1213,19 +1050,24 @@ void colvar::rmsd::calc_Jacobian_derivative() } -simple_scalar_dist_functions(rmsd) - - -colvar::eigenvector::eigenvector(std::string const &conf) - : cvc(conf) +colvar::eigenvector::eigenvector() { set_function_type("eigenvector"); provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); x.type(colvarvalue::type_scalar); +} + + +int colvar::eigenvector::init(std::string const &conf) +{ + int error_code = cvc::init(conf); atoms = parse_group(conf, "atoms"); + if (!atoms || atoms->size() == 0) { + return error_code | COLVARS_INPUT_ERROR; + } { bool const b_inline = get_keyval(conf, "refPositions", ref_pos, ref_pos); @@ -1233,9 +1075,9 @@ colvar::eigenvector::eigenvector(std::string const &conf) if (b_inline) { cvm::log("Using reference positions from input file.\n"); if (ref_pos.size() != atoms->size()) { - cvm::error("Error: reference positions do not " - "match the number of requested atoms.\n"); - return; + error_code |= cvm::error("Error: reference positions do not " + "match the number of requested atoms.\n", + COLVARS_INPUT_ERROR); } } @@ -1243,19 +1085,18 @@ colvar::eigenvector::eigenvector(std::string const &conf) if (get_keyval(conf, "refPositionsFile", file_name)) { if (b_inline) { - cvm::error("Error: refPositions and refPositionsFile cannot be specified at the same time.\n"); - return; + error_code |= cvm::error( + "Error: refPositions and refPositionsFile cannot be specified at the same time.\n", + COLVARS_INPUT_ERROR); } - std::string file_col; double file_col_value=0.0; if (get_keyval(conf, "refPositionsCol", file_col, std::string(""))) { // use PDB flags if column is provided bool found = get_keyval(conf, "refPositionsColValue", file_col_value, 0.0); - if (found && file_col_value==0.0) { - cvm::error("Error: refPositionsColValue, " - "if provided, must be non-zero.\n"); - return; + if (found && file_col_value == 0.0) { + error_code |= cvm::error("Error: refPositionsColValue, if provided, must be non-zero.\n", + COLVARS_INPUT_ERROR); } } @@ -1266,14 +1107,14 @@ colvar::eigenvector::eigenvector(std::string const &conf) } if (ref_pos.size() == 0) { - cvm::error("Error: reference positions were not provided.\n", COLVARS_INPUT_ERROR); - return; + error_code |= + cvm::error("Error: reference positions were not provided.\n", COLVARS_INPUT_ERROR); } if (ref_pos.size() != atoms->size()) { - cvm::error("Error: reference positions do not " - "match the number of requested atoms.\n", COLVARS_INPUT_ERROR); - return; + error_code |= cvm::error("Error: reference positions do not " + "match the number of requested atoms.\n", + COLVARS_INPUT_ERROR); } // save for later the geometric center of the provided positions (may not be the origin) @@ -1295,13 +1136,8 @@ colvar::eigenvector::eigenvector(std::string const &conf) atoms->center_ref_pos(); atoms->disable(f_ag_fit_gradients); // cancel out if group is fitted on itself // and cvc is translationally invariant - - // request the calculation of the derivatives of the rotation defined by the atom group - atoms->rot.request_group1_gradients(atoms->size()); - // request derivatives of optimal rotation wrt reference coordinates for Jacobian: - // this is only required for ABF, but we do both groups here for better caching - atoms->rot.request_group2_gradients(atoms->size()); } + atoms->setup_rotation_derivative(); { bool const b_inline = get_keyval(conf, "vector", eigenvec, eigenvec); @@ -1309,9 +1145,8 @@ colvar::eigenvector::eigenvector(std::string const &conf) if (b_inline) { cvm::log("Using vector components from input file.\n"); if (eigenvec.size() != atoms->size()) { - cvm::error("Error: vector components do not " - "match the number of requested atoms->\n"); - return; + error_code |= cvm::error("Error: vector components do not " + "match the number of requested atoms->\n", COLVARS_INPUT_ERROR); } } @@ -1319,8 +1154,9 @@ colvar::eigenvector::eigenvector(std::string const &conf) if (get_keyval(conf, "vectorFile", file_name)) { if (b_inline) { - cvm::error("Error: vector and vectorFile cannot be specified at the same time.\n"); - return; + error_code |= + cvm::error("Error: vector and vectorFile cannot be specified at the same time.\n", + COLVARS_INPUT_ERROR); } std::string file_col; @@ -1329,8 +1165,8 @@ colvar::eigenvector::eigenvector(std::string const &conf) // use PDB flags if column is provided bool found = get_keyval(conf, "vectorColValue", file_col_value, 0.0); if (found && file_col_value==0.0) { - cvm::error("Error: vectorColValue, if provided, must be non-zero.\n"); - return; + error_code |= cvm::error("Error: vectorColValue, if provided, must be non-zero.\n", + COLVARS_INPUT_ERROR); } } @@ -1341,9 +1177,8 @@ colvar::eigenvector::eigenvector(std::string const &conf) } if (!ref_pos.size() || !eigenvec.size()) { - cvm::error("Error: both reference coordinates" - "and eigenvector must be defined.\n"); - return; + error_code |= cvm::error("Error: both reference coordinates and eigenvector must be defined.\n", + COLVARS_INPUT_ERROR); } cvm::atom_pos eig_center(0.0, 0.0, 0.0); @@ -1367,8 +1202,9 @@ colvar::eigenvector::eigenvector(std::string const &conf) } if (atoms->is_enabled(f_ag_rotate)) { atoms->rot.calc_optimal_rotation(eigenvec, ref_pos); + const auto rot_mat = atoms->rot.matrix(); for (size_t i = 0; i < atoms->size(); i++) { - eigenvec[i] = atoms->rot.rotate(eigenvec[i]); + eigenvec[i] = rot_mat * eigenvec[i]; } } cvm::log("\"differenceVector\" is on: subtracting the reference positions from the provided vector: v = x_vec - x_ref.\n"); @@ -1416,6 +1252,8 @@ colvar::eigenvector::eigenvector(std::string const &conf) cvm::log("The norm of the vector is |v| = "+ cvm::to_str(1.0/cvm::sqrt(eigenvec_invnorm2))+".\n"); } + + return error_code; } @@ -1436,13 +1274,6 @@ void colvar::eigenvector::calc_gradients() } -void colvar::eigenvector::apply_force(colvarvalue const &force) -{ - if (!atoms->noforce) - atoms->apply_colvar_force(force.real_value); -} - - void colvar::eigenvector::calc_force_invgrads() { atoms->read_total_forces(); @@ -1466,12 +1297,14 @@ void colvar::eigenvector::calc_Jacobian_derivative() cvm::real sum = 0.0; + cvm::vector1d dq_1; + atoms->rot_deriv->prepare_derivative(rotation_derivative_dldq::use_dq); for (size_t ia = 0; ia < atoms->size(); ia++) { // Gradient of optimal quaternion wrt current Cartesian position // trick: d(R^-1)/dx = d(R^t)/dx = (dR/dx)^t // we can just transpose the derivatives of the direct matrix - cvm::vector1d &dq_1 = atoms->rot.dQ0_1[ia]; + atoms->rot_deriv->calc_derivative_wrt_group1(ia, nullptr, &dq_1); g11 = 2.0 * quat0[1]*dq_1[1]; g22 = 2.0 * quat0[2]*dq_1[2]; @@ -1506,14 +1339,18 @@ void colvar::eigenvector::calc_Jacobian_derivative() } -simple_scalar_dist_functions(eigenvector) - - -colvar::cartesian::cartesian(std::string const &conf) - : cvc(conf) +colvar::cartesian::cartesian() { set_function_type("cartesian"); + x.type(colvarvalue::type_vector); + disable(f_cvc_explicit_gradient); +} + + +int colvar::cartesian::init(std::string const &conf) +{ + int error_code = cvc::init(conf); atoms = parse_group(conf, "atoms"); @@ -1528,14 +1365,15 @@ colvar::cartesian::cartesian(std::string const &conf) if (use_z) axes.push_back(2); if (axes.size() == 0) { - cvm::error("Error: a \"cartesian\" component was defined with all three axes disabled.\n"); - return; + error_code |= + cvm::error("Error: a \"cartesian\" component was defined with all three axes disabled.\n", + COLVARS_INPUT_ERROR); } - x.type(colvarvalue::type_vector); - disable(f_cvc_explicit_gradient); // Don't try to access atoms if creation of the atom group failed - if (atoms != NULL) x.vector1d_value.resize(atoms->size() * axes.size()); + if (atoms) x.vector1d_value.resize(atoms->size() * axes.size()); + + return error_code; } @@ -1573,3 +1411,24 @@ void colvar::cartesian::apply_force(colvarvalue const &force) } } } + + +cvm::real colvar::cartesian::dist2(colvarvalue const &x1, colvarvalue const &x2) const +{ + return (x1.vector1d_value - x2.vector1d_value).norm2(); +} + + +colvarvalue colvar::cartesian::dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const +{ + return 2.0 * (x1.vector1d_value - x2.vector1d_value); +} + + +colvarvalue colvar::cartesian::dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const +{ + return cartesian::dist2_lgrad(x1, x2); +} + + +void colvar::cartesian::wrap(colvarvalue & /* x_unwrapped */) const {} diff --git a/lib/colvars/colvarcomp_gpath.cpp b/lib/colvars/colvarcomp_gpath.cpp index c73ab4f6c1..46d6b8e5a8 100644 --- a/lib/colvars/colvarcomp_gpath.cpp +++ b/lib/colvars/colvarcomp_gpath.cpp @@ -1,4 +1,4 @@ -#if (__cplusplus >= 201103L) +// -*- c++ -*- // This file is part of the Collective Variables module (Colvars). // The original version of Colvars and its updates are located at: @@ -10,17 +10,29 @@ #include #include #include -#include #include #include #include "colvarmodule.h" #include "colvarvalue.h" -#include "colvarparse.h" #include "colvar.h" #include "colvarcomp.h" -colvar::CartesianBasedPath::CartesianBasedPath(std::string const &conf): cvc(conf), atoms(nullptr), reference_frames(0) { + + +colvar::CartesianBasedPath::CartesianBasedPath() +{ + x.type(colvarvalue::type_scalar); + // Don't use implicit gradient + enable(f_cvc_explicit_gradient); +} + + +int colvar::CartesianBasedPath::init(std::string const &conf) +{ + int error_code = cvc::init(conf); + if (error_code != COLVARS_OK) return error_code; + // Parse selected atoms atoms = parse_group(conf, "atoms"); has_user_defined_fitting = false; @@ -31,13 +43,12 @@ colvar::CartesianBasedPath::CartesianBasedPath(std::string const &conf): cvc(con // Lookup reference column of PDB // Copied from the RMSD class std::string reference_column; - double reference_column_value; + double reference_column_value = 0.0; if (get_keyval(conf, "refPositionsCol", reference_column, std::string(""))) { - bool found = get_keyval(conf, "refPositionsColValue", reference_column_value, 0.0); + bool found = get_keyval(conf, "refPositionsColValue", reference_column_value, reference_column_value); if (found && reference_column_value == 0.0) { - cvm::error("Error: refPositionsColValue, " - "if provided, must be non-zero.\n"); - return; + return cvm::error("Error: refPositionsColValue, if provided, must be non-zero.\n", + COLVARS_INPUT_ERROR); } } // Lookup all reference frames @@ -66,9 +77,6 @@ colvar::CartesianBasedPath::CartesianBasedPath(std::string const &conf): cvc(con tmp_atoms->ref_pos = reference_frames[i_frame]; tmp_atoms->center_ref_pos(); tmp_atoms->enable(f_ag_fit_gradients); - tmp_atoms->rot.request_group1_gradients(tmp_atoms->size()); - tmp_atoms->rot.request_group2_gradients(tmp_atoms->size()); - comp_atoms.push_back(tmp_atoms); } else { // parse a group of atoms for fitting std::string fitting_group_name = std::string("fittingAtoms") + cvm::to_str(i_frame); @@ -91,15 +99,13 @@ colvar::CartesianBasedPath::CartesianBasedPath(std::string const &conf): cvc(con tmp_atoms->enable(f_ag_fit_gradients); tmp_atoms->enable(f_ag_fitting_group); tmp_atoms->fitting_group = tmp_fitting_atoms; - tmp_atoms->rot.request_group1_gradients(tmp_fitting_atoms->size()); - tmp_atoms->rot.request_group2_gradients(tmp_fitting_atoms->size()); reference_fitting_frames.push_back(reference_fitting_position); - comp_atoms.push_back(tmp_atoms); } + tmp_atoms->setup_rotation_derivative(); + comp_atoms.push_back(tmp_atoms); } - x.type(colvarvalue::type_scalar); - // Don't use implicit gradient - enable(f_cvc_explicit_gradient); + + return error_code; } colvar::CartesianBasedPath::~CartesianBasedPath() { @@ -125,8 +131,60 @@ void colvar::CartesianBasedPath::computeDistanceToReferenceFrames(std::vector& result) { + for (size_t i_frame = 0; i_frame < reference_frames.size() - 1; ++i_frame) { + std::vector this_frame_atom_pos(reference_frames[i_frame].size()); + std::vector next_frame_atom_pos(reference_frames[i_frame + 1].size()); + cvm::real frame_rmsd = 0.0; + const size_t this_index = i_frame; + const size_t next_index = i_frame + 1; + // compute COM of two successive images, respectively + cvm::atom_pos reference_cog_this, reference_cog_next; + for (size_t i_atom = 0; i_atom < atoms->size(); ++i_atom) { + reference_cog_this += reference_frames[this_index][i_atom]; + reference_cog_next += reference_frames[next_index][i_atom]; + } + reference_cog_this /= reference_frames[this_index].size(); + reference_cog_next /= reference_frames[next_index].size(); + // move all atoms to COM + for (size_t i_atom = 0; i_atom < atoms->size(); ++i_atom) { + this_frame_atom_pos[i_atom] = reference_frames[this_index][i_atom] - reference_cog_this; + next_frame_atom_pos[i_atom] = reference_frames[next_index][i_atom] - reference_cog_next; + } + cvm::rotation rot_this_to_next; + // compute the optimal rotation + rot_this_to_next.calc_optimal_rotation(this_frame_atom_pos, next_frame_atom_pos); + // compute rmsd between reference frames + for (size_t i_atom = 0; i_atom < atoms->size(); ++i_atom) { + frame_rmsd += (rot_this_to_next.q.rotate(this_frame_atom_pos[i_atom]) - next_frame_atom_pos[i_atom]).norm2(); + } + frame_rmsd /= cvm::real(atoms->size()); + frame_rmsd = cvm::sqrt(frame_rmsd); + result[i_frame] = frame_rmsd; + } +} + + +void colvar::CartesianBasedPath::apply_force(colvarvalue const &force) +{ + cvm::error("Error: using apply_force() in a component of type CartesianBasedPath, which is abstract.\n", + COLVARS_BUG_ERROR); +} + + + +colvar::gspath::gspath() +{ set_function_type("gspath"); +} + + +int colvar::gspath::init(std::string const &conf) +{ + int error_code = CartesianBasedPath::init(conf); + if (error_code != COLVARS_OK) return error_code; + get_keyval(conf, "useSecondClosestFrame", use_second_closest_frame, true); if (use_second_closest_frame == true) { cvm::log(std::string("Geometric path s(σ) will use the second closest frame to compute s_(m-1)\n")); @@ -140,12 +198,14 @@ colvar::gspath::gspath(std::string const &conf): CartesianBasedPath(conf) { cvm::log(std::string("Geometric path s(σ) will use the neighbouring frame to compute s_(m+1)\n")); } if (total_reference_frames < 2) { - cvm::error("Error: you have specified " + cvm::to_str(total_reference_frames) + " reference frames, but gspath requires at least 2 frames.\n"); - return; + return cvm::error("Error: you have specified " + cvm::to_str(total_reference_frames) + + " reference frames, but gspath requires at least 2 frames.\n", + COLVARS_INPUT_ERROR); } GeometricPathCV::GeometricPathBase::initialize(atoms->size(), cvm::atom_pos(), total_reference_frames, use_second_closest_frame, use_third_closest_frame); cvm::log(std::string("Geometric pathCV(s) is initialized.\n")); cvm::log(std::string("Geometric pathCV(s) loaded ") + cvm::to_str(reference_frames.size()) + std::string(" frames.\n")); + return error_code; } void colvar::gspath::updateDistanceToReferenceFrames() { @@ -192,8 +252,9 @@ void colvar::gspath::prepareVectors() { } else { rot_v3.calc_optimal_rotation(tmp_reference_frame_1, tmp_reference_frame_2); } + const auto rot_mat_v3 = rot_v3.matrix(); for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { - v3[i_atom] = rot_v3.q.rotate(tmp_reference_frame_1[i_atom]) - tmp_reference_frame_2[i_atom]; + v3[i_atom] = rot_mat_v3 * tmp_reference_frame_1[i_atom] - tmp_reference_frame_2[i_atom]; } } else { cvm::atom_pos reference_cog_1, reference_cog_3; @@ -227,9 +288,10 @@ void colvar::gspath::prepareVectors() { } else { rot_v3.calc_optimal_rotation(tmp_reference_frame_1, tmp_reference_frame_3); } + const auto rot_mat_v3 = rot_v3.matrix(); for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { // v3 = s_(m+1) - s_m - v3[i_atom] = tmp_reference_frame_3[i_atom] - rot_v3.q.rotate(tmp_reference_frame_1[i_atom]); + v3[i_atom] = tmp_reference_frame_3[i_atom] - rot_mat_v3 * tmp_reference_frame_1[i_atom]; } } } @@ -267,8 +329,17 @@ void colvar::gspath::apply_force(colvarvalue const &force) { (*(comp_atoms[min_frame_index_2])).apply_colvar_force(F); } -colvar::gzpath::gzpath(std::string const &conf): CartesianBasedPath(conf) { + +colvar::gzpath::gzpath() +{ set_function_type("gzpath"); +} + +int colvar::gzpath::init(std::string const &conf) +{ + int error_code = CartesianBasedPath::init(conf); + if (error_code != COLVARS_OK) return error_code; + get_keyval(conf, "useSecondClosestFrame", use_second_closest_frame, true); if (use_second_closest_frame == true) { cvm::log(std::string("Geometric path z(σ) will use the second closest frame to compute s_(m-1)\n")); @@ -287,13 +358,15 @@ colvar::gzpath::gzpath(std::string const &conf): CartesianBasedPath(conf) { cvm::log(std::string("Geometric path z(σ) will use the square of distance from current frame to path compute z\n")); } if (total_reference_frames < 2) { - cvm::error("Error: you have specified " + cvm::to_str(total_reference_frames) + " reference frames, but gzpath requires at least 2 frames.\n"); - return; + return cvm::error("Error: you have specified " + cvm::to_str(total_reference_frames) + + " reference frames, but gzpath requires at least 2 frames.\n", + COLVARS_INPUT_ERROR); } GeometricPathCV::GeometricPathBase::initialize(atoms->size(), cvm::atom_pos(), total_reference_frames, use_second_closest_frame, use_third_closest_frame, b_use_z_square); // Logging cvm::log(std::string("Geometric pathCV(z) is initialized.\n")); cvm::log(std::string("Geometric pathCV(z) loaded ") + cvm::to_str(reference_frames.size()) + std::string(" frames.\n")); + return error_code; } void colvar::gzpath::updateDistanceToReferenceFrames() { @@ -335,12 +408,13 @@ void colvar::gzpath::prepareVectors() { } else { rot_v4.calc_optimal_rotation(tmp_reference_frame_1, tmp_reference_frame_2); } + const auto rot_mat_v4 = rot_v4.matrix(); for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { v1[i_atom] = reference_frames[min_frame_index_1][i_atom] - (*(comp_atoms[min_frame_index_1]))[i_atom].pos; v2[i_atom] = (*(comp_atoms[min_frame_index_2]))[i_atom].pos - reference_frames[min_frame_index_2][i_atom]; // v4 only computes in gzpath // v4 = s_m - s_(m-1) - v4[i_atom] = rot_v4.q.rotate(tmp_reference_frame_1[i_atom]) - tmp_reference_frame_2[i_atom]; + v4[i_atom] = rot_mat_v4 * tmp_reference_frame_1[i_atom] - tmp_reference_frame_2[i_atom]; } if (min_frame_index_3 < 0 || min_frame_index_3 > M) { v3 = v4; @@ -368,9 +442,10 @@ void colvar::gzpath::prepareVectors() { } else { rot_v3.calc_optimal_rotation(tmp_reference_frame_1, tmp_reference_frame_3); } + const auto rot_mat_v3 = rot_v3.matrix(); for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { // v3 = s_(m+1) - s_m - v3[i_atom] = tmp_reference_frame_3[i_atom] - rot_v3.q.rotate(tmp_reference_frame_1[i_atom]); + v3[i_atom] = tmp_reference_frame_3[i_atom] - rot_mat_v3 * tmp_reference_frame_1[i_atom]; } } } @@ -399,14 +474,26 @@ void colvar::gzpath::apply_force(colvarvalue const &force) { } -colvar::CVBasedPath::CVBasedPath(std::string const &conf): cvc(conf) { +colvar::CVBasedPath::CVBasedPath() +{ + set_function_type("gspathCV"); + x.type(colvarvalue::type_scalar); +} + + +int colvar::CVBasedPath::init(std::string const &conf) +{ + int error_code = cvc::init(conf); + if (error_code != COLVARS_OK) return error_code; + // Lookup all available sub-cvcs for (auto it_cv_map = colvar::get_global_cvc_map().begin(); it_cv_map != colvar::get_global_cvc_map().end(); ++it_cv_map) { if (key_lookup(conf, it_cv_map->first.c_str())) { std::vector sub_cvc_confs; get_key_string_multi_value(conf, it_cv_map->first.c_str(), sub_cvc_confs); for (auto it_sub_cvc_conf = sub_cvc_confs.begin(); it_sub_cvc_conf != sub_cvc_confs.end(); ++it_sub_cvc_conf) { - cv.push_back((it_cv_map->second)(*(it_sub_cvc_conf))); + cv.push_back((it_cv_map->second)()); + cv.back()->init(*(it_sub_cvc_conf)); } } } @@ -429,7 +516,7 @@ colvar::CVBasedPath::CVBasedPath(std::string const &conf): cvc(conf) { cvm::log(std::string("Reading path file: ") + path_filename + std::string("\n")); auto &ifs_path = cvm::main()->proxy->input_stream(path_filename); if (!ifs_path) { - return; + return COLVARS_INPUT_ERROR; } std::string line; const std::string token(" "); @@ -450,8 +537,8 @@ colvar::CVBasedPath::CVBasedPath(std::string const &conf): cvc(conf) { cvm::log(cvm::to_str(tmp_cv[i_cv][i - start_index])); } } else { - cvm::error("Error: incorrect format of path file.\n"); - return; + error_code = cvm::error("Error: incorrect format of path file.\n", COLVARS_INPUT_ERROR); + return error_code; } } if (!fields.empty()) { @@ -461,15 +548,18 @@ colvar::CVBasedPath::CVBasedPath(std::string const &conf): cvc(conf) { } cvm::main()->proxy->close_input_stream(path_filename); if (total_reference_frames <= 1) { - cvm::error("Error: there is only 1 or 0 reference frame, which doesn't constitute a path.\n"); - return; + error_code = cvm::error( + "Error: there is only 1 or 0 reference frame, which doesn't constitute a path.\n", + COLVARS_INPUT_ERROR); + return error_code; } if (cv.size() == 0) { - cvm::error("Error: the CV " + name + - " expects one or more nesting components.\n"); - return; + error_code = + cvm::error("Error: the CV " + name + " expects one or more nesting components.\n", + COLVARS_INPUT_ERROR); + return error_code; } - x.type(colvarvalue::type_scalar); + use_explicit_gradients = true; for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { if (!cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { @@ -479,6 +569,8 @@ colvar::CVBasedPath::CVBasedPath(std::string const &conf): cvc(conf) { if (!use_explicit_gradients) { disable(f_cvc_explicit_gradient); } + + return error_code; } void colvar::CVBasedPath::computeDistanceToReferenceFrames(std::vector& result) { @@ -548,8 +640,47 @@ colvar::CVBasedPath::~CVBasedPath() { atom_groups.clear(); } -colvar::gspathCV::gspathCV(std::string const &conf): CVBasedPath(conf) { + +void colvar::CVBasedPath::apply_force(colvarvalue const &force) +{ + cvm::error("Error: using apply_force() in a component of type CVBasedPath, which is abstract.\n", + COLVARS_BUG_ERROR); +} + + +cvm::real colvar::CVBasedPath::dist2(colvarvalue const &x1, colvarvalue const &x2) const +{ + return x1.dist2(x2); +} + + +colvarvalue colvar::CVBasedPath::dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const +{ + return x1.dist2_grad(x2); +} + + +colvarvalue colvar::CVBasedPath::dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const +{ + return x2.dist2_grad(x1); +} + + +void colvar::CVBasedPath::wrap(colvarvalue & /* x_unwrapped */) const {} + + + +colvar::gspathCV::gspathCV() +{ set_function_type("gspathCV"); + x.type(colvarvalue::type_scalar); +} + +int colvar::gspathCV::init(std::string const &conf) +{ + int error_code = CVBasedPath::init(conf); + if (error_code != COLVARS_OK) return error_code; + cvm::log(std::string("Total number of frames: ") + cvm::to_str(total_reference_frames) + std::string("\n")); // Initialize variables for future calculation get_keyval(conf, "useSecondClosestFrame", use_second_closest_frame, true); @@ -565,11 +696,10 @@ colvar::gspathCV::gspathCV(std::string const &conf): CVBasedPath(conf) { cvm::log(std::string("Geometric path s(σ) will use the neighbouring frame to compute s_(m+1)\n")); } if (total_reference_frames < 2) { - cvm::error("Error: you have specified " + cvm::to_str(total_reference_frames) + " reference frames, but gspathCV requires at least 2 frames.\n"); - return; + return cvm::error("Error: you have specified " + cvm::to_str(total_reference_frames) + " reference frames, but gspathCV requires at least 2 frames.\n", COLVARS_INPUT_ERROR); } GeometricPathCV::GeometricPathBase::initialize(cv.size(), ref_cv[0], total_reference_frames, use_second_closest_frame, use_third_closest_frame); - x.type(colvarvalue::type_scalar); + return error_code; } colvar::gspathCV::~gspathCV() {} @@ -671,8 +801,16 @@ void colvar::gspathCV::apply_force(colvarvalue const &force) { } } -colvar::gzpathCV::gzpathCV(std::string const &conf): CVBasedPath(conf) { +colvar::gzpathCV::gzpathCV() +{ set_function_type("gzpathCV"); +} + +int colvar::gzpathCV::init(std::string const &conf) { + + int error_code = CVBasedPath::init(conf); + if (error_code != COLVARS_OK) return error_code; + cvm::log(std::string("Total number of frames: ") + cvm::to_str(total_reference_frames) + std::string("\n")); // Initialize variables for future calculation M = cvm::real(total_reference_frames - 1); @@ -695,11 +833,13 @@ colvar::gzpathCV::gzpathCV(std::string const &conf): CVBasedPath(conf) { cvm::log(std::string("Geometric path z(σ) will use the square of distance from current frame to path compute z\n")); } if (total_reference_frames < 2) { - cvm::error("Error: you have specified " + cvm::to_str(total_reference_frames) + " reference frames, but gzpathCV requires at least 2 frames.\n"); - return; + return cvm::error("Error: you have specified " + cvm::to_str(total_reference_frames) + + " reference frames, but gzpathCV requires at least 2 frames.\n", + COLVARS_INPUT_ERROR); } GeometricPathCV::GeometricPathBase::initialize(cv.size(), ref_cv[0], total_reference_frames, use_second_closest_frame, use_third_closest_frame, b_use_z_square); - x.type(colvarvalue::type_scalar); + + return error_code; } colvar::gzpathCV::~gzpathCV() { @@ -794,5 +934,3 @@ void colvar::gzpathCV::apply_force(colvarvalue const &force) { } } } - -#endif diff --git a/lib/colvars/colvarcomp_neuralnetwork.cpp b/lib/colvars/colvarcomp_neuralnetwork.cpp index 98bd8cddd5..2d2f89d5f5 100644 --- a/lib/colvars/colvarcomp_neuralnetwork.cpp +++ b/lib/colvars/colvarcomp_neuralnetwork.cpp @@ -1,16 +1,31 @@ -#if (__cplusplus >= 201103L) +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. #include "colvarmodule.h" #include "colvarvalue.h" -#include "colvarparse.h" #include "colvar.h" #include "colvarcomp.h" #include "colvar_neuralnetworkcompute.h" using namespace neuralnetworkCV; -colvar::neuralNetwork::neuralNetwork(std::string const &conf): linearCombination(conf) { + +colvar::neuralNetwork::neuralNetwork() +{ set_function_type("neuralNetwork"); +} + + +int colvar::neuralNetwork::init(std::string const &conf) +{ + int error_code = linearCombination::init(conf); + if (error_code != COLVARS_OK) return error_code; // the output of neural network consists of multiple values // read "output_component" key to determine it get_keyval(conf, "output_component", m_output_index); @@ -59,8 +74,8 @@ colvar::neuralNetwork::neuralNetwork(std::string const &conf): linearCombination std::string function_name; get_keyval(conf, lookup_key.c_str(), function_name, std::string("")); if (activation_function_map.find(function_name) == activation_function_map.end()) { - cvm::error("Unknown activation function name: \"" + function_name + "\".\n"); - return; + return cvm::error("Unknown activation function name: \"" + function_name + "\".\n", + COLVARS_INPUT_ERROR); } activation_functions.push_back(std::make_pair(false, function_name)); cvm::log(std::string{"The activation function for layer["} + cvm::to_str(num_activation_functions + 1) + std::string{"] is "} + function_name + '\n'); @@ -79,11 +94,13 @@ colvar::neuralNetwork::neuralNetwork(std::string const &conf): linearCombination } // expect the three numbers are equal if ((num_layers_weight != num_layers_bias) || (num_layers_bias != num_activation_functions)) { - cvm::error("Error: the numbers of weights, biases and activation functions do not match.\n"); - return; + return cvm::error( + "Error: the numbers of weights, biases and activation functions do not match.\n", + COLVARS_INPUT_ERROR); } // nn = std::make_unique(); // std::make_unique is only available in C++14 + if (nn) nn.reset(); nn = std::unique_ptr(new neuralnetworkCV::neuralNetworkCompute()); for (size_t i_layer = 0; i_layer < num_layers_weight; ++i_layer) { denseLayer d; @@ -93,8 +110,9 @@ colvar::neuralNetwork::neuralNetwork(std::string const &conf): linearCombination try { d = denseLayer(weight_files[i_layer], bias_files[i_layer], activation_functions[i_layer].second); } catch (std::exception &ex) { - cvm::error("Error on initializing layer " + cvm::to_str(i_layer) + " (" + ex.what() + ")\n", COLVARS_INPUT_ERROR); - return; + return cvm::error("Error on initializing layer " + cvm::to_str(i_layer) + + " (" + ex.what() + ")\n", + COLVARS_INPUT_ERROR); } } else { #endif @@ -104,8 +122,9 @@ colvar::neuralNetwork::neuralNetwork(std::string const &conf): linearCombination try { d = denseLayer(weight_files[i_layer], bias_files[i_layer], f, df); } catch (std::exception &ex) { - cvm::error("Error on initializing layer " + cvm::to_str(i_layer) + " (" + ex.what() + ")\n", COLVARS_INPUT_ERROR); - return; + return cvm::error("Error on initializing layer " + cvm::to_str(i_layer) + + " (" + ex.what() + ")\n", + COLVARS_INPUT_ERROR); } #ifdef LEPTON } @@ -123,11 +142,11 @@ colvar::neuralNetwork::neuralNetwork(std::string const &conf): linearCombination } } } else { - cvm::error("Error: error on adding a new dense layer.\n"); - return; + return cvm::error("Error: error on adding a new dense layer.\n", COLVARS_INPUT_ERROR); } } nn->input().resize(cv.size()); + return error_code; } colvar::neuralNetwork::~neuralNetwork() { @@ -185,4 +204,24 @@ void colvar::neuralNetwork::apply_force(colvarvalue const &force) { } } -#endif + +cvm::real colvar::neuralNetwork::dist2(colvarvalue const &x1, colvarvalue const &x2) const +{ + return x1.dist2(x2); +} + + +colvarvalue colvar::neuralNetwork::dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const +{ + return x1.dist2_grad(x2); +} + + +colvarvalue colvar::neuralNetwork::dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const +{ + return x2.dist2_grad(x1); +} + + + +void colvar::neuralNetwork::wrap(colvarvalue & /* x_unwrapped */) const {} diff --git a/lib/colvars/colvarcomp_protein.cpp b/lib/colvars/colvarcomp_protein.cpp index 80ef9b855c..f782095148 100644 --- a/lib/colvars/colvarcomp_protein.cpp +++ b/lib/colvars/colvarcomp_protein.cpp @@ -11,19 +11,23 @@ #include "colvarmodule.h" #include "colvarvalue.h" -#include "colvarparse.h" #include "colvar.h" #include "colvarcomp.h" -colvar::alpha_angles::alpha_angles(std::string const &conf) - : cvc(conf) +colvar::alpha_angles::alpha_angles() { set_function_type("alpha"); enable(f_cvc_explicit_gradient); x.type(colvarvalue::type_scalar); - colvarproxy *proxy = cvm::main()->proxy; + r0 = proxy->angstrom_to_internal(3.3); +} + + +int colvar::alpha_angles::init(std::string const &conf) +{ + int error_code = cvc::init(conf); std::string segment_id; get_keyval(conf, "psfSegID", segment_id, std::string("MAIN")); @@ -44,29 +48,29 @@ colvar::alpha_angles::alpha_angles(std::string const &conf) } } } else { - cvm::error("Error: no residues defined in \"residueRange\".\n"); - return; + error_code |= + cvm::error("Error: no residues defined in \"residueRange\".\n", COLVARS_INPUT_ERROR); } } if (residues.size() < 5) { - cvm::error("Error: not enough residues defined in \"residueRange\".\n"); - return; + error_code |= cvm::error("Error: not enough residues defined in \"residueRange\".\n", + COLVARS_INPUT_ERROR); } std::string const &sid = segment_id; std::vector const &r = residues; - get_keyval(conf, "hBondCoeff", hb_coeff, 0.5); - if ( (hb_coeff < 0.0) || (hb_coeff > 1.0) ) { - cvm::error("Error: hBondCoeff must be defined between 0 and 1.\n"); - return; + get_keyval(conf, "hBondCoeff", hb_coeff, hb_coeff); + if ((hb_coeff < 0.0) || (hb_coeff > 1.0)) { + error_code |= + cvm::error("Error: hBondCoeff must be defined between 0 and 1.\n", COLVARS_INPUT_ERROR); } - get_keyval(conf, "angleRef", theta_ref, 88.0); - get_keyval(conf, "angleTol", theta_tol, 15.0); + get_keyval(conf, "angleRef", theta_ref, theta_ref); + get_keyval(conf, "angleTol", theta_tol, theta_tol); if (hb_coeff < 1.0) { @@ -84,11 +88,9 @@ colvar::alpha_angles::alpha_angles(std::string const &conf) } { - cvm::real r0; - size_t en, ed; - get_keyval(conf, "hBondCutoff", r0, proxy->angstrom_to_internal(3.3)); - get_keyval(conf, "hBondExpNumer", en, 6); - get_keyval(conf, "hBondExpDenom", ed, 8); + get_keyval(conf, "hBondCutoff", r0, r0); + get_keyval(conf, "hBondExpNumer", en, en); + get_keyval(conf, "hBondExpDenom", ed, ed); if (hb_coeff > 0.0) { @@ -103,15 +105,8 @@ colvar::alpha_angles::alpha_angles(std::string const &conf) cvm::log("The hBondCoeff specified will disable the hydrogen bond terms.\n"); } } -} - -colvar::alpha_angles::alpha_angles() - : cvc() -{ - set_function_type("alphaAngles"); - enable(f_cvc_explicit_gradient); - x.type(colvarvalue::type_scalar); + return error_code; } @@ -273,24 +268,27 @@ void colvar::alpha_angles::apply_force(colvarvalue const &force) } -simple_scalar_dist_functions(alpha_angles) - ////////////////////////////////////////////////////////////////////// // dihedral principal component ////////////////////////////////////////////////////////////////////// -colvar::dihedPC::dihedPC(std::string const &conf) - : cvc(conf) +colvar::dihedPC::dihedPC() { - if (cvm::debug()) - cvm::log("Initializing dihedral PC object.\n"); - set_function_type("dihedPC"); // Supported through references to atom groups of children cvcs enable(f_cvc_explicit_gradient); x.type(colvarvalue::type_scalar); +} + + +int colvar::dihedPC::init(std::string const &conf) +{ + int error_code = cvc::init(conf); + + if (cvm::debug()) + cvm::log("Initializing dihedral PC object.\n"); std::string segment_id; get_keyval(conf, "psfSegID", segment_id, std::string("MAIN")); @@ -311,14 +309,14 @@ colvar::dihedPC::dihedPC(std::string const &conf) } } } else { - cvm::error("Error: no residues defined in \"residueRange\".\n"); - return; + error_code |= + cvm::error("Error: no residues defined in \"residueRange\".\n", COLVARS_INPUT_ERROR); } } if (residues.size() < 2) { - cvm::error("Error: dihedralPC requires at least two residues.\n"); - return; + error_code |= + cvm::error("Error: dihedralPC requires at least two residues.\n", COLVARS_INPUT_ERROR); } std::string const &sid = segment_id; @@ -329,15 +327,15 @@ colvar::dihedPC::dihedPC(std::string const &conf) if (get_keyval(conf, "vectorFile", vecFileName, vecFileName)) { get_keyval(conf, "vectorNumber", vecNumber, 0); if (vecNumber < 1) { - cvm::error("A positive value of vectorNumber is required."); - return; + error_code |= + cvm::error("A positive value of vectorNumber is required.", COLVARS_INPUT_ERROR); } std::istream &vecFile = cvm::main()->proxy->input_stream(vecFileName, "dihedral PCA vector file"); if (!vecFile) { - return; + return COLVARS_INPUT_ERROR; } // TODO: adapt to different formats by setting this flag @@ -383,11 +381,10 @@ colvar::dihedPC::dihedPC(std::string const &conf) } if ( coeffs.size() != 4 * (residues.size() - 1)) { - cvm::error("Error: wrong number of coefficients: " + - cvm::to_str(coeffs.size()) + ". Expected " + - cvm::to_str(4 * (residues.size() - 1)) + - " (4 coeffs per residue, minus one residue).\n"); - return; + error_code |= cvm::error("Error: wrong number of coefficients: " + cvm::to_str(coeffs.size()) + + ". Expected " + cvm::to_str(4 * (residues.size() - 1)) + + " (4 coeffs per residue, minus one residue).\n", + COLVARS_INPUT_ERROR); } for (size_t i = 0; i < residues.size()-1; i++) { @@ -413,16 +410,8 @@ colvar::dihedPC::dihedPC(std::string const &conf) if (cvm::debug()) cvm::log("Done initializing dihedPC object.\n"); -} - -colvar::dihedPC::dihedPC() - : cvc() -{ - set_function_type("dihedPC"); - // Supported through references to atom groups of children cvcs - enable(f_cvc_explicit_gradient); - x.type(colvarvalue::type_scalar); + return error_code; } @@ -491,6 +480,3 @@ void colvar::dihedPC::apply_force(colvarvalue const &force) coeffs[2*i+1] * dsindt) * force); } } - - -simple_scalar_dist_functions(dihedPC) diff --git a/lib/colvars/colvarcomp_rotations.cpp b/lib/colvars/colvarcomp_rotations.cpp index f469174a62..a04ace851a 100644 --- a/lib/colvars/colvarcomp_rotations.cpp +++ b/lib/colvars/colvarcomp_rotations.cpp @@ -9,27 +9,39 @@ #include "colvarmodule.h" #include "colvarvalue.h" -#include "colvarparse.h" #include "colvar.h" #include "colvarcomp.h" +#include "colvar_rotation_derivative.h" +struct colvar::orientation::rotation_derivative_impl_: public rotation_derivative { +public: + rotation_derivative_impl_(colvar::orientation* orientation_cvc): + rotation_derivative( + orientation_cvc->rot, orientation_cvc->ref_pos, orientation_cvc->shifted_pos) {} +}; -colvar::orientation::orientation(std::string const &conf) - : cvc() + +colvar::orientation::orientation() { set_function_type("orientation"); + rot_deriv_impl = std::unique_ptr(new rotation_derivative_impl_(this)); disable(f_cvc_explicit_gradient); x.type(colvarvalue::type_quaternion); - colvar::orientation::init(conf); } +colvar::orientation::~orientation() {} + + int colvar::orientation::init(std::string const &conf) { int error_code = cvc::init(conf); atoms = parse_group(conf, "atoms"); + if (!atoms || atoms->size() == 0) { + return error_code | COLVARS_INPUT_ERROR; + } ref_pos.reserve(atoms->size()); if (get_keyval(conf, "refPositions", ref_pos, ref_pos)) { @@ -56,17 +68,18 @@ int colvar::orientation::init(std::string const &conf) } ref_pos.resize(atoms->size()); - cvm::load_coords(file_name.c_str(), &ref_pos, atoms, + error_code |= cvm::load_coords(file_name.c_str(), &ref_pos, atoms, file_col, file_col_value); } } + if (error_code != COLVARS_OK) return error_code; + if (!ref_pos.size()) { return cvm::error("Error: must define a set of " "reference coordinates.\n", COLVARS_INPUT_ERROR); } - cvm::rvector ref_cog(0.0, 0.0, 0.0); size_t i; for (i = 0; i < ref_pos.size(); i++) { @@ -84,30 +97,21 @@ int colvar::orientation::init(std::string const &conf) get_keyval(conf, "closestToQuaternion", ref_quat, cvm::quaternion(1.0, 0.0, 0.0, 0.0)); - // initialize rot member data - if (!atoms->noforce) { - rot.request_group2_gradients(atoms->size()); - } + // If the debug gradients feature is active, debug the rotation gradients + // (note that this won't be active for the orientation CVC itself, because + // colvardeps prevents the flag's activation) + rot.b_debug_gradients = is_enabled(f_cvc_debug_gradient); return error_code; } -colvar::orientation::orientation() - : cvc() -{ - set_function_type("orientation"); - disable(f_cvc_explicit_gradient); - x.type(colvarvalue::type_quaternion); -} - - void colvar::orientation::calc_value() { - rot.b_debug_gradients = is_enabled(f_cvc_debug_gradient); atoms_cog = atoms->center_of_geometry(); - rot.calc_optimal_rotation(ref_pos, atoms->positions_shifted(-1.0 * atoms_cog)); + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); if ((rot.q).inner(ref_quat) >= 0.0) { x.quaternion_value = rot.q; @@ -131,9 +135,12 @@ void colvar::orientation::apply_force(colvarvalue const &force) cvm::quaternion const &FQ = force.quaternion_value; if (!atoms->noforce) { + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; for (size_t ia = 0; ia < atoms->size(); ia++) { + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); for (size_t i = 0; i < 4; i++) { - (*atoms)[ia].apply_force(FQ[i] * rot.dQ0_2[ia][i]); + (*atoms)[ia].apply_force(FQ[i] * dq0_2[i]); } } } @@ -161,20 +168,15 @@ colvarvalue colvar::orientation::dist2_rgrad(colvarvalue const &x1, } +void colvar::orientation::wrap(colvarvalue & /* x_unwrapped */) const {} -colvar::orientation_angle::orientation_angle(std::string const &conf) - : orientation() + + +colvar::orientation_angle::orientation_angle() { set_function_type("orientationAngle"); init_as_angle(); enable(f_cvc_explicit_gradient); - orientation_angle::init(conf); -} - - -int colvar::orientation_angle::init(std::string const &conf) -{ - return orientation::init(conf); } @@ -182,7 +184,8 @@ void colvar::orientation_angle::calc_value() { atoms_cog = atoms->center_of_geometry(); - rot.calc_optimal_rotation(ref_pos, atoms->positions_shifted(-1.0 * atoms_cog)); + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); if ((rot.q).q0 >= 0.0) { x.real_value = (180.0/PI) * 2.0 * cvm::acos((rot.q).q0); @@ -199,46 +202,59 @@ void colvar::orientation_angle::calc_gradients() ((180.0 / PI) * (-2.0) / cvm::sqrt(1.0 - ((rot.q).q0 * (rot.q).q0))) : 0.0 ); + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; for (size_t ia = 0; ia < atoms->size(); ia++) { - (*atoms)[ia].grad = (dxdq0 * (rot.dQ0_2[ia])[0]); + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); + (*atoms)[ia].grad = (dxdq0 * dq0_2[0]); } } void colvar::orientation_angle::apply_force(colvarvalue const &force) { - cvm::real const &fw = force.real_value; - if (!atoms->noforce) { - atoms->apply_colvar_force(fw); - } + cvc::apply_force(force); } -simple_scalar_dist_functions(orientation_angle) +cvm::real colvar::orientation_angle::dist2(colvarvalue const &x1, colvarvalue const &x2) const +{ + return cvc::dist2(x1, x2); +} + + +colvarvalue colvar::orientation_angle::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return cvc::dist2_lgrad(x1, x2); +} + + +colvarvalue colvar::orientation_angle::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return cvc::dist2_rgrad(x1, x2); +} + + +void colvar::orientation_angle::wrap(colvarvalue & /* x_unwrapped */) const {} -colvar::orientation_proj::orientation_proj(std::string const &conf) - : orientation() +colvar::orientation_proj::orientation_proj() { set_function_type("orientationProj"); enable(f_cvc_explicit_gradient); x.type(colvarvalue::type_scalar); init_scalar_boundaries(0.0, 1.0); - orientation_proj::init(conf); -} - - -int colvar::orientation_proj::init(std::string const &conf) -{ - return orientation::init(conf); } void colvar::orientation_proj::calc_value() { atoms_cog = atoms->center_of_geometry(); - rot.calc_optimal_rotation(ref_pos, atoms->positions_shifted(-1.0 * atoms_cog)); + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); x.real_value = 2.0 * (rot.q).q0 * (rot.q).q0 - 1.0; } @@ -246,42 +262,28 @@ void colvar::orientation_proj::calc_value() void colvar::orientation_proj::calc_gradients() { cvm::real const dxdq0 = 2.0 * 2.0 * (rot.q).q0; + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; for (size_t ia = 0; ia < atoms->size(); ia++) { - (*atoms)[ia].grad = (dxdq0 * (rot.dQ0_2[ia])[0]); + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); + (*atoms)[ia].grad = (dxdq0 * dq0_2[0]); } } -void colvar::orientation_proj::apply_force(colvarvalue const &force) -{ - cvm::real const &fw = force.real_value; - if (!atoms->noforce) { - atoms->apply_colvar_force(fw); - } -} - - -simple_scalar_dist_functions(orientation_proj) - - - -colvar::tilt::tilt(std::string const &conf) - : orientation() +colvar::tilt::tilt() { set_function_type("tilt"); x.type(colvarvalue::type_scalar); enable(f_cvc_explicit_gradient); init_scalar_boundaries(-1.0, 1.0); - tilt::init(conf); } int colvar::tilt::init(std::string const &conf) { - int error_code = COLVARS_OK; - - error_code |= orientation::init(conf); + int error_code = orientation_proj::init(conf); get_keyval(conf, "axis", axis, cvm::rvector(0.0, 0.0, 1.0)); if (axis.norm2() != 1.0) { @@ -297,7 +299,8 @@ void colvar::tilt::calc_value() { atoms_cog = atoms->center_of_geometry(); - rot.calc_optimal_rotation(ref_pos, atoms->positions_shifted(-1.0 * atoms_cog)); + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); x.real_value = rot.cos_theta(axis); } @@ -307,64 +310,24 @@ void colvar::tilt::calc_gradients() { cvm::quaternion const dxdq = rot.dcos_theta_dq(axis); + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; for (size_t ia = 0; ia < atoms->size(); ia++) { (*atoms)[ia].grad = cvm::rvector(0.0, 0.0, 0.0); + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); for (size_t iq = 0; iq < 4; iq++) { - (*atoms)[ia].grad += (dxdq[iq] * (rot.dQ0_2[ia])[iq]); + (*atoms)[ia].grad += (dxdq[iq] * dq0_2[iq]); } } } -void colvar::tilt::apply_force(colvarvalue const &force) -{ - cvm::real const &fw = force.real_value; - if (!atoms->noforce) { - atoms->apply_colvar_force(fw); - } -} - - -simple_scalar_dist_functions(tilt) - - - -colvar::spin_angle::spin_angle(std::string const &conf) - : orientation() +colvar::spin_angle::spin_angle() { set_function_type("spinAngle"); init_as_periodic_angle(); - enable(f_cvc_periodic); enable(f_cvc_explicit_gradient); - spin_angle::init(conf); -} - - -int colvar::spin_angle::init(std::string const &conf) -{ - int error_code = COLVARS_OK; - - error_code |= orientation::init(conf); - - get_keyval(conf, "axis", axis, cvm::rvector(0.0, 0.0, 1.0)); - if (axis.norm2() != 1.0) { - axis /= axis.norm(); - cvm::log("Normalizing rotation axis to "+cvm::to_str(axis)+".\n"); - } - - return error_code; -} - - -colvar::spin_angle::spin_angle() - : orientation() -{ - set_function_type("spinAngle"); - period = 360.0; - enable(f_cvc_periodic); - enable(f_cvc_explicit_gradient); - x.type(colvarvalue::type_scalar); } @@ -372,10 +335,11 @@ void colvar::spin_angle::calc_value() { atoms_cog = atoms->center_of_geometry(); - rot.calc_optimal_rotation(ref_pos, atoms->positions_shifted(-1.0 * atoms_cog)); + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); x.real_value = rot.spin_angle(axis); - this->wrap(x); + wrap(x); } @@ -383,80 +347,20 @@ void colvar::spin_angle::calc_gradients() { cvm::quaternion const dxdq = rot.dspin_angle_dq(axis); + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; for (size_t ia = 0; ia < atoms->size(); ia++) { (*atoms)[ia].grad = cvm::rvector(0.0, 0.0, 0.0); + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); for (size_t iq = 0; iq < 4; iq++) { - (*atoms)[ia].grad += (dxdq[iq] * (rot.dQ0_2[ia])[iq]); + (*atoms)[ia].grad += (dxdq[iq] * dq0_2[iq]); } } } -void colvar::spin_angle::apply_force(colvarvalue const &force) -{ - cvm::real const &fw = force.real_value; - - if (!atoms->noforce) { - atoms->apply_colvar_force(fw); - } -} - - -cvm::real colvar::spin_angle::dist2(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return diff * diff; -} - - -colvarvalue colvar::spin_angle::dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return 2.0 * diff; -} - - -colvarvalue colvar::spin_angle::dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return (-2.0) * diff; -} - - -void colvar::spin_angle::wrap(colvarvalue &x_unwrapped) const -{ - if ((x_unwrapped.real_value - wrap_center) >= 180.0) { - x_unwrapped.real_value -= 360.0; - return; - } - - if ((x_unwrapped.real_value - wrap_center) < -180.0) { - x_unwrapped.real_value += 360.0; - return; - } - - return; -} - - -colvar::euler_phi::euler_phi(std::string const &conf) - : orientation() -{ - set_function_type("eulerPhi"); - init_as_periodic_angle(); - enable(f_cvc_explicit_gradient); - euler_phi::init(conf); -} - colvar::euler_phi::euler_phi() - : orientation() { set_function_type("eulerPhi"); init_as_periodic_angle(); @@ -464,19 +368,12 @@ colvar::euler_phi::euler_phi() } -int colvar::euler_phi::init(std::string const &conf) -{ - int error_code = COLVARS_OK; - error_code |= orientation::init(conf); - return error_code; -} - - void colvar::euler_phi::calc_value() { atoms_cog = atoms->center_of_geometry(); - rot.calc_optimal_rotation(ref_pos, atoms->positions_shifted(-1.0 * atoms_cog)); + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); const cvm::real& q0 = rot.q.q0; const cvm::real& q1 = rot.q.q1; @@ -499,79 +396,20 @@ void colvar::euler_phi::calc_gradients() const cvm::real dxdq1 = (180.0/PI) * (2 * q0 * (-2 * q1 * q1 - 2 * q2 * q2 + 1) - 4 * q1 * (-2 * q0 * q1 - 2 * q2 * q3)) / denominator; const cvm::real dxdq2 = (180.0/PI) * (-4 * q2 * (-2 * q0 * q1 - 2 * q2 * q3) + 2 * q3 * (-2 * q1 * q1 - 2 * q2 * q2 + 1)) / denominator; const cvm::real dxdq3 = (180.0/PI) * 2 * q2 * (-2 * q1 * q1 - 2 * q2 * q2 + 1) / denominator; + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; for (size_t ia = 0; ia < atoms->size(); ia++) { - (*atoms)[ia].grad = (dxdq0 * (rot.dQ0_2[ia])[0]) + - (dxdq1 * (rot.dQ0_2[ia])[1]) + - (dxdq2 * (rot.dQ0_2[ia])[2]) + - (dxdq3 * (rot.dQ0_2[ia])[3]); + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); + (*atoms)[ia].grad = (dxdq0 * dq0_2[0]) + + (dxdq1 * dq0_2[1]) + + (dxdq2 * dq0_2[2]) + + (dxdq3 * dq0_2[3]); } } -void colvar::euler_phi::apply_force(colvarvalue const &force) -{ - cvm::real const &fw = force.real_value; - if (!atoms->noforce) { - atoms->apply_colvar_force(fw); - } -} - - -cvm::real colvar::euler_phi::dist2(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return diff * diff; -} - - -colvarvalue colvar::euler_phi::dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return 2.0 * diff; -} - - -colvarvalue colvar::euler_phi::dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return (-2.0) * diff; -} - - -void colvar::euler_phi::wrap(colvarvalue &x_unwrapped) const -{ - if ((x_unwrapped.real_value - wrap_center) >= 180.0) { - x_unwrapped.real_value -= 360.0; - return; - } - - if ((x_unwrapped.real_value - wrap_center) < -180.0) { - x_unwrapped.real_value += 360.0; - return; - } - - return; -} - - -colvar::euler_psi::euler_psi(std::string const &conf) - : orientation() -{ - set_function_type("eulerPsi"); - init_as_periodic_angle(); - enable(f_cvc_explicit_gradient); - euler_psi::init(conf); -} - colvar::euler_psi::euler_psi() - : orientation() { set_function_type("eulerPsi"); init_as_periodic_angle(); @@ -579,19 +417,12 @@ colvar::euler_psi::euler_psi() } -int colvar::euler_psi::init(std::string const &conf) -{ - int error_code = COLVARS_OK; - error_code |= orientation::init(conf); - return error_code; -} - - void colvar::euler_psi::calc_value() { atoms_cog = atoms->center_of_geometry(); - rot.calc_optimal_rotation(ref_pos, atoms->positions_shifted(-1.0 * atoms_cog)); + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); const cvm::real& q0 = rot.q.q0; const cvm::real& q1 = rot.q.q1; @@ -614,79 +445,20 @@ void colvar::euler_psi::calc_gradients() const cvm::real dxdq1 = (180.0/PI) * 2 * q2 * (-2 * q2 * q2 - 2 * q3 * q3 + 1) / denominator; const cvm::real dxdq2 = (180.0/PI) * (2 * q1 * (-2 * q2 * q2 - 2 * q3 * q3 + 1) - 4 * q2 * (-2 * q0 * q3 - 2 * q1 * q2)) / denominator; const cvm::real dxdq3 = (180.0/PI) * (2 * q0 * (-2 * q2 * q2 - 2 * q3 * q3 + 1) - 4 * q3 * (-2 * q0 * q3 - 2 * q1 * q2)) / denominator; + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; for (size_t ia = 0; ia < atoms->size(); ia++) { - (*atoms)[ia].grad = (dxdq0 * (rot.dQ0_2[ia])[0]) + - (dxdq1 * (rot.dQ0_2[ia])[1]) + - (dxdq2 * (rot.dQ0_2[ia])[2]) + - (dxdq3 * (rot.dQ0_2[ia])[3]); + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); + (*atoms)[ia].grad = (dxdq0 * dq0_2[0]) + + (dxdq1 * dq0_2[1]) + + (dxdq2 * dq0_2[2]) + + (dxdq3 * dq0_2[3]); } } -void colvar::euler_psi::apply_force(colvarvalue const &force) -{ - cvm::real const &fw = force.real_value; - if (!atoms->noforce) { - atoms->apply_colvar_force(fw); - } -} - - -cvm::real colvar::euler_psi::dist2(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return diff * diff; -} - - -colvarvalue colvar::euler_psi::dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return 2.0 * diff; -} - - -colvarvalue colvar::euler_psi::dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - cvm::real diff = x1.real_value - x2.real_value; - diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); - return (-2.0) * diff; -} - - -void colvar::euler_psi::wrap(colvarvalue &x_unwrapped) const -{ - if ((x_unwrapped.real_value - wrap_center) >= 180.0) { - x_unwrapped.real_value -= 360.0; - return; - } - - if ((x_unwrapped.real_value - wrap_center) < -180.0) { - x_unwrapped.real_value += 360.0; - return; - } - - return; -} - - -colvar::euler_theta::euler_theta(std::string const &conf) - : orientation() -{ - set_function_type("eulerTheta"); - init_as_angle(); - enable(f_cvc_explicit_gradient); - euler_theta::init(conf); -} - colvar::euler_theta::euler_theta() - : orientation() { set_function_type("eulerTheta"); init_as_angle(); @@ -694,19 +466,12 @@ colvar::euler_theta::euler_theta() } -int colvar::euler_theta::init(std::string const &conf) -{ - int error_code = COLVARS_OK; - error_code |= orientation::init(conf); - return error_code; -} - - void colvar::euler_theta::calc_value() { atoms_cog = atoms->center_of_geometry(); - rot.calc_optimal_rotation(ref_pos, atoms->positions_shifted(-1.0 * atoms_cog)); + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); const cvm::real& q0 = rot.q.q0; const cvm::real& q1 = rot.q.q1; @@ -727,43 +492,13 @@ void colvar::euler_theta::calc_gradients() const cvm::real dxdq1 = (180.0/PI) * -2 * q3 / denominator; const cvm::real dxdq2 = (180.0/PI) * 2 * q0 / denominator; const cvm::real dxdq3 = (180.0/PI) * -2 * q1 / denominator; + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; for (size_t ia = 0; ia < atoms->size(); ia++) { - (*atoms)[ia].grad = (dxdq0 * (rot.dQ0_2[ia])[0]) + - (dxdq1 * (rot.dQ0_2[ia])[1]) + - (dxdq2 * (rot.dQ0_2[ia])[2]) + - (dxdq3 * (rot.dQ0_2[ia])[3]); + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); + (*atoms)[ia].grad = (dxdq0 * dq0_2[0]) + + (dxdq1 * dq0_2[1]) + + (dxdq2 * dq0_2[2]) + + (dxdq3 * dq0_2[3]); } } - - -void colvar::euler_theta::apply_force(colvarvalue const &force) -{ - cvm::real const &fw = force.real_value; - if (!atoms->noforce) { - atoms->apply_colvar_force(fw); - } -} - - -cvm::real colvar::euler_theta::dist2(colvarvalue const &x1, - colvarvalue const &x2) const -{ - // theta angle is not periodic - return cvc::dist2(x1, x2); -} - - -colvarvalue colvar::euler_theta::dist2_lgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - // theta angle is not periodic - return cvc::dist2_lgrad(x1, x2); -} - - -colvarvalue colvar::euler_theta::dist2_rgrad(colvarvalue const &x1, - colvarvalue const &x2) const -{ - // theta angle is not periodic - return cvc::dist2_rgrad(x1, x2); -} diff --git a/lib/colvars/colvarcomp_volmaps.cpp b/lib/colvars/colvarcomp_volmaps.cpp index 00c7206bc1..a15501ab6a 100644 --- a/lib/colvars/colvarcomp_volmaps.cpp +++ b/lib/colvars/colvarcomp_volmaps.cpp @@ -9,35 +9,18 @@ #include "colvarmodule.h" #include "colvarvalue.h" -#include "colvarparse.h" #include "colvar.h" #include "colvarcomp.h" colvar::map_total::map_total() - : cvc() { set_function_type("mapTotal"); - volmap_id = -1; - volmap_index = -1; - atoms = NULL; x.type(colvarvalue::type_scalar); } -colvar::map_total::map_total(std::string const &conf) - : cvc() // init() will take care of this -{ - set_function_type("mapTotal"); - volmap_id = -1; - volmap_index = -1; - atoms = NULL; - x.type(colvarvalue::type_scalar); - map_total::init(conf); -} - - int colvar::map_total::init(std::string const &conf) { int error_code = cvc::init(conf); @@ -50,12 +33,12 @@ int colvar::map_total::init(std::string const &conf) if ((volmap_name.size() > 0) && (volmap_id >= 0)) { error_code |= - cvm::error("Error: mapName and mapID are mutually exclusive.\n"); + cvm::error("Error: mapName and mapID are mutually exclusive.\n", COLVARS_INPUT_ERROR); } // Parse optional group atoms = parse_group(conf, "atoms", true); - if (atoms != NULL) { + if (atoms) { // Using internal selection if (volmap_name.size()) { @@ -74,11 +57,11 @@ int colvar::map_total::init(std::string const &conf) if (volmap_id >= 0) { volmap_index = proxy->init_volmap_by_id(volmap_id); } - error_code |= volmap_index > 0 ? COLVARS_OK : COLVARS_INPUT_ERROR; + error_code |= (volmap_index >= 0) ? COLVARS_OK : COLVARS_INPUT_ERROR; } if (get_keyval(conf, "atomWeights", atom_weights, atom_weights)) { - if (atoms == NULL) { + if (!atoms) { error_code |= cvm::error("Error: weights can only be assigned when atoms " "are selected explicitly in Colvars.\n", COLVARS_INPUT_ERROR); @@ -133,11 +116,10 @@ void colvar::map_total::calc_gradients() void colvar::map_total::apply_force(colvarvalue const &force) { - colvarproxy *proxy = cvm::main()->proxy; if (atoms) { - if (!atoms->noforce) - atoms->apply_colvar_force(force.real_value); + cvc::apply_force(force); } else { + colvarproxy *proxy = cvm::main()->proxy; proxy->apply_volmap_force(volmap_index, force.real_value); } } diff --git a/lib/colvars/colvardeps.cpp b/lib/colvars/colvardeps.cpp index 2a22b1cb57..46b7917569 100644 --- a/lib/colvars/colvardeps.cpp +++ b/lib/colvars/colvardeps.cpp @@ -129,6 +129,11 @@ int colvardeps::enable(int feature_id, int res; size_t i, j; bool ok; + + if (feature_id < 0 || feature_id >= int(features().size())) { + cvm::error("Error: colvardeps::enable() called with invalid feature_id " + cvm::to_str(feature_id) + "\n"); + return COLVARS_ERROR; + } feature *f = features()[feature_id]; feature_state *fs = &feature_states[feature_id]; diff --git a/lib/colvars/colvardeps.h b/lib/colvars/colvardeps.h index cfc93d0514..1bd304b545 100644 --- a/lib/colvars/colvardeps.h +++ b/lib/colvars/colvardeps.h @@ -253,6 +253,8 @@ public: f_cvb_write_ti_pmf, /// \brief whether this bias uses an external grid to scale the biasing forces f_cvb_scale_biasing_force, + /// \brief whether this bias is applied to one or more ext-Lagrangian colvars + f_cvb_extended, f_cvb_ntot }; @@ -355,6 +357,8 @@ public: f_cvc_lower_boundary, /// This CVC provides a default value for the colvar's upper boundary f_cvc_upper_boundary, + /// CVC accesses atom groups directly (as opposed to going throuh other objects) + f_cvc_explicit_atom_groups, /// CVC calculates atom gradients f_cvc_gradient, /// CVC calculates and stores explicit atom gradients on rank 0 diff --git a/lib/colvars/colvargrid.cpp b/lib/colvars/colvargrid.cpp index 2fd385ccec..11693a7587 100644 --- a/lib/colvars/colvargrid.cpp +++ b/lib/colvars/colvargrid.cpp @@ -8,13 +8,11 @@ // Colvars repository at GitHub. #include -#include #include "colvarmodule.h" #include "colvarvalue.h" #include "colvarparse.h" #include "colvar.h" -#include "colvarcomp.h" #include "colvargrid.h" #include "colvargrid_def.h" @@ -37,6 +35,58 @@ colvar_grid_count::colvar_grid_count(std::vector &colvars, : colvar_grid(colvars, def_count, 1, margin) {} +std::string colvar_grid_count::get_state_params() const +{ + return colvar_grid::get_state_params(); +} + +int colvar_grid_count::parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode) +{ + return colvar_grid::parse_params(conf, parse_mode); +} + +std::istream & colvar_grid_count::read_restart(std::istream &is) +{ + return colvar_grid::read_restart(is); +} + +cvm::memory_stream & colvar_grid_count::read_restart(cvm::memory_stream &is) +{ + return colvar_grid::read_restart(is); +} + +std::ostream & colvar_grid_count::write_restart(std::ostream &os) +{ + return colvar_grid::write_restart(os); +} + +cvm::memory_stream & colvar_grid_count::write_restart(cvm::memory_stream &os) +{ + return colvar_grid::write_restart(os); +} + +std::istream &colvar_grid_count::read_raw(std::istream &is) +{ + return colvar_grid::read_raw(is); +} + +cvm::memory_stream &colvar_grid_count::read_raw(cvm::memory_stream &is) +{ + return colvar_grid::read_raw(is); +} + +std::ostream &colvar_grid_count::write_raw(std::ostream &os, size_t const buf_size) const +{ + return colvar_grid::write_raw(os, buf_size); +} + +cvm::memory_stream &colvar_grid_count::write_raw(cvm::memory_stream &os, + size_t const buf_size) const +{ + return colvar_grid::write_raw(os, buf_size); +} + std::istream & colvar_grid_count::read_multicol(std::istream &is, bool add) { return colvar_grid::read_multicol(is, add); @@ -96,6 +146,58 @@ colvar_grid_scalar::~colvar_grid_scalar() { } +std::string colvar_grid_scalar::get_state_params() const +{ + return colvar_grid::get_state_params(); +} + +int colvar_grid_scalar::parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode) +{ + return colvar_grid::parse_params(conf, parse_mode); +} + +std::istream &colvar_grid_scalar::read_restart(std::istream &is) +{ + return colvar_grid::read_restart(is); +} + +cvm::memory_stream &colvar_grid_scalar::read_restart(cvm::memory_stream &is) +{ + return colvar_grid::read_restart(is); +} + +std::ostream &colvar_grid_scalar::write_restart(std::ostream &os) +{ + return colvar_grid::write_restart(os); +} + +cvm::memory_stream &colvar_grid_scalar::write_restart(cvm::memory_stream &os) +{ + return colvar_grid::write_restart(os); +} + +std::istream &colvar_grid_scalar::read_raw(std::istream &is) +{ + return colvar_grid::read_raw(is); +} + +cvm::memory_stream &colvar_grid_scalar::read_raw(cvm::memory_stream &is) +{ + return colvar_grid::read_raw(is); +} + +std::ostream &colvar_grid_scalar::write_raw(std::ostream &os, size_t const buf_size) const +{ + return colvar_grid::write_raw(os, buf_size); +} + +cvm::memory_stream &colvar_grid_scalar::write_raw(cvm::memory_stream &os, + size_t const buf_size) const +{ + return colvar_grid::write_raw(os, buf_size); +} + std::istream & colvar_grid_scalar::read_multicol(std::istream &is, bool add) { return colvar_grid::read_multicol(is, add); @@ -195,30 +297,63 @@ cvm::real colvar_grid_scalar::entropy() const return bin_volume * sum; } +/// \brief Return the RMSD between this grid's data and another one +/// Grids must have the same dimensions. +cvm::real colvar_grid_scalar::grid_rmsd(colvar_grid_scalar const &other_grid) const +{ + if (other_grid.data.size() != this->data.size()) { + cvm::error("Error: trying to subtract two grids with " + "different size.\n"); + return -1.; + } + + cvm::real sum2 = 0.0; + + if (samples && other_grid.samples) { + for (size_t i = 0; i < data.size(); i++) { + size_t n = samples->get_value(i); + cvm::real us = n ? data[i] / n : 0.0; + n = other_grid.samples->get_value(i); + cvm::real them = n ? other_grid.data[i ] / n : 0.0; + cvm::real d = us - them; + sum2 += d*d; + } + } else { + for (size_t i = 0; i < data.size(); i++) { + cvm::real d = other_grid.data[i] - data[i]; + sum2 += d*d; + } + } + + return sqrt(sum2/this->data.size()); +} + colvar_grid_gradient::colvar_grid_gradient() - : colvar_grid(), - samples(NULL), - weights(NULL) + : colvar_grid(), samples(NULL), full_samples(0), min_samples(0) {} + colvar_grid_gradient::colvar_grid_gradient(std::vector const &nx_i) - : colvar_grid(nx_i, 0.0, nx_i.size()), - samples(NULL), - weights(NULL) + : colvar_grid(nx_i, 0.0, nx_i.size()), samples(NULL), full_samples(0), min_samples(0) {} + colvar_grid_gradient::colvar_grid_gradient(std::vector &colvars) - : colvar_grid(colvars, 0.0, colvars.size()), - samples(NULL), - weights(NULL) + : colvar_grid(colvars, 0.0, colvars.size()), samples(NULL), full_samples(0), min_samples(0) {} +colvar_grid_gradient::colvar_grid_gradient(std::vector &colvars, std::shared_ptr samples_in) + : colvar_grid(colvars, 0.0, colvars.size()), samples(samples_in), full_samples(0), min_samples(0) +{ + samples_in->has_parent_data = true; +} + + colvar_grid_gradient::colvar_grid_gradient(std::string &filename) : colvar_grid(), - samples(NULL), - weights(NULL) + samples(NULL) { std::istream &is = cvm::main()->proxy->input_stream(filename, "gradient file"); @@ -280,6 +415,57 @@ colvar_grid_gradient::colvar_grid_gradient(std::string &filename) cvm::main()->proxy->close_input_stream(filename); } +std::string colvar_grid_gradient::get_state_params() const +{ + return colvar_grid::get_state_params(); +} + +int colvar_grid_gradient::parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode) +{ + return colvar_grid::parse_params(conf, parse_mode); +} + +std::istream &colvar_grid_gradient::read_restart(std::istream &is) +{ + return colvar_grid::read_restart(is); +} + +cvm::memory_stream &colvar_grid_gradient::read_restart(cvm::memory_stream &is) +{ + return colvar_grid::read_restart(is); +} + +std::ostream &colvar_grid_gradient::write_restart(std::ostream &os) +{ + return colvar_grid::write_restart(os); +} + +cvm::memory_stream &colvar_grid_gradient::write_restart(cvm::memory_stream &os) +{ + return colvar_grid::write_restart(os); +} + +std::istream &colvar_grid_gradient::read_raw(std::istream &is) +{ + return colvar_grid::read_raw(is); +} + +cvm::memory_stream &colvar_grid_gradient::read_raw(cvm::memory_stream &is) +{ + return colvar_grid::read_raw(is); +} + +std::ostream &colvar_grid_gradient::write_raw(std::ostream &os, size_t const buf_size) const +{ + return colvar_grid::write_raw(os, buf_size); +} + +cvm::memory_stream &colvar_grid_gradient::write_raw(cvm::memory_stream &os, + size_t const buf_size) const +{ + return colvar_grid::write_raw(os, buf_size); +} std::istream & colvar_grid_gradient::read_multicol(std::istream &is, bool add) { @@ -371,9 +557,38 @@ void colvar_grid_gradient::write_1D_integral(std::ostream &os) } +/// \brief Return the RMSD between this grid's data and another one +/// Grids must have the same dimensions. +cvm::real colvar_grid_gradient::grid_rmsd(colvar_grid_gradient const &other_grid) const +{ + if (other_grid.multiplicity() != this->multiplicity()) { + cvm::error("Error: trying to subtract two grids with " + "different multiplicity.\n"); + return -1.; + } -integrate_potential::integrate_potential(std::vector &colvars, colvar_grid_gradient * gradients) + if (other_grid.data.size() != this->data.size()) { + cvm::error("Error: trying to subtract two grids with " + "different size.\n"); + return -1.; + } + + cvm::real sum2 = 0.0; + std::vector ix; + size_t imult; + for (ix = new_index(); index_ok(ix); incr(ix)) { + for (imult = 0; imult < this->multiplicity(); imult++) { + cvm::real d = this->value_output(ix, imult) - other_grid.value_output(ix, imult); + sum2 += d*d; + } + } + return sqrt(sum2/this->data.size()); +} + + +integrate_potential::integrate_potential(std::vector &colvars, std::shared_ptr gradients) : colvar_grid_scalar(colvars, true), + b_smoothed(false), gradients(gradients) { // parent class colvar_grid_scalar is constructed with margin option set to true @@ -402,8 +617,9 @@ integrate_potential::integrate_potential(std::vector &colvars, colvar_ } -integrate_potential::integrate_potential(colvar_grid_gradient * gradients) - : gradients(gradients) +integrate_potential::integrate_potential(std::shared_ptr gradients) + : b_smoothed(false), + gradients(gradients) { nd = gradients->num_variables(); nx = gradients->number_of_points_vec(); @@ -425,7 +641,7 @@ integrate_potential::integrate_potential(colvar_grid_gradient * gradients) } -int integrate_potential::integrate(const int itmax, const cvm::real &tol, cvm::real & err) +int integrate_potential::integrate(const int itmax, const cvm::real &tol, cvm::real & err, bool verbose) { int iter = 0; @@ -438,22 +654,24 @@ int integrate_potential::integrate(const int itmax, const cvm::real &tol, cvm::r } else { corr = 0.0; } - std::vector ix; // Iterate over valid indices in gradient grid for (ix = new_index(); gradients->index_ok(ix); incr(ix)) { set_value(ix, sum); - sum += (gradients->value_output(ix) - corr) * widths[0]; + cvm::real val = gradients->value_output_smoothed(ix, b_smoothed); + sum += (val - corr) * widths[0]; } if (index_ok(ix)) { // This will happen if non-periodic: then PMF grid has one extra bin wrt gradient grid + // If not, sum should be zero set_value(ix, sum); } } else if (nd <= 3) { nr_linbcg_sym(divergence, data, tol, itmax, iter, err); - cvm::log("Integrated in " + cvm::to_str(iter) + " steps, error: " + cvm::to_str(err) + "\n"); + if (verbose) + cvm::log("Integrated in " + cvm::to_str(iter) + " steps, error: " + cvm::to_str(err)); } else { cvm::error("Cannot integrate PMF in dimension > 3\n"); @@ -477,7 +695,7 @@ void integrate_potential::update_div_neighbors(const std::vector &ix0) std::vector ix(ix0); int i, j, k; - // If not periodic, expanded grid ensures that neighbors of ix0 are valid grid points + // If not periodic, expanded grid ensures that upper neighbors of ix0 are valid grid points if (nd == 1) { return; @@ -509,30 +727,23 @@ void integrate_potential::update_div_neighbors(const std::vector &ix0) } } + void integrate_potential::get_grad(cvm::real * g, std::vector &ix) { - size_t count, i; - bool edge = gradients->wrap_edge(ix); // Detect edge if non-PBC + size_t i; + bool edge = gradients->wrap_detect_edge(ix); // Detect edge if non-PBC - if (gradients->samples) { - count = gradients->samples->value(ix); - } else { - count = 1; + if (edge) { + for ( i = 0; ivalue(ix)); - cvm::real const fact = 1.0 / count; - for ( i = 0; ivector_value_smoothed(ix, g, b_smoothed); } + void integrate_potential::update_div_local(const std::vector &ix0) { const size_t linear_index = address(ix0); diff --git a/lib/colvars/colvargrid.h b/lib/colvars/colvargrid.h index e2fc1e0fe2..4cbbb10961 100644 --- a/lib/colvars/colvargrid.h +++ b/lib/colvars/colvargrid.h @@ -10,21 +10,23 @@ #ifndef COLVARGRID_H #define COLVARGRID_H -#include -#include +#include +#include #include "colvar.h" #include "colvarmodule.h" #include "colvarvalue.h" #include "colvarparse.h" + /// \brief Grid of values of a function of several collective /// variables \param T The data type /// /// Only scalar colvars supported so far: vector colvars are treated as arrays template class colvar_grid : public colvarparse { -protected: + //protected: +public: // TODO create accessors for these after all instantiations work /// Number of dimensions size_t nd; @@ -374,7 +376,7 @@ public: { for (size_t i = 0; i < nd; i++) { if (periodic[i]) { - ix[i] = (ix[i] + nx[i]) % nx[i]; //to ensure non-negative result + ix[i] = (ix[i] + nx[i]) % nx[i]; // Avoid modulo with negative operands (implementation-defined) } else { if (ix[i] < 0 || ix[i] >= nx[i]) { cvm::error("Trying to wrap illegal index vector (non-PBC) for a grid point: " @@ -387,25 +389,58 @@ public: /// Wrap an index vector around periodic boundary conditions /// or detects edges if non-periodic - inline bool wrap_edge(std::vector & ix) const + inline bool wrap_detect_edge(std::vector & ix) const { bool edge = false; for (size_t i = 0; i < nd; i++) { if (periodic[i]) { - ix[i] = (ix[i] + nx[i]) % nx[i]; //to ensure non-negative result - } else if (ix[i] == -1 || ix[i] == nx[i]) { + ix[i] = (ix[i] + nx[i]) % nx[i]; // Avoid modulo with negative operands (implementation-defined) + } else if (ix[i] < 0 || ix[i] >= nx[i]) { edge = true; } } return edge; } + /// Wrap an index vector around periodic boundary conditions + /// or brings back to nearest edge if non-periodic + inline bool wrap_to_edge(std::vector & ix, std::vector & edge_bin) const + { + bool edge = false; + edge_bin = ix; + for (size_t i = 0; i < nd; i++) { + if (periodic[i]) { + ix[i] = (ix[i] + nx[i]) % nx[i]; // Avoid modulo with negative operands (implementation-defined) + edge_bin[i] = ix[i]; + } else if (ix[i] < 0) { + edge = true; + edge_bin[i] = 0; + } else if (ix[i] >= nx[i]) { + edge = true; + edge_bin[i] = nx[i] - 1; + } + } + return edge; + } + + /// \brief Report the bin corresponding to the current value of variable i inline int current_bin_scalar(int const i) const { return value_to_bin_scalar(use_actual_value[i] ? cv[i]->actual_value() : cv[i]->value(), i); } + /// \brief Report the flattened bin address corresponding to the current value of all variables + /// and assign first or last bin if out of boundaries + inline int current_bin_flat_bound() const + { + std::vector index = new_index(); + for (size_t i = 0; i < nd; i++) { + index[i] = current_bin_scalar_bound(i); + } + return address(index); + } + /// \brief Report the bin corresponding to the current value of variable i /// and assign first or last bin if out of boundaries inline int current_bin_scalar_bound(int const i) const @@ -447,6 +482,9 @@ public: inline int value_to_bin_scalar_bound(colvarvalue const &value, const int i) const { int bin_index = cvm::floor( (value.real_value - lower_boundaries[i].real_value) / widths[i] ); + + // Wrap bins for periodic dimensions before truncating + if (periodic[i]) bin_index %= nx[i]; if (bin_index < 0) bin_index=0; if (bin_index >=int(nx[i])) bin_index=int(nx[i])-1; return (int) bin_index; @@ -490,6 +528,13 @@ public: data[i] = t; } + /// Get the value at the point with linear address i (for speed) + inline T get_value(size_t i) const + { + return data[i]; + } + + /// \brief Get the change from this to other_grid /// and store the result in this. /// this_grid := other_grid - this_grid @@ -515,6 +560,7 @@ public: has_data = true; } + /// \brief Copy data from another grid of the same type, AND /// identical definition (boundaries, widths) /// Added for shared ABF. @@ -753,6 +799,7 @@ public: has_data = true; } + // /// Get the pointer to the binned value indexed by ix // inline T const *value_p (std::vector const &ix) // { @@ -803,109 +850,12 @@ public: } } - /// Write the grid parameters (number of colvars, boundaries, width and number of points) - std::ostream & write_params(std::ostream &os) - { - size_t i; - os << "grid_parameters {\n n_colvars " << nd << "\n"; + /// Write the current grid parameters to a string + std::string get_state_params() const; - os << " lower_boundaries "; - for (i = 0; i < nd; i++) - os << " " << lower_boundaries[i]; - os << "\n"; - - os << " upper_boundaries "; - for (i = 0; i < nd; i++) - os << " " << upper_boundaries[i]; - os << "\n"; - - os << " widths "; - for (i = 0; i < nd; i++) - os << " " << widths[i]; - os << "\n"; - - os << " sizes "; - for (i = 0; i < nd; i++) - os << " " << nx[i]; - os << "\n"; - - os << "}\n"; - return os; - } - - /// Read a grid definition from a config string + /// Read new grid parameters from a string int parse_params(std::string const &conf, - colvarparse::Parse_Mode const parse_mode = colvarparse::parse_normal) - { - if (cvm::debug()) cvm::log("Reading grid configuration from string.\n"); - - std::vector old_nx = nx; - std::vector old_lb = lower_boundaries; - std::vector old_ub = upper_boundaries; - std::vector old_w = widths; - - { - size_t nd_in = 0; - // this is only used in state files - colvarparse::get_keyval(conf, "n_colvars", nd_in, nd, colvarparse::parse_silent); - if (nd_in != nd) { - cvm::error("Error: trying to read data for a grid " - "that contains a different number of colvars ("+ - cvm::to_str(nd_in)+") than the grid defined " - "in the configuration file("+cvm::to_str(nd)+ - ").\n"); - return COLVARS_ERROR; - } - } - - // underscore keywords are used in state file - colvarparse::get_keyval(conf, "lower_boundaries", - lower_boundaries, lower_boundaries, colvarparse::parse_silent); - colvarparse::get_keyval(conf, "upper_boundaries", - upper_boundaries, upper_boundaries, colvarparse::parse_silent); - - // camel case keywords are used in config file - colvarparse::get_keyval(conf, "lowerBoundaries", - lower_boundaries, lower_boundaries, parse_mode); - colvarparse::get_keyval(conf, "upperBoundaries", - upper_boundaries, upper_boundaries, parse_mode); - - colvarparse::get_keyval(conf, "widths", widths, widths, parse_mode); - - // only used in state file - colvarparse::get_keyval(conf, "sizes", nx, nx, colvarparse::parse_silent); - - if (nd < lower_boundaries.size()) nd = lower_boundaries.size(); - - if (! use_actual_value.size()) use_actual_value.assign(nd, false); - if (! periodic.size()) periodic.assign(nd, false); - if (! widths.size()) widths.assign(nd, 1.0); - - cvm::real eps = 1.e-10; - - bool new_params = false; - if (old_nx.size()) { - for (size_t i = 0; i < nd; i++) { - if (old_nx[i] != nx[i] || - cvm::sqrt(cv[i]->dist2(old_lb[i], lower_boundaries[i])) > eps || - cvm::sqrt(cv[i]->dist2(old_ub[i], upper_boundaries[i])) > eps || - cvm::fabs(old_w[i] - widths[i]) > eps) { - new_params = true; - } - } - } else { - new_params = true; - } - - // reallocate the array in case the grid params have just changed - if (new_params) { - init_from_boundaries(); - // data.clear(); // no longer needed: setup calls clear() - return this->setup(nx, T(), mult); - } - - return COLVARS_OK; - } + colvarparse::Parse_Mode const parse_mode = colvarparse::parse_normal); /// \brief Check that the grid information inside (boundaries, /// widths, ...) is consistent with the current setting of the @@ -950,83 +900,33 @@ public: } } + /// Read all grid parameters and data from a formatted stream + std::istream & read_restart(std::istream &is); - /// \brief Read grid entry in restart file - std::istream & read_restart(std::istream &is) - { - std::streampos const start_pos = is.tellg(); - std::string key, conf; - if ((is >> key) && (key == std::string("grid_parameters"))) { - is.seekg(start_pos, std::ios::beg); - is >> colvarparse::read_block("grid_parameters", &conf); - parse_params(conf, colvarparse::parse_silent); - } else { - cvm::log("Grid parameters are missing in the restart file, using those from the configuration.\n"); - is.seekg(start_pos, std::ios::beg); - } - read_raw(is); - return is; - } + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream & read_restart(cvm::memory_stream &is); - /// \brief Write grid entry in restart file - std::ostream & write_restart(std::ostream &os) - { - write_params(os); - write_raw(os); - return os; - } + /// Write all grid parameters and data to a formatted stream + std::ostream & write_restart(std::ostream &os); + /// Write all grid parameters and data to an unformatted stream + cvm::memory_stream & write_restart(cvm::memory_stream &os); - /// \brief Write the grid data without labels, as they are - /// represented in memory - /// \param buf_size Number of values per line - std::ostream & write_raw(std::ostream &os, - size_t const buf_size = 3) const - { - std::streamsize const w = os.width(); - std::streamsize const p = os.precision(); + /// Read all grid parameters and data from a formatted stream + std::istream &read_raw(std::istream &is); - std::vector ix = new_index(); - size_t count = 0; - for ( ; index_ok(ix); incr(ix)) { - for (size_t imult = 0; imult < mult; imult++) { - os << " " - << std::setw(w) << std::setprecision(p) - << value_output(ix, imult); - if (((++count) % buf_size) == 0) - os << "\n"; - } - } - // write a final newline only if buffer is not empty - if ((count % buf_size) != 0) - os << "\n"; + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream &read_raw(cvm::memory_stream &is); - return os; - } + /// Write all grid data to a formatted stream (without labels, as they are represented in memory) + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line + std::ostream &write_raw(std::ostream &os, size_t const buf_size = 3) const; - /// \brief Read data written by colvar_grid::write_raw() - std::istream & read_raw(std::istream &is) - { - std::streampos const start_pos = is.tellg(); - - for (std::vector ix = new_index(); index_ok(ix); incr(ix)) { - for (size_t imult = 0; imult < mult; imult++) { - T new_value; - if (is >> new_value) { - value_input(ix, new_value, imult); - } else { - is.clear(); - is.seekg(start_pos, std::ios::beg); - is.setstate(std::ios::failbit); - cvm::error("Error: failed to read all of the grid points from file. Possible explanations: grid parameters in the configuration (lowerBoundary, upperBoundary, width) are different from those in the file, or the file is corrupt/incomplete.\n"); - return is; - } - } - } - - has_data = true; - return is; - } + /// Write all grid data to an unformatted stream + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line (note: ignored because there is no formatting) + cvm::memory_stream &write_raw(cvm::memory_stream &os, size_t const buf_size = 3) const; /// Read a grid written by write_multicol(), incrementing if add is true std::istream & read_multicol(std::istream &is, bool add = false); @@ -1082,16 +982,50 @@ public: } /// \brief Get the binned count indexed by ix from the newly read data - inline size_t const & new_count(std::vector const &ix, - size_t const &imult = 0) + inline size_t const & new_value(std::vector const &ix) { - return new_data[address(ix) + imult]; + return new_data[address(ix)]; } + /// Write the current grid parameters to a string + std::string get_state_params() const; + + /// Read new grid parameters from a string + int parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode = colvarparse::parse_normal); + + /// Read all grid parameters and data from a formatted stream + std::istream & read_restart(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream & read_restart(cvm::memory_stream &is); + + /// Write all grid parameters and data to a formatted stream + std::ostream & write_restart(std::ostream &os); + + /// Write all grid parameters and data to an unformatted stream + cvm::memory_stream & write_restart(cvm::memory_stream &os); + + /// Read all grid parameters and data from a formatted stream + std::istream &read_raw(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream &read_raw(cvm::memory_stream &is); + + /// Write all grid data to a formatted stream (without labels, as they are represented in memory) + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line + std::ostream &write_raw(std::ostream &os, size_t const buf_size = 3) const; + + /// Write all grid data to an unformatted stream + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line (note: ignored because there is no formatting) + cvm::memory_stream &write_raw(cvm::memory_stream &os, size_t const buf_size = 3) const; + /// Read a grid written by write_multicol(), incrementin if data is true std::istream & read_multicol(std::istream &is, bool add = false); - /// Read a grid written by write_multicol(), incrementin if data is true + /// Read a grid written by write_multicol(), incrementing if add is true int read_multicol(std::string const &filename, std::string description = "grid file", bool add = false); @@ -1129,10 +1063,88 @@ public: has_data = true; } + /// \brief Return the average number of samples in a given "radius" around current bin + /// Really a hypercube of length 2*radius + 1 + inline int local_sample_count(int radius) + { + std::vector ix0 = new_index(); + std::vector ix = new_index(); + + for (size_t i = 0; i < nd; i++) { + ix0[i] = current_bin_scalar_bound(i); + } + if (radius < 1) { + // Simple case: no averaging + if (index_ok(ix0)) + return value(ix0); + else + return 0; + } + size_t count = 0; + size_t nbins = 0; + int i, j, k; + bool edge; + ix = ix0; + // Treat each dimension separately to simplify code + switch (nd) + { + case 1: + for (i = -radius; i <= radius; i++) { + ix[0] = ix0[0] + i; + edge = wrap_detect_edge(ix); + if (!edge) { + nbins++; + count += value(ix); + } + } + break; + case 2: + for (i = -radius; i <= radius; i++) { + ix[0] = ix0[0] + i; + for (j = -radius; j <= radius; j++) { + ix[1] = ix0[1] + j; + edge = wrap_detect_edge(ix); + if (!edge) { + nbins++; + count += value(ix); + } + } + } + break; + case 3: + for (i = -radius; i <= radius; i++) { + ix[0] = ix0[0] + i; + for (j = -radius; j <= radius; j++) { + ix[1] = ix0[1] + j; + for (k = -radius; k <= radius; k++) { + ix[2] = ix0[2] + k; + edge = wrap_detect_edge(ix); + if (!edge) { + nbins++; + count += value(ix); + } + } + } + } + break; + default: + cvm::error("Error: local_sample_count is not implemented for grids of dimension > 3", COLVARS_NOT_IMPLEMENTED); + break; + } + + if (nbins) + // Integer division - an error on the order of 1 doesn't matter + return count / nbins; + else + return 0.0; + } + + /// \brief Return the log-gradient from finite differences /// on the *same* grid for dimension n + /// (colvar_grid_count) inline cvm::real log_gradient_finite_diff(const std::vector &ix0, - int n = 0) + int n = 0, int offset = 0) { cvm::real A0, A1, A2; std::vector ix = ix0; @@ -1140,10 +1152,10 @@ public: // TODO this can be rewritten more concisely with wrap_edge() if (periodic[n]) { ix[n]--; wrap(ix); - A0 = value(ix); + A0 = value(ix) + offset; ix = ix0; ix[n]++; wrap(ix); - A1 = value(ix); + A1 = value(ix) + offset; if (A0 * A1 == 0) { return 0.; // can't handle empty bins } else { @@ -1152,10 +1164,10 @@ public: } } else if (ix[n] > 0 && ix[n] < nx[n]-1) { // not an edge ix[n]--; - A0 = value(ix); + A0 = value(ix) + offset; ix = ix0; ix[n]++; - A1 = value(ix); + A1 = value(ix) + offset; if (A0 * A1 == 0) { return 0.; // can't handle empty bins } else { @@ -1166,9 +1178,9 @@ public: // edge: use 2nd order derivative int increment = (ix[n] == 0 ? 1 : -1); // move right from left edge, or the other way around - A0 = value(ix); - ix[n] += increment; A1 = value(ix); - ix[n] += increment; A2 = value(ix); + A0 = value(ix) + offset; + ix[n] += increment; A1 = value(ix) + offset; + ix[n] += increment; A2 = value(ix) + offset; if (A0 * A1 * A2 == 0) { return 0.; // can't handle empty bins } else { @@ -1178,8 +1190,10 @@ public: } } + /// \brief Return the gradient of discrete count from finite differences /// on the *same* grid for dimension n + /// (colvar_grid_count) inline cvm::real gradient_finite_diff(const std::vector &ix0, int n = 0) { @@ -1261,10 +1275,45 @@ public: has_data = true; } + /// Write the current grid parameters to a string + std::string get_state_params() const; + + /// Read new grid parameters from a string + int parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode = colvarparse::parse_normal); + + /// Read all grid parameters and data from a formatted stream + std::istream & read_restart(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream & read_restart(cvm::memory_stream &is); + + /// Write all grid parameters and data to a formatted stream + std::ostream & write_restart(std::ostream &os); + + /// Write all grid parameters and data to an unformatted stream + cvm::memory_stream & write_restart(cvm::memory_stream &os); + + /// Read all grid parameters and data from a formatted stream + std::istream &read_raw(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream &read_raw(cvm::memory_stream &is); + + /// Write all grid data to a formatted stream (without labels, as they are represented in memory) + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line + std::ostream &write_raw(std::ostream &os, size_t const buf_size = 3) const; + + /// Write all grid data to an unformatted stream + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line (note: ignored because there is no formatting) + cvm::memory_stream &write_raw(cvm::memory_stream &os, size_t const buf_size = 3) const; + /// Read a grid written by write_multicol(), incrementin if data is true std::istream & read_multicol(std::istream &is, bool add = false); - /// Read a grid written by write_multicol(), incrementin if data is true + /// Read a grid written by write_multicol(), incrementing if add is true int read_multicol(std::string const &filename, std::string description = "grid file", bool add = false); @@ -1336,8 +1385,61 @@ public: } } + + /// \brief Return the log-gradient from finite differences + /// on the *same* grid for dimension n + /// (colvar_grid_scalar) + inline cvm::real log_gradient_finite_diff(const std::vector &ix0, + int n = 0, int offset = 0) + { + cvm::real A0, A1, A2; + std::vector ix = ix0; + + // TODO this can be rewritten more concisely with wrap_edge() + if (periodic[n]) { + ix[n]--; wrap(ix); + A0 = value(ix) + offset; + ix = ix0; + ix[n]++; wrap(ix); + A1 = value(ix) + offset; + if (A0 * A1 == 0) { + return 0.; // can't handle empty bins + } else { + return (cvm::logn(A1) - cvm::logn(A0)) + / (widths[n] * 2.); + } + } else if (ix[n] > 0 && ix[n] < nx[n]-1) { // not an edge + ix[n]--; + A0 = value(ix) + offset; + ix = ix0; + ix[n]++; + A1 = value(ix) + offset; + if (A0 * A1 == 0) { + return 0.; // can't handle empty bins + } else { + return (cvm::logn(A1) - cvm::logn(A0)) + / (widths[n] * 2.); + } + } else { + // edge: use 2nd order derivative + int increment = (ix[n] == 0 ? 1 : -1); + // move right from left edge, or the other way around + A0 = value(ix) + offset; + ix[n] += increment; A1 = value(ix) + offset; + ix[n] += increment; A2 = value(ix) + offset; + if (A0 * A1 * A2 == 0) { + return 0.; // can't handle empty bins + } else { + return (-1.5 * cvm::logn(A0) + 2. * cvm::logn(A1) + - 0.5 * cvm::logn(A2)) * increment / widths[n]; + } + } + } + + /// \brief Return the gradient of discrete count from finite differences /// on the *same* grid for dimension n + /// (colvar_grid_scalar) inline cvm::real gradient_finite_diff(const std::vector &ix0, int n = 0) { @@ -1365,7 +1467,7 @@ public: if (A0 * A1 == 0) { return 0.; // can't handle empty bins } else { - return (A1 - A0) / (widths[n] * 2.); + return cvm::real(A1 - A0) / (widths[n] * 2.); } } else { // edge: use 2nd order derivative @@ -1374,27 +1476,29 @@ public: A0 = value(ix); ix[n] += increment; A1 = value(ix); ix[n] += increment; A2 = value(ix); - return (-1.5 * A0 + 2. * A1 - - 0.5 * A2) * increment / widths[n]; + return (-1.5 * cvm::real(A0) + 2. * cvm::real(A1) + - 0.5 * cvm::real(A2)) * increment / widths[n]; } } + /// \brief Return the value of the function at ix divided by its /// number of samples (if the count grid is defined) - virtual cvm::real value_output(std::vector const &ix, - size_t const &imult = 0) const + virtual inline cvm::real value_output(std::vector const &ix, + size_t const &imult = 0) const override { + int s; if (imult > 0) { cvm::error("Error: trying to access a component " "larger than 1 in a scalar data grid.\n"); return 0.; } if (samples) { - return (samples->value(ix) > 0) ? - (data[address(ix)] / cvm::real(samples->value(ix))) : + return ( (s = samples->value(ix)) > 0) ? + (data[address(ix) + imult] / cvm::real(s)) : 0.0; } else { - return data[address(ix)]; + return data[address(ix) + imult]; } } @@ -1402,7 +1506,7 @@ public: virtual void value_input(std::vector const &ix, cvm::real const &new_value, size_t const &imult = 0, - bool add = false) + bool add = false) override { if (imult > 0) { cvm::error("Error: trying to access a component " @@ -1411,7 +1515,7 @@ public: } if (add) { if (samples) - data[address(ix)] += new_value * samples->new_count(ix); + data[address(ix)] += new_value * samples->new_value(ix); else data[address(ix)] += new_value; } else { @@ -1438,6 +1542,10 @@ public: /// \brief Assuming that the map is a normalized probability density, /// calculates the entropy (uses widths if they are defined) cvm::real entropy() const; + + /// \brief Return the RMSD between this grid's data and another one + /// Grids must have the same dimensions. + cvm::real grid_rmsd(colvar_grid_scalar const &other_grid) const; }; @@ -1449,11 +1557,7 @@ public: /// \brief Provide the sample count by which each binned value /// should be divided - colvar_grid_count *samples; - - /// \brief Provide the floating point weights by which each binned value - /// should be divided (alternate to samples, only one should be non-null) - colvar_grid_scalar *weights; + std::shared_ptr samples; /// Default constructor colvar_grid_gradient(); @@ -1471,26 +1575,68 @@ public: /// Constructor from a multicol file colvar_grid_gradient(std::string &filename); - /// Read a grid written by write_multicol(), incrementin if data is true - virtual std::istream & read_multicol(std::istream &is, bool add = false); + /// Constructor from a vector of colvars and a pointer to the count grid + colvar_grid_gradient(std::vector &colvars, std::shared_ptr samples_in); + + /// Parameters for smoothing data with low sampling + int full_samples; + int min_samples; + + /// Write the current grid parameters to a string + std::string get_state_params() const; + + /// Read new grid parameters from a string + int parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode = colvarparse::parse_normal); + + /// Read all grid parameters and data from a formatted stream + std::istream & read_restart(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream & read_restart(cvm::memory_stream &is); + + /// Write all grid parameters and data to a formatted stream + std::ostream & write_restart(std::ostream &os); + + /// Write all grid parameters and data to an unformatted stream + cvm::memory_stream & write_restart(cvm::memory_stream &os); + + /// Read all grid parameters and data from a formatted stream + std::istream &read_raw(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream &read_raw(cvm::memory_stream &is); + + /// Write all grid data to a formatted stream (without labels, as they are represented in memory) + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line + std::ostream &write_raw(std::ostream &os, size_t const buf_size = 3) const; + + /// Write all grid data to an unformatted stream + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line (note: ignored because there is no formatting) + cvm::memory_stream &write_raw(cvm::memory_stream &os, size_t const buf_size = 3) const; /// Read a grid written by write_multicol(), incrementin if data is true - virtual int read_multicol(std::string const &filename, + std::istream & read_multicol(std::istream &is, bool add = false); + + /// Read a grid written by write_multicol(), incrementin if data is true + int read_multicol(std::string const &filename, std::string description = "grid file", bool add = false); /// Write grid in a format which is both human-readable and gnuplot-friendly - virtual std::ostream & write_multicol(std::ostream &os) const; + std::ostream & write_multicol(std::ostream &os) const; /// Write grid in a format which is both human-readable and gnuplot-friendly - virtual int write_multicol(std::string const &filename, + int write_multicol(std::string const &filename, std::string description = "grid file") const; /// Write the grid data without labels, as they are represented in memory - virtual std::ostream & write_opendx(std::ostream &os) const; + std::ostream & write_opendx(std::ostream &os) const; /// Write the grid data without labels, as they are represented in memory - virtual int write_opendx(std::string const &filename, + int write_opendx(std::string const &filename, std::string description = "grid file") const; /// \brief Get a vector with the binned value(s) indexed by ix, normalized if applicable @@ -1516,6 +1662,7 @@ public: } } + /// \brief Accumulate the value inline void acc_value(std::vector const &ix, std::vector const &values) { for (size_t imult = 0; imult < mult; imult++) { @@ -1535,28 +1682,89 @@ public: samples->incr_count(ix); } - /// \brief Accumulate the gradient based on the force (i.e. sums the - /// opposite of the force) with a non-integer weight - inline void acc_force_weighted(std::vector const &ix, - cvm::real const *forces, - cvm::real weight) { - for (size_t imult = 0; imult < mult; imult++) { - data[address(ix) + imult] -= forces[imult] * weight; - } - weights->acc_value(ix, weight); - } - /// \brief Return the value of the function at ix divided by its /// number of samples (if the count grid is defined) virtual cvm::real value_output(std::vector const &ix, - size_t const &imult = 0) const + size_t const &imult = 0) const override { - if (samples) - return (samples->value(ix) > 0) ? - (data[address(ix) + imult] / cvm::real(samples->value(ix))) : + int s; + if (samples) { + return ( (s = samples->value(ix)) > 0) ? + (data[address(ix) + imult] / cvm::real(s)) : 0.0; - else + } else { return data[address(ix) + imult]; + } + } + + /// Compute the inverse weight corresponding to smoothing factor as in ABF + /// to normalize sums over steps into averages + inline cvm::real smooth_inverse_weight(cvm::real weight) + { + cvm::real fact; + if ( weight <= min_samples ) { + fact = 0.0; + } else if ( weight < full_samples ) { + fact = (weight - min_samples) / (weight * cvm::real(full_samples - min_samples)); + } else { + fact = 1.0 / weight; + } + return fact; + } + + + /// \brief Return the scalar value of the function at ix divided by its + /// number of samples (if the count grid is defined), possibly smoothed + /// by a ramp function going from 0 to 1 between minSamples and fullSamples. + /// Only makes sense if dimension is 1 + virtual inline cvm::real value_output_smoothed(std::vector const &ix, bool smoothed = true) + { + cvm::real weight, fact; + + if (samples) { + weight = cvm::real(samples->value(ix)); + } else { + weight = 1.; + } + + if (smoothed) { + fact = smooth_inverse_weight(weight); + } else { + fact = weight > 0. ? 1. / weight : 0.; + } + + return fact * data[address(ix)]; + } + + /// \brief Obtain the vector value of the function at ix divided by its + /// number of samples (if the count grid is defined), possibly smoothed + /// by a ramp function going from 0 to 1 between minSamples and fullSamples. + inline void vector_value_smoothed(std::vector const &ix, cvm::real *grad, bool smoothed = true) + { + cvm::real weight, fact; + + if (samples) { + weight = cvm::real(samples->value(ix)); + } else { + weight = 1.; + } + + if (smoothed) { + fact = smooth_inverse_weight(weight); + } else { + fact = weight > 0. ? 1. / weight : 0.; + } + + cvm::real *p = &(data[address(ix)]); + + // Appease Clang analyzer, which likes to assume that mult is zero + #ifdef __clang_analyzer__ + assert(mult > 0); + #endif + + for (size_t imult = 0; imult < mult; imult++) { + grad[imult] = fact * p[imult]; + } } /// \brief Get the value from a formatted output and transform it @@ -1565,11 +1773,11 @@ public: virtual void value_input(std::vector const &ix, cvm::real const &new_value, size_t const &imult = 0, - bool add = false) + bool add = false) override { if (add) { if (samples) - data[address(ix) + imult] += new_value * samples->new_count(ix); + data[address(ix) + imult] += new_value * samples->new_value(ix); else data[address(ix) + imult] += new_value; } else { @@ -1583,31 +1791,26 @@ public: /// Compute and return average value for a 1D gradient grid - inline cvm::real average() + inline cvm::real average(bool smoothed = false) { - size_t n = 0; - if (nd != 1 || nx[0] == 0) { return 0.0; } cvm::real sum = 0.0; - std::vector ix = new_index(); - if (samples) { - for ( ; index_ok(ix); incr(ix)) { - if ( (n = samples->value(ix)) ) - sum += value(ix) / n; - } - } else { - for ( ; index_ok(ix); incr(ix)) { - sum += value(ix); - } + for (std::vector ix = new_index(); index_ok(ix); incr(ix)) { + sum += value_output_smoothed(ix, smoothed); } + return (sum / cvm::real(nx[0])); } + /// \brief Return the RMSD between this grid's data and another one + /// Grids must have the same dimensions. + cvm::real grid_rmsd(colvar_grid_gradient const &other_grid) const; + /// \brief If the grid is 1-dimensional, integrate it and write the - /// integral to a file + /// integral to a file (DEPRECATED by the integrate_potential class) void write_1D_integral(std::ostream &os); }; @@ -1626,18 +1829,22 @@ class integrate_potential : public colvar_grid_scalar {} /// Constructor from a vector of colvars + gradient grid - integrate_potential(std::vector &colvars, colvar_grid_gradient * gradients); + integrate_potential(std::vector &colvars, std::shared_ptr gradients); /// Constructor from a gradient grid (for processing grid files without a Colvars config) - integrate_potential(colvar_grid_gradient * gradients); + integrate_potential(std::shared_ptr gradients); /// \brief Calculate potential from divergence (in 2D); return number of steps - int integrate(const int itmax, const cvm::real & tol, cvm::real & err); + int integrate(const int itmax, const cvm::real & tol, cvm::real & err, bool verbose = true); /// \brief Update matrix containing divergence and boundary conditions /// based on new gradient point value, in neighboring bins void update_div_neighbors(const std::vector &ix); + /// \brief Update matrix containing divergence and boundary conditions + /// called by update_div_neighbors and by colvarbias_abf::adiabatic_reweighting_update_gradient_pmf + void update_div_local(const std::vector &ix); + /// \brief Set matrix containing divergence and boundary conditions /// based on complete gradient grid void set_div(); @@ -1648,20 +1855,20 @@ class integrate_potential : public colvar_grid_scalar add_constant(-1.0 * minimum_value()); } + /// \brief Flag requesting the use of a smoothed version of the gradient (default: false) + bool b_smoothed; + + protected: // Reference to gradient grid - colvar_grid_gradient *gradients; + std::shared_ptr gradients; /// Array holding divergence + boundary terms (modified Neumann) if not periodic std::vector divergence; // std::vector inv_lap_diag; // Inverse of the diagonal of the Laplacian; for conditioning - /// \brief Update matrix containing divergence and boundary conditions - /// called by update_div_neighbors - void update_div_local(const std::vector &ix); - /// Obtain the gradient vector at given location ix, if available /// or zero if it is on the edge of the gradient grid /// ix gets wrapped in PBC diff --git a/lib/colvars/colvargrid_def.h b/lib/colvars/colvargrid_def.h index f2245f3d81..fa6531271b 100644 --- a/lib/colvars/colvargrid_def.h +++ b/lib/colvars/colvargrid_def.h @@ -19,6 +19,233 @@ #include "colvarproxy.h" #include "colvar.h" #include "colvargrid.h" +#include "colvars_memstream.h" + + +template IST &read_restart_template_(colvar_grid &g, IST &is) +{ + auto const start_pos = is.tellg(); + std::string conf; + if ((is >> colvarparse::read_block("grid_parameters", &conf)) && + (g.parse_params(conf, colvarparse::parse_restart) == COLVARS_OK) && g.read_raw(is)) { + return is; + } + auto const error_pos = is.tellg(); + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + cvm::error("Error: in reading grid state from stream at position " + cvm::to_str(error_pos) + + "\n", + COLVARS_INPUT_ERROR); + return is; +} + + +template std::istream &colvar_grid::read_restart(std::istream &is) +{ + return read_restart_template_(*this, is); +} + + +template cvm::memory_stream &colvar_grid::read_restart(cvm::memory_stream &is) +{ + return read_restart_template_(*this, is); +} + + +template std::ostream &colvar_grid::write_restart(std::ostream &os) +{ + os << "grid_parameters {\n" << get_state_params() << "}\n"; + write_raw(os); + return os; +} + + +template cvm::memory_stream &colvar_grid::write_restart(cvm::memory_stream &os) +{ + os << std::string("grid_parameters") << get_state_params(); + write_raw(os); + return os; +} + + +template IST &read_raw_template_(colvar_grid &g, IST &is) +{ + auto const start_pos = is.tellg(); + + for (std::vector ix = g.new_index(); g.index_ok(ix); g.incr(ix)) { + for (size_t imult = 0; imult < g.mult; imult++) { + T new_value; + if (is >> new_value) { + g.value_input(ix, new_value, imult); + } else { + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + cvm::error( + "Error: failed to read all of the grid points from file. Possible explanations: grid " + "parameters in the configuration (lowerBoundary, upperBoundary, width) are different " + "from those in the file, or the file is corrupt/incomplete.\n", + COLVARS_INPUT_ERROR); + return is; + } + } + } + + g.has_data = true; + return is; +} + + +template std::istream &colvar_grid::read_raw(std::istream &is) +{ + return read_raw_template_(*this, is); +} + + +template cvm::memory_stream &colvar_grid::read_raw(cvm::memory_stream &is) +{ + return read_raw_template_(*this, is); +} + + +template +std::ostream &colvar_grid::write_raw(std::ostream &os, size_t const buf_size) const +{ + auto const w = os.width(); + auto const p = os.precision(); + + size_t count = 0; + for (auto ix = new_index(); index_ok(ix); incr(ix)) { + for (size_t imult = 0; imult < mult; imult++) { + os << " " << std::setw(w) << std::setprecision(p) << value_output(ix, imult); + if (((++count) % buf_size) == 0) + os << "\n"; + } + } + // write a final newline only if buffer is not empty + if ((count % buf_size) != 0) + os << "\n"; + + return os; +} + + +template +cvm::memory_stream &colvar_grid::write_raw(cvm::memory_stream &os, size_t const buf_size) const +{ + for (auto ix = new_index(); index_ok(ix); incr(ix)) { + for (size_t imult = 0; imult < mult; imult++) { + os << value_output(ix, imult); + } + } + return os; +} + + +template std::string colvar_grid::get_state_params() const +{ + std::ostringstream os; + size_t i; + os << " n_colvars " << nd << "\n"; + + os << " lower_boundaries "; + for (i = 0; i < nd; i++) + os << " " << lower_boundaries[i]; + os << "\n"; + + os << " upper_boundaries "; + for (i = 0; i < nd; i++) + os << " " << upper_boundaries[i]; + os << "\n"; + + os << " widths "; + for (i = 0; i < nd; i++) + os << " " << widths[i]; + os << "\n"; + + os << " sizes "; + for (i = 0; i < nd; i++) + os << " " << nx[i]; + os << "\n"; + + return os.str(); +} + + +template int colvar_grid::parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode) +{ + if (cvm::debug()) + cvm::log("Reading grid configuration from string.\n"); + + std::vector old_nx = nx; + std::vector old_lb = lower_boundaries; + std::vector old_ub = upper_boundaries; + std::vector old_w = widths; + + { + size_t nd_in = 0; + // this is only used in state files + colvarparse::get_keyval(conf, "n_colvars", nd_in, nd, colvarparse::parse_silent); + if (nd_in != nd) { + cvm::error("Error: trying to read data for a grid " + "that contains a different number of colvars ("+ + cvm::to_str(nd_in)+") than the grid defined " + "in the configuration file("+cvm::to_str(nd)+ + ").\n"); + return COLVARS_ERROR; + } + } + + // underscore keywords are used in state file + colvarparse::get_keyval(conf, "lower_boundaries", + lower_boundaries, lower_boundaries, colvarparse::parse_silent); + colvarparse::get_keyval(conf, "upper_boundaries", + upper_boundaries, upper_boundaries, colvarparse::parse_silent); + + // camel case keywords are used in config file + colvarparse::get_keyval(conf, "lowerBoundaries", + lower_boundaries, lower_boundaries, parse_mode); + colvarparse::get_keyval(conf, "upperBoundaries", + upper_boundaries, upper_boundaries, parse_mode); + + colvarparse::get_keyval(conf, "widths", widths, widths, parse_mode); + + // only used in state file + colvarparse::get_keyval(conf, "sizes", nx, nx, colvarparse::parse_silent); + + if (nd < lower_boundaries.size()) nd = lower_boundaries.size(); + + if (! use_actual_value.size()) use_actual_value.assign(nd, false); + if (! periodic.size()) periodic.assign(nd, false); + if (! widths.size()) widths.assign(nd, 1.0); + + cvm::real eps = 1.e-10; + + bool new_params = false; + if (old_nx.size()) { + for (size_t i = 0; i < nd; i++) { + if (old_nx[i] != nx[i] || + cvm::sqrt(cv[i]->dist2(old_lb[i], lower_boundaries[i])) > eps || + cvm::sqrt(cv[i]->dist2(old_ub[i], upper_boundaries[i])) > eps || + cvm::fabs(old_w[i] - widths[i]) > eps) { + new_params = true; + } + } + } else { + new_params = true; + } + + // reallocate the array in case the grid params have just changed + if (new_params) { + init_from_boundaries(); + // data.clear(); // no longer needed: setup calls clear() + return this->setup(nx, T(), mult); + } + + return COLVARS_OK; +} template @@ -90,6 +317,9 @@ std::istream & colvar_grid::read_multicol(std::istream &is, bool add) for (size_t i = 0; i < nd; i++ ) { if ( !(is >> x) ) end_of_file = true; bin[i] = value_to_bin_scalar(x, i); + // if x is out of bounds and we are using PBC, wrap it + // Ignore out of bounds points in non-PBC + wrap_detect_edge(bin); } if (end_of_file) break; diff --git a/lib/colvars/colvarmodule.cpp b/lib/colvars/colvarmodule.cpp index 68cd402e1c..25b1efe209 100644 --- a/lib/colvars/colvarmodule.cpp +++ b/lib/colvars/colvarmodule.cpp @@ -7,13 +7,10 @@ // If you wish to distribute your changes, please submit them to the // Colvars repository at GitHub. -#include #include -#include -#include -#include +#include +#include #include -#include #include "colvarmodule.h" #include "colvarparse.h" @@ -21,6 +18,7 @@ #include "colvar.h" #include "colvarbias.h" #include "colvarbias_abf.h" +#include "colvarbias_abmd.h" #include "colvarbias_alb.h" #include "colvarbias_histogram.h" #include "colvarbias_histogram_reweight_amd.h" @@ -29,7 +27,7 @@ #include "colvarscript.h" #include "colvaratoms.h" #include "colvarcomp.h" - +#include "colvars_memstream.h" /// Track usage of Colvars features @@ -69,6 +67,11 @@ protected: }; +namespace { + constexpr uint32_t colvars_magic_number = 2013813594; +} + + colvarmodule::colvarmodule(colvarproxy *proxy_in) { depth_s = 0; @@ -98,16 +101,14 @@ colvarmodule::colvarmodule(colvarproxy *proxy_in) version_int = proxy->get_version_from_string(COLVARS_VERSION); cvm::log(cvm::line_marker); - cvm::log("Initializing the collective variables module, version "+ - version()+".\n"); + cvm::log( + "Initializing the collective variables module, version " + version() + + (patch_version_number() ? (" (patch " + cvm::to_str(patch_version_number()) + ")") : "") + + ".\n"); cvm::log("Please cite Fiorin et al, Mol Phys 2013:\n" " https://doi.org/10.1080/00268976.2013.813594\n" "as well as all other papers listed below for individual features used.\n"); - if (proxy->smp_enabled() == COLVARS_OK) { - cvm::log("SMP parallelism is enabled; if needed, use \"smp off\" to override this.\n"); - } - #if (__cplusplus >= 201103L) cvm::log("This version was built with the C++11 standard or higher.\n"); #else @@ -116,8 +117,38 @@ colvarmodule::colvarmodule(colvarproxy *proxy_in) " https://colvars.github.io/README-c++11.html\n"); #endif + cvm::log("Summary of compile-time features available in this build:\n"); + + if (proxy->check_smp_enabled() == COLVARS_NOT_IMPLEMENTED) { + cvm::log(" - SMP parallelism: not available\n"); + } else { + if (proxy->check_smp_enabled() == COLVARS_OK) { + cvm::log(" - SMP parallelism: enabled (num. threads = " + to_str(proxy->smp_num_threads()) + ")\n"); + } else { + cvm::log(" - SMP parallelism: available, but not enabled\n"); + } + } + +#if defined(LEPTON) + cvm::log(" - Lepton custom functions: available\n"); +#else + cvm::log(" - Lepton custom functions: not available\n"); +#endif + +#if defined(COLVARS_TCL) + cvm::log(" - Tcl interpreter: available\n"); +#else + cvm::log(" - Tcl interpreter: not available\n"); +#endif + // set initial default values + binary_restart = false; + char const *env_var = getenv("COLVARS_BINARY_RESTART"); + if (env_var && atoi(env_var)) { + binary_restart = true; + } + // "it_restart" will be set by the input state file, if any; // "it" should be updated by the proxy colvarmodule::it = colvarmodule::it_restart = 0; @@ -182,6 +213,13 @@ size_t colvarmodule::size() const } +void colvarmodule::set_initial_step(step_number it_in) +{ + cvm::log("Setting initial step number from MD engine: " + cvm::to_str(it_in) + "\n"); + it = it_restart = it_in; +} + + int colvarmodule::read_config_file(char const *config_filename) { cvm::log(cvm::line_marker); @@ -327,6 +365,7 @@ void colvarmodule::config_changed() int colvarmodule::parse_global_params(std::string const &conf) { + int error_code = COLVARS_OK; // TODO document and then echo this keyword parse->get_keyval(conf, "logLevel", log_level_, log_level_, colvarparse::parse_silent); @@ -334,10 +373,7 @@ int colvarmodule::parse_global_params(std::string const &conf) std::string units; if (parse->get_keyval(conf, "units", units)) { units = colvarparse::to_lower_cppstr(units); - int error_code = proxy->set_unit_system(units, (colvars.size() != 0)); - if (error_code != COLVARS_OK) { - return error_code; - } + error_code |= proxy->set_unit_system(units, (colvars.size() != 0)); } } @@ -346,7 +382,7 @@ int colvarmodule::parse_global_params(std::string const &conf) size_t pos = 0; while (parse->key_lookup(conf, "indexFile", &index_file_name, &pos)) { cvm::log("# indexFile = \""+index_file_name+"\"\n"); - read_index_file(index_file_name.c_str()); + error_code |= read_index_file(index_file_name.c_str()); index_file_name.clear(); } } @@ -358,9 +394,8 @@ int colvarmodule::parse_global_params(std::string const &conf) } bool b_analysis = true; - if (parse->get_keyval(conf, "analysis", b_analysis, true, - colvarparse::parse_silent)) { - cvm::log("Warning: keyword \"analysis\" is deprecated: it is now set " + if (parse->get_keyval(conf, "analysis", b_analysis, true, colvarparse::parse_silent)) { + cvm::log("Warning: keyword \"analysis\" is deprecated: it is now always set " "to true; individual analyses are performed only if requested."); } @@ -391,7 +426,12 @@ int colvarmodule::parse_global_params(std::string const &conf) parse->get_keyval(conf, "sourceTclFile", source_Tcl_script); #endif - return cvm::get_error(); + if (proxy->engine_name() == "GROMACS" && proxy->version_number() >= 20231003) { + parse->get_keyval(conf, "defaultInputStateFile", default_input_state_file_, + default_input_state_file_); + } + + return error_code; } @@ -522,6 +562,9 @@ int colvarmodule::parse_biases(std::string const &conf) /// initialize ABF instances parse_biases_type(conf, "abf"); + /// initialize ABMD instances + parse_biases_type(conf, "abmd"); + /// initialize adaptive linear biases parse_biases_type(conf, "ALB"); @@ -791,28 +834,26 @@ int colvarmodule::calc() // write restart files and similar data if (restart_out_freq && (cvm::step_relative() > 0) && - ((cvm::step_absolute() % restart_out_freq) == 0) ) { + ((cvm::step_absolute() % restart_out_freq) == 0)) { if (restart_out_name.size()) { // Write restart file, if different from main output error_code |= write_restart_file(restart_out_name); - } else { - error_code |= write_restart_file(output_prefix()+".colvars.state"); + } else if (output_prefix().size()) { + error_code |= write_restart_file(output_prefix() + ".colvars.state"); } - cvm::increase_depth(); - for (std::vector::iterator cvi = colvars.begin(); - cvi != colvars.end(); - cvi++) { - // TODO remove this when corrFunc becomes a bias - error_code |= (*cvi)->write_output_files(); + if (output_prefix().size()) { + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); cvi++) { + // TODO remove this when corrFunc becomes a bias + error_code |= (*cvi)->write_output_files(); + } + for (std::vector::iterator bi = biases.begin(); bi != biases.end(); bi++) { + error_code |= (*bi)->write_state_to_replicas(); + } + cvm::decrease_depth(); } - for (std::vector::iterator bi = biases.begin(); - bi != biases.end(); - bi++) { - error_code |= (*bi)->write_state_to_replicas(); - } - cvm::decrease_depth(); } // Write output files for biases, at the specified frequency for each @@ -881,7 +922,7 @@ int colvarmodule::calc_colvars() } // if SMP support is available, split up the work - if (proxy->smp_enabled() == COLVARS_OK) { + if (proxy->check_smp_enabled() == COLVARS_OK) { // first, calculate how much work (currently, how many active CVCs) each colvar has @@ -963,8 +1004,16 @@ int colvarmodule::calc_biases() } } - // if SMP support is available, split up the work - if (proxy->smp_enabled() == COLVARS_OK) { + bool biases_need_main_thread = false; + for (bi = biases_active()->begin(); bi != biases_active()->end(); bi++) { + if ((*bi)->replica_share_freq() > 0) { + // Biases that share data with replicas need read/write access to I/O or MPI + biases_need_main_thread = true; + } + } + + // If SMP support is available, split up the work (unless biases need to use main thread's memory) + if (proxy->check_smp_enabled() == COLVARS_OK && !biases_need_main_thread) { if (use_scripted_forces && !scripting_after_biases) { // calculate biases and scripted forces in parallel @@ -980,10 +1029,12 @@ int colvarmodule::calc_biases() error_code |= calc_scripted_forces(); } + // Straight loop over biases on a single thread cvm::increase_depth(); for (bi = biases_active()->begin(); bi != biases_active()->end(); bi++) { error_code |= (*bi)->update(); if (cvm::get_error()) { + cvm::decrease_depth(); return error_code; } } @@ -994,7 +1045,7 @@ int colvarmodule::calc_biases() total_bias_energy += (*bi)->get_energy(); } - return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); + return error_code; } @@ -1082,9 +1133,22 @@ int colvarmodule::write_restart_file(std::string const &out_name) cvm::log("Saving collective variables state to \""+out_name+"\".\n"); std::ostream &restart_out_os = proxy->output_stream(out_name, "state file"); if (!restart_out_os) return COLVARS_FILE_ERROR; - if (!write_restart(restart_out_os)) { - return cvm::error("Error: in writing restart file.\n", COLVARS_FILE_ERROR); + + if (binary_restart) { + cvm::memory_stream mem_os; + if (!write_state(mem_os)) { + return cvm::error("Error: in writing binary state information to file.\n", COLVARS_ERROR); + } + if (!restart_out_os.write(reinterpret_cast(mem_os.output_buffer()), + mem_os.length())) { + return cvm::error("Error: in writing restart file.\n", COLVARS_FILE_ERROR); + } + } else { + if (!write_state(restart_out_os)) { + return cvm::error("Error: in writing restart file.\n", COLVARS_FILE_ERROR); + } } + proxy->close_output_stream(out_name); // Take the opportunity to flush colvars.traj @@ -1097,7 +1161,7 @@ int colvarmodule::write_restart_string(std::string &output) { cvm::log("Saving state to output buffer.\n"); std::ostringstream os; - if (!write_restart(os)) { + if (!write_state(os)) { return cvm::error("Error: in writing restart to buffer.\n", COLVARS_FILE_ERROR); } output = os.str(); @@ -1207,9 +1271,17 @@ int colvarmodule::end_of_step() int colvarmodule::update_engine_parameters() { - if (this->size() == 0) return cvm::get_error(); - for (std::vector::iterator cvi = variables()->begin(); - cvi != variables()->end(); cvi++) { + if (size() == 0) { + // No-op if no variables or biases are defined + return cvm::get_error(); + } + if (proxy->simulation_running()) { + cvm::log("Current simulation parameters: initial step = " + cvm::to_str(it) + + ", integration timestep = " + cvm::to_str(dt()) + "\n"); + } + cvm::log("Updating atomic parameters (masses, charges, etc).\n"); + for (std::vector::iterator cvi = variables()->begin(); cvi != variables()->end(); + cvi++) { (*cvi)->setup(); } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); @@ -1250,10 +1322,10 @@ int colvarmodule::reset() parse->clear(); // Iterate backwards because we are deleting the elements as we go - for (std::vector::reverse_iterator bi = biases.rbegin(); - bi != biases.rend(); - bi++) { - delete *bi; // the bias destructor updates the biases array + while (!biases.empty()) { + colvarbias* tail = biases.back(); + biases.pop_back(); + delete tail; // the bias destructor updates the biases array } biases.clear(); biases_active_.clear(); @@ -1262,11 +1334,11 @@ int colvarmodule::reset() reinterpret_cast *>(num_biases_types_used_)->clear(); // Iterate backwards because we are deleting the elements as we go - for (std::vector::reverse_iterator cvi = colvars.rbegin(); - cvi != colvars.rend(); - cvi++) { - delete *cvi; // the colvar destructor updates the colvars array - } + while (!colvars.empty()) { + colvar* cvi = colvars.back(); + colvars.pop_back(); + delete cvi; // the colvar destructor updates the colvars array + }; colvars.clear(); reset_index_groups(); @@ -1274,64 +1346,119 @@ int colvarmodule::reset() proxy->flush_output_streams(); proxy->reset(); - return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); + clear_error(); + + return COLVARS_OK; } int colvarmodule::setup_input() { - if (proxy->input_prefix().size()) { - // Read a state file - std::string restart_in_name(proxy->input_prefix()+ - std::string(".colvars.state")); - std::istream *input_is = &(proxy->input_stream(restart_in_name, - "restart file/channel", - false)); + if (proxy->input_prefix().empty() && (!proxy->input_stream_exists("input state string")) && + input_state_buffer_.empty()) { + // If no input sources have been defined up to this point, use defaultInputStateFile + proxy->set_input_prefix(default_input_state_file_); + } + + if (!proxy->input_prefix().empty()) { + + // Read state from a file + + std::string restart_in_name(proxy->input_prefix() + std::string(".colvars.state")); + std::istream *input_is = &(proxy->input_stream(restart_in_name, "restart file/channel", false)); if (!*input_is) { // Try without the suffix ".colvars.state" restart_in_name = proxy->input_prefix(); - input_is = &(proxy->input_stream(restart_in_name, - "restart file/channel")); + input_is = &(proxy->input_stream(restart_in_name, "restart file/channel")); if (!*input_is) { + // Error message has already been printed, return now return COLVARS_FILE_ERROR; } } - // Now that the file has been opened, clear this field so that this - // function will not be called twice - proxy->input_prefix().clear(); + // Now that the file has been opened, clear this field so that this block + // will not be executed twice + proxy->set_input_prefix(""); - cvm::log(cvm::line_marker); - cvm::log("Loading state from file \""+restart_in_name+"\".\n"); - read_restart(*input_is); cvm::log(cvm::line_marker); - proxy->close_input_stream(restart_in_name); + input_is->seekg(0, std::ios::end); + size_t const file_size = input_is->tellg(); + input_is->seekg(0, std::ios::beg); - return cvm::get_error(); - } + bool binary_state_file = false; - // TODO This could soon be redundant - if (proxy->input_buffer() != NULL) { - // Read a string buffer - char const *buffer = proxy->input_buffer(); - size_t const buffer_size = strlen(proxy->input_buffer()); - // Clear proxy pointer for the next round - proxy->input_buffer() = NULL; - if (buffer_size > 0) { - std::istringstream input_is; - // Replace the buffer of input_is; work around the lack of const in - // pubsetbuf's prototype (which also needs to support output streams) - input_is.rdbuf()->pubsetbuf(const_cast(buffer), buffer_size); - cvm::log(cvm::line_marker); - cvm::log("Loading state from input buffer.\n"); - read_restart(input_is); - cvm::log(cvm::line_marker); - return cvm::get_error(); + uint32_t file_magic_number = 0; + if (file_size > sizeof(uint32_t)) { + if (input_is->read(reinterpret_cast(&file_magic_number), sizeof(uint32_t))) { + if (file_magic_number == colvars_magic_number) { + binary_state_file = true; + } + input_is->seekg(0, std::ios::beg); + } } + + if (binary_state_file) { + cvm::log("Loading state from binary file \"" + restart_in_name + "\".\n"); + // TODO integrate istream.read() into memory_stream to avoid copying + auto *buf = new unsigned char[file_size]; + if (input_is->read(reinterpret_cast(buf), file_size)) { + cvm::memory_stream mem_is(file_size, buf); + if (!read_state(mem_is)) { + input_is->setstate(std::ios::failbit); + cvm::error("Error: cannot interpret contents of binary file \"" + restart_in_name + + "\".\n", + COLVARS_INPUT_ERROR); + } + } else { + cvm::error("Error: cannot read from binary file \"" + restart_in_name + "\".\n", + COLVARS_INPUT_ERROR); + } + delete[] buf; + } else { + cvm::log("Loading state from text file \"" + restart_in_name + "\".\n"); + read_state(*input_is); + } + cvm::log(cvm::line_marker); + + // Now that an explicit state file was read, we shall ignore any other restart info + if (proxy->input_stream_exists("input state string")) { + proxy->delete_input_stream("input state string"); + } + input_state_buffer_.clear(); + + proxy->delete_input_stream(restart_in_name); } - return COLVARS_OK; + if (proxy->input_stream_exists("input state string")) { + + if (!input_state_buffer_.empty()) { + return cvm::error("Error: formatted/text and unformatted/binary input state buffers are " + "defined at the same time.\n", + COLVARS_BUG_ERROR); + } + + cvm::log(cvm::line_marker); + cvm::log("Loading state from formatted string.\n"); + read_state(proxy->input_stream("input state string")); + cvm::log(cvm::line_marker); + + proxy->delete_input_stream("input state string"); + } + + if (!input_state_buffer_.empty()) { + cvm::log(cvm::line_marker); + cvm::log("Loading state from unformatted memory.\n"); + cvm::memory_stream ms(input_state_buffer_.size(), input_state_buffer_.data()); + read_state(ms); + cvm::log(cvm::line_marker); + + input_state_buffer_.clear(); + } + + default_input_state_file_.clear(); + + return get_error(); } @@ -1344,36 +1471,39 @@ int colvarmodule::setup_output() std::string(proxy->restart_output_prefix()+".colvars.state") : std::string(""); + std::string const state_file_format(binary_restart ? " (binary format)" : ""); + if (restart_out_name.size()) { - cvm::log("The restart output state file will be \""+ + cvm::log("The restart output state file" + state_file_format + " will be \""+ restart_out_name+"\".\n"); } - output_prefix() = proxy->output_prefix(); - if (output_prefix().size()) { - cvm::log("The final output state file will be \""+ - (output_prefix().size() ? - std::string(output_prefix()+".colvars.state") : - std::string("colvars.state"))+"\".\n"); - // cvm::log (cvm::line_marker); + if (output_prefix() != proxy->output_prefix()) { + output_prefix() = proxy->output_prefix(); + if (output_prefix().size()) { + cvm::log("The final output state file will be \"" + + (output_prefix().size() ? std::string(output_prefix() + ".colvars.state") + : std::string("colvars.state")) + + "\".\n"); + } + + if (proxy->output_stream_exists(cv_traj_name)) { + // Close old file + proxy->close_output_stream(cv_traj_name); + cv_traj_write_labels = true; + } + + cv_traj_name = + (output_prefix().size() ? std::string(output_prefix() + ".colvars.traj") : std::string("")); + + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + error_code |= (*bi)->setup_output(); + } } - cv_traj_name = - (output_prefix().size() ? - std::string(output_prefix()+".colvars.traj") : - std::string("")); - - for (std::vector::iterator bi = biases.begin(); - bi != biases.end(); - bi++) { - error_code |= (*bi)->setup_output(); - } - - if (error_code != COLVARS_OK || cvm::get_error()) { - set_error_bits(COLVARS_FILE_ERROR); - } - - return cvm::get_error(); + return error_code; } @@ -1390,8 +1520,7 @@ std::string colvarmodule::state_file_prefix(char const *filename) } - -std::istream & colvarmodule::read_restart(std::istream &is) +template IST & colvarmodule::read_state_template_(IST &is) { bool warn_total_forces = false; @@ -1417,8 +1546,11 @@ std::istream & colvarmodule::read_restart(std::istream &is) } if (restart_version() != version()) { - cvm::log("This state file was generated with version "+ - restart_version()+"\n"); + cvm::log("This state file was generated with version " + restart_version() + "\n"); + if (std::is_same::value) { + cvm::log("Warning: compatibility between differetn Colvars versions is not " + "guaranteed for unformatted (binary) state files.\n"); + } } if (restart_version_number() < 20160810) { @@ -1453,35 +1585,73 @@ std::istream & colvarmodule::read_restart(std::istream &is) } +std::istream & colvarmodule::read_state(std::istream &is) +{ + return read_state_template_(is); +} + + +cvm::memory_stream &colvarmodule::read_state(cvm::memory_stream &is) +{ + uint32_t file_magic_number = 0; + if (!(is >> file_magic_number)) { + return is; + } + if (file_magic_number == colvars_magic_number) { + return read_state_template_(is); + } else { + is.setstate(std::ios::failbit); + cvm::error("Error: magic number of binary file (" + + cvm::to_str(static_cast(file_magic_number)) + + ") does not match the expected magic number for a Colvars state file (" + + cvm::to_str(static_cast(colvars_magic_number)) + ").\n", + COLVARS_INPUT_ERROR); + } + return is; +} + + +int colvarmodule::set_input_state_buffer(size_t n, unsigned char *buf) +{ + input_state_buffer_.clear(); + std::copy(buf, buf + n, std::back_inserter(input_state_buffer_)); + return COLVARS_OK; +} + + +int colvarmodule::set_input_state_buffer(std::vector &buf) +{ + input_state_buffer_ = std::move(buf); + return COLVARS_OK; +} + std::istream & colvarmodule::read_objects_state(std::istream &is) { - std::streampos pos = 0; + auto pos = is.tellg(); std::string word; - while (is.good()) { + while (is) { pos = is.tellg(); - word.clear(); - is >> word; - if (word.size()) { + if (is >> word) { - is.seekg(pos, std::ios::beg); + is.seekg(pos); if (word == "colvar") { cvm::increase_depth(); - for (std::vector::iterator cvi = colvars.begin(); - cvi != colvars.end(); - cvi++) { - if ( !((*cvi)->read_state(is)) ) { + for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); cvi++) { + if (!((*cvi)->read_state(is))) { // Here an error signals that the variable is a match, but the // state is corrupt; otherwise, the variable rewinds is silently - cvm::error("Error: in reading restart configuration for " - "collective variable \""+(*cvi)->name+"\".\n", + cvm::error("Error: in reading state for collective variable \"" + + (*cvi)->name + "\" at position " + cvm::to_str(is.tellg()) + + " in stream.\n", COLVARS_INPUT_ERROR); } - if (is.tellg() > pos) break; // found it + if (is.tellg() > pos) + break; // found it } cvm::decrease_depth(); @@ -1498,11 +1668,12 @@ std::istream & colvarmodule::read_objects_state(std::istream &is) } if (!((*bi)->read_state(is))) { // Same as above, an error means a match but the state is incorrect - cvm::error("Error: in reading restart configuration for bias \""+ - (*bi)->name+"\".\n", + cvm::error("Error: in reading state for bias \"" + (*bi)->name + "\" at position " + + cvm::to_str(is.tellg()) + " in stream.\n", COLVARS_INPUT_ERROR); } - if (is.tellg() > pos) break; // found it + if (is.tellg() > pos) + break; // found it } cvm::decrease_depth(); } @@ -1521,6 +1692,25 @@ std::istream & colvarmodule::read_objects_state(std::istream &is) } +cvm::memory_stream &colvarmodule::read_objects_state(cvm::memory_stream &is) +{ + // An unformatted stream must match the objects' exact configuration + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); cvi++) { + if (!(*cvi)->read_state(is)) { + return is; + } + } + for (std::vector::iterator bi = biases.begin(); bi != biases.end(); bi++) { + if (!(*bi)->read_state(is)) { + return is; + } + } + cvm::decrease_depth(); + return is; +} + + int colvarmodule::print_total_forces_errning(bool warn_total_forces) { if (warn_total_forces) { @@ -1643,18 +1833,24 @@ int colvarmodule::read_traj(char const *traj_filename, } -std::ostream & colvarmodule::write_restart(std::ostream &os) +template OST &colvarmodule::write_state_template_(OST &os) { - os.setf(std::ios::scientific, std::ios::floatfield); - os << "configuration {\n" - << " step " << std::setw(it_width) - << it << "\n" - << " dt " << dt() << "\n" - << " version " << std::string(COLVARS_VERSION) << "\n"; + bool const formatted = !std::is_same::value; + + std::ostringstream oss; + oss.setf(std::ios::scientific, std::ios::floatfield); + oss << " step " << std::setw(it_width) + << it << "\n" + << " dt " << dt() << "\n" + << " version " << std::string(COLVARS_VERSION) << "\n"; if (proxy->units.size() > 0) { - os << " units " << proxy->units << "\n"; + oss << " units " << proxy->units << "\n"; } - os << "}\n\n"; + + os << std::string("configuration"); + if (formatted) os << " {\n"; + os << oss.str(); + if (formatted) os << "}\n\n"; int error_code = COLVARS_OK; @@ -1681,7 +1877,32 @@ std::ostream & colvarmodule::write_restart(std::ostream &os) } -std::ostream & colvarmodule::write_traj_label(std::ostream &os) +std::ostream &colvarmodule::write_state(std::ostream &os) +{ + return write_state_template_(os); +} + + +cvm::memory_stream &colvarmodule::write_state(cvm::memory_stream &os) +{ + if (os << colvars_magic_number) { + write_state_template_(os); + } + return os; +} + + +int colvarmodule::write_state_buffer(std::vector &buffer) +{ + cvm::memory_stream os(buffer); + if (os << colvars_magic_number) { + write_state_template_(os); + } + return os ? COLVARS_OK : COLVARS_ERROR; +} + + +std::ostream &colvarmodule::write_traj_label(std::ostream &os) { os.setf(std::ios::scientific, std::ios::floatfield); @@ -1765,7 +1986,7 @@ size_t & colvarmodule::depth() { // NOTE: do not call log() or error() here, to avoid recursion colvarmodule *cv = cvm::main(); - if (proxy->smp_enabled() == COLVARS_OK) { + if (proxy->check_smp_enabled() == COLVARS_OK) { int const nt = proxy->smp_num_threads(); if (int(cv->depth_v.size()) != nt) { proxy->smp_lock(); @@ -1810,7 +2031,7 @@ void colvarmodule::clear_error() int colvarmodule::error(std::string const &message, int code) { - set_error_bits(code); + set_error_bits(code >= 0 ? code : COLVARS_ERROR); std::string const trailing_newline = (message.size() > 0) ? (message[message.size()-1] == '\n' ? "" : "\n") : ""; @@ -1930,15 +2151,6 @@ int colvarmodule::reset_index_groups() } -int cvm::load_atoms(char const *file_name, - cvm::atom_group &atoms, - std::string const &pdb_field, - double pdb_field_value) -{ - return proxy->load_atoms(file_name, atoms, pdb_field, pdb_field_value); -} - - int cvm::load_coords(char const *file_name, std::vector *pos, cvm::atom_group *atoms, @@ -1953,7 +2165,7 @@ int cvm::load_coords(char const *file_name, atoms->create_sorted_ids(); - std::vector sorted_pos(atoms->size(), cvm::rvector(0.0)); + std::vector sorted_pos(atoms->size(), cvm::rvector(0.0)); // Differentiate between PDB and XYZ files if (colvarparse::to_lower_cppstr(ext) == std::string(".xyz")) { @@ -1965,11 +2177,12 @@ int cvm::load_coords(char const *file_name, error_code |= cvm::main()->load_coords_xyz(file_name, &sorted_pos, atoms); } else { // Otherwise, call proxy function for PDB - error_code |= proxy->load_coords(file_name, - sorted_pos, atoms->sorted_ids(), - pdb_field, pdb_field_value); + error_code |= proxy->load_coords_pdb(file_name, sorted_pos, atoms->sorted_ids(), pdb_field, + pdb_field_value); } + if (error_code != COLVARS_OK) return error_code; + std::vector const &map = atoms->sorted_ids_map(); for (size_t i = 0; i < atoms->size(); i++) { (*pos)[map[i]] = sorted_pos[i]; @@ -1985,7 +2198,7 @@ int cvm::load_coords_xyz(char const *filename, bool keep_open) { std::istream &xyz_is = proxy->input_stream(filename, "XYZ file"); - unsigned int natoms; + size_t natoms; char symbol[256]; std::string line; cvm::real x = 0.0, y = 0.0, z = 0.0; @@ -2009,12 +2222,19 @@ int cvm::load_coords_xyz(char const *filename, cvm::getline(xyz_is, line); xyz_is.width(255); } else { + proxy->close_input_stream(filename); return cvm::error(error_msg, COLVARS_INPUT_ERROR); } + if (pos->size() > natoms) { + proxy->close_input_stream(filename); + return cvm::error("File \"" + std::string(filename) + "\" contains fewer atoms (" + cvm::to_str(natoms) + + ") than expected (" + cvm::to_str(pos->size()) + ").", COLVARS_INPUT_ERROR); + } + std::vector::iterator pos_i = pos->begin(); size_t xyz_natoms = 0; - if (pos->size() != natoms) { // Use specified indices + if (pos->size() < natoms) { // Use specified indices int next = 0; // indices are zero-based if (!atoms) { // In the other branch of this test, reading all positions from the file, @@ -2022,6 +2242,13 @@ int cvm::load_coords_xyz(char const *filename, return cvm::error("Trying to read partial positions with invalid atom group pointer", COLVARS_BUG_ERROR); } + + if (static_cast(atoms->sorted_ids().back()) > natoms) { + proxy->close_input_stream(filename); + return cvm::error("File \"" + std::string(filename) + "\" contains fewer atoms (" + cvm::to_str(natoms) + + ") than expected (" + cvm::to_str(atoms->sorted_ids().back()) + ").", COLVARS_INPUT_ERROR); + } + std::vector::const_iterator index = atoms->sorted_ids().begin(); for ( ; pos_i != pos->end() ; pos_i++, index++) { @@ -2038,6 +2265,7 @@ int cvm::load_coords_xyz(char const *filename, (*pos_i)[2] = proxy->angstrom_to_internal(z); xyz_natoms++; } else { + proxy->close_input_stream(filename); return cvm::error(error_msg, COLVARS_INPUT_ERROR); } } @@ -2053,12 +2281,14 @@ int cvm::load_coords_xyz(char const *filename, (*pos_i)[2] = proxy->angstrom_to_internal(z); xyz_natoms++; } else { + proxy->close_input_stream(filename); return cvm::error(error_msg, COLVARS_INPUT_ERROR); } } } if (xyz_natoms != pos->size()) { + proxy->close_input_stream(filename); return cvm::error("Error: The number of positions read from file \""+ std::string(filename)+"\" does not match the number of "+ "positions required: "+cvm::to_str(xyz_natoms)+" vs. "+ diff --git a/lib/colvars/colvarmodule.h b/lib/colvars/colvarmodule.h index 236d432a95..fa84b1ad75 100644 --- a/lib/colvars/colvarmodule.h +++ b/lib/colvars/colvarmodule.h @@ -10,7 +10,7 @@ #ifndef COLVARMODULE_H #define COLVARMODULE_H -#include +#include #include "colvars_version.h" @@ -19,9 +19,11 @@ #endif /*! \mainpage Main page -This is the Developer's documentation for the Collective Variables Module. +This is the Developer's documentation for the Collective Variables module (Colvars). You can browse the class hierarchy or the list of source files. + +Please note that this documentation is only supported for the master branch, and its features may differ from those in a given release of a simulation package. */ /// \file colvarmodule.h @@ -33,26 +35,15 @@ You can browse the class hierarchy or the list of source files. /// shared between all object instances) to be accessed from other /// objects. -#define COLVARS_OK 0 -#define COLVARS_ERROR 1 -#define COLVARS_NOT_IMPLEMENTED (1<<1) -#define COLVARS_INPUT_ERROR (1<<2) // out of bounds or inconsistent input -#define COLVARS_BUG_ERROR (1<<3) // Inconsistent state indicating bug -#define COLVARS_FILE_ERROR (1<<4) -#define COLVARS_MEMORY_ERROR (1<<5) -#define COLVARS_NO_SUCH_FRAME (1<<6) // Cannot load the requested frame - -#include +#include +#include #include #include -#include -#include class colvarparse; class colvar; class colvarbias; class colvarproxy; -class colvarscript; class colvarvalue; @@ -67,14 +58,6 @@ class colvarvalue; /// child objects class colvarmodule { -private: - - /// Impossible to initialize the main object without arguments - colvarmodule(); - - /// Integer representing the version string (allows comparisons) - int version_int; - public: /// Get the version string (YYYY-MM-DD format) @@ -89,9 +72,21 @@ public: return version_int; } - friend class colvarproxy; - // TODO colvarscript should be unaware of colvarmodule's internals - friend class colvarscript; + /// Get the patch version number (non-zero in patch releases of other packages) + int patch_version_number() const + { + return patch_version_int; + } + +private: + + /// Integer representing the version string (allows comparisons) + int version_int = 0; + + /// Patch version number (non-zero in patch releases of other packages) + int patch_version_int = 0; + +public: /// Use a 64-bit integer to store the step number typedef long long step_number; @@ -190,7 +185,9 @@ public: template class matrix2d; class quaternion; class rotation; + class usage; + class memory_stream; /// Residue identifier typedef int residue_id; @@ -205,8 +202,6 @@ public: // allow these classes to access protected data class atom; class atom_group; - friend class atom; - friend class atom_group; typedef std::vector::iterator atom_iter; typedef std::vector::const_iterator atom_const_iter; @@ -247,6 +242,8 @@ public: return it; } + bool binary_restart; + /// \brief Finite difference step size (if there is no dynamics, or /// if gradients need to be tested independently from the size of /// dt) @@ -342,9 +339,19 @@ public: /// \param Pointer to instance of the proxy class (communicate with engine) colvarmodule(colvarproxy *proxy); +private: + + /// Cannot initialize the main object without a proxy + colvarmodule(); + +public: + /// Destructor ~colvarmodule(); + /// Set the initial step number (it is 0 otherwise); may be overridden when reading a state + void set_initial_step(step_number it); + /// Actual function called by the destructor int reset(); @@ -449,17 +456,52 @@ public: /// (Re)initialize the output trajectory and state file (does not write it yet) int setup_output(); - /// Read a restart file - std::istream & read_restart(std::istream &is); +private: + + template IST & read_state_template_(IST &is); + + /// Default input state file; if given, it is read unless the MD engine provides it + std::string default_input_state_file_; + + /// Internal state buffer, to be read as an unformatted stream + std::vector input_state_buffer_; + +public: + + /// Read all objects' state fron a formatted (text) stream + std::istream & read_state(std::istream &is); + + /// Read all objects' state fron an unformatted (binary) stream + memory_stream & read_state(memory_stream &is); + + /// Set an internal state buffer, to be read later as an unformatted stream when ready + int set_input_state_buffer(size_t n, unsigned char *buf); + + /// Same as set_input_state_buffer() for C array, but uses std::move + int set_input_state_buffer(std::vector &buf); /// Read the states of individual objects; allows for changes std::istream & read_objects_state(std::istream &is); + /// Read the states of individual objects; allows for changes + memory_stream & read_objects_state(memory_stream &is); + /// If needed (old restart file), print the warning that cannot be ignored int print_total_forces_errning(bool warn_total_forces); - /// Write the output restart file - std::ostream & write_restart(std::ostream &os); +private: + template OST &write_state_template_(OST &os); + +public: + + /// Write the state of the module to a formatted (text) file + std::ostream & write_state(std::ostream &os); + + /// Write the state of the module to an unformatted (binary) file + memory_stream & write_state(memory_stream &os); + + /// Write the state of the module to an array of bytes (wrapped as a memory_stream object) + int write_state_buffer(std::vector &buffer); /// Strips .colvars.state from filename and checks that it is not empty static std::string state_file_prefix(char const *filename); @@ -650,7 +692,7 @@ public: static void log(std::string const &message, int min_log_level = 10); /// Print a message to the main log and set global error code - static int error(std::string const &message, int code = COLVARS_ERROR); + static int error(std::string const &message, int code = -1); private: @@ -715,17 +757,6 @@ public: /// Clear the index groups loaded so far int reset_index_groups(); - /// \brief Select atom IDs from a file (usually PDB) \param filename name of - /// the file \param atoms array into which atoms read from "filename" will be - /// appended \param pdb_field (optional) if the file is a PDB and this - /// string is non-empty, select atoms for which this field is non-zero - /// \param pdb_field_value (optional) if non-zero, select only atoms whose - /// pdb_field equals this - static int load_atoms(char const *filename, - atom_group &atoms, - std::string const &pdb_field, - double pdb_field_value = 0.0); - /// \brief Load coordinates for a group of atoms from a file (PDB or XYZ); /// if "pos" is already allocated, the number of its elements must match the /// number of entries in "filename" \param filename name of the file \param @@ -755,7 +786,7 @@ public: std::string restart_out_name; /// Pseudo-random number with Gaussian distribution - static real rand_gaussian(void); + static real rand_gaussian(); protected: @@ -838,9 +869,20 @@ public: typedef colvarmodule cvm; - std::ostream & operator << (std::ostream &os, cvm::rvector const &v); std::istream & operator >> (std::istream &is, cvm::rvector &v); +namespace { + constexpr int32_t COLVARS_OK = 0; + constexpr int32_t COLVARS_ERROR = 1; + constexpr int32_t COLVARS_NOT_IMPLEMENTED = (1<<1); + constexpr int32_t COLVARS_INPUT_ERROR = (1<<2); // out of bounds or inconsistent input + constexpr int32_t COLVARS_BUG_ERROR = (1<<3); // Inconsistent state indicating bug + constexpr int32_t COLVARS_FILE_ERROR = (1<<4); + constexpr int32_t COLVARS_MEMORY_ERROR = (1<<5); + constexpr int32_t COLVARS_NO_SUCH_FRAME = (1<<6); // Cannot load the requested frame +} + + #endif diff --git a/lib/colvars/colvarmodule_refs.h b/lib/colvars/colvarmodule_refs.h index c3fccf297c..2e9615e3b4 100644 --- a/lib/colvars/colvarmodule_refs.h +++ b/lib/colvars/colvarmodule_refs.h @@ -14,6 +14,22 @@ " url = {https://doi.org/10.1016/j.softx.2015.06.001}\n" "}\n"; + paper_count_[std::string("BouRabee2010")] = 0; + paper_url_[std::string("BouRabee2010")] = "https://doi.org/10.1137/090758842"; + paper_bibtex_[std::string("BouRabee2010")] = + "\n" + "@article{BouRabee2010,\n" + " doi = {10.1137/090758842},\n" + " url = {https://doi.org/10.1137/090758842},\n" + " year = {2010},\n" + " volume = {48},\n" + " number = {1},\n" + " pages = {278--297},\n" + " author = {Nawaf Bou-Rabee and Houman Owhadi},\n" + " title = {Long-Run Accuracy of Variational Integrators in the Stochastic Context},\n" + " journal = {{SIAM} Journal on Numerical Analysis}\n" + "}\n"; + paper_count_[std::string("Chen2021")] = 0; paper_url_[std::string("Chen2021")] = "https://doi.org/10.1021/acs.jctc.1c00103"; paper_bibtex_[std::string("Chen2021")] = @@ -182,6 +198,19 @@ " url = {https://doi.org/10.1021/ct9004432}\n" "}\n"; + paper_count_[std::string("Henin2021")] = 0; + paper_url_[std::string("Henin2021")] = "https://doi.org/10.1021/acs.jctc.1c00593"; + paper_bibtex_[std::string("Henin2021")] = + "\n" + "@Article{Henin2021,\n" + " author = {H\\'enin, J.},\n" + " journal = {J. Chem. Theory Comput.},\n" + " title = {Fast and accurate multidimensional free energy integration},\n" + " year = {2021},\n" + " doi = {10.1021/acs.jctc.1c00593},\n" + " url = {https://doi.org/10.1021/acs.jctc.1c00593},\n" + "}\n"; + paper_count_[std::string("Humphrey1996")] = 0; paper_url_[std::string("Humphrey1996")] = "https://doi.org/10.1016/0263-7855(96)00018-5"; paper_bibtex_[std::string("Humphrey1996")] = @@ -215,19 +244,6 @@ " url = {https://doi.org/10.1021/acs.jpcb.6b10055}\n" "}\n"; - paper_count_[std::string("Henin2021")] = 0; - paper_url_[std::string("Henin2021")] = "https://doi.org/10.1021/acs.jctc.1c00593"; - paper_bibtex_[std::string("Henin2021")] = - "\n" - "@Article{Henin2021,\n" - " author = {H\\'enin, J.},\n" - " journal = {J. Chem. Theory Comput.},\n" - " title = {Fast and accurate multidimensional free energy integration},\n" - " year = {2021},\n" - " doi = {10.1021/acs.jctc.1c00593},\n" - " url = {https://doi.org/10.1021/acs.jctc.1c00593},\n" - "}\n"; - paper_count_[std::string("Marinelli2015")] = 0; paper_url_[std::string("Marinelli2015")] = "https://doi.org/10.1016/j.bpj.2015.05.024"; paper_bibtex_[std::string("Marinelli2015")] = @@ -335,6 +351,9 @@ feature_count_[std::string("GROMACS engine")] = 0; feature_paper_map_[std::string("GROMACS engine")] = "Abraham2015"; + feature_count_[std::string("BAOA integrator")] = 0; + feature_paper_map_[std::string("BAOA integrator")] = "BouRabee2010"; + feature_count_[std::string("reweightaMD colvar bias implementation (NAMD)")] = 0; feature_paper_map_[std::string("reweightaMD colvar bias implementation (NAMD)")] = "Chen2021"; @@ -422,14 +441,14 @@ feature_count_[std::string("orientationAngle colvar component (derived from orientation)")] = 0; feature_paper_map_[std::string("orientationAngle colvar component (derived from orientation)")] = "Fiorin2013"; - feature_count_[std::string("orientationProj colvar component (derived from orientation)")] = 0; - feature_paper_map_[std::string("orientationProj colvar component (derived from orientation)")] = "Fiorin2013"; + feature_count_[std::string("orientationProj colvar component (derived from orientationAngle)")] = 0; + feature_paper_map_[std::string("orientationProj colvar component (derived from orientationAngle)")] = "Fiorin2013"; - feature_count_[std::string("spinAngle colvar component (derived from orientation)")] = 0; - feature_paper_map_[std::string("spinAngle colvar component (derived from orientation)")] = "Fiorin2013"; + feature_count_[std::string("spinAngle colvar component (derived from tilt)")] = 0; + feature_paper_map_[std::string("spinAngle colvar component (derived from tilt)")] = "Fiorin2013"; - feature_count_[std::string("tilt colvar component (derived from orientation)")] = 0; - feature_paper_map_[std::string("tilt colvar component (derived from orientation)")] = "Fiorin2013"; + feature_count_[std::string("tilt colvar component (derived from orientationProj)")] = 0; + feature_paper_map_[std::string("tilt colvar component (derived from orientationProj)")] = "Fiorin2013"; feature_count_[std::string("alpha colvar component")] = 0; feature_paper_map_[std::string("alpha colvar component")] = "Fiorin2013"; @@ -479,14 +498,14 @@ feature_count_[std::string("polarPhi colvar component")] = 0; feature_paper_map_[std::string("polarPhi colvar component")] = "Fu2017"; - feature_count_[std::string("eulerPhi colvar component (derived from orientation)")] = 0; - feature_paper_map_[std::string("eulerPhi colvar component (derived from orientation)")] = "Fu2017"; + feature_count_[std::string("eulerPhi colvar component (derived from orientation_angle)")] = 0; + feature_paper_map_[std::string("eulerPhi colvar component (derived from orientation_angle)")] = "Fu2017"; - feature_count_[std::string("eulerTheta colvar component (derived from orientation)")] = 0; - feature_paper_map_[std::string("eulerTheta colvar component (derived from orientation)")] = "Fu2017"; + feature_count_[std::string("eulerTheta colvar component (derived from orientation_angle)")] = 0; + feature_paper_map_[std::string("eulerTheta colvar component (derived from orientation_angle)")] = "Fu2017"; - feature_count_[std::string("eulerPsi colvar component (derived from orientation)")] = 0; - feature_paper_map_[std::string("eulerPsi colvar component (derived from orientation)")] = "Fu2017"; + feature_count_[std::string("eulerPsi colvar component (derived from orientation_angle)")] = 0; + feature_paper_map_[std::string("eulerPsi colvar component (derived from orientation_angle)")] = "Fu2017"; feature_count_[std::string("dipoleAngle colvar component")] = 0; feature_paper_map_[std::string("dipoleAngle colvar component")] = "Garate2019"; @@ -500,6 +519,9 @@ feature_count_[std::string("Internal-forces free energy estimator")] = 0; feature_paper_map_[std::string("Internal-forces free energy estimator")] = "Henin2010"; + feature_count_[std::string("Poisson integration of 2D/3D free energy surfaces")] = 0; + feature_paper_map_[std::string("Poisson integration of 2D/3D free energy surfaces")] = "Henin2021"; + feature_count_[std::string("VMD engine")] = 0; feature_paper_map_[std::string("VMD engine")] = "Humphrey1996"; @@ -509,9 +531,6 @@ feature_count_[std::string("CZAR eABF estimator")] = 0; feature_paper_map_[std::string("CZAR eABF estimator")] = "Lesage2017"; - feature_count_[std::string("Poisson integration of 2D/3D free energy surfaces")] = 0; - feature_paper_map_[std::string("Poisson integration of 2D/3D free energy surfaces")] = "Henin2021"; - feature_count_[std::string("Ensemble-biased metadynamics (ebMetaD)")] = 0; feature_paper_map_[std::string("Ensemble-biased metadynamics (ebMetaD)")] = "Marinelli2015"; @@ -539,9 +558,6 @@ feature_count_[std::string("Colvars-GROMACS interface")] = 0; feature_paper_map_[std::string("Colvars-GROMACS interface")] = "n/a"; - feature_count_[std::string("Colvars Dashboard (Colvars-VMD graphical user interface)")] = 0; - feature_paper_map_[std::string("Colvars Dashboard (Colvars-VMD graphical user interface)")] = "n/a"; - feature_count_[std::string("gspath colvar component")] = 0; feature_paper_map_[std::string("gspath colvar component")] = "n/a"; @@ -571,3 +587,6 @@ feature_count_[std::string("Scripted functions (Tcl)")] = 0; feature_paper_map_[std::string("Scripted functions (Tcl)")] = "n/a"; + + feature_count_[std::string("ABMD bias")] = 0; + feature_paper_map_[std::string("ABMD bias")] = "n/a"; diff --git a/lib/colvars/colvarparse.cpp b/lib/colvars/colvarparse.cpp index 350ec1b56b..76b5c694c1 100644 --- a/lib/colvars/colvarparse.cpp +++ b/lib/colvars/colvarparse.cpp @@ -8,12 +8,12 @@ // Colvars repository at GitHub. #include -#include #include #include "colvarmodule.h" #include "colvarvalue.h" #include "colvarparse.h" +#include "colvars_memstream.h" // space & tab @@ -100,7 +100,7 @@ bool colvarparse::get_key_string_multi_value(std::string const &conf, char const *key, std::vector& data) { bool b_found = false, b_found_any = false; - size_t save_pos = 0, found_count = 0; + size_t save_pos = 0; data.clear(); @@ -110,7 +110,6 @@ bool colvarparse::get_key_string_multi_value(std::string const &conf, if (b_found) { if (!b_found_any) b_found_any = true; - found_count++; data.push_back(data_this); } } while (b_found); @@ -786,14 +785,12 @@ bool colvarparse::key_lookup(std::string const &conf, if (line[brace] == '{') brace_count++; if (line[brace] == '}') brace_count--; if (brace_count == 0) { - data_end = brace+1; break; } brace = line.find_first_of("{}", brace+1); } if (brace_count == 0) { - data_end = brace+1; break; } @@ -869,55 +866,107 @@ colvarparse::read_block::~read_block() std::istream & operator>> (std::istream &is, colvarparse::read_block const &rb) { - std::streampos start_pos = is.tellg(); - std::string read_key, next; + auto start_pos = is.tellg(); - if ( !(is >> read_key) || !(read_key == rb.key) || - !(is >> next) ) { - // the requested keyword has not been found, or it is not possible - // to read data after it + std::string read_key; + if ( !(is >> read_key) || !(read_key == rb.key) ) { + // the requested keyword has not been found is.clear(); - is.seekg(start_pos, std::ios::beg); + is.seekg(start_pos); is.setstate(std::ios::failbit); return is; } - if (next != "{") { - if (rb.data) { - *(rb.data) = next; + std::string next; + if (is >> next) { + if (next == "{") { + // Parse a formatted brace-delimited block + rb.read_block_contents(is); + } else { + if (rb.data) { + *(rb.data) = next; + } } - return is; + } else { + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::badbit); } - size_t brace_count = 1; + return is; +} + + +std::istream &colvarparse::read_block::read_block_contents(std::istream &is, + bool block_only) const +{ + int brace_count = block_only ? 0 : 1; + auto const start_pos = is.tellg(); std::string line; while (colvarparse::getline_nocomments(is, line)) { size_t br = 0, br_old = 0; - while ( (br = line.find_first_of("{}", br)) != std::string::npos) { - if (line[br] == '{') brace_count++; - if (line[br] == '}') brace_count--; + while ((br = line.find_first_of("{}", br)) != std::string::npos) { + if (line[br] == '{') + brace_count++; + if (line[br] == '}') + brace_count--; br_old = br; br++; } - if (brace_count) { - if (rb.data) { - (rb.data)->append(line + "\n"); + if (brace_count || block_only) { + // Add whole line if (1) brace are unmatched or (2) we're reading the whole stream anyway + if (data) { + data->append(line + "\n"); } - } - else { - if (rb.data) { - (rb.data)->append(line, 0, br_old); + } else { + // Not reading whole block and braces are matched; add until before the last brace + if (data) { + data->append(line.substr(0, br_old) + "\n"); } break; } } - if (brace_count) { - // end-of-file reached - // restore initial position - is.clear(); - is.seekg(start_pos, std::ios::beg); - is.setstate(std::ios::failbit); + + if (block_only) { + if (is.rdstate() & std::ios::eofbit) { + // Clear EOF errors if we were meant to read the whole block + is.clear(); + } + } else { + if (brace_count) { + // Could not match braces, restore initial position and set fail bit + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + } } + + return is; +} + + +cvm::memory_stream &operator>>(cvm::memory_stream &is, colvarparse::read_block const &rb) +{ + auto const start_pos = is.tellg(); + + std::string read_key; + if ( !(is >> read_key) || !(read_key == rb.key) ) { + // the requested keyword has not been found + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + return is; + } + + std::string content; + if (is >> content) { + std::istringstream iss(content); + if (!rb.read_block_contents(iss, true)) { + is.seekg(start_pos); + is.setstate(std::ios::failbit); + } + } + return is; } diff --git a/lib/colvars/colvarparse.h b/lib/colvars/colvarparse.h index d2286de6cf..105a0857a4 100644 --- a/lib/colvars/colvarparse.h +++ b/lib/colvars/colvarparse.h @@ -11,6 +11,7 @@ #define COLVARPARSE_H #include +#include #include #include "colvarmodule.h" @@ -40,7 +41,7 @@ public: void set_string(std::string const &conf); /// Default destructor - virtual ~colvarparse(); + ~colvarparse() override; /// Get the configuration string (includes comments) inline std::string const & get_config() const @@ -109,12 +110,12 @@ public: bool get_keyval(std::string const &conf, char const *key, int &value, - int const &def_value = (int)0, + int const &def_value = 0, Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, size_t &value, - size_t const &def_value = (size_t)0, + size_t const &def_value = 0, Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, @@ -134,7 +135,7 @@ public: bool get_keyval(std::string const &conf, char const *key, cvm::real &value, - cvm::real const &def_value = (cvm::real)0.0, + cvm::real const &def_value = 0.0, Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, @@ -159,17 +160,17 @@ public: bool get_keyval(std::string const &conf, char const *key, std::vector &values, - std::vector const &def_values = std::vector(0, (int)0), + std::vector const &def_values = std::vector(0, 0), Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, std::vector &values, - std::vector const &def_values = std::vector(0, (size_t)0), + std::vector const &def_values = std::vector(0, 0), Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, std::vector &values, - std::vector const &def_values = std::vector(0, (long)0), + std::vector const &def_values = std::vector(0, 0), Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, @@ -179,7 +180,7 @@ public: bool get_keyval(std::string const &conf, char const *key, std::vector &values, - std::vector const &def_values = std::vector(0, (cvm::real)0.0), + std::vector const &def_values = std::vector(0, 0.0), Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, @@ -262,33 +263,41 @@ public: { std::string out = ""; for (size_t i = 0; i < in.size(); i++) { - out.append(1, (char) ::tolower(in[i]) ); + out.append(1, static_cast( ::tolower(in[i])) ); } return out; } - /// \brief Helper class to read a block of the type "key { ... }" - /// from a stream and store it in a string + /// Helper class to read a block "key { ... }" from a stream and store it in a string /// - /// Useful on restarts, where the file is too big to be loaded in a - /// string by key_lookup; it can only check that the keyword is - /// correct and the block is properly delimited by braces, not - /// skipping other blocks + /// Useful on restarts, where the file is too big to be loaded in a string + /// by key_lookup(); it can only check that the keyword is correct and the + /// block is properly delimited by braces, not skipping other blocks class read_block { - - /// The keyword that identifies the block - std::string const key; - - /// Where to keep the data (may be NULL) - std::string * const data; - public: - read_block(std::string const &key_in, std::string *data_in = NULL); + read_block(std::string const &key, std::string *data = nullptr); ~read_block(); + /// Read block from stream, first check that key matches, then call read_contents() friend std::istream & operator >> (std::istream &is, read_block const &rb); + + /// Read block from stream, first check that key matches, then call read_contents() + friend cvm::memory_stream & operator >> (cvm::memory_stream &is, read_block const &rb); + + private: + + /// Keyword that identifies the block + std::string const key; + + /// Where to keep the data + std::string * const data; + + /// Read the contents of a formatted block after checking that the keyword matches + /// \param[in] is Stream object + /// \param[in] block_only If true, stream is assumed to contain only the block without braces + std::istream & read_block_contents(std::istream &is, bool block_only = false) const; }; @@ -304,8 +313,8 @@ public: /// within "conf", useful when doing multiple calls bool key_lookup(std::string const &conf, char const *key, - std::string *data = NULL, - size_t *save_pos = NULL); + std::string *data = nullptr, + size_t *save_pos = nullptr); /// \brief Reads a configuration line, adds it to config_string, and returns /// the stream \param is Input stream \param line String that will hold the diff --git a/lib/colvars/colvarproxy.cpp b/lib/colvars/colvarproxy.cpp index b09ea6667c..588b7c68d8 100644 --- a/lib/colvars/colvarproxy.cpp +++ b/lib/colvars/colvarproxy.cpp @@ -13,8 +13,9 @@ #include "colvarmodule.h" #include "colvarproxy.h" +#include "colvar.h" +#include "colvarbias.h" #include "colvarscript.h" -#include "colvaratoms.h" #include "colvarmodule_utils.h" @@ -23,6 +24,7 @@ colvarproxy_atoms::colvarproxy_atoms() { atoms_rms_applied_force_ = atoms_max_applied_force_ = 0.0; atoms_max_applied_force_id_ = -1; + modified_atom_list_ = false; updated_masses_ = updated_charges_ = false; } @@ -55,6 +57,7 @@ int colvarproxy_atoms::add_atom_slot(int atom_id) atoms_positions.push_back(cvm::rvector(0.0, 0.0, 0.0)); atoms_total_forces.push_back(cvm::rvector(0.0, 0.0, 0.0)); atoms_new_colvar_forces.push_back(cvm::rvector(0.0, 0.0, 0.0)); + modified_atom_list_ = true; return (atoms_ids.size() - 1); } @@ -71,6 +74,12 @@ int colvarproxy_atoms::check_atom_id(int /* atom_number */) } +int colvarproxy_atoms::check_atom_name_selections_available() +{ + return COLVARS_NOT_IMPLEMENTED; +} + + int colvarproxy_atoms::init_atom(cvm::residue_id const & /* residue */, std::string const & /* atom_name */, std::string const & /* segment_id */) @@ -112,29 +121,6 @@ size_t colvarproxy_atoms::get_num_active_atoms() const } -int colvarproxy_atoms::load_atoms(char const * /* filename */, - cvm::atom_group & /* atoms */, - std::string const & /* pdb_field */, - double) -{ - return cvm::error("Error: loading atom identifiers from a file " - "is currently not implemented.\n", - COLVARS_NOT_IMPLEMENTED); -} - - -int colvarproxy_atoms::load_coords(char const * /* filename */, - std::vector & /* pos */, - std::vector const & /* sorted_ids */, - std::string const & /* pdb_field */, - double) -{ - return cvm::error("Error: loading atomic coordinates from a file " - "is currently not implemented.\n", - COLVARS_NOT_IMPLEMENTED); -} - - void colvarproxy_atoms::compute_rms_atoms_applied_force() { atoms_rms_applied_force_ = @@ -280,7 +266,7 @@ colvarproxy_smp::~colvarproxy_smp() } -int colvarproxy_smp::smp_enabled() +int colvarproxy_smp::check_smp_enabled() { #if defined(_OPENMP) if (b_smp_active) { @@ -299,7 +285,7 @@ int colvarproxy_smp::smp_colvars_loop() colvarmodule *cv = cvm::main(); colvarproxy *proxy = cv->proxy; #pragma omp parallel for - for (size_t i = 0; i < cv->variables_active_smp()->size(); i++) { + for (int i = 0; i < static_cast(cv->variables_active_smp()->size()); i++) { colvar *x = (*(cv->variables_active_smp()))[i]; int x_item = (*(cv->variables_active_smp_items()))[i]; if (cvm::debug()) { @@ -324,7 +310,7 @@ int colvarproxy_smp::smp_biases_loop() #pragma omp parallel { #pragma omp for - for (size_t i = 0; i < cv->biases_active()->size(); i++) { + for (int i = 0; i < static_cast(cv->biases_active()->size()); i++) { colvarbias *b = (*(cv->biases_active()))[i]; if (cvm::debug()) { cvm::log("Calculating bias \""+b->name+"\" on thread "+ @@ -351,7 +337,7 @@ int colvarproxy_smp::smp_biases_script_loop() cv->calc_scripted_forces(); } #pragma omp for - for (size_t i = 0; i < cv->biases_active()->size(); i++) { + for (int i = 0; i < static_cast(cv->biases_active()->size()); i++) { colvarbias *b = (*(cv->biases_active()))[i]; if (cvm::debug()) { cvm::log("Calculating bias \""+b->name+"\" on thread "+ @@ -484,16 +470,21 @@ colvarproxy::~colvarproxy() bool colvarproxy::io_available() { - return (smp_enabled() == COLVARS_OK && smp_thread_id() == 0) || - (smp_enabled() != COLVARS_OK); + return (check_smp_enabled() == COLVARS_OK && smp_thread_id() == 0) || + (check_smp_enabled() != COLVARS_OK); } int colvarproxy::reset() { + if (cvm::debug()) { + cvm::log("colvarproxy::reset()\n"); + } int error_code = COLVARS_OK; error_code |= colvarproxy_atoms::reset(); error_code |= colvarproxy_atom_groups::reset(); + error_code |= colvarproxy_volmaps::reset(); + total_force_requested = false; return error_code; } @@ -541,6 +532,31 @@ int colvarproxy::parse_module_config() } +int colvarproxy::load_atoms_pdb(char const * /* filename */, + cvm::atom_group & /* atoms */, + std::string const & /* pdb_field */, + double /* pdb_field_value */) +{ + return cvm::error( + "Error: loading atom indices from a PDB file is currently not implemented in " + + engine_name() + ".\n", + COLVARS_NOT_IMPLEMENTED); +} + + +int colvarproxy::load_coords_pdb(char const * /* filename */, + std::vector & /* pos */, + std::vector const & /* sorted_ids */, + std::string const & /* pdb_field */, + double /* pdb_field_value */) +{ + return cvm::error( + "Error: loading atomic coordinates from a PDB file is currently not implemented in " + + engine_name() + ".\n", + COLVARS_NOT_IMPLEMENTED); +} + + int colvarproxy::update_input() { return COLVARS_OK; @@ -591,47 +607,78 @@ void colvarproxy::print_input_atomic_data() cvm::log(cvm::line_marker); cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "atoms_ids = "+cvm::to_str(atoms_ids)+"\n"); + "atoms_ids[size = "+cvm::to_str(atoms_ids.size())+ + "] = "+cvm::to_str(atoms_ids)+"\n"); + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "atoms_refcount = "+cvm::to_str(atoms_refcount)+"\n"); + "atoms_refcount[size = "+cvm::to_str(atoms_refcount.size())+ + "] = "+cvm::to_str(atoms_refcount)+"\n"); + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "atoms_masses = "+cvm::to_str(atoms_masses)+"\n"); + "atoms_masses[size = "+cvm::to_str(atoms_masses.size())+ + "] = "+cvm::to_str(atoms_masses)+"\n"); + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "atoms_charges = "+cvm::to_str(atoms_charges)+"\n"); + "atoms_charges[size = "+cvm::to_str(atoms_charges.size())+ + "] = "+cvm::to_str(atoms_charges)+"\n"); + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "atoms_positions = "+cvm::to_str(atoms_positions, - cvm::cv_width, - cvm::cv_prec)+"\n"); + "atoms_positions[size = "+cvm::to_str(atoms_positions.size())+ + "] = "+cvm::to_str(atoms_positions, + cvm::cv_width, + cvm::cv_prec)+"\n"); + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "atoms_total_forces = "+cvm::to_str(atoms_total_forces, - cvm::cv_width, - cvm::cv_prec)+"\n"); + "atoms_total_forces[size = "+ + cvm::to_str(atoms_total_forces.size())+ + "] = "+cvm::to_str(atoms_total_forces, + cvm::cv_width, + cvm::cv_prec)+"\n"); cvm::log(cvm::line_marker); cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "atom_groups_ids = "+cvm::to_str(atom_groups_ids)+"\n"); + "atom_groups_ids[size = "+cvm::to_str(atom_groups_ids.size())+ + "] = "+cvm::to_str(atom_groups_ids)+"\n"); + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "atom_groups_refcount = "+cvm::to_str(atom_groups_refcount)+"\n"); + "atom_groups_refcount[size = "+ + cvm::to_str(atom_groups_refcount.size())+ + "] = "+cvm::to_str(atom_groups_refcount)+"\n"); + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "atom_groups_masses = "+cvm::to_str(atom_groups_masses)+"\n"); + "atom_groups_masses[size = "+ + cvm::to_str(atom_groups_masses.size())+ + "] = "+cvm::to_str(atom_groups_masses)+"\n"); + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "atom_groups_charges = "+cvm::to_str(atom_groups_charges)+"\n"); + "atom_groups_charges[size = "+ + cvm::to_str(atom_groups_charges.size())+ + "] = "+cvm::to_str(atom_groups_charges)+"\n"); + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "atom_groups_coms = "+cvm::to_str(atom_groups_coms, - cvm::cv_width, - cvm::cv_prec)+"\n"); + "atom_groups_coms[size = "+ + cvm::to_str(atom_groups_coms.size())+ + "] = "+cvm::to_str(atom_groups_coms, + cvm::cv_width, + cvm::cv_prec)+"\n"); + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "atom_groups_total_forces = "+cvm::to_str(atom_groups_total_forces, - cvm::cv_width, - cvm::cv_prec)+"\n"); + "atom_groups_total_forces[size = "+ + cvm::to_str(atom_groups_total_forces.size())+ + "] = "+cvm::to_str(atom_groups_total_forces, + cvm::cv_width, + cvm::cv_prec)+"\n"); cvm::log(cvm::line_marker); cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "volmaps_ids = "+cvm::to_str(volmaps_ids)+"\n"); + "volmaps_ids[size = "+cvm::to_str(volmaps_ids.size())+ + "] = "+cvm::to_str(volmaps_ids)+"\n"); + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ - "volmaps_values = "+cvm::to_str(volmaps_values)+"\n"); + "volmaps_values[size = "+cvm::to_str(volmaps_values.size())+ + "] = "+cvm::to_str(volmaps_values)+"\n"); cvm::log(cvm::line_marker); } diff --git a/lib/colvars/colvarproxy.h b/lib/colvars/colvarproxy.h index 5733b8b66d..91db6011e9 100644 --- a/lib/colvars/colvarproxy.h +++ b/lib/colvars/colvarproxy.h @@ -12,7 +12,6 @@ #include "colvarmodule.h" #include "colvartypes.h" -#include "colvarvalue.h" #include "colvarproxy_io.h" #include "colvarproxy_system.h" #include "colvarproxy_tcl.h" @@ -56,6 +55,9 @@ public: /// corresponding atom yet virtual int check_atom_id(int atom_number); + /// Check whether it is possible to select atoms by residue number name + virtual int check_atom_name_selections_available(); + /// Select this atom for collective variables calculation, using name and /// residue number. Not all programs support this: leave this function as /// is in those cases. @@ -72,31 +74,6 @@ public: /// (costly) set the corresponding atoms_refcount to zero virtual void clear_atom(int index); - /// \brief Select atom IDs from a file (usually PDB) \param filename name of - /// the file \param atoms array to which atoms read from "filename" will be - /// appended \param pdb_field (optional) if the file is a PDB and this - /// string is non-empty, select atoms for which this field is non-zero - /// \param pdb_field_value (optional) if non-zero, select only atoms whose - /// pdb_field equals this - virtual int load_atoms(char const *filename, - cvm::atom_group &atoms, - std::string const &pdb_field, - double pdb_field_value = 0.0); - - /// \brief Load a set of coordinates from a file (usually PDB); if "pos" is - /// already allocated, the number of its elements must match the number of - /// entries in "filename" \param filename name of the file \param pos array - /// of coordinates \param sorted_ids array of sorted internal IDs, used to - /// loop through the file only once \param pdb_field (optional) if the file - /// is a PDB and this string is non-empty, select atoms for which this field - /// is non-zero \param pdb_field_value (optional) if non-zero, select only - /// atoms whose pdb_field equals this - virtual int load_coords(char const *filename, - std::vector &pos, - std::vector const &sorted_ids, - std::string const &pdb_field, - double pdb_field_value = 0.0); - /// Clear atomic data int reset(); @@ -245,6 +222,18 @@ public: return atoms_max_applied_force_id_; } + /// Whether the atom list has been modified internally + inline bool modified_atom_list() const + { + return modified_atom_list_; + } + + /// Reset the modified atom list flag + inline void reset_modified_atom_list() + { + modified_atom_list_ = false; + } + /// Record whether masses have been updated inline bool updated_masses() const { @@ -284,6 +273,9 @@ protected: /// ID of the atom with the maximum norm among all applied forces int atoms_max_applied_force_id_; + /// Whether the atom list has been modified internally + bool modified_atom_list_; + /// Whether the masses and charges have been updated from the host code bool updated_masses_, updated_charges_; @@ -466,7 +458,7 @@ public: bool b_smp_active; /// Whether threaded parallelization is available (TODO: make this a cvm::deps feature) - virtual int smp_enabled(); + virtual int check_smp_enabled(); /// Distribute calculation of colvars (and their components) across threads virtual int smp_colvars_loop(); @@ -565,9 +557,9 @@ public: -/// \brief Interface between the collective variables module and -/// the simulation or analysis program (NAMD, VMD, LAMMPS...). -/// This is the base class: each interfaced program is supported by a derived class. +/// Interface between Colvars and MD engine (GROMACS, LAMMPS, NAMD, VMD...) +/// +/// This is the base class: each engine is supported by a derived class. class colvarproxy : public colvarproxy_system, public colvarproxy_atoms, @@ -589,15 +581,20 @@ public: colvarproxy(); /// Destructor - virtual ~colvarproxy(); + ~colvarproxy() override; - virtual bool io_available() /* override */; + inline std::string const &engine_name() const + { + return engine_name_; + } + + bool io_available() override; /// Request deallocation of the module (currently only implemented by VMD) virtual int request_deletion(); /// Whether deallocation was requested - inline bool delete_requested() + inline bool delete_requested() const { return b_delete_requested; } @@ -608,6 +605,26 @@ public: /// (Re)initialize the module virtual int parse_module_config(); + /// \brief Read a selection of atom IDs from a PDB coordinate file + /// \param[in] filename name of the file + /// \param[in,out] atoms array into which atoms will be read from "filename" + /// \param[in] pdb_field if the file is a PDB and this string is non-empty, + /// select atoms for which this field is non-zero + /// \param[in] pdb_field_value if non-zero, select only atoms whose pdb_field equals this + virtual int load_atoms_pdb(char const *filename, cvm::atom_group &atoms, + std::string const &pdb_field, double pdb_field_value); + + /// \brief Load a set of coordinates from a PDB file + /// \param[in] filename name of the file + /// \param[in,out] pos array of coordinates to fill; if not empty, the number of its elements must match + /// the number of entries in "filename" + /// \param[in] sorted_ids array of sorted internal IDs, used to loop through the file only once + /// \param[in] pdb_field if non-empty, only atoms for which this field is non-zero will be processed + /// \param[in] pdb_field_value if non-zero, process only atoms whose pdb_field equals this + virtual int load_coords_pdb(char const *filename, std::vector &pos, + std::vector const &sorted_ids, std::string const &pdb_field, + double pdb_field_value); + /// (Re)initialize required member data (called after the module) virtual int setup(); @@ -703,7 +720,10 @@ protected: /// Track which features have been acknowledged during the last run size_t features_hash; -private: +protected: + + /// Name of the simulation engine that the derived proxy object supports + std::string engine_name_ = "standalone"; /// Queue of config strings or files to be fed to the module void *config_queue_; diff --git a/lib/colvars/colvarproxy_io.cpp b/lib/colvars/colvarproxy_io.cpp index 225ca40bef..4cfdfeec26 100644 --- a/lib/colvars/colvarproxy_io.cpp +++ b/lib/colvars/colvarproxy_io.cpp @@ -29,7 +29,6 @@ colvarproxy_io::colvarproxy_io() { - input_buffer_ = NULL; restart_frequency_engine = 0; input_stream_error_ = new std::istringstream(); input_stream_error_->setstate(std::ios::badbit); @@ -135,7 +134,9 @@ int colvarproxy_io::rename_file(char const *filename, char const *newfilename) int error_code = COLVARS_OK; #if defined(_WIN32) && !defined(__CYGWIN__) // On straight Windows, must remove the destination before renaming it - error_code |= remove_file(newfilename); + if (_access(newfilename, 00) == 0) { + error_code |= remove_file(newfilename); + } #endif int rename_exit_code = 0; while ((rename_exit_code = std::rename(filename, newfilename)) != 0) { @@ -151,6 +152,51 @@ int colvarproxy_io::rename_file(char const *filename, char const *newfilename) } +int colvarproxy_io::set_input_prefix(std::string const &prefix) +{ + // set input restart name and strip the extension, if present + input_prefix_str = prefix; + if (input_prefix_str.rfind(".colvars.state") != std::string::npos) { + input_prefix_str.erase(input_prefix_str.rfind(".colvars.state"), + std::string(".colvars.state").size()); + } + return COLVARS_OK; +} + + +int colvarproxy_io::set_output_prefix(std::string const &prefix) +{ + // set input restart name and strip the extension, if present + output_prefix_str = prefix; + if (output_prefix_str.rfind(".colvars.state") != std::string::npos) { + output_prefix_str.erase(output_prefix_str.rfind(".colvars.state"), + std::string(".colvars.state").size()); + } + return COLVARS_OK; +} + + +int colvarproxy_io::set_restart_output_prefix(std::string const &prefix) +{ + // set input restart name and strip the extension, if present + restart_output_prefix_str = prefix; + if (restart_output_prefix_str.rfind(".colvars.state") != std::string::npos) { + restart_output_prefix_str.erase(restart_output_prefix_str.rfind(".colvars.state"), + std::string(".colvars.state").size()); + } + return COLVARS_OK; +} + + +int colvarproxy_io::set_default_restart_frequency(int freq) +{ + // TODO check for compatibility with colvarsRestartFrequency + restart_frequency_engine = freq; + return COLVARS_OK; +} + + + std::istream &colvarproxy_io::input_stream(std::string const &input_name, std::string const description, bool error_on_fail) @@ -162,14 +208,19 @@ std::istream &colvarproxy_io::input_stream(std::string const &input_name, } if (colvarproxy_io::input_stream_exists(input_name)) { - return *(input_streams_[input_name]); + std::ifstream *ifs = + dynamic_cast(input_streams_[input_name]); + if (ifs && !ifs->is_open()) { + // This file was opened before, re-open it. Using std::ios::binary to + // work around differences in line termination conventions + // See https://github.com/Colvars/colvars/commit/8236879f7de4 + ifs->open(input_name.c_str(), std::ios::binary); + } + } else { + input_streams_[input_name] = new std::ifstream(input_name.c_str(), + std::ios::binary); } - // Using binary to work around differences in line termination conventions - // See https://github.com/Colvars/colvars/commit/8236879f7de4 - input_streams_[input_name] = new std::ifstream(input_name.c_str(), - std::ios::binary); - if (input_streams_[input_name]->fail() && error_on_fail) { cvm::error("Error: cannot open "+description+" \""+input_name+"\".\n", COLVARS_FILE_ERROR); @@ -179,6 +230,41 @@ std::istream &colvarproxy_io::input_stream(std::string const &input_name, } +std::istream & +colvarproxy_io::input_stream_from_string(std::string const &input_name, + std::string const &content, + std::string const description) +{ + if (!io_available()) { + cvm::error("Error: trying to access an input file/channel " + "from the wrong thread.\n", COLVARS_BUG_ERROR); + return *input_stream_error_; + } + + if (colvarproxy_io::input_stream_exists(input_name)) { + + std::istringstream *iss = + dynamic_cast(input_streams_[input_name]); + if (iss) { + // If there is already a stringstream, replace it + delete iss; + } else { + std::ifstream *ifs = + dynamic_cast(input_streams_[input_name]); + if (ifs) { + if (ifs->is_open()) { + ifs->close(); + } + } + } + } + + input_streams_[input_name] = new std::istringstream(content); + + return *(input_streams_[input_name]); +} + + bool colvarproxy_io::input_stream_exists(std::string const &input_name) { return (input_streams_.count(input_name) > 0); @@ -188,7 +274,38 @@ bool colvarproxy_io::input_stream_exists(std::string const &input_name) int colvarproxy_io::close_input_stream(std::string const &input_name) { if (colvarproxy_io::input_stream_exists(input_name)) { - delete input_streams_[input_name]; + std::ifstream *ifs = dynamic_cast(input_streams_[input_name]); + if (ifs) { + if (ifs->is_open()) { + ifs->close(); + } + } else { + // From a string, just rewind to the begining + std::istringstream * iss = dynamic_cast(input_streams_[input_name]); + if (iss) { + iss->clear(); + iss->seekg(0); + } + } + return COLVARS_OK; + } + return cvm::error("Error: input file/channel \""+input_name+ + "\" does not exist.\n", COLVARS_FILE_ERROR); +} + + +int colvarproxy_io::delete_input_stream(std::string const &input_name) +{ + if (colvarproxy_io::close_input_stream(input_name) == COLVARS_OK) { + std::ifstream *ifs = dynamic_cast(input_streams_[input_name]); + if (ifs) { + delete ifs; + } else { + std::istringstream * iss = dynamic_cast(input_streams_[input_name]); + if (iss) { + delete iss; + } + } input_streams_.erase(input_name); return COLVARS_OK; } @@ -199,7 +316,8 @@ int colvarproxy_io::close_input_stream(std::string const &input_name) int colvarproxy_io::close_input_streams() { - for (std::map::iterator ii = input_streams_.begin(); + for (std::map::iterator ii = input_streams_.begin(); ii != input_streams_.end(); ii++) { delete ii->second; @@ -209,6 +327,19 @@ int colvarproxy_io::close_input_streams() } +std::list colvarproxy_io::list_input_stream_names() const +{ + std::list result; + for (std::map::const_iterator ii = input_streams_.begin(); + ii != input_streams_.end(); + ii++) { + result.push_back(ii->first); + } + return result; +} + + std::ostream & colvarproxy_io::output_stream(std::string const &output_name, std::string const description) { @@ -228,7 +359,7 @@ std::ostream & colvarproxy_io::output_stream(std::string const &output_name, backup_file(output_name.c_str()); - output_streams_[output_name] = new std::ofstream(output_name.c_str()); + output_streams_[output_name] = new std::ofstream(output_name.c_str(), std::ios::binary); if (!*(output_streams_[output_name])) { cvm::error("Error: cannot write to "+description+" \""+output_name+"\".\n", COLVARS_FILE_ERROR); @@ -303,6 +434,7 @@ int colvarproxy_io::close_output_streams() osi != output_streams_.end(); osi++) { (dynamic_cast(osi->second))->close(); + delete osi->second; } output_streams_.clear(); diff --git a/lib/colvars/colvarproxy_io.h b/lib/colvars/colvarproxy_io.h index ee217362d4..726f915c97 100644 --- a/lib/colvars/colvarproxy_io.h +++ b/lib/colvars/colvarproxy_io.h @@ -10,9 +10,10 @@ #ifndef COLVARPROXY_IO_H #define COLVARPROXY_IO_H +#include +#include #include #include -#include /// Methods for data input/output @@ -66,57 +67,80 @@ public: } /// Prefix of the input state file to be read next - inline std::string & input_prefix() + inline std::string const & input_prefix() const { return input_prefix_str; } + /// Initialize input_prefix (NOTE: it will be erased after state file is read) + virtual int set_input_prefix(std::string const &prefix); + /// Default prefix to be used for all output files (final configuration) - inline std::string & output_prefix() + inline std::string const & output_prefix() const { return output_prefix_str; } + /// Set default output prefix + virtual int set_output_prefix(std::string const &prefix); + /// Prefix of the restart (checkpoint) file to be written next - inline std::string & restart_output_prefix() + inline std::string const & restart_output_prefix() const { return restart_output_prefix_str; } + /// Set default restart state file prefix + virtual int set_restart_output_prefix(std::string const &prefix); + /// Default restart frequency (as set by the simulation engine) inline int default_restart_frequency() const { return restart_frequency_engine; } - /// Buffer from which the input state information may be read - inline char const * & input_buffer() - { - return input_buffer_; - } + /// Communicate/set the restart frequency of the simulation engine + virtual int set_default_restart_frequency(int freq); + + // The input stream functions below are not virtual, because they currently + // rely on the fact that either std::ifstream or std::istringstream is used /// Returns a reference to given input stream, creating it if needed /// \param input_name File name (later only a handle) /// \param description Purpose of the file /// \param error_on_fail Raise error when failing to open (allow testing) - virtual std::istream &input_stream(std::string const &input_name, - std::string const description = "file/channel", - bool error_on_fail = true); + std::istream & input_stream(std::string const &input_name, + std::string const description = "file/channel", + bool error_on_fail = true); + + /// Returns a reference to given input stream, creating it if needed + /// \param input_name Identifier of the input stream + /// \param content Set this string as the stream's buffer + /// \param description Purpose of the stream + std::istream & input_stream_from_string(std::string const &input_name, + std::string const &content, + std::string const description = "string"); /// Check if the file/channel is open (without opening it if not) - virtual bool input_stream_exists(std::string const &input_name); + bool input_stream_exists(std::string const &input_name); /// Closes the given input stream - virtual int close_input_stream(std::string const &input_name); + int close_input_stream(std::string const &input_name); /// Closes all input streams - virtual int close_input_streams(); + int close_input_streams(); + + /// Same as close_input_stream(), but also removes the corresponding entry from memory + int delete_input_stream(std::string const &input_name); + + /// List all input streams that were opened at some point + std::list list_input_stream_names() const; /// Returns a reference to the named output file/channel (open it if needed) /// \param output_name File name or identifier /// \param description Purpose of the file virtual std::ostream &output_stream(std::string const &output_name, - std::string const description = "file/channel"); + std::string const description); /// Check if the file/channel is open (without opening it if not) virtual bool output_stream_exists(std::string const &output_name); @@ -158,9 +182,6 @@ protected: /// Object whose reference is returned when write errors occur std::ostream *output_stream_error_; - - /// Buffer from which the input state information may be read - char const *input_buffer_; }; diff --git a/lib/colvars/colvarproxy_system.cpp b/lib/colvars/colvarproxy_system.cpp index 0a2769dcc9..acab5fa4c3 100644 --- a/lib/colvars/colvarproxy_system.cpp +++ b/lib/colvars/colvarproxy_system.cpp @@ -18,6 +18,7 @@ colvarproxy_system::colvarproxy_system() { angstrom_value_ = 0.0; kcal_mol_value_ = 0.0; + timestep_ = 1.0; target_temperature_ = 0.0; boltzmann_ = 0.001987191; // Default: kcal/mol/K boundaries_type = boundaries_unsupported; @@ -46,10 +47,10 @@ int colvarproxy_system::set_target_temperature(cvm::real T) } -cvm::real colvarproxy_system::dt() +int colvarproxy_system::set_integration_timestep(cvm::real dt) { - // TODO define, document and implement a user method to set the value of this - return 1.0; + timestep_ = dt; + return COLVARS_OK; } diff --git a/lib/colvars/colvarproxy_system.h b/lib/colvars/colvarproxy_system.h index be3cd346c2..67d0938e54 100644 --- a/lib/colvars/colvarproxy_system.h +++ b/lib/colvars/colvarproxy_system.h @@ -61,8 +61,14 @@ public: /// Set the current target temperature of the simulation (K units) virtual int set_target_temperature(cvm::real T); - /// \brief Time step of the simulation (fs) - virtual cvm::real dt(); + /// Time step of the simulation (fs units) + inline double dt() const + { + return timestep_; + } + + /// Set the current integration timestep of the simulation (fs units) + virtual int set_integration_timestep(cvm::real dt); /// \brief Pseudo-random number with Gaussian distribution virtual cvm::real rand_gaussian(void); @@ -137,9 +143,12 @@ protected: /// Boltzmann constant in internal Colvars units cvm::real boltzmann_; - /// Most up to date target temperature for the system (in K) + /// Most up to date target temperature (K units); default to 0.0 if undefined cvm::real target_temperature_; + /// Current integration timestep (engine units); default to 1.0 if undefined + double timestep_; + /// \brief Value of 1 Angstrom in the internal (front-end) Colvars unit for atomic coordinates /// * defaults to 0 in the base class; derived proxy classes must set it /// * in VMD proxy, can only be changed when no variables are defined diff --git a/lib/colvars/colvarproxy_tcl.cpp b/lib/colvars/colvarproxy_tcl.cpp index 5bf97a0d98..fa06b7a3c8 100644 --- a/lib/colvars/colvarproxy_tcl.cpp +++ b/lib/colvars/colvarproxy_tcl.cpp @@ -83,7 +83,7 @@ int colvarproxy_tcl::tcl_run_file(std::string const &fileName) Tcl_Interp *const interp = get_tcl_interp(); int err = Tcl_EvalFile(interp, fileName.c_str()); if (err != TCL_OK) { - cvm::log("Error while executing Tcl script file" + fileName + ":\n"); + cvm::log("Error while executing Tcl script file \"" + fileName + "\":\n"); cvm::error(Tcl_GetStringResult(interp)); return COLVARS_ERROR; } diff --git a/lib/colvars/colvarproxy_volmaps.cpp b/lib/colvars/colvarproxy_volmaps.cpp index 6c1f11e32e..3d02d8085a 100644 --- a/lib/colvars/colvarproxy_volmaps.cpp +++ b/lib/colvars/colvarproxy_volmaps.cpp @@ -21,7 +21,7 @@ colvarproxy_volmaps::colvarproxy_volmaps() colvarproxy_volmaps::~colvarproxy_volmaps() {} -int colvarproxy_volmaps::volmaps_available() +int colvarproxy_volmaps::check_volmaps_available() { return COLVARS_NOT_IMPLEMENTED; } diff --git a/lib/colvars/colvarproxy_volmaps.h b/lib/colvars/colvarproxy_volmaps.h index f8c9ba8539..6e1ffcc7c1 100644 --- a/lib/colvars/colvarproxy_volmaps.h +++ b/lib/colvars/colvarproxy_volmaps.h @@ -18,8 +18,8 @@ public: /// Clear volumetric map data int reset(); - /// \brief Whether this implementation has capability to use volumetric maps - virtual int volmaps_available(); + /// Test whether this implementation can use volumetric maps as CVs + virtual int check_volmaps_available(); /// Create a slot for a volumetric map not requested yet int add_volmap_slot(int volmap_id); diff --git a/lib/colvars/colvars_memstream.cpp b/lib/colvars/colvars_memstream.cpp new file mode 100644 index 0000000000..13cb8fb343 --- /dev/null +++ b/lib/colvars/colvars_memstream.cpp @@ -0,0 +1,102 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + + +#include "colvarmodule.h" +#include "colvartypes.h" +#include "colvarvalue.h" +#include "colvars_memstream.h" + + +bool cvm::memory_stream::expand_output_buffer(size_t add_bytes) +{ + auto &buffer = external_output_buffer_ ? *external_output_buffer_ : internal_buffer_; + if ((buffer.size() + add_bytes) <= max_length_) { + buffer.resize((buffer.size() + add_bytes)); + } else { + setstate(std::ios::badbit); + } + return bool(*this); +} + + +template <> void cvm::memory_stream::write_object(std::string const &t) +{ + size_t const string_length = t.size(); + size_t const new_data_size = sizeof(size_t) + sizeof(char) * string_length; + if (expand_output_buffer(new_data_size)) { + std::memcpy(output_location(), &string_length, sizeof(size_t)); + incr_write_pos(sizeof(size_t)); + std::memcpy(output_location(), t.c_str(), t.size() * sizeof(char)); + incr_write_pos(t.size() * sizeof(char)); + } +} + +template <> cvm::memory_stream &operator<<(cvm::memory_stream &os, std::string const &t) +{ + os.write_object(t); + return os; +} + +template <> void cvm::memory_stream::write_object(colvarvalue const &t) +{ + *this << t; +} + +template <> void cvm::memory_stream::write_object(cvm::vector1d const &t) +{ + return write_vector(t.data_array()); +} + +template <> +cvm::memory_stream &operator<<(cvm::memory_stream &os, cvm::vector1d const &t) +{ + os.write_vector(t.data_array()); + return os; +} + + +template <> void cvm::memory_stream::read_object(std::string &t) +{ + begin_reading(); + size_t string_length = 0; + if (has_remaining(sizeof(size_t))) { + std::memcpy(&string_length, input_location(), sizeof(size_t)); + incr_read_pos(sizeof(size_t)); + if (has_remaining(string_length * sizeof(char))) { + t.assign(reinterpret_cast(input_location()), string_length); + incr_read_pos(string_length * sizeof(char)); + done_reading(); + } else { + setstate(std::ios::failbit); + } + } +} + +template <> cvm::memory_stream &operator>>(cvm::memory_stream &is, std::string &t) +{ + is.read_object(t); + return is; +} + +template <> void cvm::memory_stream::read_object(colvarvalue &t) +{ + *this >> t; +} + +template <> void cvm::memory_stream::read_object(cvm::vector1d &t) +{ + return read_vector(t.data_array()); +} + +template <> cvm::memory_stream &operator>>(cvm::memory_stream &is, cvm::vector1d &t) +{ + is.read_vector(t.data_array()); + return is; +} diff --git a/lib/colvars/colvars_memstream.h b/lib/colvars/colvars_memstream.h new file mode 100644 index 0000000000..0d80d2794d --- /dev/null +++ b/lib/colvars/colvars_memstream.h @@ -0,0 +1,289 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef MEMORY_STREAM_H +#define MEMORY_STREAM_H + +#include +#include +#include +#include +#include + + +// Work around missing std::is_trivially_copyable in old GCC and Clang versions +// TODO remove this after CentOS 7 has been beyond EOL for a while +#if (defined(__GNUC__) && (__GNUC__ < 5) && !defined(__clang__)) || (defined(__clang__) && (__clang_major__ < 7)) +// Clang needs an exception, because it defines __GNUC__ as well +#define IS_TRIVIALLY_COPYABLE(T) __has_trivial_copy(T) +#else +#define IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable::value +#endif + + +class cvm::memory_stream { + +public: + + /// Set up an empty stream with an internal buffer, suitable for writing to + /// \param max_length Maximum allowed capacity (default is 64 GiB) + memory_stream(size_t max_length = (static_cast(1L) << 36)) : max_length_(max_length) {} + + /// Set up a stream based on an external input buffer + memory_stream(size_t n, unsigned char const *buf) + : external_input_buffer_(buf), internal_buffer_(), data_length_(n), max_length_(data_length_) + { + } + + /// Set up a stream based on an external output buffer + memory_stream(std::vector &buf) : memory_stream() + { + external_output_buffer_ = &buf; + } + + /// Length of the buffer + inline size_t length() const { return data_length_; } + + /// Output buffer + inline unsigned char *output_buffer() + { + return (external_output_buffer_ ? external_output_buffer_->data() : internal_buffer_.data()); + } + + /// Next location to write to + inline unsigned char *output_location() { return output_buffer() + data_length_; } + + /// Input buffer + inline unsigned char const *input_buffer() const + { + return (external_input_buffer_ ? external_input_buffer_ : internal_buffer_.data()); + } + + /// Next location to read from + inline unsigned char const *input_location() const { return input_buffer() + read_pos_; } + + /// Cast operator to be used to test for errors + inline explicit operator bool() const { return state_ == std::ios::goodbit; } + + /// Write a simple object to the output buffer + template void write_object(T const &t); + + /// Wrapper to write_object() + template friend memory_stream &operator<<(memory_stream &os, T const &t); + + /// Write a vector of simple objects to the output buffer + template void write_vector(std::vector const &t); + + /// Wrapper to write_vector() + template + friend memory_stream &operator<<(memory_stream &os, std::vector const &t); + + /// Read a simple object from the buffer + template void read_object(T &t); + + /// Wrapper to read_object() + template friend memory_stream &operator>>(memory_stream &is, T &t); + + /// Read a vector of simple objects from the buffer + template void read_vector(std::vector &t); + + /// Wrapper to read_vector() + template friend memory_stream &operator>>(memory_stream &is, std::vector &t); + + + // Compatibility with STL stream functions + + /// Report the current position in the buffer + inline size_t tellg() const { return read_pos_; } + + /// Report the current position in the buffer + inline memory_stream & seekg(size_t pos) { read_pos_ = pos; return *this; } + + /// Ignore formatting operators + inline void setf(decltype(std::ios::fmtflags(0)), decltype(std::ios::floatfield)) {} + + /// Ignore formatting operators + inline void flags(decltype(std::ios::fmtflags(0))) {} + + /// Get the current formatting flags (i.e. none because this stream is unformatted) + inline decltype(std::ios::fmtflags(0)) flags() const { return std::ios::fmtflags(0); } + + /// Get the error code + inline std::ios::iostate rdstate() const { return state_; } + + /// Set the error code + inline void setstate(std::ios::iostate new_state) { state_ |= new_state; } + + /// Clear the error code + inline void clear() { state_ = std::ios::goodbit; } + +protected: + + /// External output buffer + std::vector *external_output_buffer_ = nullptr; + + /// External input buffer + unsigned char const *external_input_buffer_ = nullptr; + + /// Internal buffer (may server for both input and output) + std::vector internal_buffer_; + + /// Length of the data buffer (either internal or external) + size_t data_length_ = 0L; + + /// Largest allowed capacity of the data buffer + size_t const max_length_; + + /// Error status + std::ios::iostate state_ = std::ios::goodbit; + + /// Add the requester number of bytes to the array capacity; return false if buffer is external + bool expand_output_buffer(size_t add_bytes); + + /// Move the buffer position past the data just written + inline void incr_write_pos(size_t c) { data_length_ += c; } + + /// Current position when reading from the buffer + size_t read_pos_ = 0L; + + /// Begin an attempt to read an object; assume EOF unless there is space remaining + inline void begin_reading() { setstate(std::ios::eofbit); } + + /// Mark the reading attempt succesful + inline void done_reading() { clear(); } + + /// Move the buffer position past the data just read + inline void incr_read_pos(size_t c) { read_pos_ += c; } + + /// Check that the buffer contains enough bytes to read as the argument says, set error + /// otherwise + inline bool has_remaining(size_t c) { return c <= (data_length_ - read_pos_); } + }; + +template void cvm::memory_stream::write_object(T const &t) +{ + static_assert(IS_TRIVIALLY_COPYABLE(T), "Cannot use write_object() on complex type"); + size_t const new_data_size = sizeof(T); + if (expand_output_buffer(new_data_size)) { + std::memcpy(output_location(), &t, sizeof(T)); + incr_write_pos(new_data_size); + } +} + +template cvm::memory_stream &operator<<(cvm::memory_stream &os, T const &t) +{ + os.write_object(t); + return os; +} + +template void cvm::memory_stream::write_vector(std::vector const &t) +{ + static_assert(IS_TRIVIALLY_COPYABLE(T), "Cannot use write_vector() on complex type"); + size_t const vector_length = t.size(); + size_t const new_data_size = sizeof(size_t) + sizeof(T) * vector_length; + if (expand_output_buffer(new_data_size)) { + std::memcpy(output_location(), &vector_length, sizeof(size_t)); + incr_write_pos(sizeof(T)); + std::memcpy(output_location(), t.data(), t.size() * sizeof(T)); + incr_write_pos(t.size() * sizeof(T)); + } +} + +template +cvm::memory_stream &operator<<(cvm::memory_stream &os, std::vector const &t) +{ + os.write_vector(t); + return os; +} + +template void cvm::memory_stream::read_object(T &t) +{ + static_assert(IS_TRIVIALLY_COPYABLE(T), "Cannot use read_object() on complex type"); + begin_reading(); + if (has_remaining(sizeof(T))) { + std::memcpy(&t, input_location(), sizeof(T)); + incr_read_pos(sizeof(T)); + done_reading(); + } +} + +template cvm::memory_stream &operator>>(cvm::memory_stream &is, T &t) +{ + is.read_object(t); + return is; +} + +template void cvm::memory_stream::read_vector(std::vector &t) +{ + static_assert(IS_TRIVIALLY_COPYABLE(T), "Cannot use read_vector() on complex type"); + begin_reading(); + size_t vector_length = 0; + if (has_remaining(sizeof(size_t))) { + std::memcpy(&vector_length, input_location(), sizeof(size_t)); + incr_read_pos(sizeof(size_t)); + if (has_remaining(vector_length * sizeof(T))) { + t.resize(vector_length); + std::memcpy(t.data(), input_location(), vector_length * sizeof(T)); + incr_read_pos(vector_length * sizeof(T)); + done_reading(); + } else { + setstate(std::ios::failbit); + } + } +} + +template cvm::memory_stream &operator>>(cvm::memory_stream &is, std::vector &t) +{ + is.read_vector(t); + return is; +} + +template cvm::memory_stream &operator<<(cvm::memory_stream &os, + decltype(std::setprecision(10)) const &) +{ + return os; +} + +#if !defined(_MSC_VER) && !defined(__SUNPRO_CC) +// Visual Studio and MSVC use the same return type for both modifiers +template cvm::memory_stream &operator<<(cvm::memory_stream &os, + decltype(std::setw(10)) const &) +{ + return os; +} +#endif + +// Declare specializations + +template <> void cvm::memory_stream::write_object(std::string const &t); + +template <> cvm::memory_stream &operator<<(cvm::memory_stream &os, std::string const &t); + +template <> void cvm::memory_stream::write_object(colvarvalue const &t); + +template <> cvm::memory_stream &operator<<(cvm::memory_stream &os, colvarvalue const &x); + +template <> void cvm::memory_stream::write_object(cvm::vector1d const &t); + +template <> +cvm::memory_stream &operator<<(cvm::memory_stream &os, cvm::vector1d const &t); + +template <> void cvm::memory_stream::read_object(std::string &t); + +template <> cvm::memory_stream &operator>>(cvm::memory_stream &is, std::string &t); + +template <> void cvm::memory_stream::read_object(colvarvalue &t); + +template <> cvm::memory_stream &operator>>(cvm::memory_stream &is, colvarvalue &t); + +template <> void cvm::memory_stream::read_object(cvm::vector1d &t); + +template <> cvm::memory_stream &operator>>(cvm::memory_stream &is, cvm::vector1d &t); + +#endif diff --git a/lib/colvars/colvars_version.h b/lib/colvars/colvars_version.h index d500c0e5ec..d50a00fff5 100644 --- a/lib/colvars/colvars_version.h +++ b/lib/colvars/colvars_version.h @@ -1,3 +1,3 @@ #ifndef COLVARS_VERSION -#define COLVARS_VERSION "2023-05-01" +#define COLVARS_VERSION "2024-06-04" #endif diff --git a/lib/colvars/colvarscript.cpp b/lib/colvars/colvarscript.cpp index 68dd218a62..085319bd40 100644 --- a/lib/colvars/colvarscript.cpp +++ b/lib/colvars/colvarscript.cpp @@ -7,8 +7,6 @@ // If you wish to distribute your changes, please submit them to the // Colvars repository at GitHub. -#include -#include #include #include "colvarproxy.h" @@ -35,6 +33,7 @@ colvarscript::colvarscript(colvarproxy *p, colvarmodule *m) : proxy_(p), colvars(m) { + cmdline_main_cmd_ = std::string("cv"); cmd_names = NULL; init_commands(); #ifdef COLVARS_TCL @@ -141,7 +140,7 @@ int colvarscript::init_command(colvarscript::command const &comm, } cmd_full_help[comm] = cmd_help[comm]+"\n"; - if (cmd_n_args_min[comm] > 0) { + if (cmd_n_args_max[comm] > 0) { cmd_full_help[comm] += "\nParameters\n"; cmd_full_help[comm] += "----------\n\n"; size_t i; @@ -281,11 +280,15 @@ std::string colvarscript::get_command_cmdline_syntax(colvarscript::Object_type t switch (t) { case use_module: - return std::string("cv "+cmdline_cmd+cmdline_args); break; + return std::string(cmdline_main_cmd_ + " " + cmdline_cmd + cmdline_args); + break; case use_colvar: - return std::string("cv colvar name "+cmdline_cmd+cmdline_args); break; + return std::string(cmdline_main_cmd_ + " colvar name " + cmdline_cmd+ + cmdline_args); + break; case use_bias: - return std::string("cv bias name "+cmdline_cmd+cmdline_args); break; + return std::string(cmdline_main_cmd_ + " bias name " + cmdline_cmd+cmdline_args); + break; default: // Already handled, but silence the warning return std::string(""); @@ -353,7 +356,7 @@ int colvarscript::run(int objc, unsigned char *const objv[]) } if (objc < 2) { - set_result_str("No commands given: use \"cv help\" " + set_result_str("No commands given: use \""+cmdline_main_cmd_+" help\" " "for a list of commands."); return COLVARSCRIPT_ERROR; } @@ -436,7 +439,8 @@ int colvarscript::run(int objc, unsigned char *const objv[]) error_code = (*cmd_fn)(obj_for_cmd, objc, objv); } else { add_error_msg("Syntax error: "+cmdline+"\n" - " Run \"cv help\" or \"cv help \" " + " Run \""+main_cmd+" help\" or \""+ + main_cmd+" help \" " "to get the correct syntax.\n"); error_code = COLVARSCRIPT_ERROR; } @@ -763,7 +767,7 @@ extern "C" int tcl_run_colvarscript_command(ClientData /* clientData */, int colvarscript::set_result_text_from_str(std::string const &x_str, unsigned char *obj) { if (obj) { - strcpy(reinterpret_cast(obj), x_str.c_str()); + std::memcpy(reinterpret_cast(obj), x_str.c_str(), x_str.size()); } else { set_result_str(x_str); } diff --git a/lib/colvars/colvarscript.h b/lib/colvars/colvarscript.h index d624d9ce38..229fe0ff39 100644 --- a/lib/colvars/colvarscript.h +++ b/lib/colvars/colvarscript.h @@ -16,7 +16,6 @@ #include "colvarmodule.h" #include "colvarvalue.h" -#include "colvarbias.h" #include "colvarproxy.h" @@ -25,6 +24,8 @@ #define COLVARSCRIPT_OK 0 +class colvardeps; + class colvarscript { private: @@ -161,6 +162,11 @@ public: /// \param cmd Name of the command's function (e.g. "cv_units") int get_command_n_args_max(char const *cmd); + /// Set the main command for the CLI, when it is not "cv" (e.g. LAMMPS) + inline void set_cmdline_main_cmd(std::string const &cmd) { + cmdline_main_cmd_ = cmd; + } + /// Get help string for a command (does not specify how it is launched) /// \param cmd Name of the command's function (e.g. "cv_units") char const *get_command_full_help(char const *cmd); @@ -263,6 +269,9 @@ private: // TODO /// Internal identifiers of command strings std::map cmd_str_map; + /// Main command used in command line ("cv" by default) + std::string cmdline_main_cmd_; + /// Inverse of cmd_str_map (to be exported outside this class) char const **cmd_names; diff --git a/lib/colvars/colvarscript_commands.cpp b/lib/colvars/colvarscript_commands.cpp index c0a28825bf..5db3b96f03 100644 --- a/lib/colvars/colvarscript_commands.cpp +++ b/lib/colvars/colvarscript_commands.cpp @@ -8,9 +8,9 @@ // Colvars repository at GitHub. #include -#include -#include +#include "colvar.h" +#include "colvarbias.h" #include "colvarproxy.h" #include "colvardeps.h" #include "colvarscript.h" diff --git a/lib/colvars/colvarscript_commands.h b/lib/colvars/colvarscript_commands.h index 6dd63d82d2..bdad74e433 100644 --- a/lib/colvars/colvarscript_commands.h +++ b/lib/colvars/colvarscript_commands.h @@ -47,14 +47,10 @@ // If CVSCRIPT is not defined, this file yields the function prototypes #ifndef CVSCRIPT -#ifdef __cplusplus #define CVSCRIPT_COMM_PROTO(COMM) \ extern "C" int CVSCRIPT_COMM_FNAME(COMM)(void *, \ - int, unsigned char *const *); -#else -#define CVSCRIPT_COMM_PROTO(COMM) \ - int CVSCRIPT_COMM_FNAME(COMM)(void *, int, unsigned char *const *); -#endif + int, \ + unsigned char *const *); #define CVSCRIPT(COMM,HELP,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY) \ CVSCRIPT_COMM_PROTO(COMM) @@ -454,7 +450,8 @@ CVSCRIPT(cv_listcommands, ) CVSCRIPT(cv_listindexfiles, - "Get a list of the index files loaded in this session", + "Get a list of the index files loaded in this session\n" + "list : sequence of strings - List of index file names", 0, 0, "", int const n_files = script->module()->index_file_names.size(); @@ -467,19 +464,36 @@ CVSCRIPT(cv_listindexfiles, return COLVARS_OK; ) +CVSCRIPT(cv_listinputfiles, + "Get a list of all input/configuration files loaded in this session\n" + "list : sequence of strings - List of file names", + 0, 0, + "", + std::list const l = + script->proxy()->list_input_stream_names(); + std::string result; + for (std::list::const_iterator li = l.begin(); + li != l.end(); li++) { + if (li != l.begin()) result.append(1, ' '); + result.append(*li); + } + script->set_result_str(result); + return COLVARS_OK; + ) + CVSCRIPT(cv_load, "Load data from a state file into all matching colvars and biases", 1, 1, "prefix : string - Path to existing state file or input prefix", char const *arg = script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); - script->proxy()->input_prefix() = cvm::state_file_prefix(arg); - if (script->module()->setup_input() == COLVARS_OK) { - return COLVARS_OK; - } else { + int error_code = + script->proxy()->set_input_prefix(cvm::state_file_prefix(arg)); + error_code |= script->module()->setup_input(); + if (error_code != COLVARS_OK) { script->add_error_msg("Error loading state file"); - return COLVARSCRIPT_ERROR; } + return error_code; ) CVSCRIPT(cv_loadfromstring, @@ -488,7 +502,8 @@ CVSCRIPT(cv_loadfromstring, "buffer : string - String buffer containing the state information", char const *arg = script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); - script->proxy()->input_buffer() = arg; + script->proxy()->input_stream_from_string("input state string", + std::string(arg)); if (script->module()->setup_input() == COLVARS_OK) { return COLVARS_OK; } else { @@ -560,8 +575,7 @@ CVSCRIPT(cv_save, "prefix : string - Output prefix with trailing \".colvars.state\" gets removed)", std::string const prefix = cvm::state_file_prefix(script->obj_to_str(script->get_module_cmd_arg(0, objc, objv))); - script->proxy()->output_prefix() = prefix; - int error_code = COLVARS_OK; + int error_code = script->proxy()->set_output_prefix(prefix); error_code |= script->module()->setup_output(); error_code |= script->module()->write_restart_file(prefix+ ".colvars.state"); @@ -578,10 +592,10 @@ CVSCRIPT(cv_savetostring, ) CVSCRIPT(cv_targettemperature, - "Get/set target temperature, overriding what the MD engine provides\n" + "Get/set target temperature, overriding internally what the MD engine reports\n" "T : float - Current target temperature in K", 0, 1, - "T : float - New target temperature in K", + "T : float - New target temperature in K (internal use)", char const *Targ = script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); if (Targ == NULL) { @@ -591,6 +605,20 @@ CVSCRIPT(cv_targettemperature, } ) +CVSCRIPT(cv_timestep, + "Get/set integration timestep, overriding internally what the MD engine reports\n" + "dt : float - Current integration timestep in MD engine units", + 0, 1, + "dt : float - New integration timestep in MD engine units", + char const *arg = + script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); + if (arg == NULL) { + return script->set_result_real(script->proxy()->dt()); + } else { + return script->proxy()->set_integration_timestep(strtod(arg, NULL)); + } + ) + CVSCRIPT(cv_units, "Get or set the current Colvars unit system\n" "units : string - The current unit system", diff --git a/lib/colvars/colvarscript_commands_bias.cpp b/lib/colvars/colvarscript_commands_bias.cpp index 2a94efb07e..0efab64a59 100644 --- a/lib/colvars/colvarscript_commands_bias.cpp +++ b/lib/colvars/colvarscript_commands_bias.cpp @@ -9,11 +9,9 @@ #include -#include -#include -#include #include "colvarproxy.h" +#include "colvarbias.h" #include "colvardeps.h" #include "colvarscript.h" #include "colvarscript_commands.h" diff --git a/lib/colvars/colvarscript_commands_bias.h b/lib/colvars/colvarscript_commands_bias.h index 420bbabcc0..5cecfcacaf 100644 --- a/lib/colvars/colvarscript_commands_bias.h +++ b/lib/colvars/colvarscript_commands_bias.h @@ -9,7 +9,7 @@ CVSCRIPT(bias_bin, - "Get the current grid bin index (1D ABF only for now)\n" + "Get the current grid bin index (flattened if more than 1d)\n" "bin : integer - Bin index", 0, 0, "", @@ -17,6 +17,8 @@ CVSCRIPT(bias_bin, return COLVARS_OK; ) +/// This is deprecated in the context of mwABF; no other known uses +/// But removing it may break user scripts CVSCRIPT(bias_bincount, "Get the number of samples at the given grid bin (1D ABF only for now)\n" "samples : integer - Number of samples", @@ -36,6 +38,25 @@ CVSCRIPT(bias_bincount, return COLVARS_OK; ) +CVSCRIPT(bias_local_sample_count, + "Get the number of samples around the current bin" + "samples : integer - Number of samples", + 0, 1, + "radius : integer - Sum over radius bins around current bin", + int radius = 0; + char const *arg = + script->obj_to_str(script->get_bias_cmd_arg(0, objc, objv)); + if (arg) { + std::string const param(arg); + if (!(std::istringstream(param) >> radius)) { + script->add_error_msg("local_sample_count: error parsing radius"); + return COLVARSCRIPT_ERROR; + } + } + script->set_result_str(cvm::to_str(this_bias->local_sample_count(radius))); + return COLVARS_OK; + ) + CVSCRIPT(bias_binnum, "Get the total number of grid points of this bias (1D ABF only for now)\n" "Bins : integer - Number of grid points", diff --git a/lib/colvars/colvarscript_commands_colvar.cpp b/lib/colvars/colvarscript_commands_colvar.cpp index da1f74d11d..0d03baaa8c 100644 --- a/lib/colvars/colvarscript_commands_colvar.cpp +++ b/lib/colvars/colvarscript_commands_colvar.cpp @@ -11,8 +11,8 @@ #include #include #include -#include +#include "colvar.h" #include "colvarproxy.h" #include "colvardeps.h" #include "colvarscript.h" diff --git a/lib/colvars/colvarscript_commands_colvar.h b/lib/colvars/colvarscript_commands_colvar.h index 103ff22daf..f6bb6b8c98 100644 --- a/lib/colvars/colvarscript_commands_colvar.h +++ b/lib/colvars/colvarscript_commands_colvar.h @@ -9,7 +9,7 @@ CVSCRIPT(colvar_addforce, - "Apply the given force onto this colvar and return the same\n" + "Apply the given force onto this colvar (no effects outside run)\n" "force : float or array - Applied force; matches colvar dimensionality", 1, 1, "force : float or array - Applied force; must match colvar dimensionality", diff --git a/lib/colvars/colvartypes.cpp b/lib/colvars/colvartypes.cpp index 04e007cbc0..f51791d015 100644 --- a/lib/colvars/colvartypes.cpp +++ b/lib/colvars/colvartypes.cpp @@ -7,12 +7,10 @@ // If you wish to distribute your changes, please submit them to the // Colvars repository at GitHub. -#include -#include - #include "colvarmodule.h" #include "colvartypes.h" -#include "colvarparse.h" +#include "colvaratoms.h" +#include "colvar_rotation_derivative.h" #ifdef COLVARS_LAMMPS // Use open-source Jacobi implementation @@ -204,14 +202,6 @@ cvm::quaternion::position_derivative_inner(cvm::rvector const &pos, return result; } - - - -// Calculate the optimal rotation between two groups, and implement it -// as a quaternion. Uses the method documented in: Coutsias EA, -// Seok C, Dill KA. Using quaternions to calculate RMSD. J Comput -// Chem. 25(15):1849-57 (2004) DOI: 10.1002/jcc.20110 PubMed: 15376254 - #ifdef COLVARS_LAMMPS namespace { inline void *new_Jacobi_solver(int size) { @@ -226,7 +216,7 @@ namespace { int colvarmodule::rotation::init() { b_debug_gradients = false; - lambda = 0.0; + // lambda = 0.0; cvm::main()->cite_feature("Optimal rotation via flexible fitting"); return COLVARS_OK; } @@ -300,6 +290,25 @@ void colvarmodule::rotation::build_correlation_matrix( } } +void colvarmodule::rotation::build_correlation_matrix( + std::vector const &pos1, + std::vector const &pos2) +{ + // build the correlation matrix + size_t i; + for (i = 0; i < pos1.size(); i++) { + C.xx += pos1[i].pos.x * pos2[i].x; + C.xy += pos1[i].pos.x * pos2[i].y; + C.xz += pos1[i].pos.x * pos2[i].z; + C.yx += pos1[i].pos.y * pos2[i].x; + C.yy += pos1[i].pos.y * pos2[i].y; + C.yz += pos1[i].pos.y * pos2[i].z; + C.zx += pos1[i].pos.z * pos2[i].x; + C.zy += pos1[i].pos.z * pos2[i].y; + C.zz += pos1[i].pos.z * pos2[i].z; + } +} + void colvarmodule::rotation::compute_overlap_matrix() { @@ -325,28 +334,26 @@ void colvarmodule::rotation::compute_overlap_matrix() #ifndef COLVARS_LAMMPS -namespace { +namespace NR { -void diagonalize_matrix(cvm::matrix2d &m, - cvm::vector1d &eigval, - cvm::matrix2d &eigvec) +void diagonalize_matrix(cvm::real m[4][4], + cvm::real eigval[4], + cvm::real eigvec[4][4]) { - eigval.resize(4); - eigval.reset(); - eigvec.resize(4, 4); - eigvec.reset(); + std::memset(eigval, 0, sizeof(cvm::real) * 4); + std::memset(eigvec, 0, sizeof(cvm::real) * 4 * 4); // diagonalize int jac_nrot = 0; - if (NR_Jacobi::jacobi(m.c_array(), eigval.c_array(), eigvec.c_array(), &jac_nrot) != + if (NR_Jacobi::jacobi(m, eigval, eigvec, &jac_nrot) != COLVARS_OK) { cvm::error("Too many iterations in jacobi diagonalization.\n" "This is usually the result of an ill-defined set of atoms for " "rotational alignment (RMSD, rotateReference, etc).\n"); } - NR_Jacobi::eigsrt(eigval.c_array(), eigvec.c_array()); + NR_Jacobi::eigsrt(eigval, eigvec); // jacobi saves eigenvectors by columns - NR_Jacobi::transpose(eigvec.c_array()); + NR_Jacobi::transpose(eigvec); // normalize eigenvectors for (size_t ie = 0; ie < 4; ie++) { @@ -375,27 +382,51 @@ void colvarmodule::rotation::calc_optimal_rotation( C.reset(); build_correlation_matrix(pos1, pos2); - S.resize(4, 4); - S.reset(); + calc_optimal_rotation_impl(); + + if (b_debug_gradients) debug_gradients(*this, pos1, pos2); +} + +void colvarmodule::rotation::calc_optimal_rotation( + std::vector const &pos1, + std::vector const &pos2) +{ + C.reset(); + build_correlation_matrix(pos1, pos2); + + calc_optimal_rotation_impl(); + + if (b_debug_gradients) debug_gradients(*this, pos1, pos2); +} + +// Calculate the optimal rotation between two groups, and implement it +// as a quaternion. Uses the method documented in: Coutsias EA, +// Seok C, Dill KA. Using quaternions to calculate RMSD. J Comput +// Chem. 25(15):1849-57 (2004) DOI: 10.1002/jcc.20110 PubMed: 15376254 +void colvarmodule::rotation::calc_optimal_rotation_impl() { compute_overlap_matrix(); - S_backup.resize(4, 4); - S_backup = S; + // S_backup = S; + std::memcpy(&S_backup[0][0], &S, 4*4*sizeof(cvm::real)); if (b_debug_gradients) { - cvm::log("S = "+cvm::to_str(S_backup, cvm::cv_width, cvm::cv_prec)+"\n"); + cvm::matrix2d S_backup_out(4, 4); + for (size_t i = 0; i < 4; ++i) { + for (size_t j = 0; j < 4; ++j) { + S_backup_out[i][j] = S_backup[i][j]; + } + } + cvm::log("S = "+cvm::to_str(S_backup_out, cvm::cv_width, cvm::cv_prec)+"\n"); } - S_eigval.resize(4); - S_eigvec.resize(4, 4); #ifdef COLVARS_LAMMPS MathEigen::Jacobi &, - cvm::matrix2d &> *ecalc = + cvm::real[4], + cvm::real[4][4]> *ecalc = reinterpret_cast &, - cvm::matrix2d &> *>(jacobi); + cvm::real[4], + cvm::real[4][4]> *>(jacobi); int ierror = ecalc->Diagonalize(S, S_eigval, S_eigvec); if (ierror) { @@ -404,22 +435,9 @@ void colvarmodule::rotation::calc_optimal_rotation( "rotational alignment (RMSD, rotateReference, etc).\n"); } #else - diagonalize_matrix(S, S_eigval, S_eigvec); + NR::diagonalize_matrix(S, S_eigval, S_eigvec); #endif - - - // eigenvalues and eigenvectors - cvm::real const L0 = S_eigval[0]; - cvm::real const L1 = S_eigval[1]; - cvm::real const L2 = S_eigval[2]; - cvm::real const L3 = S_eigval[3]; - cvm::quaternion const Q0(S_eigvec[0]); - cvm::quaternion const Q1(S_eigvec[1]); - cvm::quaternion const Q2(S_eigvec[2]); - cvm::quaternion const Q3(S_eigvec[3]); - - lambda = L0; - q = Q0; + q = cvm::quaternion{S_eigvec[0][0], S_eigvec[0][1], S_eigvec[0][2], S_eigvec[0][3]}; if (cvm::rotation::monitor_crossings) { if (q_old.norm2() > 0.0) { @@ -431,178 +449,4 @@ void colvarmodule::rotation::calc_optimal_rotation( } q_old = q; } - - if (b_debug_gradients) { - cvm::log("L0 = "+cvm::to_str(L0, cvm::cv_width, cvm::cv_prec)+ - ", Q0 = "+cvm::to_str(Q0, cvm::cv_width, cvm::cv_prec)+ - ", Q0*Q0 = "+cvm::to_str(Q0.inner(Q0), cvm::cv_width, cvm::cv_prec)+ - "\n"); - cvm::log("L1 = "+cvm::to_str(L1, cvm::cv_width, cvm::cv_prec)+ - ", Q1 = "+cvm::to_str(Q1, cvm::cv_width, cvm::cv_prec)+ - ", Q0*Q1 = "+cvm::to_str(Q0.inner(Q1), cvm::cv_width, cvm::cv_prec)+ - "\n"); - cvm::log("L2 = "+cvm::to_str(L2, cvm::cv_width, cvm::cv_prec)+ - ", Q2 = "+cvm::to_str(Q2, cvm::cv_width, cvm::cv_prec)+ - ", Q0*Q2 = "+cvm::to_str(Q0.inner(Q2), cvm::cv_width, cvm::cv_prec)+ - "\n"); - cvm::log("L3 = "+cvm::to_str(L3, cvm::cv_width, cvm::cv_prec)+ - ", Q3 = "+cvm::to_str(Q3, cvm::cv_width, cvm::cv_prec)+ - ", Q0*Q3 = "+cvm::to_str(Q0.inner(Q3), cvm::cv_width, cvm::cv_prec)+ - "\n"); - } - - // calculate derivatives of L0 and Q0 with respect to each atom in - // either group; note: if dS_1 is a null vector, nothing will be - // calculated - size_t ia; - for (ia = 0; ia < dS_1.size(); ia++) { - - cvm::real const &a2x = pos2[ia].x; - cvm::real const &a2y = pos2[ia].y; - cvm::real const &a2z = pos2[ia].z; - - cvm::matrix2d &ds_1 = dS_1[ia]; - - // derivative of the S matrix - ds_1.reset(); - ds_1[0][0].set( a2x, a2y, a2z); - ds_1[1][0].set( 0.0, a2z, -a2y); - ds_1[0][1] = ds_1[1][0]; - ds_1[2][0].set(-a2z, 0.0, a2x); - ds_1[0][2] = ds_1[2][0]; - ds_1[3][0].set( a2y, -a2x, 0.0); - ds_1[0][3] = ds_1[3][0]; - ds_1[1][1].set( a2x, -a2y, -a2z); - ds_1[2][1].set( a2y, a2x, 0.0); - ds_1[1][2] = ds_1[2][1]; - ds_1[3][1].set( a2z, 0.0, a2x); - ds_1[1][3] = ds_1[3][1]; - ds_1[2][2].set(-a2x, a2y, -a2z); - ds_1[3][2].set( 0.0, a2z, a2y); - ds_1[2][3] = ds_1[3][2]; - ds_1[3][3].set(-a2x, -a2y, a2z); - - cvm::rvector &dl0_1 = dL0_1[ia]; - cvm::vector1d &dq0_1 = dQ0_1[ia]; - - // matrix multiplications; derivatives of L_0 and Q_0 are - // calculated using Hellmann-Feynman theorem (i.e. exploiting the - // fact that the eigenvectors Q_i form an orthonormal basis) - - dl0_1.reset(); - for (size_t i = 0; i < 4; i++) { - for (size_t j = 0; j < 4; j++) { - dl0_1 += Q0[i] * ds_1[i][j] * Q0[j]; - } - } - - dq0_1.reset(); - for (size_t p = 0; p < 4; p++) { - for (size_t i = 0; i < 4; i++) { - for (size_t j = 0; j < 4; j++) { - dq0_1[p] += - (Q1[i] * ds_1[i][j] * Q0[j]) / (L0-L1) * Q1[p] + - (Q2[i] * ds_1[i][j] * Q0[j]) / (L0-L2) * Q2[p] + - (Q3[i] * ds_1[i][j] * Q0[j]) / (L0-L3) * Q3[p]; - } - } - } - } - - // do the same for the second group - for (ia = 0; ia < dS_2.size(); ia++) { - - cvm::real const &a1x = pos1[ia].x; - cvm::real const &a1y = pos1[ia].y; - cvm::real const &a1z = pos1[ia].z; - - cvm::matrix2d &ds_2 = dS_2[ia]; - - ds_2.reset(); - ds_2[0][0].set( a1x, a1y, a1z); - ds_2[1][0].set( 0.0, -a1z, a1y); - ds_2[0][1] = ds_2[1][0]; - ds_2[2][0].set( a1z, 0.0, -a1x); - ds_2[0][2] = ds_2[2][0]; - ds_2[3][0].set(-a1y, a1x, 0.0); - ds_2[0][3] = ds_2[3][0]; - ds_2[1][1].set( a1x, -a1y, -a1z); - ds_2[2][1].set( a1y, a1x, 0.0); - ds_2[1][2] = ds_2[2][1]; - ds_2[3][1].set( a1z, 0.0, a1x); - ds_2[1][3] = ds_2[3][1]; - ds_2[2][2].set(-a1x, a1y, -a1z); - ds_2[3][2].set( 0.0, a1z, a1y); - ds_2[2][3] = ds_2[3][2]; - ds_2[3][3].set(-a1x, -a1y, a1z); - - cvm::rvector &dl0_2 = dL0_2[ia]; - cvm::vector1d &dq0_2 = dQ0_2[ia]; - - dl0_2.reset(); - for (size_t i = 0; i < 4; i++) { - for (size_t j = 0; j < 4; j++) { - dl0_2 += Q0[i] * ds_2[i][j] * Q0[j]; - } - } - - dq0_2.reset(); - for (size_t p = 0; p < 4; p++) { - for (size_t i = 0; i < 4; i++) { - for (size_t j = 0; j < 4; j++) { - dq0_2[p] += - (Q1[i] * ds_2[i][j] * Q0[j]) / (L0-L1) * Q1[p] + - (Q2[i] * ds_2[i][j] * Q0[j]) / (L0-L2) * Q2[p] + - (Q3[i] * ds_2[i][j] * Q0[j]) / (L0-L3) * Q3[p]; - } - } - } - - if (b_debug_gradients) { - - cvm::matrix2d S_new(4, 4); - cvm::vector1d S_new_eigval(4); - cvm::matrix2d S_new_eigvec(4, 4); - - // make an infitesimal move along each cartesian coordinate of - // this atom, and solve again the eigenvector problem - for (size_t comp = 0; comp < 3; comp++) { - - S_new = S_backup; - // diagonalize the new overlap matrix - for (size_t i = 0; i < 4; i++) { - for (size_t j = 0; j < 4; j++) { - S_new[i][j] += - colvarmodule::debug_gradients_step_size * ds_2[i][j][comp]; - } - } - - // cvm::log("S_new = "+cvm::to_str(cvm::to_str (S_new), cvm::cv_width, cvm::cv_prec)+"\n"); - -#ifdef COLVARS_LAMMPS - ecalc->Diagonalize(S_new, S_new_eigval, S_new_eigvec); -#else - diagonalize_matrix(S_new, S_new_eigval, S_new_eigvec); -#endif - - cvm::real const &L0_new = S_new_eigval[0]; - cvm::quaternion const Q0_new(S_new_eigvec[0]); - - cvm::real const DL0 = (dl0_2[comp]) * colvarmodule::debug_gradients_step_size; - cvm::quaternion const DQ0(dq0_2[0][comp] * colvarmodule::debug_gradients_step_size, - dq0_2[1][comp] * colvarmodule::debug_gradients_step_size, - dq0_2[2][comp] * colvarmodule::debug_gradients_step_size, - dq0_2[3][comp] * colvarmodule::debug_gradients_step_size); - - cvm::log( "|(l_0+dl_0) - l_0^new|/l_0 = "+ - cvm::to_str(cvm::fabs(L0+DL0 - L0_new)/L0, cvm::cv_width, cvm::cv_prec)+ - ", |(q_0+dq_0) - q_0^new| = "+ - cvm::to_str((Q0+DQ0 - Q0_new).norm(), cvm::cv_width, cvm::cv_prec)+ - "\n"); - } - } - } } - - - diff --git a/lib/colvars/colvartypes.h b/lib/colvars/colvartypes.h index 13b6a5bdb3..455e628f1b 100644 --- a/lib/colvars/colvartypes.h +++ b/lib/colvars/colvartypes.h @@ -10,8 +10,14 @@ #ifndef COLVARTYPES_H #define COLVARTYPES_H +#include // TODO specialize templates and replace this with iosfwd #include +#ifdef COLVARS_LAMMPS +// Use open-source Jacobi implementation +#include "math_eigen_impl.h" +#endif + #include "colvarmodule.h" #ifndef PI @@ -53,6 +59,12 @@ public: } } + /// Explicit Copy constructor + inline vector1d(const vector1d&) = default; + + /// Explicit Copy assignement + inline vector1d& operator=(const vector1d&) = default; + /// Return a pointer to the data location inline T * c_array() { @@ -69,6 +81,12 @@ public: return data; } + /// Return a reference to the data + inline std::vector const &data_array() const + { + return data; + } + inline ~vector1d() { data.clear(); @@ -493,6 +511,12 @@ public: return data; } + /// Return a reference to the data + inline std::vector const &data_array() const + { + return data; + } + inline row & operator [] (size_t const i) { return rows[i]; @@ -896,9 +920,6 @@ public: zz = zzi; } - /// Destructor - inline ~rmatrix() - {} inline void reset() { @@ -1278,59 +1299,50 @@ public: }; +#ifndef COLVARS_LAMMPS +namespace NR { +void diagonalize_matrix(cvm::real m[4][4], + cvm::real eigval[4], + cvm::real eigvec[4][4]); +} +#endif + /// \brief A rotation between two sets of coordinates (for the moment /// a wrapper for colvarmodule::quaternion) class colvarmodule::rotation { -public: - - /// \brief The rotation itself (implemented as a quaternion) - cvm::quaternion q; - - /// \brief Eigenvalue corresponding to the optimal rotation - cvm::real lambda; - - /// \brief Perform gradient tests - bool b_debug_gradients; - +private: /// Correlation matrix C (3, 3) cvm::rmatrix C; /// Overlap matrix S (4, 4) - cvm::matrix2d S; + cvm::real S[4][4]; /// Eigenvalues of S - cvm::vector1d S_eigval; + cvm::real S_eigval[4]; /// Eigenvectors of S - cvm::matrix2d S_eigvec; + cvm::real S_eigvec[4][4]; /// Used for debugging gradients - cvm::matrix2d S_backup; + cvm::real S_backup[4][4]; - /// Derivatives of S - std::vector< cvm::matrix2d > dS_1, dS_2; - /// Derivatives of leading eigenvalue - std::vector< cvm::rvector > dL0_1, dL0_2; - /// Derivatives of leading eigenvector - std::vector< cvm::vector1d > dQ0_1, dQ0_2; +public: + /// \brief Perform gradient tests + bool b_debug_gradients; - /// Allocate space for the derivatives of the rotation - inline void request_group1_gradients(size_t n) - { - dS_1.resize(n, cvm::matrix2d(4, 4)); - dL0_1.resize(n, cvm::rvector(0.0, 0.0, 0.0)); - dQ0_1.resize(n, cvm::vector1d(4)); - } + /// \brief The rotation itself (implemented as a quaternion) + cvm::quaternion q; - /// Allocate space for the derivatives of the rotation - inline void request_group2_gradients(size_t n) - { - dS_2.resize(n, cvm::matrix2d(4, 4)); - dL0_2.resize(n, cvm::rvector(0.0, 0.0, 0.0)); - dQ0_2.resize(n, cvm::vector1d(4)); - } + template + friend struct rotation_derivative; + + template + friend void debug_gradients( + cvm::rotation &rot, + const std::vector &pos1, + const std::vector &pos2); /// \brief Calculate the optimal rotation and store the /// corresponding eigenvalue and eigenvector in the arguments l0 and @@ -1344,6 +1356,8 @@ public: /// DOI: 10.1002/jcc.20110 PubMed: 15376254 void calc_optimal_rotation(std::vector const &pos1, std::vector const &pos2); + void calc_optimal_rotation(std::vector const &pos1, + std::vector const &pos2); /// Initialize member data int init(); @@ -1478,6 +1492,11 @@ protected: /// Build the correlation matrix C (used by calc_optimal_rotation()) void build_correlation_matrix(std::vector const &pos1, std::vector const &pos2); + void build_correlation_matrix(std::vector const &pos1, + std::vector const &pos2); + + /// \brief Actual implementation of `calc_optimal_rotation` (and called by it) + void calc_optimal_rotation_impl(); /// Compute the overlap matrix S (used by calc_optimal_rotation()) void compute_overlap_matrix(); diff --git a/lib/colvars/colvarvalue.cpp b/lib/colvars/colvarvalue.cpp index e57859dfa3..3b8077d2e7 100644 --- a/lib/colvars/colvarvalue.cpp +++ b/lib/colvars/colvarvalue.cpp @@ -8,11 +8,10 @@ // Colvars repository at GitHub. #include -#include -#include #include "colvarmodule.h" #include "colvarvalue.h" +#include "colvars_memstream.h" @@ -721,29 +720,43 @@ int colvarvalue::from_simple_string(std::string const &s) return COLVARS_ERROR; } -std::ostream & operator << (std::ostream &os, colvarvalue const &x) + +template void colvarvalue::write_to_stream_template_(OST &os) const { - switch (x.type()) { + switch (type()) { case colvarvalue::type_scalar: - os << x.real_value; + os << real_value; break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: - os << x.rvector_value; + os << rvector_value; break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: - os << x.quaternion_value; + os << quaternion_value; break; case colvarvalue::type_vector: - os << x.vector1d_value; + os << vector1d_value; break; case colvarvalue::type_notset: default: os << "not set"; break; } +} + + +std::ostream & operator << (std::ostream &os, colvarvalue const &x) +{ + x.write_to_stream_template_(os); + return os; +} + + +cvm::memory_stream & operator << (cvm::memory_stream &os, colvarvalue const &x) +{ + x.write_to_stream_template_(os); return os; } @@ -758,44 +771,55 @@ std::ostream & operator << (std::ostream &os, std::vector const &v) } -std::istream & operator >> (std::istream &is, colvarvalue &x) +template void colvarvalue::read_from_stream_template_(IST &is) { - if (x.type() == colvarvalue::type_notset) { + if (type() == colvarvalue::type_notset) { cvm::error("Trying to read from a stream a colvarvalue, " "which has not yet been assigned a data type.\n"); - return is; } - switch (x.type()) { + switch (type()) { case colvarvalue::type_scalar: - is >> x.real_value; + is >> real_value; break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vectorderiv: - is >> x.rvector_value; + is >> rvector_value; break; case colvarvalue::type_unit3vector: - is >> x.rvector_value; - x.apply_constraints(); + is >> rvector_value; + apply_constraints(); break; case colvarvalue::type_quaternion: - is >> x.quaternion_value; - x.apply_constraints(); + is >> quaternion_value; + apply_constraints(); break; case colvarvalue::type_quaternionderiv: - is >> x.quaternion_value; + is >> quaternion_value; break; case colvarvalue::type_vector: - is >> x.vector1d_value; + is >> vector1d_value; break; case colvarvalue::type_notset: default: - x.undef_op(); + undef_op(); } +} + + +std::istream & operator >> (std::istream &is, colvarvalue &x) +{ + x.read_from_stream_template_(is); return is; } +cvm::memory_stream & operator >> (cvm::memory_stream &is, colvarvalue &x) +{ + x.read_from_stream_template_(is); + return is; +} + size_t colvarvalue::output_width(size_t const &real_width) const { switch (this->value_type) { diff --git a/lib/colvars/colvarvalue.h b/lib/colvars/colvarvalue.h index 9ab8dbe31d..e8a6a849d3 100644 --- a/lib/colvars/colvarvalue.h +++ b/lib/colvars/colvarvalue.h @@ -10,6 +10,8 @@ #ifndef COLVARVALUE_H #define COLVARVALUE_H +#include + #include "colvarmodule.h" #include "colvartypes.h" @@ -120,7 +122,8 @@ public: /// number and always behaves like it unless you change its type colvarvalue(); - /// Constructor from a type specification + /// Constructor from a type flag (note: type_vector also needs the vector length to be set) + /// \param[in] vti Value of the \link Type \endlink enum colvarvalue(Type const &vti); /// Copy constructor from real base type @@ -297,12 +300,31 @@ public: /// Undefined operation void undef_op() const; +private: - /// \brief Formatted output operator - friend std::ostream & operator << (std::ostream &os, colvarvalue const &q); + /// Generic stream writing function (formatted and not) + template void write_to_stream_template_(OST &os) const; - /// \brief Formatted input operator - friend std::istream & operator >> (std::istream &is, colvarvalue &q); +public: + + /// Formatted output operator + friend std::ostream & operator << (std::ostream &os, colvarvalue const &x); + + /// Unformatted output operator + friend cvm::memory_stream & operator << (cvm::memory_stream &os, colvarvalue const &x); + +private: + + /// Generic stream reading function (formatted and not) + template void read_from_stream_template_(IST &is); + +public: + + /// Formatted input operator + friend std::istream & operator >> (std::istream &is, colvarvalue &x); + + /// Unformatted input operator + friend cvm::memory_stream & operator >> (cvm::memory_stream &is, colvarvalue &x); /// Give the number of characters required to output this /// colvarvalue, given the current type assigned and the number of