diff --git a/doc/src/PDF/colvars-refman-lammps.pdf b/doc/src/PDF/colvars-refman-lammps.pdf index 37201275f..a14d93cd6 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/Install.py b/lib/colvars/Install.py index 18b426f92..a367b8eee 100644 --- a/lib/colvars/Install.py +++ b/lib/colvars/Install.py @@ -1,82 +1,136 @@ #!/usr/bin/env python -# install.py tool to do a generic build of a library -# soft linked to by many of the lib/Install.py files -# used to automate the steps described in the corresponding lib/README +# Install.py tool to do automate build of Colvars -import sys,commands,os +from __future__ import print_function +import sys,os,subprocess # help message help = """ -Syntax: python Install.py -m machine -e suffix - specify -m and optionally -e, order does not matter +Syntax from src dir: make lib-colvars args="-m machine -e suffix" +Syntax from lib/colvars dir: python Install.py -m machine -e suffix + +specify -m and optionally -e, order does not matter + -m = peform a clean followed by "make -f Makefile.machine" - machine = suffix of a lib/Makefile.* file + machine = suffix of a lib/colvars/Makefile.* or of a + src/MAKE/MACHINES/Makefile.* file -e = set EXTRAMAKE variable in Makefile.machine to Makefile.lammps.suffix does not alter existing Makefile.machine + +Examples: + +make lib-colvars args="-m g++" # build COLVARS lib with GNU g++ compiler """ # print error message or help def error(str=None): - if not str: print help - else: print "ERROR",str + if not str: print(help) + else: print("ERROR"),str sys.exit() # parse args args = sys.argv[1:] nargs = len(args) if nargs == 0: error() machine = None -extraflag = 0 +extraflag = False iarg = 0 while iarg < nargs: if args[iarg] == "-m": - if iarg+2 > nargs: error() + if iarg+2 > len(args): error() machine = args[iarg+1] iarg += 2 elif args[iarg] == "-e": - if iarg+2 > nargs: error() - extraflag = 1 + if iarg+2 > len(args): error() + extraflag = True suffix = args[iarg+1] iarg += 2 else: error() # set lib from working dir cwd = os.getcwd() lib = os.path.basename(cwd) -# create Makefile.auto as copy of Makefile.machine -# reset EXTRAMAKE if requested - +def get_lammps_machine_flags(machine): + """Parse Makefile.machine from LAMMPS, return dictionary of compiler flags""" + if not os.path.exists("../../src/MAKE/MACHINES/Makefile.%s" % machine): + error("Cannot locate src/MAKE/MACHINES/Makefile.%s" % machine) + lines = open("../../src/MAKE/MACHINES/Makefile.%s" % machine, + 'r').readlines() + machine_flags = {} + for line in lines: + line = line.partition('#')[0] + line = line.rstrip() + words = line.split() + if (len(words) > 2): + if ((words[0] == 'CC') or (words[0] == 'CCFLAGS') or + (words[0] == 'SHFLAGS') or (words[0] == 'ARCHIVE') or + (words[0] == 'ARFLAGS') or (words[0] == 'SHELL')): + machine_flags[words[0]] = ' '.join(words[2:]) + return machine_flags + +def gen_colvars_makefile_machine(machine, machine_flags): + """Generate Makefile.machine for Colvars given the compiler flags""" + machine_makefile = open("Makefile.%s" % machine, 'w') + machine_makefile.write('''# -*- makefile -*- to build Colvars module with %s + +COLVARS_LIB = libcolvars.a +COLVARS_OBJ_DIR = + +CXX = %s +CXXFLAGS = %s %s +AR = %s +ARFLAGS = %s +SHELL = %s + +include Makefile.common + +.PHONY: default clean + +default: $(COLVARS_LIB) Makefile.lammps + +clean: + -rm -f $(COLVARS_OBJS) $(COLVARS_LIB) +''' % (machine, machine_flags['CC'], + machine_flags['CCFLAGS'], machine_flags['SHFLAGS'] , + machine_flags['ARCHIVE'], machine_flags['ARFLAGS'], + machine_flags['SHELL'])) + +if not os.path.exists("Makefile.%s" % machine): + machine_flags = get_lammps_machine_flags(machine) + gen_colvars_makefile_machine(machine, machine_flags) if not os.path.exists("Makefile.%s" % machine): error("lib/%s/Makefile.%s does not exist" % (lib,machine)) +# create Makefile.auto as copy of Makefile.machine +# reset EXTRAMAKE if requested + lines = open("Makefile.%s" % machine,'r').readlines() fp = open("Makefile.auto",'w') - for line in lines: words = line.split() if len(words) == 3 and extraflag and \ words[0] == "EXTRAMAKE" and words[1] == '=': line = line.replace(words[2],"Makefile.lammps.%s" % suffix) - print >>fp,line, - + fp.write(line) fp.close() # make the library via Makefile.auto -print "Building lib%s.a ..." % lib -cmd = "make -f Makefile.auto clean; make -f Makefile.auto" -txt = commands.getoutput(cmd) -print txt +print("Building lib%s.a ..." % lib) +cmd = ["make -f Makefile.auto clean"] +print(subprocess.check_output(cmd, shell=True)) +cmd = ["make -f Makefile.auto -j12"] +print(subprocess.check_output(cmd, shell=True)) -if os.path.exists("lib%s.a" % lib): print "Build was successful" +if os.path.exists("lib%s.a" % lib): print("Build was successful") else: error("Build of lib/%s/lib%s.a was NOT successful" % (lib,lib)) if not os.path.exists("Makefile.lammps"): - print "lib/%s/Makefile.lammps was NOT created" % lib + print("lib/%s/Makefile.lammps was NOT created" % lib) diff --git a/lib/colvars/Makefile.colvars b/lib/colvars/Makefile.colvars deleted file mode 100644 index d1a204403..000000000 --- a/lib/colvars/Makefile.colvars +++ /dev/null @@ -1,119 +0,0 @@ -# library build -*- makefile -*- for colvars module - -# which file will be copied to Makefile.lammps - -EXTRAMAKE = Makefile.lammps.empty - -# ------ SETTINGS ------ - -CXX = g++ -CXXFLAGS = -O2 -g -Wall -fPIC -funroll-loops # -DCOLVARS_DEBUG -ARCHIVE = ar -ARCHFLAG = -rscv -SHELL = /bin/sh - -# ------ DEFINITIONS ------ - -SRC = colvaratoms.cpp colvarbias_abf.cpp colvarbias_alb.cpp colvarbias.cpp \ - colvarbias_histogram.cpp colvarbias_meta.cpp colvarbias_restraint.cpp \ - colvarcomp_angles.cpp colvarcomp_coordnums.cpp colvarcomp.cpp \ - colvarcomp_distances.cpp colvarcomp_protein.cpp colvarcomp_rotations.cpp \ - colvardeps.cpp colvar.cpp colvargrid.cpp colvarmodule.cpp colvarparse.cpp \ - colvarscript.cpp colvartypes.cpp colvarvalue.cpp - -LIB = libcolvars.a -OBJ = $(SRC:.cpp=.o) -EXE = #colvars_standalone - -# ------ MAKE PROCEDURE ------ - -default: $(LIB) $(EXE) Makefile.lammps - -Makefile.lammps: - @cp $(EXTRAMAKE) Makefile.lammps - -$(LIB): $(OBJ) - $(ARCHIVE) $(ARFLAGS) $(LIB) $(OBJ) - -colvars_standalone: colvars_main.o colvarproxy_standalone.o $(LIB) - $(CXX) -o $@ $(CXXFLAGS) $^ - -# ------ MAKE FLAGS ------ - -.SUFFIXES: -.SUFFIXES: .cpp .o - -.PHONY: default clean - -# ------ COMPILE RULES ------ - -.cpp.o: - $(CXX) $(CXXFLAGS) -c $< - -# ------ DEPENDENCIES ------ -# -colvaratoms.o: colvaratoms.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvaratoms.h -colvarbias_abf.o: colvarbias_abf.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvar.h colvarparse.h colvardeps.h \ - colvarbias_abf.h colvarbias.h colvargrid.h -colvarbias_alb.o: colvarbias_alb.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarbias_alb.h colvar.h colvarparse.h \ - colvardeps.h colvarbias_restraint.h colvarbias.h -colvarbias.o: colvarbias.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarbias.h colvar.h colvarparse.h colvardeps.h -colvarbias_histogram.o: colvarbias_histogram.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvar.h colvarparse.h \ - colvardeps.h colvarbias_histogram.h colvarbias.h colvargrid.h -colvarbias_meta.o: colvarbias_meta.cpp colvar.h colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvarbias_meta.h colvarbias.h colvargrid.h -colvarbias_restraint.o: colvarbias_restraint.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarbias_restraint.h \ - colvarbias.h colvar.h colvarparse.h colvardeps.h -colvarcomp_angles.o: colvarcomp_angles.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvar.h colvarparse.h colvardeps.h \ - colvarcomp.h colvaratoms.h -colvarcomp_coordnums.o: colvarcomp_coordnums.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvaratoms.h colvar.h colvarcomp.h -colvarcomp.o: colvarcomp.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvar.h colvarparse.h colvardeps.h colvarcomp.h \ - colvaratoms.h -colvarcomp_distances.o: colvarcomp_distances.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvar.h colvarcomp.h colvaratoms.h -colvarcomp_protein.o: colvarcomp_protein.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h colvar.h \ - colvarcomp.h colvaratoms.h -colvarcomp_rotations.o: colvarcomp_rotations.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvar.h colvarcomp.h colvaratoms.h -colvar.o: colvar.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvar.h colvarcomp.h \ - colvaratoms.h colvarscript.h colvarbias.h -colvardeps.o: colvardeps.cpp colvardeps.h colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h -colvargrid.o: colvargrid.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvar.h colvarcomp.h \ - colvaratoms.h colvargrid.h -colvarmodule.o: colvarmodule.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h colvar.h \ - colvarbias.h colvarbias_abf.h colvargrid.h colvarbias_alb.h \ - colvarbias_restraint.h colvarbias_histogram.h colvarbias_meta.h \ - colvarscript.h -colvarparse.o: colvarparse.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h -colvarscript.o: colvarscript.cpp colvarscript.h colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarbias.h colvar.h \ - colvarparse.h colvardeps.h -colvartypes.o: colvartypes.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h -colvarvalue.o: colvarvalue.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h - -# ------ CLEAN ------ - -clean: - -rm *.o *~ $(LIB) - diff --git a/lib/colvars/Makefile.common b/lib/colvars/Makefile.common new file mode 100644 index 000000000..818373abe --- /dev/null +++ b/lib/colvars/Makefile.common @@ -0,0 +1,65 @@ +# Shared -*- makefile -*- for multiple architectures + +# Detect settings from PYTHON package (if defined) +include ../../src/Makefile.package.settings +ifeq ($(python_SYSINC),) +COLVARS_PYTHON_INCFLAGS = +else +COLVARS_PYTHON_INCFLAGS = -DCOLVARS_PYTHON $(python_SYSINC) +endif + +# Detect debug settings +ifeq ($(COLVARS_DEBUG),) +COLVARS_DEBUG_INCFLAGS = +else +COLVARS_DEBUG_INCFLAGS= -DCOLVARS_DEBUG +endif + +COLVARS_INCFLAGS = $(COLVARS_DEBUG_INCFLAGS) $(COLVARS_PYTHON_INCFLAGS) + + +.SUFFIXES: +.SUFFIXES: .cpp .o + +COLVARS_SRCS = \ + colvaratoms.cpp \ + colvarbias_abf.cpp \ + colvarbias_alb.cpp \ + colvarbias.cpp \ + colvarbias_histogram.cpp \ + colvarbias_meta.cpp \ + colvarbias_restraint.cpp \ + colvarcomp_angles.cpp \ + colvarcomp_coordnums.cpp \ + colvarcomp.cpp \ + colvarcomp_distances.cpp \ + colvarcomp_protein.cpp \ + colvarcomp_rotations.cpp \ + colvar.cpp \ + colvardeps.cpp \ + colvargrid.cpp \ + colvarmodule.cpp \ + colvarparse.cpp \ + colvarproxy.cpp \ + colvarscript.cpp \ + colvartypes.cpp \ + colvarvalue.cpp + +COLVARS_OBJS = $(COLVARS_SRCS:.cpp=.o) + +.cpp.o: + $(CXX) $(CXXFLAGS) $(COLVARS_INCFLAGS) -c $< + +$(COLVARS_LIB): Makefile.deps $(COLVARS_OBJS) + $(AR) $(ARFLAGS) $(COLVARS_LIB) $(COLVARS_OBJS) + + +Makefile.deps: $(COLVARS_SRCS) + @echo > $@ + @for src in $^ ; do \ + obj=`basename $$src .cpp`.o ; \ + $(CXX) -MM $(COLVARS_INCFLAGS) \ + -MT '$$(COLVARS_OBJ_DIR)'$$obj $$src >> $@ ; \ + done + +include Makefile.deps diff --git a/lib/colvars/Makefile.deps b/lib/colvars/Makefile.deps new file mode 100644 index 000000000..f463da5f8 --- /dev/null +++ b/lib/colvars/Makefile.deps @@ -0,0 +1,78 @@ + +$(COLVARS_OBJ_DIR)colvaratoms.o: colvaratoms.cpp colvarmodule.h \ + colvars_version.h colvartypes.h colvarproxy.h colvarvalue.h \ + colvarparse.h colvaratoms.h colvardeps.h +$(COLVARS_OBJ_DIR)colvarbias_abf.o: colvarbias_abf.cpp colvarmodule.h \ + colvars_version.h colvartypes.h colvarproxy.h colvarvalue.h colvar.h \ + colvarparse.h colvardeps.h colvarbias_abf.h colvarbias.h colvargrid.h +$(COLVARS_OBJ_DIR)colvarbias_alb.o: colvarbias_alb.cpp colvarmodule.h \ + colvars_version.h colvartypes.h colvarproxy.h colvarvalue.h \ + colvarbias_alb.h colvar.h colvarparse.h colvardeps.h colvarbias.h +$(COLVARS_OBJ_DIR)colvarbias.o: colvarbias.cpp colvarmodule.h \ + colvars_version.h colvartypes.h colvarproxy.h colvarvalue.h colvarbias.h \ + colvar.h colvarparse.h colvardeps.h +$(COLVARS_OBJ_DIR)colvarbias_histogram.o: colvarbias_histogram.cpp \ + colvarmodule.h colvars_version.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvar.h colvarparse.h colvardeps.h colvarbias_histogram.h \ + colvarbias.h colvargrid.h +$(COLVARS_OBJ_DIR)colvarbias_meta.o: colvarbias_meta.cpp colvar.h \ + colvarmodule.h colvars_version.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarparse.h colvardeps.h colvarbias_meta.h colvarbias.h \ + colvargrid.h +$(COLVARS_OBJ_DIR)colvarbias_restraint.o: colvarbias_restraint.cpp \ + colvarmodule.h colvars_version.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarbias_restraint.h colvarbias.h colvar.h colvarparse.h \ + colvardeps.h +$(COLVARS_OBJ_DIR)colvarcomp_angles.o: colvarcomp_angles.cpp \ + colvarmodule.h colvars_version.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvar.h colvarparse.h colvardeps.h colvarcomp.h \ + colvaratoms.h +$(COLVARS_OBJ_DIR)colvarcomp_coordnums.o: colvarcomp_coordnums.cpp \ + colvarmodule.h colvars_version.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarparse.h colvaratoms.h colvardeps.h colvar.h \ + colvarcomp.h +$(COLVARS_OBJ_DIR)colvarcomp.o: colvarcomp.cpp colvarmodule.h \ + colvars_version.h colvartypes.h colvarproxy.h colvarvalue.h colvar.h \ + colvarparse.h colvardeps.h colvarcomp.h colvaratoms.h +$(COLVARS_OBJ_DIR)colvarcomp_distances.o: colvarcomp_distances.cpp \ + colvarmodule.h colvars_version.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarparse.h colvar.h colvardeps.h colvarcomp.h \ + colvaratoms.h +$(COLVARS_OBJ_DIR)colvarcomp_protein.o: colvarcomp_protein.cpp \ + colvarmodule.h colvars_version.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarparse.h colvar.h colvardeps.h colvarcomp.h \ + colvaratoms.h +$(COLVARS_OBJ_DIR)colvarcomp_rotations.o: colvarcomp_rotations.cpp \ + colvarmodule.h colvars_version.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarparse.h colvar.h colvardeps.h colvarcomp.h \ + colvaratoms.h +$(COLVARS_OBJ_DIR)colvar.o: colvar.cpp colvarmodule.h colvars_version.h \ + colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvar.h \ + colvardeps.h colvarcomp.h colvaratoms.h colvarscript.h colvarbias.h +$(COLVARS_OBJ_DIR)colvardeps.o: colvardeps.cpp colvardeps.h \ + colvarmodule.h colvars_version.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarparse.h +$(COLVARS_OBJ_DIR)colvargrid.o: colvargrid.cpp colvarmodule.h \ + colvars_version.h colvartypes.h colvarproxy.h colvarvalue.h \ + colvarparse.h colvar.h colvardeps.h colvarcomp.h colvaratoms.h \ + colvargrid.h +$(COLVARS_OBJ_DIR)colvarmodule.o: colvarmodule.cpp colvarmodule.h \ + colvars_version.h colvartypes.h colvarproxy.h colvarvalue.h \ + colvarparse.h colvar.h colvardeps.h colvarbias.h colvarbias_abf.h \ + colvargrid.h colvarbias_alb.h colvarbias_histogram.h colvarbias_meta.h \ + colvarbias_restraint.h colvarscript.h colvaratoms.h +$(COLVARS_OBJ_DIR)colvarparse.o: colvarparse.cpp colvarmodule.h \ + colvars_version.h colvartypes.h colvarproxy.h colvarvalue.h \ + colvarparse.h +$(COLVARS_OBJ_DIR)colvarproxy.o: colvarproxy.cpp colvarmodule.h \ + colvars_version.h colvartypes.h colvarproxy.h colvarvalue.h \ + colvarscript.h colvarbias.h colvar.h colvarparse.h colvardeps.h \ + colvaratoms.h +$(COLVARS_OBJ_DIR)colvarscript.o: colvarscript.cpp colvarscript.h \ + colvarmodule.h colvars_version.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarbias.h colvar.h colvarparse.h colvardeps.h +$(COLVARS_OBJ_DIR)colvartypes.o: colvartypes.cpp colvarmodule.h \ + colvars_version.h colvartypes.h colvarproxy.h colvarvalue.h \ + colvarparse.h +$(COLVARS_OBJ_DIR)colvarvalue.o: colvarvalue.cpp colvarmodule.h \ + colvars_version.h colvartypes.h colvarproxy.h colvarvalue.h diff --git a/lib/colvars/Makefile.fermi b/lib/colvars/Makefile.fermi deleted file mode 100644 index 906675ae1..000000000 --- a/lib/colvars/Makefile.fermi +++ /dev/null @@ -1,120 +0,0 @@ -# library build -*- makefile -*- for colvars module - -# which file will be copied to Makefile.lammps - -EXTRAMAKE = Makefile.lammps.empty - -# ------ SETTINGS ------ - -CXX = g++ -CXXFLAGS = -O2 -mpc64 -g -fPIC \ - -Wall -Wno-sign-compare # -DCOLVARS_DEBUG -ARCHIVE = ar -ARCHFLAG = -rscv -SHELL = /bin/sh - -# ------ DEFINITIONS ------ - -SRC = colvaratoms.cpp colvarbias_abf.cpp colvarbias_alb.cpp colvarbias.cpp \ - colvarbias_histogram.cpp colvarbias_meta.cpp colvarbias_restraint.cpp \ - colvarcomp_angles.cpp colvarcomp_coordnums.cpp colvarcomp.cpp \ - colvarcomp_distances.cpp colvarcomp_protein.cpp colvarcomp_rotations.cpp \ - colvardeps.cpp colvar.cpp colvargrid.cpp colvarmodule.cpp colvarparse.cpp \ - colvarscript.cpp colvartypes.cpp colvarvalue.cpp - -LIB = libcolvars.a -OBJ = $(SRC:.cpp=.o) -EXE = #colvars_standalone - -# ------ MAKE PROCEDURE ------ - -default: $(LIB) $(EXE) Makefile.lammps - -Makefile.lammps: - @cp $(EXTRAMAKE) Makefile.lammps - -$(LIB): $(OBJ) - $(ARCHIVE) $(ARFLAGS) $(LIB) $(OBJ) - -colvars_standalone: colvars_main.o colvarproxy_standalone.o $(LIB) - $(CXX) -o $@ $(CXXFLAGS) $^ - -# ------ MAKE FLAGS ------ - -.SUFFIXES: -.SUFFIXES: .cpp .o - -.PHONY: default clean - -# ------ COMPILE RULES ------ - -.cpp.o: - $(CXX) $(CXXFLAGS) -c $< - -# ------ DEPENDENCIES ------ -# -colvaratoms.o: colvaratoms.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvaratoms.h -colvarbias_abf.o: colvarbias_abf.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvar.h colvarparse.h colvardeps.h \ - colvarbias_abf.h colvarbias.h colvargrid.h -colvarbias_alb.o: colvarbias_alb.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarbias_alb.h colvar.h colvarparse.h \ - colvardeps.h colvarbias_restraint.h colvarbias.h -colvarbias.o: colvarbias.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarbias.h colvar.h colvarparse.h colvardeps.h -colvarbias_histogram.o: colvarbias_histogram.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvar.h colvarparse.h \ - colvardeps.h colvarbias_histogram.h colvarbias.h colvargrid.h -colvarbias_meta.o: colvarbias_meta.cpp colvar.h colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvarbias_meta.h colvarbias.h colvargrid.h -colvarbias_restraint.o: colvarbias_restraint.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarbias_restraint.h \ - colvarbias.h colvar.h colvarparse.h colvardeps.h -colvarcomp_angles.o: colvarcomp_angles.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvar.h colvarparse.h colvardeps.h \ - colvarcomp.h colvaratoms.h -colvarcomp_coordnums.o: colvarcomp_coordnums.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvaratoms.h colvar.h colvarcomp.h -colvarcomp.o: colvarcomp.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvar.h colvarparse.h colvardeps.h colvarcomp.h \ - colvaratoms.h -colvarcomp_distances.o: colvarcomp_distances.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvar.h colvarcomp.h colvaratoms.h -colvarcomp_protein.o: colvarcomp_protein.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h colvar.h \ - colvarcomp.h colvaratoms.h -colvarcomp_rotations.o: colvarcomp_rotations.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvar.h colvarcomp.h colvaratoms.h -colvar.o: colvar.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvar.h colvarcomp.h \ - colvaratoms.h colvarscript.h colvarbias.h -colvardeps.o: colvardeps.cpp colvardeps.h colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h -colvargrid.o: colvargrid.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvar.h colvarcomp.h \ - colvaratoms.h colvargrid.h -colvarmodule.o: colvarmodule.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h colvar.h \ - colvarbias.h colvarbias_abf.h colvargrid.h colvarbias_alb.h \ - colvarbias_restraint.h colvarbias_histogram.h colvarbias_meta.h \ - colvarscript.h -colvarparse.o: colvarparse.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h -colvarscript.o: colvarscript.cpp colvarscript.h colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarbias.h colvar.h \ - colvarparse.h colvardeps.h -colvartypes.o: colvartypes.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h -colvarvalue.o: colvarvalue.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h - -# ------ CLEAN ------ - -clean: - -rm *.o *~ $(LIB) - diff --git a/lib/colvars/Makefile.g++ b/lib/colvars/Makefile.g++ index c80fa1065..cd7f72a83 100644 --- a/lib/colvars/Makefile.g++ +++ b/lib/colvars/Makefile.g++ @@ -1,119 +1,25 @@ -# library build -*- makefile -*- for colvars module - -# which file will be copied to Makefile.lammps +# -*- makefile -*- to build Colvars module with GNU compiler EXTRAMAKE = Makefile.lammps.empty -# ------ SETTINGS ------ +COLVARS_LIB = libcolvars.a +COLVARS_OBJ_DIR = CXX = g++ -CXXFLAGS = -O2 -g -fPIC -funroll-loops # -DCOLVARS_DEBUG -ARCHIVE = ar -ARCHFLAG = -rscv +CXXFLAGS = -O2 -g -Wall -fPIC -funroll-loops +AR = ar +ARFLAGS = -rscv SHELL = /bin/sh -# ------ DEFINITIONS ------ - -SRC = colvaratoms.cpp colvarbias_abf.cpp colvarbias_alb.cpp colvarbias.cpp \ - colvarbias_histogram.cpp colvarbias_meta.cpp colvarbias_restraint.cpp \ - colvarcomp_angles.cpp colvarcomp_coordnums.cpp colvarcomp.cpp \ - colvarcomp_distances.cpp colvarcomp_protein.cpp colvarcomp_rotations.cpp \ - colvardeps.cpp colvar.cpp colvargrid.cpp colvarmodule.cpp colvarparse.cpp \ - colvarscript.cpp colvartypes.cpp colvarvalue.cpp - -LIB = libcolvars.a -OBJ = $(SRC:.cpp=.o) -EXE = #colvars_standalone - -# ------ MAKE PROCEDURE ------ - -default: $(LIB) $(EXE) Makefile.lammps - -Makefile.lammps: - @cp $(EXTRAMAKE) Makefile.lammps - -$(LIB): $(OBJ) - $(ARCHIVE) $(ARFLAGS) $(LIB) $(OBJ) - -colvars_standalone: colvars_main.o colvarproxy_standalone.o $(LIB) - $(CXX) -o $@ $(CXXFLAGS) $^ - -# ------ MAKE FLAGS ------ - -.SUFFIXES: -.SUFFIXES: .cpp .o +include Makefile.common .PHONY: default clean -# ------ COMPILE RULES ------ - -.cpp.o: - $(CXX) $(CXXFLAGS) -c $< - -# ------ DEPENDENCIES ------ -# -colvaratoms.o: colvaratoms.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvaratoms.h -colvarbias_abf.o: colvarbias_abf.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvar.h colvarparse.h colvardeps.h \ - colvarbias_abf.h colvarbias.h colvargrid.h -colvarbias_alb.o: colvarbias_alb.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarbias_alb.h colvar.h colvarparse.h \ - colvardeps.h colvarbias_restraint.h colvarbias.h -colvarbias.o: colvarbias.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarbias.h colvar.h colvarparse.h colvardeps.h -colvarbias_histogram.o: colvarbias_histogram.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvar.h colvarparse.h \ - colvardeps.h colvarbias_histogram.h colvarbias.h colvargrid.h -colvarbias_meta.o: colvarbias_meta.cpp colvar.h colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvarbias_meta.h colvarbias.h colvargrid.h -colvarbias_restraint.o: colvarbias_restraint.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarbias_restraint.h \ - colvarbias.h colvar.h colvarparse.h colvardeps.h -colvarcomp_angles.o: colvarcomp_angles.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvar.h colvarparse.h colvardeps.h \ - colvarcomp.h colvaratoms.h -colvarcomp_coordnums.o: colvarcomp_coordnums.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvaratoms.h colvar.h colvarcomp.h -colvarcomp.o: colvarcomp.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvar.h colvarparse.h colvardeps.h colvarcomp.h \ - colvaratoms.h -colvarcomp_distances.o: colvarcomp_distances.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvar.h colvarcomp.h colvaratoms.h -colvarcomp_protein.o: colvarcomp_protein.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h colvar.h \ - colvarcomp.h colvaratoms.h -colvarcomp_rotations.o: colvarcomp_rotations.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvar.h colvarcomp.h colvaratoms.h -colvar.o: colvar.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvar.h colvarcomp.h \ - colvaratoms.h colvarscript.h colvarbias.h -colvardeps.o: colvardeps.cpp colvardeps.h colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h -colvargrid.o: colvargrid.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvar.h colvarcomp.h \ - colvaratoms.h colvargrid.h -colvarmodule.o: colvarmodule.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h colvar.h \ - colvarbias.h colvarbias_abf.h colvargrid.h colvarbias_alb.h \ - colvarbias_restraint.h colvarbias_histogram.h colvarbias_meta.h \ - colvarscript.h -colvarparse.o: colvarparse.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h -colvarscript.o: colvarscript.cpp colvarscript.h colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarbias.h colvar.h \ - colvarparse.h colvardeps.h -colvartypes.o: colvartypes.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h -colvarvalue.o: colvarvalue.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h - -# ------ CLEAN ------ +default: $(COLVARS_LIB) Makefile.lammps clean: - -rm *.o *~ $(LIB) + -rm -f $(COLVARS_OBJS) $(COLVARS_LIB) + +Makefile.lammps: + -cp $(EXTRAMAKE) Makefile.lammps diff --git a/lib/colvars/Makefile.g++-debug b/lib/colvars/Makefile.g++-debug new file mode 100644 index 000000000..a6ca2f812 --- /dev/null +++ b/lib/colvars/Makefile.g++-debug @@ -0,0 +1,5 @@ +# -*- makefile -*- to build Colvars module with GNU compiler + +COLVARS_DEBUG = "YES" + +include Makefile.g++ diff --git a/lib/colvars/Makefile.lammps.debug b/lib/colvars/Makefile.lammps similarity index 76% copy from lib/colvars/Makefile.lammps.debug copy to lib/colvars/Makefile.lammps index 1ef229d58..99f57b050 100644 --- a/lib/colvars/Makefile.lammps.debug +++ b/lib/colvars/Makefile.lammps @@ -1,5 +1,5 @@ # Settings that the LAMMPS build will import when this package library is used -colvars_SYSINC = # -DCOLVARS_DEBUG +colvars_SYSINC = colvars_SYSLIB = colvars_SYSPATH = diff --git a/lib/colvars/Makefile.lammps.debug b/lib/colvars/Makefile.lammps.debug index 1ef229d58..1c4399a2c 100644 --- a/lib/colvars/Makefile.lammps.debug +++ b/lib/colvars/Makefile.lammps.debug @@ -1,5 +1,5 @@ # Settings that the LAMMPS build will import when this package library is used -colvars_SYSINC = # -DCOLVARS_DEBUG +colvars_SYSINC = -DCOLVARS_DEBUG colvars_SYSLIB = colvars_SYSPATH = diff --git a/lib/colvars/Makefile.lammps.empty b/lib/colvars/Makefile.lammps.empty index 1ef229d58..99f57b050 100644 --- a/lib/colvars/Makefile.lammps.empty +++ b/lib/colvars/Makefile.lammps.empty @@ -1,5 +1,5 @@ # Settings that the LAMMPS build will import when this package library is used -colvars_SYSINC = # -DCOLVARS_DEBUG +colvars_SYSINC = colvars_SYSLIB = colvars_SYSPATH = diff --git a/lib/colvars/Makefile.mingw32-cross b/lib/colvars/Makefile.mingw32-cross index eba83c555..e2873ecda 100644 --- a/lib/colvars/Makefile.mingw32-cross +++ b/lib/colvars/Makefile.mingw32-cross @@ -1,127 +1,31 @@ -# library build -*- makefile -*- for colvars module - -# which file will be copied to Makefile.lammps +# -*- makefile -*- to build Colvars module with MinGW 32-bit EXTRAMAKE = Makefile.lammps.empty -# ------ SETTINGS ------ +COLVARS_LIB = libcolvars.a +COLVARS_OBJ_DIR = Obj_mingw64/ CXX = i686-w64-mingw32-g++ CXXFLAGS = -O2 -march=i686 -mtune=generic -mfpmath=387 -mpc64 \ -fno-rtti -fno-exceptions -finline-functions \ -ffast-math -funroll-loops -fstrict-aliasing \ -Wall -W -Wno-uninitialized -ARCHIVE = i686-w64-mingw32-ar -ARCHFLAG = -rscv +AR = i686-w64-mingw32-ar +ARFLAGS = -rscv SHELL = /bin/sh -# ------ DEFINITIONS ------ - -SRC = colvaratoms.cpp colvarbias_abf.cpp colvarbias_alb.cpp colvarbias.cpp \ - colvarbias_histogram.cpp colvarbias_meta.cpp colvarbias_restraint.cpp \ - colvarcomp_angles.cpp colvarcomp_coordnums.cpp colvarcomp.cpp \ - colvarcomp_distances.cpp colvarcomp_protein.cpp colvarcomp_rotations.cpp \ - colvardeps.cpp colvar.cpp colvargrid.cpp colvarmodule.cpp colvarparse.cpp \ - colvarscript.cpp colvartypes.cpp colvarvalue.cpp - -DIR = Obj_mingw32/ -LIB = $(DIR)libcolvars.a -OBJ = $(SRC:%.cpp=$(DIR)%.o) -EXE = #colvars_standalone - -# ------ MAKE PROCEDURE ------ - -default: $(DIR) $(LIB) $(EXE) Makefile.lammps - -$(DIR): - mkdir $(DIR) - -Makefile.lammps: - @cp $(EXTRAMAKE) Makefile.lammps - -$(LIB): $(DIR) $(OBJ) - $(ARCHIVE) $(ARFLAGS) $(LIB) $(OBJ) - @cp $(EXTRAMAKE) Makefile.lammps - -$(DIR)colvars_standalone: colvars_main.o colvarproxy_standalone.o $(LIB) - $(CXX) -o $@ $(CXXFLAGS) $^ - -# ------ MAKE FLAGS ------ - -.SUFFIXES: -.SUFFIXES: .cpp .o +include Makefile.common .PHONY: default clean -# ------ COMPILE RULES ------ +default: $(COLVARS_OBJ_DIR) $(COLVARS_LIB) Makefile.lammps -$(DIR)%.o: %.cpp - $(CXX) $(CXXFLAGS) -c $< -o $@ - -# ------ DEPENDENCIES ------ -# -$(DIR)colvaratoms.o: colvaratoms.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvaratoms.h -$(DIR)colvarbias_abf.o: colvarbias_abf.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvar.h colvarparse.h colvardeps.h \ - colvarbias_abf.h colvarbias.h colvargrid.h -$(DIR)colvarbias_alb.o: colvarbias_alb.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarbias_alb.h colvar.h colvarparse.h \ - colvardeps.h colvarbias_restraint.h colvarbias.h -$(DIR)colvarbias.o: colvarbias.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarbias.h colvar.h colvarparse.h colvardeps.h -$(DIR)colvarbias_histogram.o: colvarbias_histogram.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvar.h colvarparse.h \ - colvardeps.h colvarbias_histogram.h colvarbias.h colvargrid.h -$(DIR)colvarbias_meta.o: colvarbias_meta.cpp colvar.h colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvarbias_meta.h colvarbias.h colvargrid.h -$(DIR)colvarbias_restraint.o: colvarbias_restraint.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarbias_restraint.h \ - colvarbias.h colvar.h colvarparse.h colvardeps.h -$(DIR)colvarcomp_angles.o: colvarcomp_angles.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvar.h colvarparse.h colvardeps.h \ - colvarcomp.h colvaratoms.h -$(DIR)colvarcomp_coordnums.o: colvarcomp_coordnums.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvaratoms.h colvar.h colvarcomp.h -$(DIR)colvarcomp.o: colvarcomp.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvar.h colvarparse.h colvardeps.h colvarcomp.h \ - colvaratoms.h -$(DIR)colvarcomp_distances.o: colvarcomp_distances.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvar.h colvarcomp.h colvaratoms.h -$(DIR)colvarcomp_protein.o: colvarcomp_protein.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h colvar.h \ - colvarcomp.h colvaratoms.h -$(DIR)colvarcomp_rotations.o: colvarcomp_rotations.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvar.h colvarcomp.h colvaratoms.h -$(DIR)colvar.o: colvar.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvar.h colvarcomp.h \ - colvaratoms.h colvarscript.h colvarbias.h -$(DIR)colvardeps.o: colvardeps.cpp colvardeps.h colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h -$(DIR)colvargrid.o: colvargrid.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvar.h colvarcomp.h \ - colvaratoms.h colvargrid.h -$(DIR)colvarmodule.o: colvarmodule.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h colvar.h \ - colvarbias.h colvarbias_abf.h colvargrid.h colvarbias_alb.h \ - colvarbias_restraint.h colvarbias_histogram.h colvarbias_meta.h \ - colvarscript.h -$(DIR)colvarparse.o: colvarparse.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h -$(DIR)colvarscript.o: colvarscript.cpp colvarscript.h colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarbias.h colvar.h \ - colvarparse.h colvardeps.h -$(DIR)colvartypes.o: colvartypes.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h -$(DIR)colvarvalue.o: colvarvalue.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h - -# ------ CLEAN ------ +$(COLVARS_OBJ_DIR): + mkdir $(COLVARS_OBJ_DIR) clean: - -rm $(DIR)*.o *~ $(LIB) - -rmdir $(DIR) + -rm -f $(COLVARS_OBJS) $(COLVARS_LIB) + -rmdir $(COLVARS_OBJ_DIR) + +Makefile.lammps: + -cp $(EXTRAMAKE) Makefile.lammps diff --git a/lib/colvars/Makefile.mingw64-cross b/lib/colvars/Makefile.mingw64-cross index 1d83b6a0a..09d6bd4fa 100644 --- a/lib/colvars/Makefile.mingw64-cross +++ b/lib/colvars/Makefile.mingw64-cross @@ -1,127 +1,31 @@ -# library build -*- makefile -*- for colvars module - -# which file will be copied to Makefile.lammps +# -*- makefile -*- to build Colvars module with MinGW 32-bit EXTRAMAKE = Makefile.lammps.empty -# ------ SETTINGS ------ +COLVARS_LIB = libcolvars.a +COLVARS_OBJ_DIR = Obj_mingw32/ CXX = x86_64-w64-mingw32-g++ CXXFLAGS = -O2 -march=core2 -mtune=core2 -mpc64 -msse2 \ -fno-rtti -fno-exceptions -finline-functions \ -ffast-math -funroll-loops -fstrict-aliasing \ -Wall -W -Wno-uninitialized -ARCHIVE = x86_64-w64-mingw32-ar -ARCHFLAG = -rscv +AR = x86_64-w64-mingw32-ar +ARFLAGS = -rscv SHELL = /bin/sh -# ------ DEFINITIONS ------ - -SRC = colvaratoms.cpp colvarbias_abf.cpp colvarbias_alb.cpp colvarbias.cpp \ - colvarbias_histogram.cpp colvarbias_meta.cpp colvarbias_restraint.cpp \ - colvarcomp_angles.cpp colvarcomp_coordnums.cpp colvarcomp.cpp \ - colvarcomp_distances.cpp colvarcomp_protein.cpp colvarcomp_rotations.cpp \ - colvardeps.cpp colvar.cpp colvargrid.cpp colvarmodule.cpp colvarparse.cpp \ - colvarscript.cpp colvartypes.cpp colvarvalue.cpp - -DIR = Obj_mingw64/ -LIB = $(DIR)libcolvars.a -OBJ = $(SRC:%.cpp=$(DIR)%.o) -EXE = #colvars_standalone - -# ------ MAKE PROCEDURE ------ - -default: $(DIR) $(LIB) $(EXE) Makefile.lammps - -$(DIR): - mkdir $(DIR) - -Makefile.lammps: - @cp $(EXTRAMAKE) Makefile.lammps - -$(LIB): $(DIR) $(OBJ) - $(ARCHIVE) $(ARFLAGS) $(LIB) $(OBJ) - @cp $(EXTRAMAKE) Makefile.lammps - -$(DIR)colvars_standalone: colvars_main.o colvarproxy_standalone.o $(LIB) - $(CXX) -o $@ $(CXXFLAGS) $^ - -# ------ MAKE FLAGS ------ - -.SUFFIXES: -.SUFFIXES: .cpp .o +include Makefile.common .PHONY: default clean -# ------ COMPILE RULES ------ +default: $(COLVARS_OBJ_DIR) $(COLVARS_LIB) Makefile.lammps -$(DIR)%.o: %.cpp - $(CXX) $(CXXFLAGS) -c $< -o $@ - -# ------ DEPENDENCIES ------ -# -$(DIR)colvaratoms.o: colvaratoms.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvaratoms.h -$(DIR)colvarbias_abf.o: colvarbias_abf.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvar.h colvarparse.h colvardeps.h \ - colvarbias_abf.h colvarbias.h colvargrid.h -$(DIR)colvarbias_alb.o: colvarbias_alb.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarbias_alb.h colvar.h colvarparse.h \ - colvardeps.h colvarbias_restraint.h colvarbias.h -$(DIR)colvarbias.o: colvarbias.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarbias.h colvar.h colvarparse.h colvardeps.h -$(DIR)colvarbias_histogram.o: colvarbias_histogram.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvar.h colvarparse.h \ - colvardeps.h colvarbias_histogram.h colvarbias.h colvargrid.h -$(DIR)colvarbias_meta.o: colvarbias_meta.cpp colvar.h colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvarbias_meta.h colvarbias.h colvargrid.h -$(DIR)colvarbias_restraint.o: colvarbias_restraint.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarbias_restraint.h \ - colvarbias.h colvar.h colvarparse.h colvardeps.h -$(DIR)colvarcomp_angles.o: colvarcomp_angles.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvar.h colvarparse.h colvardeps.h \ - colvarcomp.h colvaratoms.h -$(DIR)colvarcomp_coordnums.o: colvarcomp_coordnums.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvaratoms.h colvar.h colvarcomp.h -$(DIR)colvarcomp.o: colvarcomp.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvar.h colvarparse.h colvardeps.h colvarcomp.h \ - colvaratoms.h -$(DIR)colvarcomp_distances.o: colvarcomp_distances.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvar.h colvarcomp.h colvaratoms.h -$(DIR)colvarcomp_protein.o: colvarcomp_protein.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h colvar.h \ - colvarcomp.h colvaratoms.h -$(DIR)colvarcomp_rotations.o: colvarcomp_rotations.cpp colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h \ - colvar.h colvarcomp.h colvaratoms.h -$(DIR)colvar.o: colvar.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvar.h colvarcomp.h \ - colvaratoms.h colvarscript.h colvarbias.h -$(DIR)colvardeps.o: colvardeps.cpp colvardeps.h colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h -$(DIR)colvargrid.o: colvargrid.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h colvardeps.h colvar.h colvarcomp.h \ - colvaratoms.h colvargrid.h -$(DIR)colvarmodule.o: colvarmodule.cpp colvarmodule.h colvartypes.h \ - colvarproxy.h colvarvalue.h colvarparse.h colvardeps.h colvar.h \ - colvarbias.h colvarbias_abf.h colvargrid.h colvarbias_alb.h \ - colvarbias_restraint.h colvarbias_histogram.h colvarbias_meta.h \ - colvarscript.h -$(DIR)colvarparse.o: colvarparse.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h -$(DIR)colvarscript.o: colvarscript.cpp colvarscript.h colvarmodule.h \ - colvartypes.h colvarproxy.h colvarvalue.h colvarbias.h colvar.h \ - colvarparse.h colvardeps.h -$(DIR)colvartypes.o: colvartypes.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h colvarparse.h -$(DIR)colvarvalue.o: colvarvalue.cpp colvarmodule.h colvartypes.h colvarproxy.h \ - colvarvalue.h - -# ------ CLEAN ------ +$(COLVARS_OBJ_DIR): + mkdir $(COLVARS_OBJ_DIR) clean: - -rm $(DIR)*.o *~ $(LIB) - -rmdir $(DIR) + -rm -f $(COLVARS_OBJS) $(COLVARS_LIB) + -rmdir $(COLVARS_OBJ_DIR) + +Makefile.lammps: + -cp $(EXTRAMAKE) Makefile.lammps diff --git a/lib/colvars/README b/lib/colvars/README index a5e5938b2..a4f04221b 100644 --- a/lib/colvars/README +++ b/lib/colvars/README @@ -1,73 +1,78 @@ -This library is the portable "colvars" module, originally interfaced -with the NAMD MD code, to provide an extensible software framework, -that allows enhanced sampling in molecular dynamics simulations. -The module is written to maximize performance, portability, -flexibility of usage for the user, and extensibility for the developer. +## Collective variables module (Colvars) -The development of the colvars library is now hosted on github at: -http://colvars.github.io/ -You can use this site to get access to the latest development sources -and the up-to-date documentation. +A software module for molecular simulation and analysis that provides a +high-performance implementation of sampling algorithms defined on a reduced +space of continuously differentiable functions (aka collective variables). -Copy of the specific documentation is also in - doc/PDF/colvars-refman-lammps.pdf +The module itself implements a variety of functions and algorithms, including +free-energy estimators based on thermodynamic forces, non-equilibrium work and +probability distributions. -Please report bugs and request new features at: -https://github.com/colvars/colvars/issues - -The following publications describe the principles of -the implementation of this library: +For a brief description see: + http://colvars.github.io/ + https://github.com/colvars/colvars/ - Using collective variables to drive molecular dynamics simulations, - Giacomo Fiorin , Michael L. Klein & Jérôme Hénin (2013): - Molecular Physics DOI:10.1080/00268976.2013.813594 - Exploring Multidimensional Free Energy Landscapes Using - Time-Dependent Biases on Collective Variables, - J. Hénin, G. Fiorin, C. Chipot, and M. L. Klein, - J. Chem. Theory Comput., 6, 35-47 (2010). - -------------------------------------------------- +## How to build This directory has source files to build a library that LAMMPS links against when using the USER-COLVARS package. -This library must be built with a C++ compiler, before LAMMPS is -built, so LAMMPS can link against it. +This library must be built with a C++ compiler, *before* LAMMPS is built, so +that LAMMPS can link against it. You can use the provided Makefile.* files or +create your own, specific to your compiler and system. For example: -You can type "make lib-colvars" from the src directory to see help on -how to build this library via make commands, or you can do the same -thing by typing "python Install.py" from within this directory, or you -can do it manually by following the instructions below. + make -f Makefile.g++ -Build the library using one of the provided Makefile.* files or create -your own, specific to your compiler and system. For example: +will use the GNU C++ compiler and is a good template to start. -make -f Makefile.g++ +**Optional**: if you use the Install.py script provided in this folder, you +can give the machine name as the '-m' argument. This can be the suffix of one +of the files from either this folder, or from src/MAKE. +*This is only supported by the Install.py within the lib/colvars folder*. When you are done building this library, two files should exist in this directory: libcolvars.a the library LAMMPS will link against Makefile.lammps settings the LAMMPS Makefile will import -Makefile.lammps is created by the make command, by copying one of the -Makefile.lammps.* files. See the EXTRAMAKE setting at the top of the -Makefile.* files. - IMPORTANT: You must examine the final Makefile.lammps to insure it is correct for your system, else the LAMMPS build will likely fail. -Makefile.lammps has settings for 3 variables: +If you want to set a debug flag recognized by the library, the +settings in Makefile.common should work. -user-colvars_SYSINC = leave blank for this package unless debugging -user-colvars_SYSLIB = leave blank for this package -user-colvars_SYSPATH = leave blank for this package -You have several choices for these settings: +## Documentation -Since they do not normally need to be set, the settings in -Makefile.lammps.empty should work. +For the reference manual see: + http://colvars.github.io/colvars-refman-lammps + +A copy of reference manual is also in: + doc/PDF/colvars-refman-lammps.pdf + +Also included is a Doxygen-based developer documentation: + http://colvars.github.io/doxygen/html/ + +The reference article is: + G. Fiorin, M. L. Klein, and J. Henin, + Molecular Physics 111, 3345 (2013). + http://dx.doi.org/10.1080/00268976.2013.813594 + + +## Updating to the latest version + +To recompile LAMMPS with the most recent version of this module, the `master` +branch of this repository from GitHub, or clone it via git: + + git clone https://github.com/colvars/colvars.git + +and run the provided `update-colvars-code.sh` script against the unpacked +LAMMPS source tree: + + ./update-colvars-code.sh /path/to/lammps/folder + +Please report bugs and request new features at: +https://github.com/colvars/colvars/issues -If you want to set a debug flag recognized by the library, the -settings in Makefile.lammps.debug should work. diff --git a/lib/colvars/colvar.cpp b/lib/colvars/colvar.cpp index e8c7e8832..d23bd852a 100644 --- a/lib/colvars/colvar.cpp +++ b/lib/colvars/colvar.cpp @@ -1,2050 +1,2284 @@ - // -*- 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 "colvarscript.h" + +// used in build_atom_list() #include /// Compare two cvcs using their names /// Used to sort CVC array in scripted coordinates bool compare(colvar::cvc *i, colvar::cvc *j) { return i->name < j->name; } colvar::colvar() + : prev_timestep(-1) { // Initialize static array once and for all + runave_os = NULL; init_cv_requires(); } int colvar::init(std::string const &conf) { cvm::log("Initializing a new collective variable.\n"); colvarparse::init(conf); int error_code = COLVARS_OK; colvarmodule *cv = cvm::main(); get_keyval(conf, "name", this->name, (std::string("colvar")+cvm::to_str(cv->variables()->size()+1))); if ((cvm::colvar_by_name(this->name) != NULL) && (cvm::colvar_by_name(this->name) != this)) { cvm::error("Error: this colvar cannot have the same name, \""+this->name+ "\", as another colvar.\n", INPUT_ERROR); return INPUT_ERROR; } // Initialize dependency members // Could be a function defined in a different source file, for space? this->description = "colvar " + this->name; kinetic_energy = 0.0; potential_energy = 0.0; error_code |= init_components(conf); if (error_code != COLVARS_OK) { return cvm::get_error(); } size_t i; +#ifdef LEPTON + error_code |= init_custom_function(conf); + if (error_code != COLVARS_OK) { + return cvm::get_error(); + } +#endif + // Setup colvar as scripted function of components if (get_keyval(conf, "scriptedFunction", scripted_function, "", colvarparse::parse_silent)) { enable(f_cv_scripted); cvm::log("This colvar uses scripted function \"" + scripted_function + "\"."); std::string type_str; get_keyval(conf, "scriptedFunctionType", type_str, "scalar"); x.type(colvarvalue::type_notset); int t; for (t = 0; t < colvarvalue::type_all; t++) { if (type_str == colvarvalue::type_keyword(colvarvalue::Type(t))) { x.type(colvarvalue::Type(t)); break; } } if (x.type() == colvarvalue::type_notset) { cvm::error("Could not parse scripted colvar type.", INPUT_ERROR); return INPUT_ERROR; } cvm::log(std::string("Expecting colvar value of type ") + colvarvalue::type_desc(x.type())); if (x.type() == colvarvalue::type_vector) { int size; if (!get_keyval(conf, "scriptedFunctionVectorSize", size)) { cvm::error("Error: no size specified for vector scripted function.", INPUT_ERROR); return INPUT_ERROR; } x.vector1d_value.resize(size); } x_reported.type(x); // 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(), compare); if(cvcs.size() > 1) { cvm::log("Sorted list of components for this scripted colvar:"); for (i = 0; i < cvcs.size(); i++) { cvm::log(cvm::to_str(i+1) + " " + cvcs[i]->name); } } // Build ordered list of component values that will be // passed to the script for (i = 0; i < cvcs.size(); i++) { sorted_cvc_values.push_back(&(cvcs[i]->value())); } } - if (!is_enabled(f_cv_scripted)) { + if (!(is_enabled(f_cv_scripted) || is_enabled(f_cv_custom_function))) { colvarvalue const &cvc_value = (cvcs[0])->value(); if (cvm::debug()) cvm::log ("This collective variable is a "+ colvarvalue::type_desc(cvc_value.type())+ ((cvc_value.size() > 1) ? " with "+ cvm::to_str(cvc_value.size())+" individual components.\n" : ".\n")); x.type(cvc_value); x_reported.type(cvc_value); } // If using scripted biases, any colvar may receive bias forces // and will need its gradient if (cvm::scripted_forces()) { enable(f_cv_gradient); } // check for linear combinations { - bool lin = !is_enabled(f_cv_scripted); + bool lin = !(is_enabled(f_cv_scripted) || is_enabled(f_cv_custom_function)); for (i = 0; i < cvcs.size(); i++) { // FIXME this is a reverse dependency, ie. cv feature depends on cvc flag // need to clarify this case // if ((cvcs[i])->b_debug_gradients) // enable(task_gradients); if ((cvcs[i])->sup_np != 1) { if (cvm::debug() && lin) cvm::log("Warning: You are using a non-linear polynomial " "combination to define this collective variable, " "some biasing methods may be unavailable.\n"); lin = false; 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"); } } } set_enabled(f_cv_linear, lin); } // Colvar is homogeneous if: // - it is linear (hence not scripted) // - all cvcs have coefficient 1 or -1 // i.e. sum or difference of cvcs { bool homogeneous = is_enabled(f_cv_linear); for (i = 0; i < cvcs.size(); i++) { if ((std::fabs(cvcs[i]->sup_coeff) - 1.0) > 1.0e-10) { homogeneous = false; } } set_enabled(f_cv_homogeneous, homogeneous); } // Colvar is deemed periodic if: // - it is homogeneous // - all cvcs are periodic // - all cvcs have the same period if (cvcs[0]->b_periodic) { // TODO make this a CVC feature bool b_periodic = true; period = cvcs[0]->period; for (i = 1; i < cvcs.size(); i++) { if (!cvcs[i]->b_periodic || cvcs[i]->period != period) { b_periodic = false; period = 0.0; cvm::log("Warning: although one component is periodic, this colvar will " "not be treated as periodic, either because the exponent is not " "1, or because components of different periodicity are defined. " "Make sure that you know what you are doing!"); } } set_enabled(f_cv_periodic, b_periodic); } // check that cvcs are compatible for (i = 0; i < cvcs.size(); i++) { // components may have different types only for scripted functions - if (!is_enabled(f_cv_scripted) && (colvarvalue::check_types(cvcs[i]->value(), + if (!(is_enabled(f_cv_scripted) || is_enabled(f_cv_custom_function)) && (colvarvalue::check_types(cvcs[i]->value(), cvcs[0]->value())) ) { cvm::error("ERROR: you are definining this collective variable " "by using components of different types. " "You must use the same type in order to " "sum them together.\n", INPUT_ERROR); return INPUT_ERROR; } } active_cvc_square_norm = 0.; for (i = 0; i < cvcs.size(); i++) { active_cvc_square_norm += cvcs[i]->sup_coeff * cvcs[i]->sup_coeff; } // at this point, the colvar's type is defined f.type(value()); - f_accumulated.type(value()); x_old.type(value()); v_fdiff.type(value()); v_reported.type(value()); fj.type(value()); ft.type(value()); ft_reported.type(value()); f_old.type(value()); f_old.reset(); x_restart.type(value()); after_restart = false; reset_bias_force(); + get_keyval(conf, "timeStepFactor", time_step_factor, 1); + if (time_step_factor < 0) { + cvm::error("Error: timeStepFactor must be positive.\n"); + return COLVARS_ERROR; + } + if (time_step_factor != 1) { + enable(f_cv_multiple_ts); + } + // TODO use here information from the CVCs' own natural boundaries error_code |= init_grid_parameters(conf); - get_keyval(conf, "timeStepFactor", time_step_factor, 1); - error_code |= init_extended_Lagrangian(conf); error_code |= init_output_flags(conf); - // Start in active state by default + // Now that the children are defined we can solve dependencies enable(f_cv_active); - // Make sure dependency side-effects are correct - refresh_deps(); if (cvm::b_analysis) parse_analysis(conf); if (cvm::debug()) cvm::log("Done initializing collective variable \""+this->name+"\".\n"); return error_code; } +#ifdef LEPTON +int colvar::init_custom_function(std::string const &conf) +{ + std::string expr; + std::vector pexprs; + Lepton::ParsedExpression pexpr; + size_t pos = 0; // current position in config string + double *ref; + + if (!key_lookup(conf, "customFunction", &expr, &pos)) { + return COLVARS_OK; + } + + enable(f_cv_custom_function); + cvm::log("This colvar uses a custom function.\n"); + + do { + if (cvm::debug()) + cvm::log("Parsing expression \"" + expr + "\".\n"); + try { + pexpr = Lepton::Parser::parse(expr); + pexprs.push_back(pexpr); + } + catch (...) { + cvm::error("Error parsing expression \"" + expr + "\".\n", INPUT_ERROR); + return INPUT_ERROR; + } + + try { + value_evaluators.push_back( + new Lepton::CompiledExpression(pexpr.createCompiledExpression())); + // Define variables for cvc values + // Stored in order: expr1, cvc1, cvc2, expr2, cvc1... + for (size_t i = 0; i < cvcs.size(); i++) { + for (size_t j = 0; j < cvcs[i]->value().size(); j++) { + std::string vn = cvcs[i]->name + + (cvcs[i]->value().size() > 1 ? cvm::to_str(j+1) : ""); + try { + ref =&value_evaluators.back()->getVariableReference(vn); + } + catch (...) { // Variable is absent from expression + // To keep the same workflow, we use a pointer to a double here + // that will receive CVC values - even though none was allocated by Lepton + ref = &dev_null; + if (cvm::debug()) + cvm::log("Variable " + vn + " is absent from expression \"" + expr + "\".\n"); + } + value_eval_var_refs.push_back(ref); + } + } + } + catch (...) { + cvm::error("Error compiling expression \"" + expr + "\".\n", INPUT_ERROR); + return INPUT_ERROR; + } + } while (key_lookup(conf, "customFunction", &expr, &pos)); + + + // Now define derivative with respect to each scalar sub-component + for (size_t i = 0; i < cvcs.size(); i++) { + for (size_t j = 0; j < cvcs[i]->value().size(); j++) { + std::string vn = cvcs[i]->name + + (cvcs[i]->value().size() > 1 ? cvm::to_str(j+1) : ""); + // Element ordering: we want the + // gradient vector of derivatives of all elements of the colvar + // wrt to a given element of a cvc ([i][j]) + for (size_t c = 0; c < pexprs.size(); c++) { + gradient_evaluators.push_back( + new Lepton::CompiledExpression(pexprs[c].differentiate(vn).createCompiledExpression())); + // and record the refs to each variable in those expressions + for (size_t k = 0; k < cvcs.size(); k++) { + for (size_t l = 0; l < cvcs[k]->value().size(); l++) { + std::string vvn = cvcs[k]->name + + (cvcs[k]->value().size() > 1 ? cvm::to_str(l+1) : ""); + try { + ref = &gradient_evaluators.back()->getVariableReference(vvn); + } + catch (...) { // Variable is absent from derivative + // To keep the same workflow, we use a pointer to a double here + // that will receive CVC values - even though none was allocated by Lepton + if (cvm::debug()) + cvm::log("Variable " + vvn + " is absent from derivative of \"" + expr + "\" wrt " + vn + ".\n"); + ref = &dev_null; + } + grad_eval_var_refs.push_back(ref); + } + } + } + } + } + + + if (value_evaluators.size() == 0) { + cvm::error("Error: no custom function defined.\n", INPUT_ERROR); + return INPUT_ERROR; + } + + std::string type_str; + bool b_type_specified = get_keyval(conf, "customFunctionType", + type_str, "scalar", parse_silent); + x.type(colvarvalue::type_notset); + int t; + for (t = 0; t < colvarvalue::type_all; t++) { + if (type_str == colvarvalue::type_keyword(colvarvalue::Type(t))) { + x.type(colvarvalue::Type(t)); + break; + } + } + if (x.type() == colvarvalue::type_notset) { + cvm::error("Could not parse custom colvar type.", INPUT_ERROR); + return INPUT_ERROR; + } + + // Guess type based on number of expressions + if (!b_type_specified) { + if (value_evaluators.size() == 1) { + x.type(colvarvalue::type_scalar); + } else { + x.type(colvarvalue::type_vector); + } + } + + if (x.type() == colvarvalue::type_vector) { + x.vector1d_value.resize(value_evaluators.size()); + } + + x_reported.type(x); + cvm::log(std::string("Expecting colvar value of type ") + + colvarvalue::type_desc(x.type()) + + (x.type()==colvarvalue::type_vector ? " of size " + cvm::to_str(x.size()) : "") + + ".\n"); + + if (x.size() != value_evaluators.size()) { + cvm::error("Error: based on custom function type, expected " + + cvm::to_str(x.size()) + " scalar expressions, but " + + cvm::to_str(value_evaluators.size() + " were found.\n")); + return INPUT_ERROR; + } + + return COLVARS_OK; +} + +#else + +int colvar::init_custom_function(std::string const &conf) +{ + return COLVARS_OK; +} + +#endif // #ifdef LEPTON + + int colvar::init_grid_parameters(std::string const &conf) { colvarmodule *cv = cvm::main(); get_keyval(conf, "width", width, 1.0); if (width <= 0.0) { cvm::error("Error: \"width\" must be positive.\n", INPUT_ERROR); return INPUT_ERROR; } lower_boundary.type(value()); upper_boundary.type(value()); upper_wall.type(value()); set_enabled(f_cv_scalar, (value().type() == colvarvalue::type_scalar)); if (is_enabled(f_cv_scalar)) { if (get_keyval(conf, "lowerBoundary", lower_boundary, lower_boundary)) { enable(f_cv_lower_boundary); } std::string lw_conf, uw_conf; if (get_keyval(conf, "lowerWallConstant", lower_wall_k, 0.0, parse_silent)) { cvm::log("Warning: lowerWallConstant and lowerWall are deprecated, " "please define a harmonicWalls bias instead.\n"); lower_wall.type(value()); get_keyval(conf, "lowerWall", lower_wall, lower_boundary); lw_conf = std::string("\n\ lowerWallConstant "+cvm::to_str(lower_wall_k*width*width)+"\n\ lowerWalls "+cvm::to_str(lower_wall)+"\n"); } if (get_keyval(conf, "upperBoundary", upper_boundary, upper_boundary)) { enable(f_cv_upper_boundary); } if (get_keyval(conf, "upperWallConstant", upper_wall_k, 0.0, parse_silent)) { cvm::log("Warning: upperWallConstant and upperWall are deprecated, " "please define a harmonicWalls bias instead.\n"); upper_wall.type(value()); get_keyval(conf, "upperWall", upper_wall, upper_boundary); uw_conf = std::string("\n\ upperWallConstant "+cvm::to_str(upper_wall_k*width*width)+"\n\ upperWalls "+cvm::to_str(upper_wall)+"\n"); } if (lw_conf.size() && uw_conf.size()) { if (lower_wall >= upper_wall) { cvm::error("Error: the upper wall, "+ cvm::to_str(upper_wall)+ ", is not higher than the lower wall, "+ cvm::to_str(lower_wall)+".\n", INPUT_ERROR); return INPUT_ERROR; } } if (lw_conf.size() || uw_conf.size()) { cvm::log("Generating a new harmonicWalls bias for compatibility purposes.\n"); std::string const walls_conf("\n\ harmonicWalls {\n\ name "+this->name+"w\n\ - colvars "+this->name+"\n"+lw_conf+uw_conf+ + colvars "+this->name+"\n"+lw_conf+uw_conf+"\ + timeStepFactor "+cvm::to_str(time_step_factor)+"\n"+ "}\n"); cv->append_new_config(walls_conf); } } if (is_enabled(f_cv_lower_boundary)) { get_keyval(conf, "hardLowerBoundary", hard_lower_boundary, false); } if (is_enabled(f_cv_upper_boundary)) { get_keyval(conf, "hardUpperBoundary", hard_upper_boundary, false); } // consistency checks for boundaries and walls if (is_enabled(f_cv_lower_boundary) && is_enabled(f_cv_upper_boundary)) { if (lower_boundary >= upper_boundary) { cvm::error("Error: the upper boundary, "+ cvm::to_str(upper_boundary)+ ", is not higher than the lower boundary, "+ cvm::to_str(lower_boundary)+".\n", INPUT_ERROR); return INPUT_ERROR; } } get_keyval(conf, "expandBoundaries", expand_boundaries, false); if (expand_boundaries && periodic_boundaries()) { cvm::error("Error: trying to expand boundaries that already " "cover a whole period of a periodic colvar.\n", INPUT_ERROR); return INPUT_ERROR; } if (expand_boundaries && hard_lower_boundary && hard_upper_boundary) { cvm::error("Error: inconsistent configuration " "(trying to expand boundaries with both " "hardLowerBoundary and hardUpperBoundary enabled).\n", INPUT_ERROR); return INPUT_ERROR; } return COLVARS_OK; } int colvar::init_extended_Lagrangian(std::string const &conf) { - bool b_extended_Lagrangian; - get_keyval(conf, "extendedLagrangian", b_extended_Lagrangian, false); + get_keyval_feature(this, conf, "extendedLagrangian", f_cv_extended_Lagrangian, false); - if (b_extended_Lagrangian) { + if (is_enabled(f_cv_extended_Lagrangian)) { cvm::real temp, tolerance, period; cvm::log("Enabling the extended Lagrangian term for colvar \""+ this->name+"\".\n"); - enable(f_cv_extended_Lagrangian); - xr.type(value()); vr.type(value()); fr.type(value()); const bool found = get_keyval(conf, "extendedTemp", temp, cvm::temperature()); if (temp <= 0.0) { if (found) cvm::error("Error: \"extendedTemp\" must be positive.\n", INPUT_ERROR); else cvm::error("Error: a positive temperature must be provided, either " "by enabling a thermostat, or through \"extendedTemp\".\n", INPUT_ERROR); return INPUT_ERROR; } get_keyval(conf, "extendedFluctuation", tolerance); if (tolerance <= 0.0) { cvm::error("Error: \"extendedFluctuation\" must be positive.\n", INPUT_ERROR); return INPUT_ERROR; } ext_force_k = cvm::boltzmann() * temp / (tolerance * tolerance); - cvm::log("Computed extended system force constant: " + cvm::to_str(ext_force_k) + " kcal/mol/U^2"); + cvm::log("Computed extended system force constant: " + cvm::to_str(ext_force_k) + " [E]/U^2"); get_keyval(conf, "extendedTimeConstant", period, 200.0); if (period <= 0.0) { cvm::error("Error: \"extendedTimeConstant\" must be positive.\n", INPUT_ERROR); } ext_mass = (cvm::boltzmann() * temp * period * period) / (4.0 * PI * PI * tolerance * tolerance); - cvm::log("Computed fictitious mass: " + cvm::to_str(ext_mass) + " kcal/mol/(U/fs)^2 (U: colvar unit)"); + cvm::log("Computed fictitious mass: " + cvm::to_str(ext_mass) + " [E]/(U/fs)^2 (U: colvar unit)"); { bool b_output_energy; get_keyval(conf, "outputEnergy", b_output_energy, false); if (b_output_energy) { enable(f_cv_output_energy); } } get_keyval(conf, "extendedLangevinDamping", ext_gamma, 1.0); if (ext_gamma < 0.0) { cvm::error("Error: \"extendedLangevinDamping\" may not be negative.\n", INPUT_ERROR); return INPUT_ERROR; } if (ext_gamma != 0.0) { enable(f_cv_Langevin); - ext_gamma *= 1.0e-3; // convert from ps-1 to fs-1 - ext_sigma = std::sqrt(2.0 * cvm::boltzmann() * temp * ext_gamma * ext_mass / cvm::dt()); + 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 = std::sqrt(2.0 * cvm::boltzmann() * temp * ext_gamma * ext_mass / (cvm::dt() * cvm::real(time_step_factor))); } } return COLVARS_OK; } int colvar::init_output_flags(std::string const &conf) { { bool b_output_value; get_keyval(conf, "outputValue", b_output_value, true); if (b_output_value) { enable(f_cv_output_value); } } { bool b_output_velocity; get_keyval(conf, "outputVelocity", b_output_velocity, false); if (b_output_velocity) { enable(f_cv_output_velocity); } } { bool temp; if (get_keyval(conf, "outputSystemForce", temp, false, colvarparse::parse_silent)) { cvm::error("Option outputSystemForce is deprecated: only outputTotalForce is supported instead.\n" "The two are NOT identical: see http://colvars.github.io/totalforce.html.\n", INPUT_ERROR); return INPUT_ERROR; } } get_keyval_feature(this, conf, "outputTotalForce", f_cv_output_total_force, false); get_keyval_feature(this, conf, "outputAppliedForce", f_cv_output_applied_force, false); get_keyval_feature(this, conf, "subtractAppliedForce", f_cv_subtract_applied_force, false); return COLVARS_OK; } // read the configuration and set up corresponding instances, for // each type of component implemented template int colvar::init_components_type(std::string const &conf, char const *def_desc, char const *def_config_key) { size_t def_count = 0; std::string def_conf = ""; size_t pos = 0; while ( this->key_lookup(conf, def_config_key, - def_conf, - pos) ) { + &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")); cvm::increase_depth(); cvc *cvcp = new def_class_name(def_conf); if (cvcp != NULL) { cvcs.push_back(cvcp); cvcp->check_keywords(def_conf, def_config_key); if (cvm::get_error()) { cvm::error("Error: in setting up component \""+ std::string(def_config_key)+"\".\n", INPUT_ERROR); return INPUT_ERROR; } cvm::decrease_depth(); } else { cvm::error("Error: in allocating component \""+ std::string(def_config_key)+"\".\n", MEMORY_ERROR); return MEMORY_ERROR; } if ( (cvcp->period != 0.0) || (cvcp->wrap_center != 0.0) ) { if ( (cvcp->function_type != std::string("distance_z")) && (cvcp->function_type != std::string("dihedral")) && + (cvcp->function_type != std::string("polar_phi")) && (cvcp->function_type != std::string("spin_angle")) ) { 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), INPUT_ERROR); return INPUT_ERROR; } } if ( ! cvcs.back()->name.size()) { std::ostringstream s; s << def_config_key << std::setfill('0') << std::setw(4) << ++def_count; cvcs.back()->name = s.str(); /* pad cvc number for correct ordering when sorting by name */ } cvcs.back()->setup(); if (cvm::debug()) { 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"); } } return COLVARS_OK; } int colvar::init_components(std::string const &conf) { int error_code = COLVARS_OK; 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, "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"); if (!cvcs.size() || (error_code != COLVARS_OK)) { cvm::error("Error: no valid components were provided " "for this collective variable.\n", INPUT_ERROR); return INPUT_ERROR; } n_active_cvcs = cvcs.size(); cvm::log("All components initialized.\n"); // Store list of children cvcs for dependency checking purposes for (size_t i = 0; i < cvcs.size(); i++) { add_child(cvcs[i]); } return COLVARS_OK; } -int colvar::refresh_deps() +void colvar::do_feature_side_effects(int id) { - // If enabled features are changed upstream, the features below should be refreshed - if (is_enabled(f_cv_total_force_calc)) { - cvm::request_total_force(); - } - if (is_enabled(f_cv_collect_gradient) && atom_ids.size() == 0) { - build_atom_list(); + switch (id) { + case f_cv_total_force_calc: + cvm::request_total_force(); + break; + case f_cv_collect_gradient: + if (atom_ids.size() == 0) { + build_atom_list(); + } + break; } - return COLVARS_OK; } void colvar::build_atom_list(void) { // If atomic gradients are requested, build full list of atom ids from all cvcs std::list temp_id_list; for (size_t i = 0; i < cvcs.size(); i++) { for (size_t j = 0; j < cvcs[i]->atom_groups.size(); j++) { cvm::atom_group &ag = *(cvcs[i]->atom_groups[j]); for (size_t k = 0; k < ag.size(); k++) { temp_id_list.push_back(ag[k].id); } } } temp_id_list.sort(); temp_id_list.unique(); // atom_ids = std::vector (temp_id_list.begin(), temp_id_list.end()); unsigned int id_i = 0; std::list::iterator li; for (li = temp_id_list.begin(); li != temp_id_list.end(); ++li) { atom_ids[id_i] = *li; id_i++; } temp_id_list.clear(); atomic_gradients.resize(atom_ids.size()); if (atom_ids.size()) { if (cvm::debug()) cvm::log("Colvar: created atom list with " + cvm::to_str(atom_ids.size()) + " atoms.\n"); } else { cvm::log("Warning: colvar components communicated no atom IDs.\n"); } } int colvar::parse_analysis(std::string const &conf) { // if (cvm::debug()) // cvm::log ("Parsing analysis flags for collective variable \""+ // this->name+"\".\n"); runave_length = 0; bool b_runave = false; if (get_keyval(conf, "runAve", b_runave) && b_runave) { enable(f_cv_runave); get_keyval(conf, "runAveLength", runave_length, 1000); get_keyval(conf, "runAveStride", runave_stride, 1); if ((cvm::restart_out_freq % runave_stride) != 0) { cvm::error("Error: runAveStride must be commensurate with the restart frequency.\n", INPUT_ERROR); } - std::string runave_outfile; get_keyval(conf, "runAveOutputFile", runave_outfile, std::string(cvm::output_prefix()+"."+ this->name+".runave.traj")); size_t const this_cv_width = x.output_width(cvm::cv_width); - cvm::backup_file(runave_outfile.c_str()); - runave_os.open(runave_outfile.c_str()); - runave_os << "# " << cvm::wrap_string("step", cvm::it_width-2) - << " " - << cvm::wrap_string("running average", this_cv_width) - << " " - << cvm::wrap_string("running stddev", this_cv_width) - << "\n"; + cvm::proxy->backup_file(runave_outfile); + runave_os = cvm::proxy->output_stream(runave_outfile); + *runave_os << "# " << cvm::wrap_string("step", cvm::it_width-2) + << " " + << cvm::wrap_string("running average", this_cv_width) + << " " + << cvm::wrap_string("running stddev", this_cv_width) + << "\n"; } acf_length = 0; bool b_acf = false; if (get_keyval(conf, "corrFunc", b_acf) && b_acf) { enable(f_cv_corrfunc); std::string acf_colvar_name; get_keyval(conf, "corrFuncWithColvar", acf_colvar_name, this->name); if (acf_colvar_name == this->name) { cvm::log("Calculating auto-correlation function.\n"); } else { cvm::log("Calculating correlation function with \""+ this->name+"\".\n"); } std::string acf_type_str; get_keyval(conf, "corrFuncType", acf_type_str, to_lower_cppstr(std::string("velocity"))); if (acf_type_str == to_lower_cppstr(std::string("coordinate"))) { acf_type = acf_coor; } else if (acf_type_str == to_lower_cppstr(std::string("velocity"))) { acf_type = acf_vel; enable(f_cv_fdiff_velocity); if (acf_colvar_name.size()) (cvm::colvar_by_name(acf_colvar_name))->enable(f_cv_fdiff_velocity); } else if (acf_type_str == to_lower_cppstr(std::string("coordinate_p2"))) { acf_type = acf_p2coor; } else { cvm::log("Unknown type of correlation function, \""+ acf_type_str+"\".\n"); cvm::set_error_bits(INPUT_ERROR); } get_keyval(conf, "corrFuncOffset", acf_offset, 0); get_keyval(conf, "corrFuncLength", acf_length, 1000); get_keyval(conf, "corrFuncStride", acf_stride, 1); if ((cvm::restart_out_freq % acf_stride) != 0) { cvm::error("Error: corrFuncStride must be commensurate with the restart frequency.\n", INPUT_ERROR); } get_keyval(conf, "corrFuncNormalize", acf_normalize, true); get_keyval(conf, "corrFuncOutputFile", acf_outfile, std::string(cvm::output_prefix()+"."+this->name+ ".corrfunc.dat")); } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } void colvar::setup() { // loop over all components to reset masses of all groups for (size_t i = 0; i < cvcs.size(); i++) { for (size_t ig = 0; ig < cvcs[i]->atom_groups.size(); ig++) { cvm::atom_group &atoms = *(cvcs[i]->atom_groups[ig]); atoms.setup(); atoms.reset_mass(name,i,ig); atoms.read_positions(); } } } colvar::~colvar() { + // There is no need to call free_children_deps() here + // because the children are cvcs and will be deleted + // just below + // Clear references to this colvar's cvcs as children // 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 (*ci)->remove_all_children(); delete *ci; } // remove reference to this colvar from the CVM colvarmodule *cv = cvm::main(); for (std::vector::iterator cvi = cv->variables()->begin(); cvi != cv->variables()->end(); ++cvi) { if ( *cvi == this) { cv->variables()->erase(cvi); break; } } + +#ifdef LEPTON + for (std::vector::iterator cei = value_evaluators.begin(); + cei != value_evaluators.end(); + ++cei) { + if (*cei != NULL) delete (*cei); + } + value_evaluators.clear(); + + for (std::vector::iterator gei = gradient_evaluators.begin(); + gei != gradient_evaluators.end(); + ++gei) { + if (*gei != NULL) delete (*gei); + } + gradient_evaluators.clear(); +#endif } // ******************** CALC FUNCTIONS ******************** // Default schedule (everything is serialized) int colvar::calc() { // Note: if anything is added here, it should be added also in the SMP block of calc_colvars() int error_code = COLVARS_OK; if (is_enabled(f_cv_active)) { error_code |= update_cvc_flags(); if (error_code != COLVARS_OK) return error_code; error_code |= calc_cvcs(); if (error_code != COLVARS_OK) return error_code; error_code |= collect_cvc_data(); } return error_code; } int colvar::calc_cvcs(int first_cvc, size_t num_cvcs) { int error_code = COLVARS_OK; if (cvm::debug()) cvm::log("Calculating colvar \""+this->name+"\", components "+ cvm::to_str(first_cvc)+" through "+cvm::to_str(first_cvc+num_cvcs)+".\n"); error_code |= check_cvc_range(first_cvc, num_cvcs); if (error_code != COLVARS_OK) { return error_code; } if (cvm::step_relative() > 0) { // Total force depends on Jacobian derivative from previous timestep error_code |= calc_cvc_total_force(first_cvc, num_cvcs); } // atom coordinates are updated by the next line error_code |= calc_cvc_values(first_cvc, num_cvcs); error_code |= calc_cvc_gradients(first_cvc, num_cvcs); error_code |= calc_cvc_Jacobians(first_cvc, num_cvcs); if (cvm::debug()) cvm::log("Done calculating colvar \""+this->name+"\".\n"); return error_code; } int colvar::collect_cvc_data() { if (cvm::debug()) cvm::log("Calculating colvar \""+this->name+"\"'s properties.\n"); int error_code = COLVARS_OK; if (cvm::step_relative() > 0) { // Total force depends on Jacobian derivative from previous timestep error_code |= collect_cvc_total_forces(); } error_code |= collect_cvc_values(); error_code |= collect_cvc_gradients(); error_code |= collect_cvc_Jacobians(); error_code |= calc_colvar_properties(); if (cvm::debug()) cvm::log("Done calculating colvar \""+this->name+"\"'s properties.\n"); return error_code; } int colvar::check_cvc_range(int first_cvc, size_t num_cvcs) { if ((first_cvc < 0) || (first_cvc >= ((int) cvcs.size()))) { cvm::error("Error: trying to address a component outside the " "range defined for colvar \""+name+"\".\n", BUG_ERROR); return BUG_ERROR; } return COLVARS_OK; } int colvar::calc_cvc_values(int first_cvc, size_t num_cvcs) { size_t const cvc_max_count = num_cvcs ? num_cvcs : num_active_cvcs(); size_t i, cvc_count; // calculate the value of the colvar if (cvm::debug()) cvm::log("Calculating colvar components.\n"); // First, calculate component values cvm::increase_depth(); for (i = first_cvc, cvc_count = 0; (i < cvcs.size()) && (cvc_count < cvc_max_count); i++) { if (!cvcs[i]->is_enabled()) continue; cvc_count++; (cvcs[i])->read_data(); (cvcs[i])->calc_value(); if (cvm::debug()) cvm::log("Colvar component no. "+cvm::to_str(i+1)+ " within colvar \""+this->name+"\" has value "+ cvm::to_str((cvcs[i])->value(), cvm::cv_width, cvm::cv_prec)+".\n"); } cvm::decrease_depth(); return COLVARS_OK; } int colvar::collect_cvc_values() { x.reset(); - size_t i; // combine them appropriately, using either a scripted function or a polynomial if (is_enabled(f_cv_scripted)) { // cvcs combined by user script int res = cvm::proxy->run_colvar_callback(scripted_function, sorted_cvc_values, x); if (res == COLVARS_NOT_IMPLEMENTED) { cvm::error("Scripted colvars are not implemented."); return COLVARS_NOT_IMPLEMENTED; } if (res != COLVARS_OK) { cvm::error("Error running scripted colvar"); return COLVARS_OK; } + +#ifdef LEPTON + } else if (is_enabled(f_cv_custom_function)) { + + size_t l = 0; // index in the vector of variable references + + for (size_t i = 0; i < x.size(); i++) { + // Fill Lepton evaluator variables with CVC values, serialized into scalars + for (size_t j = 0; j < cvcs.size(); j++) { + for (size_t k = 0; k < cvcs[j]->value().size(); k++) { + *(value_eval_var_refs[l++]) = cvcs[j]->value()[k]; + } + } + x[i] = value_evaluators[i]->evaluate(); + } +#endif + } else if (x.type() == colvarvalue::type_scalar) { // polynomial combination allowed - for (i = 0; i < cvcs.size(); i++) { + for (size_t i = 0; i < cvcs.size(); i++) { if (!cvcs[i]->is_enabled()) continue; x += (cvcs[i])->sup_coeff * ( ((cvcs[i])->sup_np != 1) ? std::pow((cvcs[i])->value().real_value, (cvcs[i])->sup_np) : (cvcs[i])->value().real_value ); } } else { - for (i = 0; i < cvcs.size(); i++) { + for (size_t i = 0; i < cvcs.size(); i++) { if (!cvcs[i]->is_enabled()) continue; x += (cvcs[i])->sup_coeff * (cvcs[i])->value(); } } if (cvm::debug()) cvm::log("Colvar \""+this->name+"\" has value "+ cvm::to_str(x, cvm::cv_width, cvm::cv_prec)+".\n"); if (after_restart) { if (cvm::proxy->simulation_running()) { cvm::real const jump2 = dist2(x, x_restart) / (width*width); if (jump2 > 0.25) { cvm::error("Error: the calculated value of colvar \""+name+ "\":\n"+cvm::to_str(x)+"\n differs greatly from the value " "last read from the state file:\n"+cvm::to_str(x_restart)+ "\nPossible causes are changes in configuration, " "wrong state file, or how PBC wrapping is handled.\n", INPUT_ERROR); return INPUT_ERROR; } } } return COLVARS_OK; } int colvar::calc_cvc_gradients(int first_cvc, size_t num_cvcs) { size_t const cvc_max_count = num_cvcs ? num_cvcs : num_active_cvcs(); size_t i, cvc_count; if (cvm::debug()) cvm::log("Calculating gradients of colvar \""+this->name+"\".\n"); // calculate the gradients of each component cvm::increase_depth(); for (i = first_cvc, cvc_count = 0; (i < cvcs.size()) && (cvc_count < cvc_max_count); i++) { if (!cvcs[i]->is_enabled()) continue; cvc_count++; if ((cvcs[i])->is_enabled(f_cvc_gradient)) { (cvcs[i])->calc_gradients(); // if requested, propagate (via chain rule) the gradients above // to the atoms used to define the roto-translation - // This could be integrated in the CVC base class - for (size_t ig = 0; ig < cvcs[i]->atom_groups.size(); ig++) { - if (cvcs[i]->atom_groups[ig]->b_fit_gradients) - cvcs[i]->atom_groups[ig]->calc_fit_gradients(); - - if (cvcs[i]->is_enabled(f_cvc_debug_gradient)) { - cvm::log("Debugging gradients for " + cvcs[i]->description); - cvcs[i]->debug_gradients(cvcs[i]->atom_groups[ig]); - } - } + (cvcs[i])->calc_fit_gradients(); + if ((cvcs[i])->is_enabled(f_cvc_debug_gradient)) + (cvcs[i])->debug_gradients(); } cvm::decrease_depth(); if (cvm::debug()) cvm::log("Done calculating gradients of colvar \""+this->name+"\".\n"); } return COLVARS_OK; } int colvar::collect_cvc_gradients() { size_t i; if (is_enabled(f_cv_collect_gradient)) { - - if (is_enabled(f_cv_scripted)) { - cvm::error("Collecting atomic gradients is not implemented for " - "scripted colvars.", COLVARS_NOT_IMPLEMENTED); - return COLVARS_NOT_IMPLEMENTED; - } - // Collect the atomic gradients inside colvar object for (unsigned int a = 0; a < atomic_gradients.size(); a++) { atomic_gradients[a].reset(); } for (i = 0; i < cvcs.size(); i++) { if (!cvcs[i]->is_enabled()) continue; // Coefficient: d(a * x^n) = a * n * x^(n-1) * dx cvm::real coeff = (cvcs[i])->sup_coeff * cvm::real((cvcs[i])->sup_np) * std::pow((cvcs[i])->value().real_value, (cvcs[i])->sup_np-1); for (size_t j = 0; j < cvcs[i]->atom_groups.size(); j++) { cvm::atom_group &ag = *(cvcs[i]->atom_groups[j]); // If necessary, apply inverse rotation to get atomic // gradient in the laboratory frame if (ag.b_rotate) { cvm::rotation const rot_inv = ag.rot.inverse(); 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); } } else { 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 * ag[k].grad; } } } } } return COLVARS_OK; } int colvar::calc_cvc_total_force(int first_cvc, size_t num_cvcs) { size_t const cvc_max_count = num_cvcs ? num_cvcs : num_active_cvcs(); size_t i, cvc_count; if (is_enabled(f_cv_total_force_calc)) { if (cvm::debug()) cvm::log("Calculating total force of colvar \""+this->name+"\".\n"); cvm::increase_depth(); for (i = first_cvc, cvc_count = 0; (i < cvcs.size()) && (cvc_count < cvc_max_count); i++) { if (!cvcs[i]->is_enabled()) continue; cvc_count++; (cvcs[i])->calc_force_invgrads(); } cvm::decrease_depth(); if (cvm::debug()) cvm::log("Done calculating total force of colvar \""+this->name+"\".\n"); } return COLVARS_OK; } int colvar::collect_cvc_total_forces() { if (is_enabled(f_cv_total_force_calc)) { ft.reset(); if (cvm::step_relative() > 0) { // get from the cvcs the total forces from the PREVIOUS step for (size_t i = 0; i < cvcs.size(); i++) { if (!cvcs[i]->is_enabled()) continue; if (cvm::debug()) cvm::log("Colvar component no. "+cvm::to_str(i+1)+ " within colvar \""+this->name+"\" has total force "+ cvm::to_str((cvcs[i])->total_force(), cvm::cv_width, cvm::cv_prec)+".\n"); // linear combination is assumed ft += (cvcs[i])->total_force() * (cvcs[i])->sup_coeff / active_cvc_square_norm; } } if (!is_enabled(f_cv_hide_Jacobian)) { // add the Jacobian force to the total force, and don't apply any silent // correction internally: biases such as colvarbias_abf will handle it ft += fj; } } return COLVARS_OK; } int colvar::calc_cvc_Jacobians(int first_cvc, size_t num_cvcs) { size_t const cvc_max_count = num_cvcs ? num_cvcs : num_active_cvcs(); if (is_enabled(f_cv_Jacobian)) { cvm::increase_depth(); size_t i, cvc_count; for (i = first_cvc, cvc_count = 0; (i < cvcs.size()) && (cvc_count < cvc_max_count); i++) { if (!cvcs[i]->is_enabled()) continue; cvc_count++; (cvcs[i])->calc_Jacobian_derivative(); } cvm::decrease_depth(); } return COLVARS_OK; } int colvar::collect_cvc_Jacobians() { if (is_enabled(f_cv_Jacobian)) { fj.reset(); for (size_t i = 0; i < cvcs.size(); i++) { if (!cvcs[i]->is_enabled()) continue; if (cvm::debug()) cvm::log("Colvar component no. "+cvm::to_str(i+1)+ " within colvar \""+this->name+"\" has Jacobian derivative"+ cvm::to_str((cvcs[i])->Jacobian_derivative(), cvm::cv_width, cvm::cv_prec)+".\n"); // linear combination is assumed fj += (cvcs[i])->Jacobian_derivative() * (cvcs[i])->sup_coeff / active_cvc_square_norm; } fj *= cvm::boltzmann() * cvm::temperature(); } return COLVARS_OK; } int colvar::calc_colvar_properties() { if (is_enabled(f_cv_fdiff_velocity)) { // calculate the velocity by finite differences if (cvm::step_relative() == 0) x_old = x; else { v_fdiff = fdiff_velocity(x_old, x); v_reported = v_fdiff; } } if (is_enabled(f_cv_extended_Lagrangian)) { // initialize the restraint center in the first step to the value // just calculated from the cvcs if (cvm::step_relative() == 0 && !after_restart) { xr = x; vr.reset(); // (already 0; added for clarity) } // report the restraint center as "value" x_reported = xr; v_reported = vr; // the "total force" with the extended Lagrangian is // calculated in update_forces_energy() below } else { if (is_enabled(f_cv_subtract_applied_force)) { // correct the total force only if it has been measured // TODO add a specific test instead of relying on sq norm if (ft.norm2() > 0.0) { ft -= f_old; } } x_reported = x; ft_reported = ft; } // At the end of the first update after a restart, we can reset the flag after_restart = false; return COLVARS_OK; } cvm::real colvar::update_forces_energy() { if (cvm::debug()) cvm::log("Updating colvar \""+this->name+"\".\n"); // set to zero the applied force f.type(value()); f.reset(); + fr.reset(); + + // If we are not active at this timestep, that's all we have to do + // return with energy == zero + if (!is_enabled(f_cv_active)) return 0.; // add the biases' force, which at this point should already have // been summed over each bias using this colvar f += fb; if (is_enabled(f_cv_Jacobian)) { // the instantaneous Jacobian force was not included in the reported total force; // instead, it is subtracted from the applied force (silent Jacobian correction) // This requires the Jacobian term for the *current* timestep if (is_enabled(f_cv_hide_Jacobian)) f -= fj; } // At this point f is the force f from external biases that will be applied to the // extended variable if there is one if (is_enabled(f_cv_extended_Lagrangian)) { if (cvm::debug()) { cvm::log("Updating extended-Lagrangian degree of freedom.\n"); } - cvm::real dt = cvm::dt(); + if (prev_timestep > -1) { + // Keep track of slow timestep to integrate MTS colvars + // the colvar checks the interval after waking up twice + int n_timesteps = cvm::step_relative() - prev_timestep; + if (n_timesteps != 0 && n_timesteps != time_step_factor) { + cvm::error("Error: extended-Lagrangian " + description + " has timeStepFactor " + + cvm::to_str(time_step_factor) + ", but was activated after " + cvm::to_str(n_timesteps) + + " steps at timestep " + cvm::to_str(cvm::step_absolute()) + " (relative step: " + + cvm::to_str(cvm::step_relative()) + ").\n" + + "Make sure that this colvar is requested by biases at multiples of timeStepFactor.\n"); + return 0.; + } + } + prev_timestep = cvm::step_relative(); + + // Integrate with slow timestep (if time_step_factor != 1) + cvm::real dt = cvm::dt() * cvm::real(time_step_factor); + colvarvalue f_ext(fr.type()); // force acting on the extended variable f_ext.reset(); // 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) fr = f; - f_ext = f + (-0.5 * ext_force_k) * this->dist2_lgrad(xr, x); - f = (-0.5 * ext_force_k) * this->dist2_rgrad(xr, x); - - if (is_enabled(f_cv_subtract_applied_force)) { - // Report a "system" force without the biases on this colvar - // that is, just the spring force - ft_reported = (-0.5 * ext_force_k) * this->dist2_lgrad(xr, x); - } else { - // The total force acting on the extended variable is f_ext - // This will be used in the next timestep - ft_reported = f_ext; - } + // 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 + f_ext = f / cvm::real(time_step_factor); + f_ext += (-0.5 * ext_force_k) * this->dist2_lgrad(xr, x); + f = (-0.5 * ext_force_k) * this->dist2_rgrad(xr, x); + // Coupling force is a slow force, to be applied to atomic coords impulse-style + f *= cvm::real(time_step_factor); + + // The total force acting on the extended variable is f_ext + // This will be used in the next timestep + ft_reported = f_ext; // leapfrog: starting from x_i, f_i, v_(i-1/2) vr += (0.5 * dt) * f_ext / ext_mass; // Because of leapfrog, kinetic energy at time i is approximate kinetic_energy = 0.5 * ext_mass * vr * vr; potential_energy = 0.5 * ext_force_k * this->dist2(xr, x); // leap to v_(i+1/2) if (is_enabled(f_cv_Langevin)) { vr -= dt * ext_gamma * vr; colvarvalue rnd(x); rnd.set_random(); vr += dt * ext_sigma * rnd / ext_mass; } vr += (0.5 * dt) * f_ext / ext_mass; xr += dt * vr; xr.apply_constraints(); if (this->is_enabled(f_cv_periodic)) this->wrap(xr); } - // Now adding the force on the actual colvar (for those biases who + // Now adding the force on the actual colvar (for those biases that // bypass the extended Lagrangian mass) f += fb_actual; - // Store force to be applied, possibly summed over several timesteps - f_accumulated += f; - if (is_enabled(f_cv_fdiff_velocity)) { // set it for the next step x_old = x; } if (is_enabled(f_cv_subtract_applied_force)) { f_old = f; } if (cvm::debug()) cvm::log("Done updating colvar \""+this->name+"\".\n"); return (potential_energy + kinetic_energy); } void colvar::communicate_forces() { size_t i; if (cvm::debug()) { cvm::log("Communicating forces from colvar \""+this->name+"\".\n"); - cvm::log("Force to be applied: " + cvm::to_str(f_accumulated) + "\n"); + cvm::log("Force to be applied: " + cvm::to_str(f) + "\n"); } if (is_enabled(f_cv_scripted)) { std::vector > func_grads; func_grads.reserve(cvcs.size()); for (i = 0; i < cvcs.size(); i++) { if (!cvcs[i]->is_enabled()) continue; func_grads.push_back(cvm::matrix2d (x.size(), cvcs[i]->value().size())); } int res = cvm::proxy->run_colvar_gradient_callback(scripted_function, sorted_cvc_values, func_grads); if (res != COLVARS_OK) { if (res == COLVARS_NOT_IMPLEMENTED) { cvm::error("Colvar gradient scripts are not implemented.", COLVARS_NOT_IMPLEMENTED); } else { cvm::error("Error running colvar gradient script"); } return; } int grad_index = 0; // index in the scripted gradients, to account for some components being disabled for (i = 0; i < cvcs.size(); i++) { if (!cvcs[i]->is_enabled()) continue; // cvc force is colvar force times colvar/cvc Jacobian // (vector-matrix product) - (cvcs[i])->apply_force(colvarvalue(f_accumulated.as_vector() * func_grads[grad_index++], + (cvcs[i])->apply_force(colvarvalue(f.as_vector() * func_grads[grad_index++], cvcs[i]->value().type())); } + +#ifdef LEPTON + } else if (is_enabled(f_cv_custom_function)) { + + size_t r = 0; // index in the vector of variable references + size_t e = 0; // index of the gradient evaluator + + for (size_t i = 0; i < cvcs.size(); i++) { // gradient with respect to cvc i + cvm::matrix2d jacobian (x.size(), cvcs[i]->value().size()); + for (size_t j = 0; j < cvcs[i]->value().size(); j++) { // j-th element + for (size_t c = 0; c < x.size(); c++) { // derivative of scalar element c of the colvarvalue + + // Feed cvc values to the evaluator + for (size_t k = 0; k < cvcs.size(); k++) { // + for (size_t l = 0; l < cvcs[k]->value().size(); l++) { + *(grad_eval_var_refs[r++]) = cvcs[k]->value()[l]; + } + } + jacobian[c][j] = gradient_evaluators[e++]->evaluate(); + } + } + // cvc force is colvar force times colvar/cvc Jacobian + // (vector-matrix product) + (cvcs[i])->apply_force(colvarvalue(f.as_vector() * jacobian, + cvcs[i]->value().type())); + } +#endif + } else if (x.type() == colvarvalue::type_scalar) { for (i = 0; i < cvcs.size(); i++) { if (!cvcs[i]->is_enabled()) continue; - (cvcs[i])->apply_force(f_accumulated * (cvcs[i])->sup_coeff * + (cvcs[i])->apply_force(f * (cvcs[i])->sup_coeff * cvm::real((cvcs[i])->sup_np) * (std::pow((cvcs[i])->value().real_value, (cvcs[i])->sup_np-1)) ); } } else { for (i = 0; i < cvcs.size(); i++) { if (!cvcs[i]->is_enabled()) continue; - (cvcs[i])->apply_force(f_accumulated * (cvcs[i])->sup_coeff); + (cvcs[i])->apply_force(f * (cvcs[i])->sup_coeff); } } - // Accumulated forces have been applied, impulse-style - // Reset to start accumulating again - f_accumulated.reset(); - if (cvm::debug()) cvm::log("Done communicating forces from colvar \""+this->name+"\".\n"); } int colvar::set_cvc_flags(std::vector const &flags) { if (flags.size() != cvcs.size()) { cvm::error("ERROR: Wrong number of CVC flags provided."); return COLVARS_ERROR; } // We cannot enable or disable cvcs in the middle of a timestep or colvar evaluation sequence // so we store the flags that will be enforced at the next call to calc() cvc_flags = flags; return COLVARS_OK; } int colvar::update_cvc_flags() { // Update the enabled/disabled status of cvcs if necessary if (cvc_flags.size()) { n_active_cvcs = 0; active_cvc_square_norm = 0.; for (size_t i = 0; i < cvcs.size(); i++) { cvcs[i]->set_enabled(f_cvc_active, cvc_flags[i]); if (cvcs[i]->is_enabled()) { n_active_cvcs++; active_cvc_square_norm += cvcs[i]->sup_coeff * cvcs[i]->sup_coeff; } } if (!n_active_cvcs) { cvm::error("ERROR: All CVCs are disabled for colvar " + this->name +"\n"); return COLVARS_ERROR; } - cvc_flags.resize(0); + cvc_flags.clear(); } return COLVARS_OK; } // ******************** METRIC FUNCTIONS ******************** // Use the metrics defined by \link cvc \endlink objects bool colvar::periodic_boundaries(colvarvalue const &lb, colvarvalue const &ub) const { if ( (!is_enabled(f_cv_lower_boundary)) || (!is_enabled(f_cv_upper_boundary)) ) { cvm::log("Error: checking periodicity for collective variable \""+this->name+"\" " "requires lower and upper boundaries to be defined.\n"); cvm::set_error_bits(INPUT_ERROR); } if (period > 0.0) { if ( ((std::sqrt(this->dist2(lb, ub))) / this->width) < 1.0E-10 ) { return true; } } return false; } bool colvar::periodic_boundaries() const { if ( (!is_enabled(f_cv_lower_boundary)) || (!is_enabled(f_cv_upper_boundary)) ) { cvm::log("Error: checking periodicity for collective variable \""+this->name+"\" " "requires lower and upper boundaries to be defined.\n"); } return periodic_boundaries(lower_boundary, upper_boundary); } cvm::real colvar::dist2(colvarvalue const &x1, colvarvalue const &x2) const { if (is_enabled(f_cv_homogeneous)) { return (cvcs[0])->dist2(x1, x2); } else { return x1.dist2(x2); } } colvarvalue colvar::dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const { if (is_enabled(f_cv_homogeneous)) { return (cvcs[0])->dist2_lgrad(x1, x2); } else { return x1.dist2_grad(x2); } } colvarvalue colvar::dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const { if (is_enabled(f_cv_homogeneous)) { return (cvcs[0])->dist2_rgrad(x1, x2); } else { return x2.dist2_grad(x1); } } void colvar::wrap(colvarvalue &x) const { if (is_enabled(f_cv_homogeneous)) { (cvcs[0])->wrap(x); } return; } // ******************** INPUT FUNCTIONS ******************** std::istream & colvar::read_restart(std::istream &is) { size_t const start_pos = is.tellg(); std::string conf; if ( !(is >> colvarparse::read_block("colvar", conf)) ) { // this is not a colvar block is.clear(); is.seekg(start_pos, std::ios::beg); is.setstate(std::ios::failbit); return is; } { std::string check_name = ""; if ( (get_keyval(conf, "name", check_name, std::string(""), colvarparse::parse_silent)) && (check_name != name) ) { cvm::error("Error: the state file does not match the " "configuration file, at colvar \""+name+"\".\n"); } if (check_name.size() == 0) { cvm::error("Error: Collective variable in the " "restart file without any identifier.\n"); } } 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"); } else { cvm::log("Restarting collective variable \""+name+"\" from value: "+ cvm::to_str(x)+"\n"); x_restart = x; after_restart = true; } if (is_enabled(f_cv_extended_Lagrangian)) { if ( !(get_keyval(conf, "extended_x", xr, colvarvalue(x.type()), colvarparse::parse_silent)) && !(get_keyval(conf, "extended_v", vr, 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"); } x_reported = xr; } else { x_reported = x; } if (is_enabled(f_cv_output_velocity)) { 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"); } if (is_enabled(f_cv_extended_Lagrangian)) { v_reported = vr; } else { v_reported = v_fdiff; } } return is; } std::istream & colvar::read_traj(std::istream &is) { size_t const start_pos = is.tellg(); if (is_enabled(f_cv_output_value)) { if (!(is >> x)) { cvm::log("Error: in reading the value of colvar \""+ this->name+"\" from trajectory.\n"); is.clear(); is.seekg(start_pos, std::ios::beg); is.setstate(std::ios::failbit); return is; } if (is_enabled(f_cv_extended_Lagrangian)) { is >> xr; x_reported = xr; } else { x_reported = x; } } if (is_enabled(f_cv_output_velocity)) { is >> v_fdiff; if (is_enabled(f_cv_extended_Lagrangian)) { is >> vr; v_reported = vr; } else { v_reported = v_fdiff; } } if (is_enabled(f_cv_output_total_force)) { is >> ft; ft_reported = ft; } if (is_enabled(f_cv_output_applied_force)) { is >> f; } return is; } // ******************** OUTPUT FUNCTIONS ******************** std::ostream & colvar::write_restart(std::ostream &os) { os << "colvar {\n" << " name " << name << "\n" << " x " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << x << "\n"; if (is_enabled(f_cv_output_velocity)) { os << " v " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << v_reported << "\n"; } if (is_enabled(f_cv_extended_Lagrangian)) { os << " extended_x " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << xr << "\n" << " extended_v " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << vr << "\n"; } os << "}\n\n"; return os; } std::ostream & colvar::write_traj_label(std::ostream & os) { size_t const this_cv_width = x.output_width(cvm::cv_width); os << " "; if (is_enabled(f_cv_output_value)) { os << " " << cvm::wrap_string(this->name, this_cv_width); if (is_enabled(f_cv_extended_Lagrangian)) { // extended DOF os << " r_" << cvm::wrap_string(this->name, this_cv_width-2); } } if (is_enabled(f_cv_output_velocity)) { os << " v_" << cvm::wrap_string(this->name, this_cv_width-2); if (is_enabled(f_cv_extended_Lagrangian)) { // extended DOF os << " vr_" << cvm::wrap_string(this->name, this_cv_width-3); } } if (is_enabled(f_cv_output_energy)) { os << " Ep_" << cvm::wrap_string(this->name, this_cv_width-3) << " Ek_" << cvm::wrap_string(this->name, this_cv_width-3); } if (is_enabled(f_cv_output_total_force)) { os << " ft_" << cvm::wrap_string(this->name, this_cv_width-3); } if (is_enabled(f_cv_output_applied_force)) { os << " fa_" << cvm::wrap_string(this->name, this_cv_width-3); } return os; } std::ostream & colvar::write_traj(std::ostream &os) { os << " "; if (is_enabled(f_cv_output_value)) { if (is_enabled(f_cv_extended_Lagrangian)) { os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << x; } os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << x_reported; } if (is_enabled(f_cv_output_velocity)) { if (is_enabled(f_cv_extended_Lagrangian)) { os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << v_fdiff; } os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << v_reported; } if (is_enabled(f_cv_output_energy)) { os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << potential_energy << " " << kinetic_energy; } if (is_enabled(f_cv_output_total_force)) { os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << ft_reported; } if (is_enabled(f_cv_output_applied_force)) { os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << applied_force(); } return os; } int colvar::write_output_files() { if (cvm::b_analysis) { if (acf.size()) { cvm::log("Writing acf to file \""+acf_outfile+"\".\n"); cvm::backup_file(acf_outfile.c_str()); - cvm::ofstream acf_os(acf_outfile.c_str()); - if (! acf_os.is_open()) { - cvm::error("Cannot open file \""+acf_outfile+"\".\n", FILE_ERROR); - } - write_acf(acf_os); - acf_os.close(); + std::ostream *acf_os = cvm::proxy->output_stream(acf_outfile); + if (!acf_os) return cvm::get_error(); + write_acf(*acf_os); + cvm::proxy->close_output_stream(acf_outfile); } - if (runave_os.is_open()) { - runave_os.close(); + if (runave_os) { + cvm::proxy->close_output_stream(runave_outfile); + runave_os = NULL; } } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } // ******************** ANALYSIS FUNCTIONS ******************** void colvar::analyze() { if (is_enabled(f_cv_runave)) { calc_runave(); } if (is_enabled(f_cv_corrfunc)) { calc_acf(); } } inline void history_add_value(size_t const &history_length, std::list &history, colvarvalue const &new_value) { history.push_front(new_value); if (history.size() > history_length) history.pop_back(); } inline void history_incr(std::list< std::list > &history, std::list< std::list >::iterator &history_p) { if ((++history_p) == history.end()) history_p = history.begin(); } int colvar::calc_acf() { // using here an acf_stride-long list of vectors for either // coordinates(acf_x_history) or velocities (acf_v_history); each vector can // contain up to acf_length values, which are contiguous in memory // representation but separated by acf_stride in the time series; // the pointer to each vector is changed at every step if (acf_x_history.empty() && acf_v_history.empty()) { // first-step operations colvar *cfcv = (acf_colvar_name.size() ? cvm::colvar_by_name(acf_colvar_name) : this); if (colvarvalue::check_types(cfcv->value(), value())) { cvm::error("Error: correlation function between \""+cfcv->name+ "\" and \""+this->name+"\" cannot be calculated, " "because their value types are different.\n", INPUT_ERROR); } acf_nframes = 0; cvm::log("Colvar \""+this->name+"\": initializing ACF calculation.\n"); if (acf.size() < acf_length+1) acf.resize(acf_length+1, 0.0); size_t i; switch (acf_type) { case acf_vel: // allocate space for the velocities history for (i = 0; i < acf_stride; i++) { acf_v_history.push_back(std::list()); } acf_v_history_p = acf_v_history.begin(); break; case acf_coor: case acf_p2coor: // allocate space for the coordinates history for (i = 0; i < acf_stride; i++) { acf_x_history.push_back(std::list()); } acf_x_history_p = acf_x_history.begin(); break; default: break; } } else { colvar *cfcv = (acf_colvar_name.size() ? cvm::colvar_by_name(acf_colvar_name) : this); switch (acf_type) { case acf_vel: if (is_enabled(f_cv_fdiff_velocity)) { // calc() should do this already, but this only happens in a // simulation; better do it again in case a trajectory is // being read v_reported = v_fdiff = fdiff_velocity(x_old, cfcv->value()); } calc_vel_acf((*acf_v_history_p), cfcv->velocity()); // store this value in the history history_add_value(acf_length+acf_offset, *acf_v_history_p, cfcv->velocity()); // if stride is larger than one, cycle among different histories history_incr(acf_v_history, acf_v_history_p); break; case acf_coor: calc_coor_acf((*acf_x_history_p), cfcv->value()); history_add_value(acf_length+acf_offset, *acf_x_history_p, cfcv->value()); history_incr(acf_x_history, acf_x_history_p); break; case acf_p2coor: calc_p2coor_acf((*acf_x_history_p), cfcv->value()); history_add_value(acf_length+acf_offset, *acf_x_history_p, cfcv->value()); history_incr(acf_x_history, acf_x_history_p); break; default: break; } } if (is_enabled(f_cv_fdiff_velocity)) { // set it for the next step x_old = x; } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } int colvar::calc_vel_acf(std::list &v_list, colvarvalue const &v) { // loop over stored velocities and add to the ACF, but only the // length is sufficient to hold an entire row of ACF values if (v_list.size() >= acf_length+acf_offset) { std::list::iterator vs_i = v_list.begin(); std::vector::iterator acf_i = acf.begin(); for (size_t i = 0; i < acf_offset; i++) ++vs_i; // current vel with itself *(acf_i) += v.norm2(); ++acf_i; // inner products of previous velocities with current (acf_i and // vs_i are updated) colvarvalue::inner_opt(v, vs_i, v_list.end(), acf_i); acf_nframes++; } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } void colvar::calc_coor_acf(std::list &x_list, colvarvalue const &x) { // same as above but for coordinates if (x_list.size() >= acf_length+acf_offset) { std::list::iterator xs_i = x_list.begin(); std::vector::iterator acf_i = acf.begin(); for (size_t i = 0; i < acf_offset; i++) ++xs_i; *(acf_i++) += x.norm2(); colvarvalue::inner_opt(x, xs_i, x_list.end(), acf_i); acf_nframes++; } } void colvar::calc_p2coor_acf(std::list &x_list, colvarvalue const &x) { // same as above but with second order Legendre polynomial instead // of just the scalar product if (x_list.size() >= acf_length+acf_offset) { std::list::iterator xs_i = x_list.begin(); std::vector::iterator acf_i = acf.begin(); for (size_t i = 0; i < acf_offset; i++) ++xs_i; // value of P2(0) = 1 *(acf_i++) += 1.0; colvarvalue::p2leg_opt(x, xs_i, x_list.end(), acf_i); acf_nframes++; } } void colvar::write_acf(std::ostream &os) { if (!acf_nframes) cvm::log("Warning: ACF was not calculated (insufficient frames).\n"); os.setf(std::ios::scientific, std::ios::floatfield); os << "# Autocorrelation function for collective variable \"" << this->name << "\"\n"; // one frame is used for normalization, the statistical sample is // hence decreased os << "# nframes = " << (acf_normalize ? acf_nframes - 1 : acf_nframes) << "\n"; cvm::real const acf_norm = acf.front() / cvm::real(acf_nframes); std::vector::iterator acf_i; size_t it = acf_offset; for (acf_i = acf.begin(); acf_i != acf.end(); ++acf_i) { os << std::setw(cvm::it_width) << acf_stride * (it++) << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << ( acf_normalize ? (*acf_i)/(acf_norm * cvm::real(acf_nframes)) : (*acf_i)/(cvm::real(acf_nframes)) ) << "\n"; } } void colvar::calc_runave() { if (x_history.empty()) { runave.type(value().type()); runave.reset(); // first-step operations if (cvm::debug()) cvm::log("Colvar \""+this->name+ "\": initializing running average calculation.\n"); acf_nframes = 0; x_history.push_back(std::list()); x_history_p = x_history.begin(); } else { if ( (cvm::step_relative() % runave_stride) == 0) { if ((*x_history_p).size() >= runave_length-1) { runave = x; std::list::iterator xs_i; for (xs_i = (*x_history_p).begin(); xs_i != (*x_history_p).end(); ++xs_i) { runave += (*xs_i); } runave *= 1.0 / cvm::real(runave_length); runave.apply_constraints(); runave_variance = 0.0; runave_variance += this->dist2(x, runave); for (xs_i = (*x_history_p).begin(); xs_i != (*x_history_p).end(); ++xs_i) { runave_variance += this->dist2(x, (*xs_i)); } runave_variance *= 1.0 / cvm::real(runave_length-1); - 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::sqrt(runave_variance) << "\n"; + *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::sqrt(runave_variance) << "\n"; } history_add_value(runave_length, *x_history_p, x); } } } // Static members std::vector colvar::cv_features; diff --git a/lib/colvars/colvar.h b/lib/colvars/colvar.h index 0cbda450b..6113e1678 100644 --- a/lib/colvars/colvar.h +++ b/lib/colvars/colvar.h @@ -1,624 +1,641 @@ // -*- 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 COLVAR_H #define COLVAR_H #include #include #include #include "colvarmodule.h" #include "colvarvalue.h" #include "colvarparse.h" #include "colvardeps.h" +#ifdef LEPTON +#include "Lepton.h" // for runtime custom expressions +#endif /// \brief A collective variable (main class); to be defined, it needs /// at least one object of a derived class of colvar::cvc; it /// calculates and returns a \link colvarvalue \endlink object /// /// This class parses the configuration, defines the behaviour and /// stores the value (\link colvar::x \endlink) and all related data /// of a collective variable. How the value is calculated is defined /// in \link colvar::cvc \endlink and its derived classes. The /// \link colvar \endlink object contains pointers to multiple \link /// colvar::cvc \endlink derived objects, which can be combined /// together into one collective variable. This makes possible to /// implement new collective variables at runtime based on the /// existing ones. Currently, this possibility is limited to a /// polynomial, using the coefficients cvc::sup_coeff and the /// exponents cvc::sup_np. In case of non-scalar variables, /// only exponents equal to 1 are accepted. /// /// Please note that most of its members are \link colvarvalue /// \endlink objects, i.e. they can handle different data types /// together, and must all be set to the same type of colvar::value() /// before using them together in assignments or other operations; this is usually done /// automatically in the constructor. If you add a new member of /// \link colvarvalue \endlink type, you should also add its /// initialization line in the \link colvar \endlink constructor. class colvar : public colvarparse, public colvardeps { public: /// Name std::string name; /// \brief Current value (previously set by calc() or by read_traj()) colvarvalue const & value() const; /// \brief Current actual value (not extended DOF) colvarvalue const & actual_value() const; /// \brief Current velocity (previously set by calc() or by read_traj()) colvarvalue const & velocity() const; /// \brief Current total force (previously obtained from calc() or /// read_traj()). Note: this is calculated using the atomic forces /// from the last simulation step. /// /// Total atom forces are read from the MD program, the total force /// acting on the collective variable is calculated summing those /// from all colvar components, the bias and walls forces are /// subtracted. colvarvalue const & total_force() const; /// \brief Typical fluctuation amplitude for this collective /// variable (e.g. local width of a free energy basin) /// /// In metadynamics calculations, \link colvarbias_meta \endlink, /// this value is used to calculate the width of a gaussian. In ABF /// calculations, \link colvarbias_abf \endlink, it is used to /// calculate the grid spacing in the direction of this collective /// variable. cvm::real width; /// \brief Implementation of the feature list for colvar static std::vector cv_features; /// \brief Implementation of the feature list accessor for colvar std::vector &features() { return cv_features; } - int refresh_deps(); + /// 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); /// List of biases that depend on this colvar std::vector biases; protected: /* extended: H = H_{0} + \sum_{i} 1/2*\lambda*(S_i(x(t))-s_i(t))^2 \\ + \sum_{i} 1/2*m_i*(ds_i(t)/dt)^2 \\ + \sum_{t' 0.0) ? (1.0/dt) : 1.0 ) * 0.5 * dist2_lgrad(xnew, xold) ); } /// Cached reported velocity colvarvalue v_reported; // Options for extended_lagrangian /// Restraint center colvarvalue xr; /// Velocity of the restraint center colvarvalue vr; /// Mass of the restraint center cvm::real ext_mass; /// Restraint force constant cvm::real ext_force_k; /// Friction coefficient for Langevin extended dynamics cvm::real ext_gamma; /// Amplitude of Gaussian white noise for Langevin extended dynamics cvm::real ext_sigma; /// \brief Harmonic restraint force colvarvalue fr; /// \brief Jacobian force, when Jacobian_force is enabled colvarvalue fj; /// Cached reported total force colvarvalue ft_reported; public: /// \brief Bias force; reset_bias_force() should be called before /// the biases are updated colvarvalue fb; /// \brief Bias force to the actual value (only useful with extended Lagrangian) colvarvalue fb_actual; /// \brief Total \em applied force; fr (if extended_lagrangian /// is defined), fb (if biases are applied) and the walls' forces /// (if defined) contribute to it colvarvalue f; /// Applied force at the previous step (to be subtracted from total force if needed) colvarvalue f_old; /// \brief Total force, as derived from the atomic trajectory; /// should equal the system force plus \link f \endlink colvarvalue ft; /// Period, if this variable is periodic cvm::real period; /// \brief Expand the boundaries of multiples of width, to keep the /// value always within range bool expand_boundaries; /// \brief Location of the lower boundary colvarvalue lower_boundary; /// \brief Location of the lower wall colvarvalue lower_wall; /// \brief Force constant for the lower boundary potential (|x-xb|^2) cvm::real lower_wall_k; /// \brief Whether this colvar has a hard lower boundary bool hard_lower_boundary; /// \brief Location of the upper boundary colvarvalue upper_boundary; /// \brief Location of the upper wall colvarvalue upper_wall; /// \brief Force constant for the upper boundary potential (|x-xb|^2) cvm::real upper_wall_k; /// \brief Whether this colvar has a hard upper boundary bool hard_upper_boundary; /// \brief Is the interval defined by the two boundaries periodic? bool periodic_boundaries() const; /// \brief Is the interval defined by the two boundaries periodic? bool periodic_boundaries(colvarvalue const &lb, colvarvalue const &ub) const; /// Constructor colvar(); /// Main init function int init(std::string const &conf); /// Parse the CVC configuration and allocate their data int init_components(std::string const &conf); + /// Parse parameters for custom function with Lepton + int init_custom_function(std::string const &conf); + /// Init defaults for grid options int init_grid_parameters(std::string const &conf); /// Init extended Lagrangian parameters int init_extended_Lagrangian(std::string const &conf); /// Init output flags int init_output_flags(std::string const &conf); 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); public: /// Get ready for a run and re-initialize internal data if needed void setup(); /// Destructor ~colvar(); /// \brief Calculate the colvar's value and related quantities int calc(); /// \brief Calculate a subset of the colvar components (CVCs) currently active /// (default: all active CVCs) /// Note: both arguments refer to the sect of *active* CVCs, not all CVCs int calc_cvcs(int first = 0, size_t num_cvcs = 0); /// Ensure that the selected range of CVCs is consistent int check_cvc_range(int first_cvc, size_t num_cvcs); /// \brief Calculate the values of the given subset of CVCs int calc_cvc_values(int first, size_t num_cvcs); /// \brief Same as \link colvar::calc_cvc_values \endlink but for gradients int calc_cvc_gradients(int first, size_t num_cvcs); /// \brief Same as \link colvar::calc_cvc_values \endlink but for total forces int calc_cvc_total_force(int first, size_t num_cvcs); /// \brief Same as \link colvar::calc_cvc_values \endlink but for Jacobian derivatives/forces int calc_cvc_Jacobians(int first, size_t num_cvcs); /// \brief Collect quantities from CVCs and update aggregated data for the colvar int collect_cvc_data(); /// \brief Collect the values of the CVCs int collect_cvc_values(); /// \brief Same as \link colvar::collect_cvc_values \endlink but for gradients int collect_cvc_gradients(); /// \brief Same as \link colvar::collect_cvc_values \endlink but for total forces int collect_cvc_total_forces(); /// \brief Same as \link colvar::collect_cvc_values \endlink but for Jacobian derivatives/forces int collect_cvc_Jacobians(); /// \brief Calculate the quantities associated to the colvar (but not to the CVCs) int calc_colvar_properties(); /// Get the current applied force inline colvarvalue const applied_force() const { if (is_enabled(f_cv_extended_Lagrangian)) { return fr; } return f; } /// Set the total biasing force to zero void reset_bias_force(); /// Add to the total force from biases void add_bias_force(colvarvalue const &force); /// Apply a force to the actual value (only meaningful with extended Lagrangian) void add_bias_force_actual_value(colvarvalue const &force); /// \brief Collect all forces on this colvar, integrate internal /// equations of motion of internal degrees of freedom; see also /// colvar::communicate_forces() /// return colvar energy if extended Lagrandian active cvm::real update_forces_energy(); /// \brief Communicate forces (previously calculated in /// colvar::update()) to the external degrees of freedom void communicate_forces(); /// \brief Enables and disables individual CVCs based on flags int set_cvc_flags(std::vector const &flags); /// \brief Updates the flags in the CVC objects, and their number int update_cvc_flags(); protected: /// \brief Number of CVC objects with an active flag size_t n_active_cvcs; /// Sum of square coefficients for active cvcs cvm::real active_cvc_square_norm; - /// Time step multiplier (for coarse-time-step colvars) - /// Colvar will only be calculated at those times; biases may ignore the information and - /// always update their own forces (which is typically inexpensive) especially if - /// they rely on other colvars. In this case, the colvar will accumulate forces applied between - /// colvar updates. Alternately they may use it to calculate "impulse" biasing - /// forces at longer intervals. Impulse forces must be multiplied by the timestep factor. - int time_step_factor; - - /// Biasing force collected between updates, to be applied at next update for coarse-time-step colvars - colvarvalue f_accumulated; + /// \brief Absolute timestep number when this colvar was last updated + int prev_timestep; public: /// \brief Return the number of CVC objects with an active flag (as set by update_cvc_flags) inline size_t num_active_cvcs() const { return n_active_cvcs; } - /// \brief returns time_step_factor - inline int get_time_step_factor() const {return time_step_factor;} - /// \brief Use the internal metrics (as from \link cvc /// \endlink objects) to calculate square distances and gradients /// /// Handles correctly symmetries and periodic boundary conditions cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const; /// \brief Use the internal metrics (as from \link cvc /// \endlink objects) to calculate square distances and gradients /// /// Handles correctly symmetries and periodic boundary conditions colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const; /// \brief Use the internal metrics (as from \link cvc /// \endlink objects) to calculate square distances and gradients /// /// Handles correctly symmetries and periodic boundary conditions colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const; /// \brief Use the internal metrics (as from \link cvc /// \endlink objects) to wrap a value into a standard interval /// /// Handles correctly symmetries and periodic boundary conditions void wrap(colvarvalue &x) const; /// Read the analysis tasks int parse_analysis(std::string const &conf); /// Perform analysis tasks void analyze(); /// Read the value from a collective variable trajectory file std::istream & read_traj(std::istream &is); /// Output formatted values to the trajectory file std::ostream & write_traj(std::ostream &os); /// 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 std::istream & read_restart(std::istream &is); /// Write the collective variable to a restart file std::ostream & write_restart(std::ostream &os); /// Write output files (if defined, e.g. in analysis mode) int write_output_files(); protected: /// Previous value (to calculate velocities during analysis) colvarvalue x_old; /// Value read from the most recent state file (if any) colvarvalue x_restart; /// True if a state file was just read bool after_restart; /// Time series of values and velocities used in correlation /// functions std::list< std::list > acf_x_history, acf_v_history; /// Time series of values and velocities used in correlation /// functions (pointers)x std::list< std::list >::iterator acf_x_history_p, acf_v_history_p; /// Time series of values and velocities used in running averages std::list< std::list > x_history; /// Time series of values and velocities used in correlation /// functions (pointers)x std::list< std::list >::iterator x_history_p; /// \brief Collective variable with which the correlation is /// calculated (default: itself) std::string acf_colvar_name; /// Length of autocorrelation function (ACF) size_t acf_length; /// After how many steps the ACF starts size_t acf_offset; /// How many timesteps separate two ACF values size_t acf_stride; /// Number of frames for each ACF point size_t acf_nframes; /// Normalize the ACF to a maximum value of 1? bool acf_normalize; /// ACF values std::vector acf; /// Name of the file to write the ACF std::string acf_outfile; /// Type of autocorrelation function (ACF) enum acf_type_e { /// Unset type acf_notset, /// Velocity ACF, scalar product between v(0) and v(t) acf_vel, /// Coordinate ACF, scalar product between x(0) and x(t) acf_coor, /// \brief Coordinate ACF, second order Legendre polynomial /// between x(0) and x(t) (does not work with scalar numbers) acf_p2coor }; /// Type of autocorrelation function (ACF) acf_type_e acf_type; /// \brief Velocity ACF, scalar product between v(0) and v(t) int calc_vel_acf(std::list &v_history, colvarvalue const &v); /// \brief Coordinate ACF, scalar product between x(0) and x(t) /// (does not work with scalar numbers) void calc_coor_acf(std::list &x_history, colvarvalue const &x); /// \brief Coordinate ACF, second order Legendre polynomial between /// x(0) and x(t) (does not work with scalar numbers) void calc_p2coor_acf(std::list &x_history, colvarvalue const &x); /// Calculate the auto-correlation function (ACF) int calc_acf(); /// Save the ACF to a file void write_acf(std::ostream &os); /// Length of running average series size_t runave_length; /// Timesteps to skip between two values in the running average series size_t runave_stride; /// Name of the file to write the running average - cvm::ofstream runave_os; + std::string runave_outfile; + /// File to write the running average + std::ostream *runave_os; /// Current value of the running average colvarvalue runave; /// Current value of the square deviation from the running average cvm::real runave_variance; /// Calculate the running average and its standard deviation void calc_runave(); /// If extended Lagrangian active: colvar energies (kinetic and harmonic potential) cvm::real kinetic_energy; cvm::real potential_energy; public: // collective variable component base class class cvc; // currently available collective variable components // scalar colvar components class distance; class distance_z; class distance_xy; + class polar_theta; + class polar_phi; class distance_inv; class distance_pairs; class angle; class dipole_angle; class dihedral; class coordnum; class selfcoordnum; class groupcoordnum; class h_bond; class rmsd; class orientation_angle; class orientation_proj; class tilt; class spin_angle; class gyration; class inertia; class inertia_z; class eigenvector; class alpha_dihedrals; class alpha_angles; class dihedPC; // non-scalar components class distance_vec; class distance_dir; class cartesian; class orientation; protected: /// \brief Array of \link cvc \endlink objects std::vector cvcs; /// \brief Flags to enable or disable cvcs at next colvar evaluation std::vector cvc_flags; /// \brief Initialize the sorted list of atom IDs for atoms involved /// in all cvcs (called when enabling f_cv_collect_gradients) void build_atom_list(void); private: /// Name of scripted function to be used std::string scripted_function; /// Current cvc values in the order requested by script /// when using scriptedFunction std::vector sorted_cvc_values; +#ifdef LEPTON + /// Vector of evaluators for custom functions using Lepton + std::vector value_evaluators; + + /// Vector of evaluators for gradients of custom functions + std::vector gradient_evaluators; + + /// Vector of references to cvc values to be passed to Lepton evaluators + 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; +#endif + public: /// \brief Sorted array of (zero-based) IDs for all atoms involved std::vector atom_ids; /// \brief Array of atomic gradients collected from all cvcs /// with appropriate components, rotations etc. /// For scalar variables only! std::vector atomic_gradients; inline size_t n_components() const { return cvcs.size(); } }; inline colvarvalue const & colvar::value() const { return x_reported; } inline colvarvalue const & colvar::actual_value() const { return x; } inline colvarvalue const & colvar::velocity() const { return v_reported; } inline colvarvalue const & colvar::total_force() const { return ft_reported; } inline void colvar::add_bias_force(colvarvalue const &force) { if (cvm::debug()) { cvm::log("Adding biasing force "+cvm::to_str(force)+" to colvar \""+name+"\".\n"); } fb += force; } inline void colvar::add_bias_force_actual_value(colvarvalue const &force) { if (cvm::debug()) { cvm::log("Adding biasing force "+cvm::to_str(force)+" to colvar \""+name+"\".\n"); } fb_actual += force; } inline void colvar::reset_bias_force() { fb.type(value()); fb.reset(); fb_actual.type(value()); fb_actual.reset(); } #endif diff --git a/lib/colvars/colvaratoms.cpp b/lib/colvars/colvaratoms.cpp index 32cfadf3b..9b4a922e3 100644 --- a/lib/colvars/colvaratoms.cpp +++ b/lib/colvars/colvaratoms.cpp @@ -1,1244 +1,1290 @@ // -*- 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 "colvarparse.h" #include "colvaratoms.h" cvm::atom::atom() { index = -1; id = -1; reset_data(); } cvm::atom::atom(int atom_number) { colvarproxy *p = cvm::proxy; index = p->init_atom(atom_number); if (cvm::debug()) { cvm::log("The index of this atom in the colvarproxy arrays is "+ cvm::to_str(index)+".\n"); } id = p->get_atom_id(index); update_mass(); reset_data(); } cvm::atom::atom(cvm::residue_id const &residue, std::string const &atom_name, std::string const &segment_id) { colvarproxy *p = cvm::proxy; index = p->init_atom(residue, atom_name, segment_id); if (cvm::debug()) { cvm::log("The index of this atom in the colvarproxy_namd arrays is "+ cvm::to_str(index)+".\n"); } id = p->get_atom_id(index); update_mass(); reset_data(); } cvm::atom::atom(atom const &a) : index(a.index) { id = (cvm::proxy)->get_atom_id(index); update_mass(); reset_data(); } cvm::atom::~atom() { if (index >= 0) { (cvm::proxy)->clear_atom(index); } } -// TODO change this arrangement -// Note: "conf" is the configuration of the cvc who is using this atom group; -// "key" is the name of the atom group (e.g. "atoms", "group1", "group2", ...) -cvm::atom_group::atom_group(std::string const &conf, - char const *key_in) +cvm::atom_group::atom_group() { - key = key_in; - cvm::log("Defining atom group \"" + key + "\".\n"); init(); - // real work is done by parse - parse(conf); - setup(); } -cvm::atom_group::atom_group(std::vector const &atoms_in) +cvm::atom_group::atom_group(char const *key_in) { + key = key_in; init(); - atoms = atoms_in; - setup(); } -cvm::atom_group::atom_group() +cvm::atom_group::atom_group(std::vector const &atoms_in) { init(); + atoms = atoms_in; + setup(); } cvm::atom_group::~atom_group() { if (is_enabled(f_ag_scalable) && !b_dummy) { (cvm::proxy)->clear_atom_group(index); index = -1; } if (fitting_group) { delete fitting_group; fitting_group = NULL; } } int cvm::atom_group::add_atom(cvm::atom const &a) { if (a.id < 0) { return COLVARS_ERROR; } for (size_t i = 0; i < atoms_ids.size(); i++) { if (atoms_ids[i] == a.id) { if (cvm::debug()) cvm::log("Discarding doubly counted atom with number "+ cvm::to_str(a.id+1)+".\n"); return COLVARS_OK; } } // for consistency with add_atom_id(), we update the list as well atoms_ids.push_back(a.id); atoms.push_back(a); total_mass += a.mass; total_charge += a.charge; return COLVARS_OK; } int cvm::atom_group::add_atom_id(int aid) { if (aid < 0) { return COLVARS_ERROR; } for (size_t i = 0; i < atoms_ids.size(); i++) { if (atoms_ids[i] == aid) { if (cvm::debug()) cvm::log("Discarding doubly counted atom with number "+ cvm::to_str(aid+1)+".\n"); return COLVARS_OK; } } atoms_ids.push_back(aid); return COLVARS_OK; } int cvm::atom_group::remove_atom(cvm::atom_iter ai) { if (is_enabled(f_ag_scalable)) { cvm::error("Error: cannot remove atoms from a scalable group.\n", INPUT_ERROR); return COLVARS_ERROR; } if (!this->size()) { cvm::error("Error: trying to remove an atom from an empty group.\n", INPUT_ERROR); return COLVARS_ERROR; } else { total_mass -= ai->mass; total_charge -= ai->charge; atoms_ids.erase(atoms_ids.begin() + (ai - atoms.begin())); atoms.erase(ai); } return COLVARS_OK; } int cvm::atom_group::init() { if (!key.size()) key = "unnamed"; description = "atom group " + key; - // These will be overwritten by parse(), if initializing from a config string + // These may be overwritten by parse(), if a name is provided atoms.clear(); // TODO: check with proxy whether atom forces etc are available init_ag_requires(); index = -1; b_dummy = false; b_center = false; b_rotate = false; b_user_defined_fit = false; - b_fit_gradients = false; fitting_group = NULL; noforce = false; total_mass = 0.0; total_charge = 0.0; cog.reset(); com.reset(); return COLVARS_OK; } int cvm::atom_group::setup() { for (cvm::atom_iter ai = atoms.begin(); ai != atoms.end(); ai++) { ai->update_mass(); ai->update_charge(); } update_total_mass(); update_total_charge(); return COLVARS_OK; } void cvm::atom_group::update_total_mass() { if (b_dummy) { total_mass = 1.0; return; } if (is_enabled(f_ag_scalable)) { total_mass = (cvm::proxy)->get_atom_group_mass(index); } else { total_mass = 0.0; for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { total_mass += ai->mass; } } } void cvm::atom_group::reset_mass(std::string &name, int i, int j) { update_total_mass(); cvm::log("Re-initialized atom group "+name+":"+cvm::to_str(i)+"/"+ cvm::to_str(j)+". "+ cvm::to_str(atoms_ids.size())+ " atoms: total mass = "+cvm::to_str(total_mass)+".\n"); } void cvm::atom_group::update_total_charge() { if (b_dummy) { total_charge = 0.0; return; } if (is_enabled(f_ag_scalable)) { total_charge = (cvm::proxy)->get_atom_group_charge(index); } else { total_charge = 0.0; for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { total_charge += ai->charge; } } } -int cvm::atom_group::parse(std::string const &conf) +int cvm::atom_group::parse(std::string const &group_conf) { - std::string group_conf; - - // TODO move this to the cvc class constructor/init - - // save_delimiters is set to false for this call, because "conf" is - // not the config string of this group, but of its parent object - // (which has already taken care of the delimiters) - save_delimiters = false; - key_lookup(conf, key.c_str(), group_conf, dummy_pos); - // restoring the normal value, because we do want keywords checked - // inside "group_conf" - save_delimiters = true; - - if (group_conf.size() == 0) { - cvm::error("Error: atom group \""+key+ - "\" is set, but has no definition.\n", - INPUT_ERROR); - return COLVARS_ERROR; - } - - cvm::increase_depth(); - cvm::log("Initializing atom group \""+key+"\".\n"); - description = "atom group " + key; - // whether or not to include messages in the log // colvarparse::Parse_Mode mode = parse_silent; // { // bool b_verbose; // get_keyval (group_conf, "verboseOutput", b_verbose, false, parse_silent); // if (b_verbose) mode = parse_normal; // } // colvarparse::Parse_Mode mode = parse_normal; int parse_error = COLVARS_OK; + // Optional group name will let other groups reuse atom definition + if (get_keyval(group_conf, "name", name)) { + if ((cvm::atom_group_by_name(this->name) != NULL) && + (cvm::atom_group_by_name(this->name) != this)) { + cvm::error("Error: this atom group cannot have the same name, \""+this->name+ + "\", as another atom group.\n", + INPUT_ERROR); + return INPUT_ERROR; + } + cvm::main()->register_named_atom_group(this); + description = "atom group " + name; + } + + // We need to know about fitting to decide whether the group is scalable + // and we need to know about scalability before adding atoms + bool b_defined_center = get_keyval(group_conf, "centerReference", b_center, false); + bool b_defined_rotate = get_keyval(group_conf, "rotateReference", b_rotate, false); + // is the user setting explicit options? + b_user_defined_fit = b_defined_center || b_defined_rotate; + + if (is_available(f_ag_scalable_com) && !b_rotate && !b_center) { + enable(f_ag_scalable_com); + enable(f_ag_scalable); + } + + { + std::string atoms_of = ""; + if (get_keyval(group_conf, "atomsOfGroup", atoms_of)) { + atom_group * ag = atom_group_by_name(atoms_of); + if (ag == NULL) { + cvm::error("Error: cannot find atom group with name " + atoms_of + ".\n"); + return COLVARS_ERROR; + } + parse_error |= add_atoms_of_group(ag); + } + } + +// if (get_keyval(group_conf, "copyOfGroup", source)) { +// // Goal: Initialize this as a full copy +// // for this we'll need an atom_group copy constructor +// return COLVARS_OK; +// } + { std::string numbers_conf = ""; size_t pos = 0; - while (key_lookup(group_conf, "atomNumbers", numbers_conf, pos)) { + while (key_lookup(group_conf, "atomNumbers", &numbers_conf, &pos)) { parse_error |= add_atom_numbers(numbers_conf); numbers_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); } } { std::string range_conf = ""; size_t pos = 0; while (key_lookup(group_conf, "atomNumbersRange", - range_conf, pos)) { + &range_conf, &pos)) { parse_error |= add_atom_numbers_range(range_conf); range_conf = ""; } } { std::vector psf_segids; get_keyval(group_conf, "psfSegID", psf_segids, std::vector()); std::vector::iterator psii; for (psii = psf_segids.begin(); psii < psf_segids.end(); ++psii) { if ( (psii->size() == 0) || (psii->size() > 4) ) { cvm::error("Error: invalid PSF segment identifier provided, \""+ (*psii)+"\".\n", INPUT_ERROR); } } std::string range_conf = ""; size_t pos = 0; size_t range_count = 0; psii = psf_segids.begin(); while (key_lookup(group_conf, "atomNameResidueRange", - range_conf, pos)) { + &range_conf, &pos)) { range_count++; if (psf_segids.size() && (range_count > psf_segids.size())) { cvm::error("Error: more instances of \"atomNameResidueRange\" than " "values of \"psfSegID\".\n", INPUT_ERROR); } else { parse_error |= add_atom_name_residue_range(psf_segids.size() ? *psii : std::string(""), range_conf); if (psf_segids.size()) psii++; } range_conf = ""; } } { // read the atoms from a file std::string atoms_file_name; if (get_keyval(group_conf, "atomsFile", atoms_file_name, std::string(""))) { std::string atoms_col; if (!get_keyval(group_conf, "atomsCol", atoms_col, std::string(""))) { cvm::error("Error: parameter atomsCol is required if atomsFile is set.\n", INPUT_ERROR); } double atoms_col_value; bool const atoms_col_value_defined = get_keyval(group_conf, "atomsColValue", atoms_col_value, 0.0); if (atoms_col_value_defined && (!atoms_col_value)) { cvm::error("Error: atomsColValue, if provided, must be non-zero.\n", INPUT_ERROR); } // NOTE: calls to add_atom() and/or add_atom_id() are in the proxy-implemented function cvm::load_atoms(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()); // checks of doubly-counted atoms have been handled by add_atom() already if (get_keyval(group_conf, "dummyAtom", dummy_atom_pos, cvm::atom_pos())) { b_dummy = true; // note: atoms_ids.size() is used here in lieu of atoms.size(), // which can be empty for scalable groups if (atoms_ids.size()) { cvm::error("Error: cannot set up group \""+ key+"\" as a dummy atom " "and provide it with atom definitions.\n", INPUT_ERROR); } } else { b_dummy = false; if (!(atoms_ids.size())) { cvm::error("Error: no atoms defined for atom group \""+ key+"\".\n", INPUT_ERROR); } // whether these atoms will ever receive forces or not bool enable_forces = true; // disableForces is deprecated if (get_keyval(group_conf, "enableForces", enable_forces, true)) { noforce = !enable_forces; } else { get_keyval(group_conf, "disableForces", noforce, false, colvarparse::parse_silent); } } - // We need to know the fitting options to decide whether the group is scalable + // Now that atoms are defined we can parse the detailed fitting options parse_error |= parse_fitting_options(group_conf); - if (is_available(f_ag_scalable_com) && !b_rotate && !b_center) { - enable(f_ag_scalable_com); - enable(f_ag_scalable); - } - if (is_enabled(f_ag_scalable) && !b_dummy) { cvm::log("Enabling scalable calculation for group \""+this->key+"\".\n"); index = (cvm::proxy)->init_atom_group(atoms_ids); } bool b_print_atom_ids = false; get_keyval(group_conf, "printAtomIDs", b_print_atom_ids, false, colvarparse::parse_silent); - // TODO move this to colvarparse object - check_keywords(group_conf, key.c_str()); - if (cvm::get_error()) { - cvm::error("Error setting up atom group \""+key+"\"."); - return COLVARS_ERROR; - } - // Calculate all required properties (such as total mass) setup(); if (cvm::debug()) cvm::log("Done initializing atom group \""+key+"\".\n"); cvm::log("Atom group \""+key+"\" defined, "+ cvm::to_str(atoms_ids.size())+" atoms initialized: total mass = "+ - cvm::to_str(total_mass)+", total charge = "+ + cvm::to_str(total_mass)+", total charge = "+ cvm::to_str(total_charge)+".\n"); if (b_print_atom_ids) { cvm::log("Internal definition of the atom group:\n"); cvm::log(print_atom_ids()); } - cvm::decrease_depth(); - return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } +int cvm::atom_group::add_atoms_of_group(atom_group const * ag) +{ + std::vector const &source_ids = ag->atoms_ids; + + if (source_ids.size()) { + atoms_ids.reserve(atoms_ids.size()+source_ids.size()); + + if (is_enabled(f_ag_scalable)) { + for (size_t i = 0; i < source_ids.size(); i++) { + add_atom_id(source_ids[i]); + } + } else { + atoms.reserve(atoms.size()+source_ids.size()); + for (size_t i = 0; i < source_ids.size(); i++) { + // We could use the atom copy constructor, but only if the source + // group is not scalable - whereas this works in both cases + // atom constructor expects 1-based atom number + add_atom(cvm::atom(source_ids[i] + 1)); + } + } + + if (cvm::get_error()) return COLVARS_ERROR; + } else { + cvm::error("Error: source atom group contains no atoms\".\n", INPUT_ERROR); + return COLVARS_ERROR; + } + + return COLVARS_OK; +} + + int cvm::atom_group::add_atom_numbers(std::string const &numbers_conf) { std::vector atom_indexes; if (numbers_conf.size()) { std::istringstream is(numbers_conf); int ia; while (is >> ia) { atom_indexes.push_back(ia); } } if (atom_indexes.size()) { atoms_ids.reserve(atoms_ids.size()+atom_indexes.size()); if (is_enabled(f_ag_scalable)) { for (size_t i = 0; i < atom_indexes.size(); i++) { add_atom_id((cvm::proxy)->check_atom_id(atom_indexes[i])); } } else { // if we are handling the group on rank 0, better allocate the vector in one shot atoms.reserve(atoms.size()+atom_indexes.size()); for (size_t i = 0; i < atom_indexes.size(); i++) { add_atom(cvm::atom(atom_indexes[i])); } } if (cvm::get_error()) return COLVARS_ERROR; } else { cvm::error("Error: no numbers provided for \"" "atomNumbers\".\n", INPUT_ERROR); return COLVARS_ERROR; } return COLVARS_OK; } int cvm::atom_group::add_index_group(std::string const &index_group_name) { colvarmodule *cv = cvm::main(); std::list::iterator names_i = cv->index_group_names.begin(); std::list >::iterator index_groups_i = cv->index_groups.begin(); for ( ; names_i != cv->index_group_names.end() ; ++names_i, ++index_groups_i) { if (*names_i == index_group_name) break; } if (names_i == cv->index_group_names.end()) { cvm::error("Error: could not find index group "+ index_group_name+" among those provided by the index file.\n", INPUT_ERROR); return COLVARS_ERROR; } atoms_ids.reserve(atoms_ids.size()+index_groups_i->size()); if (is_enabled(f_ag_scalable)) { for (size_t i = 0; i < index_groups_i->size(); i++) { add_atom_id((cvm::proxy)->check_atom_id((*index_groups_i)[i])); } } else { atoms.reserve(atoms.size()+index_groups_i->size()); for (size_t i = 0; i < index_groups_i->size(); i++) { add_atom(cvm::atom((*index_groups_i)[i])); } } if (cvm::get_error()) return COLVARS_ERROR; return COLVARS_OK; } int cvm::atom_group::add_atom_numbers_range(std::string const &range_conf) { if (range_conf.size()) { std::istringstream is(range_conf); int initial, final; char dash; if ( (is >> initial) && (initial > 0) && (is >> dash) && (dash == '-') && (is >> final) && (final > 0) ) { atoms_ids.reserve(atoms_ids.size() + (final - initial + 1)); if (is_enabled(f_ag_scalable)) { for (int anum = initial; anum <= final; anum++) { add_atom_id((cvm::proxy)->check_atom_id(anum)); } } else { atoms.reserve(atoms.size() + (final - initial + 1)); for (int anum = initial; anum <= final; anum++) { add_atom(cvm::atom(anum)); } } } if (cvm::get_error()) return COLVARS_ERROR; } else { cvm::error("Error: no valid definition for \"atomNumbersRange\", \""+ range_conf+"\".\n", INPUT_ERROR); return COLVARS_ERROR; } return COLVARS_OK; } int cvm::atom_group::add_atom_name_residue_range(std::string const &psf_segid, std::string const &range_conf) { if (range_conf.size()) { std::istringstream is(range_conf); std::string atom_name; int initial, final; char dash; if ( (is >> atom_name) && (atom_name.size()) && (is >> initial) && (initial > 0) && (is >> dash) && (dash == '-') && (is >> final) && (final > 0) ) { atoms_ids.reserve(atoms_ids.size() + (final - initial + 1)); if (is_enabled(f_ag_scalable)) { for (int resid = initial; resid <= final; resid++) { add_atom_id((cvm::proxy)->check_atom_id(resid, atom_name, psf_segid)); } } else { atoms.reserve(atoms.size() + (final - initial + 1)); for (int resid = initial; resid <= final; resid++) { add_atom(cvm::atom(resid, atom_name, psf_segid)); } } if (cvm::get_error()) return COLVARS_ERROR; } else { cvm::error("Error: cannot parse definition for \"" "atomNameResidueRange\", \""+ range_conf+"\".\n"); return COLVARS_ERROR; } } else { cvm::error("Error: atomNameResidueRange with empty definition.\n"); return COLVARS_ERROR; } return COLVARS_OK; } std::string const cvm::atom_group::print_atom_ids() const { size_t line_count = 0; std::ostringstream os(""); for (size_t i = 0; i < atoms_ids.size(); i++) { os << " " << std::setw(9) << atoms_ids[i]; if (++line_count == 7) { os << "\n"; line_count = 0; } } return os.str(); } int cvm::atom_group::parse_fitting_options(std::string const &group_conf) { - bool b_defined_center = get_keyval(group_conf, "centerReference", b_center, false); - bool b_defined_rotate = get_keyval(group_conf, "rotateReference", b_rotate, false); - // is the user setting explicit options? - b_user_defined_fit = b_defined_center || b_defined_rotate; - - get_keyval(group_conf, "enableFitGradients", b_fit_gradients, true); - if (b_center || b_rotate) { if (b_dummy) cvm::error("Error: centerReference or rotateReference " "cannot be defined for a dummy atom.\n"); bool b_ref_pos_group = false; - if (key_lookup(group_conf, "refPositionsGroup")) { + std::string fitting_group_conf; + if (key_lookup(group_conf, "refPositionsGroup", &fitting_group_conf)) { b_ref_pos_group = true; cvm::log("Warning: keyword \"refPositionsGroup\" is deprecated: please use \"fittingGroup\" instead.\n"); } - if (b_ref_pos_group || key_lookup(group_conf, "fittingGroup")) { + if (b_ref_pos_group || key_lookup(group_conf, "fittingGroup", &fitting_group_conf)) { // instead of this group, define another group to compute the fit if (fitting_group) { cvm::error("Error: the atom group \""+ key+"\" has already a reference group " "for the rototranslational fit, which was communicated by the " "colvar component. You should not use fittingGroup " - "in this case.\n"); + "in this case.\n", INPUT_ERROR); + return INPUT_ERROR; } cvm::log("Within atom group \""+key+"\":\n"); - fitting_group = b_ref_pos_group ? - new atom_group(group_conf, "refPositionsGroup") : - new atom_group(group_conf, "fittingGroup"); - - // regardless of the configuration, fit gradients must be calculated by fittingGroup - fitting_group->b_fit_gradients = this->b_fit_gradients; + fitting_group = new atom_group("fittingGroup"); + if (fitting_group->parse(fitting_group_conf) == COLVARS_OK) { + fitting_group->check_keywords(fitting_group_conf, "fittingGroup"); + if (cvm::get_error()) { + cvm::error("Error setting up atom group \"fittingGroup\".", INPUT_ERROR); + return INPUT_ERROR; + } + } } atom_group *group_for_fit = fitting_group ? fitting_group : this; get_keyval(group_conf, "refPositions", ref_pos, ref_pos); std::string ref_pos_file; if (get_keyval(group_conf, "refPositionsFile", ref_pos_file, std::string(""))) { if (ref_pos.size()) { cvm::error("Error: cannot specify \"refPositionsFile\" and " "\"refPositions\" at the same time.\n"); } std::string ref_pos_col; double ref_pos_col_value=0.0; if (get_keyval(group_conf, "refPositionsCol", ref_pos_col, std::string(""))) { // if provided, use PDB column to select coordinates bool found = get_keyval(group_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", INPUT_ERROR); return COLVARS_ERROR; } } else { // if not, rely on existing atom indices for the group group_for_fit->create_sorted_ids(); ref_pos.resize(group_for_fit->size()); } cvm::load_coords(ref_pos_file.c_str(), ref_pos, group_for_fit->sorted_ids, ref_pos_col, ref_pos_col_value); } if (ref_pos.size()) { if (b_rotate) { if (ref_pos.size() != group_for_fit->size()) cvm::error("Error: the number of reference positions provided("+ cvm::to_str(ref_pos.size())+ ") does not match the number of atoms within \""+ key+ "\" ("+cvm::to_str(group_for_fit->size())+ "): to perform a rotational fit, "+ "these numbers should be equal.\n", INPUT_ERROR); } // save the center of geometry of ref_pos and subtract it center_ref_pos(); } else { cvm::error("Error: no reference positions provided.\n", INPUT_ERROR); return COLVARS_ERROR; } - if (b_fit_gradients) { - 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()); - } - if (b_rotate && !noforce) { cvm::log("Warning: atom group \""+key+ "\" will be aligned to a fixed orientation given by the reference positions provided. " "If the internal structure of the group changes too much (i.e. its RMSD is comparable " "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()); } } + // Enable fit gradient calculation only if necessary, and not disabled by the user + // This must happen after fitting group is defined so that side-effects are performed + // properly (ie. allocating fitting group gradients) + { + bool b_fit_gradients; + get_keyval(group_conf, "enableFitGradients", b_fit_gradients, true); + + if (b_fit_gradients && (b_center || b_rotate)) { + enable(f_ag_fit_gradients); + } + } + return COLVARS_OK; } +void cvm::atom_group::do_feature_side_effects(int id) +{ + // If enabled features are changed upstream, the features below should be refreshed + switch (id) { + case f_ag_fit_gradients: + if (b_center || b_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; + } +} + + int cvm::atom_group::create_sorted_ids(void) { // Only do the work if the vector is not yet populated if (sorted_ids.size()) return COLVARS_OK; std::list temp_id_list; for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { temp_id_list.push_back(ai->id); } temp_id_list.sort(); temp_id_list.unique(); if (temp_id_list.size() != this->size()) { cvm::error("Error: duplicate atom IDs in atom group? (found " + cvm::to_str(temp_id_list.size()) + " unique atom IDs instead of" + cvm::to_str(this->size()) + ").\n"); return COLVARS_ERROR; } sorted_ids = std::vector (temp_id_list.size()); unsigned int id_i = 0; std::list::iterator li; for (li = temp_id_list.begin(); li != temp_id_list.end(); ++li) { sorted_ids[id_i] = *li; id_i++; } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } void cvm::atom_group::center_ref_pos() { ref_pos_cog = cvm::atom_pos(0.0, 0.0, 0.0); std::vector::iterator pi; for (pi = ref_pos.begin(); pi != ref_pos.end(); ++pi) { ref_pos_cog += *pi; } ref_pos_cog /= (cvm::real) ref_pos.size(); for (pi = ref_pos.begin(); pi != ref_pos.end(); ++pi) { (*pi) -= ref_pos_cog; } } void cvm::atom_group::read_positions() { if (b_dummy) return; for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { ai->read_position(); } if (fitting_group) fitting_group->read_positions(); } int cvm::atom_group::calc_required_properties() { // TODO check if the com is needed? calc_center_of_mass(); calc_center_of_geometry(); if (!is_enabled(f_ag_scalable)) { if (b_center || b_rotate) { if (fitting_group) { fitting_group->calc_center_of_geometry(); } calc_apply_roto_translation(); // update COM and COG after fitting calc_center_of_geometry(); calc_center_of_mass(); if (fitting_group) { fitting_group->calc_center_of_geometry(); } } } // TODO calculate elements of scalable cvc's here before reduction return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } void cvm::atom_group::calc_apply_roto_translation() { // store the laborarory-frame COGs for when they are needed later cog_orig = this->center_of_geometry(); if (fitting_group) { fitting_group->cog_orig = fitting_group->center_of_geometry(); } if (b_center) { // center on the origin first cvm::atom_pos const rpg_cog = fitting_group ? fitting_group->center_of_geometry() : this->center_of_geometry(); apply_translation(-1.0 * rpg_cog); if (fitting_group) { fitting_group->apply_translation(-1.0 * rpg_cog); } } if (b_rotate) { // rotate the group (around the center of geometry if b_center is // true, around the origin otherwise) rot.calc_optimal_rotation(fitting_group ? fitting_group->positions() : this->positions(), ref_pos); cvm::atom_iter ai; for (ai = this->begin(); ai != this->end(); ai++) { ai->pos = rot.rotate(ai->pos); } if (fitting_group) { for (ai = fitting_group->begin(); ai != fitting_group->end(); ai++) { ai->pos = rot.rotate(ai->pos); } } } if (b_center) { // align with the center of geometry of ref_pos apply_translation(ref_pos_cog); if (fitting_group) { fitting_group->apply_translation(ref_pos_cog); } } // update of COM and COG is done from the calling routine } void cvm::atom_group::apply_translation(cvm::rvector const &t) { if (b_dummy) { cvm::error("Error: cannot translate the coordinates of a dummy atom group.\n", INPUT_ERROR); return; } if (is_enabled(f_ag_scalable)) { cvm::error("Error: cannot translate the coordinates of a scalable atom group.\n", INPUT_ERROR); return; } for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { ai->pos += t; } } void cvm::atom_group::read_velocities() { if (b_dummy) return; if (b_rotate) { for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { ai->read_velocity(); ai->vel = rot.rotate(ai->vel); } } else { for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { ai->read_velocity(); } } } // TODO make this a calc function void cvm::atom_group::read_total_forces() { if (b_dummy) return; if (b_rotate) { for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { ai->read_total_force(); ai->total_force = rot.rotate(ai->total_force); } } else { for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { ai->read_total_force(); } } } int cvm::atom_group::calc_center_of_geometry() { if (b_dummy) { cog = dummy_atom_pos; } else { cog.reset(); for (cvm::atom_const_iter ai = this->begin(); ai != this->end(); ai++) { cog += ai->pos; } cog /= this->size(); } return COLVARS_OK; } int cvm::atom_group::calc_center_of_mass() { if (b_dummy) { com = dummy_atom_pos; if (cvm::debug()) { cvm::log("Dummy atom center of mass = "+cvm::to_str(com)+"\n"); } } else if (is_enabled(f_ag_scalable)) { com = (cvm::proxy)->get_atom_group_com(index); } else { com.reset(); for (cvm::atom_const_iter ai = this->begin(); ai != this->end(); ai++) { com += ai->mass * ai->pos; } com /= total_mass; } return COLVARS_OK; } int cvm::atom_group::calc_dipole(cvm::atom_pos const &com) { if (b_dummy) { cvm::error("Error: trying to compute the dipole of an empty group.\n", INPUT_ERROR); return COLVARS_ERROR; } dip.reset(); for (cvm::atom_const_iter ai = this->begin(); ai != this->end(); ai++) { dip += ai->charge * (ai->pos - com); } return COLVARS_OK; } void cvm::atom_group::set_weighted_gradient(cvm::rvector const &grad) { if (b_dummy) return; if (is_enabled(f_ag_scalable)) { scalar_com_gradient = grad; return; } for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { ai->grad = (ai->mass/total_mass) * grad; } } void cvm::atom_group::calc_fit_gradients() { - if (b_dummy) return; + if (b_dummy || ! is_enabled(f_ag_fit_gradients)) return; if (cvm::debug()) cvm::log("Calculating fit gradients.\n"); - atom_group *group_for_fit = fitting_group ? fitting_group : this; + cvm::atom_group *group_for_fit = fitting_group ? fitting_group : this; if (b_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 (b_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 (b_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((b_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 (cvm::debug()) cvm::log("Done calculating fit gradients.\n"); } std::vector cvm::atom_group::positions() const { if (b_dummy) { cvm::error("Error: positions are not available " "from a dummy atom group.\n", INPUT_ERROR); } if (is_enabled(f_ag_scalable)) { cvm::error("Error: atomic positions are not available " "from a scalable atom group.\n", INPUT_ERROR); } std::vector x(this->size(), 0.0); cvm::atom_const_iter ai = this->begin(); std::vector::iterator xi = x.begin(); for ( ; ai != this->end(); ++xi, ++ai) { *xi = ai->pos; } return x; } std::vector cvm::atom_group::positions_shifted(cvm::rvector const &shift) const { if (b_dummy) { cvm::error("Error: positions are not available " "from a dummy atom group.\n", INPUT_ERROR); } if (is_enabled(f_ag_scalable)) { cvm::error("Error: atomic positions are not available " "from a scalable atom group.\n", INPUT_ERROR); } std::vector x(this->size(), 0.0); cvm::atom_const_iter ai = this->begin(); std::vector::iterator xi = x.begin(); for ( ; ai != this->end(); ++xi, ++ai) { *xi = (ai->pos + shift); } return x; } std::vector cvm::atom_group::velocities() const { if (b_dummy) { cvm::error("Error: velocities are not available " "from a dummy atom group.\n", INPUT_ERROR); } if (is_enabled(f_ag_scalable)) { cvm::error("Error: atomic velocities are not available " "from a scalable atom group.\n", INPUT_ERROR); } std::vector v(this->size(), 0.0); cvm::atom_const_iter ai = this->begin(); std::vector::iterator vi = v.begin(); for ( ; ai != this->end(); vi++, ai++) { *vi = ai->vel; } return v; } std::vector cvm::atom_group::total_forces() const { if (b_dummy) { cvm::error("Error: total forces are not available " "from a dummy atom group.\n", INPUT_ERROR); } if (is_enabled(f_ag_scalable)) { cvm::error("Error: atomic total forces are not available " "from a scalable atom group.\n", INPUT_ERROR); } std::vector f(this->size(), 0.0); cvm::atom_const_iter ai = this->begin(); std::vector::iterator fi = f.begin(); for ( ; ai != this->end(); ++fi, ++ai) { *fi = ai->total_force; } return f; } // TODO make this an accessor cvm::rvector cvm::atom_group::total_force() const { if (b_dummy) { cvm::error("Error: total total forces are not available " "from a dummy atom group.\n", INPUT_ERROR); } if (is_enabled(f_ag_scalable)) { return (cvm::proxy)->get_atom_group_total_force(index); } cvm::rvector f(0.0); for (cvm::atom_const_iter ai = this->begin(); ai != this->end(); ai++) { f += ai->total_force; } return f; } void cvm::atom_group::apply_colvar_force(cvm::real const &force) { if (cvm::debug()) { log("Communicating a colvar force from atom group to the MD engine.\n"); } if (b_dummy) return; if (noforce) { cvm::error("Error: sending a force to a group that has " "\"enableForces\" set to off.\n"); return; } if (is_enabled(f_ag_scalable)) { (cvm::proxy)->apply_atom_group_force(index, force * scalar_com_gradient); return; } if (b_rotate) { // rotate forces back to the original frame cvm::rotation const rot_inv = rot.inverse(); for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { ai->apply_force(rot_inv.rotate(force * ai->grad)); } } else { for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { ai->apply_force(force * ai->grad); } } - if ((b_center || b_rotate) && b_fit_gradients) { + if ((b_center || b_rotate) && is_enabled(f_ag_fit_gradients)) { atom_group *group_for_fit = fitting_group ? fitting_group : this; // Fit gradients are already calculated in "laboratory" frame for (size_t j = 0; j < group_for_fit->size(); j++) { (*group_for_fit)[j].apply_force(force * group_for_fit->fit_gradients[j]); } } } void cvm::atom_group::apply_force(cvm::rvector const &force) { if (cvm::debug()) { log("Communicating a colvar force from atom group to the MD engine.\n"); } if (b_dummy) return; if (noforce) { cvm::error("Error: sending a force to a group that has " "\"enableForces\" set to off.\n"); return; } if (is_enabled(f_ag_scalable)) { (cvm::proxy)->apply_atom_group_force(index, force); return; } if (b_rotate) { cvm::rotation const rot_inv = rot.inverse(); for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { ai->apply_force(rot_inv.rotate((ai->mass/total_mass) * force)); } } else { for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { ai->apply_force((ai->mass/total_mass) * force); } } } // Static members std::vector cvm::atom_group::ag_features; diff --git a/lib/colvars/colvaratoms.h b/lib/colvars/colvaratoms.h index 85f621295..dba2890ab 100644 --- a/lib/colvars/colvaratoms.h +++ b/lib/colvars/colvaratoms.h @@ -1,470 +1,471 @@ // -*- 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 COLVARATOMS_H #define COLVARATOMS_H #include "colvarmodule.h" #include "colvarparse.h" #include "colvardeps.h" /// \brief Stores numeric id, mass and all mutable data for an atom, /// mostly used by a \link cvc \endlink /// /// This class may be used to keep atomic data such as id, mass, /// position and collective variable derivatives) altogether. /// There may be multiple instances with identical /// numeric id, all acting independently: forces communicated through /// these instances will be summed together. class colvarmodule::atom { protected: /// Index in the colvarproxy arrays (\b NOT in the global topology!) int index; public: /// Identifier for the MD program (0-based) int id; /// Mass cvm::real mass; /// Charge cvm::real charge; /// \brief Current position (copied from the program, can be /// modified if necessary) cvm::atom_pos pos; /// \brief Current velocity (copied from the program, can be /// modified if necessary) cvm::rvector vel; /// \brief System force at the previous step (copied from the /// program, can be modified if necessary) cvm::rvector total_force; /// \brief Gradient of a scalar collective variable with respect /// to this atom /// /// This can only handle a scalar collective variable (i.e. when /// the \link colvarvalue::real_value \endlink member is used /// from the \link colvarvalue \endlink class), which is also the /// most frequent case. For more complex types of \link /// colvarvalue \endlink objects, atomic gradients should be /// defined within the specific \link cvc \endlink /// implementation cvm::rvector grad; /// \brief Default constructor (sets index and id both to -1) atom(); /// \brief Initialize an atom for collective variable calculation /// and get its internal identifier \param atom_number Atom index in /// the system topology (1-based) atom(int atom_number); /// \brief Initialize an atom for collective variable calculation /// and get its internal identifier \param residue Residue number /// \param atom_name Name of the atom in the residue \param /// segment_id For PSF topologies, the segment identifier; for other /// type of topologies, may not be required atom(cvm::residue_id const &residue, std::string const &atom_name, std::string const &segment_id); /// Copy constructor atom(atom const &a); /// Destructor ~atom(); /// Set mutable data (everything except id and mass) to zero; update mass inline void reset_data() { pos = cvm::atom_pos(0.0); vel = grad = total_force = cvm::rvector(0.0); } /// Get the latest value of the mass inline void update_mass() { mass = (cvm::proxy)->get_atom_mass(index); } /// Get the latest value of the charge inline void update_charge() { charge = (cvm::proxy)->get_atom_charge(index); } /// Get the current position inline void read_position() { pos = (cvm::proxy)->get_atom_position(index); } /// Get the current velocity inline void read_velocity() { vel = (cvm::proxy)->get_atom_velocity(index); } /// Get the total force inline void read_total_force() { total_force = (cvm::proxy)->get_atom_total_force(index); } /// \brief Apply a force to the atom /// /// Note: the force is not applied instantly, but will be used later /// by the MD integrator (the colvars module does not integrate /// equations of motion. /// /// Multiple calls to this function by either the same /// \link atom \endlink object or different objects with identical /// \link id \endlink will all be added together. inline void apply_force(cvm::rvector const &new_force) const { (cvm::proxy)->apply_atom_force(index, new_force); } }; /// \brief Group of \link atom \endlink objects, mostly used by a /// \link cvc \endlink object to gather all atomic data class colvarmodule::atom_group : public colvarparse, public colvardeps { public: - /// \brief Initialize the group by looking up its configuration - /// string in conf and parsing it; this is actually done by parse(), - /// which is a member function so that a group can be initialized - /// also after construction - atom_group(std::string const &conf, - char const *key); + + /// \brief Default constructor + atom_group(); + + /// \brief Create a group object, assign a name to it + atom_group(char const *key); + + /// \brief Initialize the group after a (temporary) vector of atoms + atom_group(std::vector const &atoms_in); + + /// \brief Destructor + ~atom_group(); + + /// \brief Optional name to reuse properties of this in other groups + std::string name; /// \brief Keyword used to define the group // TODO Make this field part of the data structures that link a group to a CVC std::string key; /// \brief Set default values for common flags int init(); /// \brief Update data required to calculate cvc's int setup(); /// \brief Initialize the group by looking up its configuration /// string in conf and parsing it int parse(std::string const &conf); int add_atom_numbers(std::string const &numbers_conf); + int add_atoms_of_group(atom_group const * ag); int add_index_group(std::string const &index_group_name); int add_atom_numbers_range(std::string const &range_conf); int add_atom_name_residue_range(std::string const &psf_segid, std::string const &range_conf); int parse_fitting_options(std::string const &group_conf); - /// \brief Initialize the group after a (temporary) vector of atoms - atom_group(std::vector const &atoms_in); - /// \brief Add an atom object to this group int add_atom(cvm::atom const &a); /// \brief Add an atom ID to this group (the actual atomicdata will be not be handled by the group) int add_atom_id(int aid); /// \brief Remove an atom object from this group int remove_atom(cvm::atom_iter ai); /// \brief Re-initialize the total mass of a group. /// This is needed in case the hosting MD code has an option to /// change atom masses after their initialization. void reset_mass(std::string &name, int i, int j); /// \brief Implementation of the feature list for atom group static std::vector ag_features; /// \brief Implementation of the feature list accessor for atom group virtual std::vector &features() { return ag_features; } - /// \brief Default constructor - atom_group(); - - /// \brief Destructor - ~atom_group(); - protected: /// \brief Array of atom objects std::vector atoms; /// \brief Array of atom identifiers for the MD program (0-based) std::vector atoms_ids; /// \brief Dummy atom position cvm::atom_pos dummy_atom_pos; /// \brief Index in the colvarproxy arrays (if the group is scalable) int index; public: inline cvm::atom & operator [] (size_t const i) { return atoms[i]; } inline cvm::atom const & operator [] (size_t const i) const { return atoms[i]; } inline cvm::atom_iter begin() { return atoms.begin(); } inline cvm::atom_const_iter begin() const { return atoms.begin(); } inline cvm::atom_iter end() { return atoms.end(); } inline cvm::atom_const_iter end() const { return atoms.end(); } inline size_t size() const { return atoms.size(); } std::string const print_atom_ids() const; /// \brief If this option is on, this group merely acts as a wrapper /// for a fixed position; any calls to atoms within or to /// functions that return disaggregated data will fail bool b_dummy; /// Sorted list of zero-based (internal) atom ids /// (populated on-demand by create_sorted_ids) std::vector sorted_ids; /// Allocates and populates the sorted list of atom ids int create_sorted_ids(void); /// \brief When updating atomic coordinates, translate them to align with the /// center of mass of the reference coordinates bool b_center; /// \brief When updating atom coordinates (and after /// centering them if b_center is set), rotate the group to /// align with the reference coordinates. /// /// Note: gradients will be calculated in the rotated frame: when /// forces will be applied, they will rotated back to the original /// frame bool b_rotate; /// The rotation calculated automatically if b_rotate is defined cvm::rotation rot; /// \brief Indicates that the user has explicitly set centerReference or /// rotateReference, and the corresponding reference: /// cvc's (eg rmsd, eigenvector) will not override the user's choice bool b_user_defined_fit; - /// \brief Whether or not the derivatives of the roto-translation - /// should be included when calculating the colvar's gradients (default: yes) - bool b_fit_gradients; - /// \brief use reference coordinates for b_center or b_rotate std::vector ref_pos; /// \brief Center of geometry of the reference coordinates; regardless /// of whether b_center is true, ref_pos is centered to zero at /// initialization, and ref_pos_cog serves to center the positions cvm::atom_pos ref_pos_cog; /// \brief If b_center or b_rotate is true, use this group to /// define the transformation (default: this group itself) atom_group *fitting_group; /// Total mass of the atom group cvm::real total_mass; void update_total_mass(); /// Total charge of the atom group cvm::real total_charge; void update_total_charge(); /// \brief Don't apply any force on this group (use its coordinates /// only to calculate a colvar) bool noforce; /// \brief Get the current positions void read_positions(); /// \brief (Re)calculate the optimal roto-translation void calc_apply_roto_translation(); /// \brief Save aside the center of geometry of the reference positions, /// then subtract it from them /// /// In this way it will be possible to use ref_pos also for the /// rotational fit. /// This is called either by atom_group::parse or by CVCs that assign /// reference positions (eg. RMSD, eigenvector). void center_ref_pos(); /// \brief Move all positions void apply_translation(cvm::rvector const &t); /// \brief Get the current velocities; this must be called always /// *after* read_positions(); if b_rotate is defined, the same /// rotation applied to the coordinates will be used void read_velocities(); /// \brief Get the current total_forces; this must be called always /// *after* read_positions(); if b_rotate is defined, the same /// rotation applied to the coordinates will be used void read_total_forces(); /// Call reset_data() for each atom inline void reset_atoms_data() { for (cvm::atom_iter ai = atoms.begin(); ai != atoms.end(); ai++) ai->reset_data(); if (fitting_group) fitting_group->reset_atoms_data(); } /// \brief Recompute all mutable quantities that are required to compute CVCs int calc_required_properties(); /// \brief Return a copy of the current atom positions std::vector positions() const; /// \brief Calculate the center of geometry of the atomic positions, assuming /// that they are already pbc-wrapped int calc_center_of_geometry(); private: /// \brief Center of geometry cvm::atom_pos cog; /// \brief Center of geometry before any fitting cvm::atom_pos cog_orig; public: /// \brief Return the center of geometry of the atomic positions inline cvm::atom_pos center_of_geometry() const { return cog; } /// \brief Calculate the center of mass of the atomic positions, assuming that /// they are already pbc-wrapped int calc_center_of_mass(); private: /// \brief Center of mass cvm::atom_pos com; /// \brief The derivative of a scalar variable with respect to the COM // TODO for scalable calculations of more complex variables (e.g. rotation), // use a colvarvalue of vectors to hold the entire derivative cvm::rvector scalar_com_gradient; public: /// \brief Return the center of mass of the atomic positions inline cvm::atom_pos center_of_mass() const { return com; } /// \brief Return a copy of the current atom positions, shifted by a constant vector std::vector positions_shifted(cvm::rvector const &shift) const; /// \brief Return a copy of the current atom velocities std::vector velocities() const; ///\brief Calculate the dipole of the atom group around the specified center int calc_dipole(cvm::atom_pos const &com); private: cvm::rvector dip; public: ///\brief Return the (previously calculated) dipole of the atom group inline cvm::rvector dipole() const { return dip; } /// \brief Return a copy of the total forces std::vector total_forces() const; /// \brief Return a copy of the aggregated total force on the group cvm::rvector total_force() const; /// \brief Shorthand: save the specified gradient on each atom, /// weighting with the atom mass (mostly used in combination with /// \link center_of_mass() \endlink) void set_weighted_gradient(cvm::rvector const &grad); /// \brief Calculate the derivatives of the fitting transformation void calc_fit_gradients(); /// \brief Derivatives of the fitting transformation std::vector fit_gradients; /// \brief Used by a (scalar) colvar to apply its force on its \link /// atom_group \endlink members /// /// The (scalar) force is multiplied by the colvar gradient for each /// atom; this should be used when a colvar with scalar \link /// colvarvalue \endlink type is used (this is the most frequent /// case: for colvars with a non-scalar type, the most convenient /// solution is to sum together the Cartesian forces from all the /// colvar components, and use apply_force() or apply_forces()). If /// the group is being rotated to a reference frame (e.g. to express /// the colvar independently from the solute rotation), the /// gradients are temporarily rotated to the original frame. void apply_colvar_force(cvm::real const &force); /// \brief Apply a force "to the center of mass", i.e. the force is /// distributed on each atom according to its mass /// /// If the group is being rotated to a reference frame (e.g. to /// express the colvar independently from the solute rotation), the /// force is rotated back to the original frame. Colvar gradients /// are not used, either because they were not defined (e.g because /// the colvar has not a scalar value) or the biases require to /// micromanage the force. /// This function will be phased out eventually, in favor of /// apply_colvar_force() once that is implemented for non-scalar values void apply_force(cvm::rvector const &force); + /// 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); }; #endif diff --git a/lib/colvars/colvarbias.cpp b/lib/colvars/colvarbias.cpp index 3779c82aa..e437466be 100644 --- a/lib/colvars/colvarbias.cpp +++ b/lib/colvars/colvarbias.cpp @@ -1,379 +1,393 @@ // -*- 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 "colvarbias.h" colvarbias::colvarbias(char const *key) : bias_type(to_lower_cppstr(key)) { init_cvb_requires(); rank = 1; has_data = false; b_output_energy = false; reset(); state_file_step = 0; - - // Start in active state by default - enable(f_cvb_active); + description = "uninitialized " + cvm::to_str(key) + " bias"; } int colvarbias::init(std::string const &conf) { colvarparse::init(conf); if (name.size() == 0) { // first initialization cvm::log("Initializing a new \""+bias_type+"\" instance.\n"); rank = cvm::num_biases_type(bias_type); get_keyval(conf, "name", name, bias_type+cvm::to_str(rank)); { colvarbias *bias_with_name = cvm::bias_by_name(this->name); if (bias_with_name != NULL) { if ((bias_with_name->rank != this->rank) || (bias_with_name->bias_type != this->bias_type)) { cvm::error("Error: this bias cannot have the same name, \""+this->name+ "\", as another bias.\n", INPUT_ERROR); return INPUT_ERROR; } } } description = "bias " + name; { // lookup the associated colvars std::vector colvar_names; if (get_keyval(conf, "colvars", colvar_names)) { if (num_variables()) { cvm::error("Error: cannot redefine the colvars that a bias was already defined on.\n", INPUT_ERROR); return INPUT_ERROR; } for (size_t i = 0; i < colvar_names.size(); i++) { add_colvar(colvar_names[i]); } } } if (!num_variables()) { cvm::error("Error: no collective variables specified.\n", INPUT_ERROR); return INPUT_ERROR; } - } else { cvm::log("Reinitializing bias \""+name+"\".\n"); } output_prefix = cvm::output_prefix(); get_keyval(conf, "outputEnergy", b_output_energy, b_output_energy); + get_keyval(conf, "timeStepFactor", time_step_factor, 1); + if (time_step_factor < 1) { + cvm::error("Error: timeStepFactor must be 1 or greater.\n"); + return COLVARS_ERROR; + } + + // Now that children are defined, we can solve dependencies + enable(f_cvb_active); + if (cvm::debug()) print_state(); + return COLVARS_OK; } int colvarbias::reset() { bias_energy = 0.0; for (size_t i = 0; i < num_variables(); i++) { colvar_forces[i].reset(); } return COLVARS_OK; } colvarbias::colvarbias() : colvarparse(), has_data(false) {} colvarbias::~colvarbias() { colvarbias::clear(); } int colvarbias::clear() { + free_children_deps(); + // Remove references to this bias from colvars for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); ++cvi) { for (std::vector::iterator bi = (*cvi)->biases.begin(); bi != (*cvi)->biases.end(); ++bi) { if ( *bi == this) { (*cvi)->biases.erase(bi); break; } } } colvarmodule *cv = cvm::main(); // ...and from the colvars module for (std::vector::iterator bi = cv->biases.begin(); bi != cv->biases.end(); ++bi) { if ( *bi == this) { cv->biases.erase(bi); break; } } return COLVARS_OK; } int colvarbias::add_colvar(std::string const &cv_name) { if (colvar *cv = cvm::colvar_by_name(cv_name)) { if (cvm::debug()) { cvm::log("Applying this bias to collective variable \""+ cv->name+"\".\n"); } colvars.push_back(cv); colvar_forces.push_back(colvarvalue()); colvar_forces.back().type(cv->value()); // make sure each force is initialized to zero colvar_forces.back().is_derivative(); // colvar constraints are not applied to the force colvar_forces.back().reset(); cv->biases.push_back(this); // add back-reference to this bias to colvar if (is_enabled(f_cvb_apply_force)) { cv->enable(f_cv_gradient); } // Add dependency link. // All biases need at least the value of each colvar // although possibly not at all timesteps add_child(cv); } else { cvm::error("Error: cannot find a colvar named \""+ cv_name+"\".\n", INPUT_ERROR); return INPUT_ERROR; } return COLVARS_OK; } int colvarbias::update() { if (cvm::debug()) { cvm::log("Updating the "+bias_type+" bias \""+this->name+"\".\n"); } has_data = true; bias_energy = 0.0; for (size_t ir = 0; ir < num_variables(); ir++) { colvar_forces[ir].reset(); } return COLVARS_OK; } void colvarbias::communicate_forces() { for (size_t i = 0; i < num_variables(); i++) { if (cvm::debug()) { cvm::log("Communicating a force to colvar \""+ variables(i)->name+"\".\n"); } - variables(i)->add_bias_force(colvar_forces[i]); + // Impulse-style multiple timestep + // Note that biases with different values of time_step_factor + // may send forces to the same colvar + // which is why rescaling has to happen now: the colvar is not + // aware of this bias' time_step_factor + variables(i)->add_bias_force(cvm::real(time_step_factor) * colvar_forces[i]); } } int colvarbias::change_configuration(std::string const &conf) { cvm::error("Error: change_configuration() not implemented.\n", COLVARS_NOT_IMPLEMENTED); return COLVARS_NOT_IMPLEMENTED; } cvm::real colvarbias::energy_difference(std::string const &conf) { cvm::error("Error: energy_difference() not implemented.\n", COLVARS_NOT_IMPLEMENTED); return 0.0; } // So far, these are only implemented in colvarbias_abf 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::replica_share() { cvm::error("Error: replica_share() not implemented.\n"); return COLVARS_NOT_IMPLEMENTED; } std::string const colvarbias::get_state_params() const { std::ostringstream os; os << "step " << cvm::step_absolute() << "\n" << "name " << this->name << "\n"; return os.str(); } int colvarbias::set_state_params(std::string const &conf) { std::string new_name = ""; if (colvarparse::get_keyval(conf, "name", new_name, std::string(""), colvarparse::parse_silent) && (new_name != this->name)) { cvm::error("Error: in the state file, the " "\""+bias_type+"\" block has a different name, \""+new_name+ "\": different system?\n", INPUT_ERROR); } if (name.size() == 0) { cvm::error("Error: \""+bias_type+"\" block within the restart file " "has no identifiers.\n", INPUT_ERROR); } colvarparse::get_keyval(conf, "step", state_file_step, cvm::step_absolute(), colvarparse::parse_silent); return COLVARS_OK; } std::ostream & colvarbias::write_state(std::ostream &os) { if (cvm::debug()) { cvm::log("Writing state file for bias \""+name+"\"\n"); } os.setf(std::ios::scientific, std::ios::floatfield); os.precision(cvm::cv_prec); os << bias_type << " {\n" << " configuration {\n"; std::istringstream is(get_state_params()); std::string line; while (std::getline(is, line)) { os << " " << line << "\n"; } os << " }\n"; write_state_data(os); os << "}\n\n"; return os; } std::istream & colvarbias::read_state(std::istream &is) { size_t const start_pos = is.tellg(); std::string key, brace, conf; if ( !(is >> key) || !(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(is.tellg())+" in stream.\n", INPUT_ERROR); is.clear(); is.seekg(start_pos, std::ios::beg); is.setstate(std::ios::failbit); return is; } if (!read_state_data(is)) { cvm::error("Error: in reading state data for \""+bias_type+"\" bias \""+ this->name+"\" at position "+ cvm::to_str(is.tellg())+" in stream.\n", INPUT_ERROR); is.clear(); is.seekg(start_pos, std::ios::beg); is.setstate(std::ios::failbit); } is >> brace; if (brace != "}") { cvm::error("Error: corrupt restart information for \""+bias_type+"\" bias \""+ this->name+"\": no matching brace at position "+ cvm::to_str(is.tellg())+" in stream.\n"); is.setstate(std::ios::failbit); } return is; } std::istream & colvarbias::read_state_data_key(std::istream &is, char const *key) { size_t const start_pos = is.tellg(); std::string key_in; if ( !(is >> key_in) || !(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(is.tellg())+" in stream.\n", INPUT_ERROR); is.clear(); is.seekg(start_pos, std::ios::beg); is.setstate(std::ios::failbit); return is; } return is; } std::ostream & colvarbias::write_traj_label(std::ostream &os) { os << " "; if (b_output_energy) os << " E_" << cvm::wrap_string(this->name, cvm::en_width-2); return os; } std::ostream & colvarbias::write_traj(std::ostream &os) { os << " "; if (b_output_energy) os << " " << bias_energy; return os; } // Static members std::vector colvarbias::cvb_features; diff --git a/lib/colvars/colvarbias.h b/lib/colvars/colvarbias.h index 6d5776d3d..205e761cf 100644 --- a/lib/colvars/colvarbias.h +++ b/lib/colvars/colvarbias.h @@ -1,208 +1,208 @@ // -*- 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_H #define COLVARBIAS_H #include "colvar.h" #include "colvarparse.h" #include "colvardeps.h" /// \brief Collective variable bias, base class class colvarbias : public virtual colvarparse, public virtual colvardeps { public: /// Name of this bias std::string name; /// Type of this bias std::string bias_type; /// If there is more than one bias of this type, record its rank int rank; /// Add a new collective variable to this bias int add_colvar(std::string const &cv_name); /// How many variables are defined for this bias inline size_t num_variables() const { return colvars.size(); } /// Access the variables vector inline std::vector *variables() { return &colvars; } /// Access the i-th variable inline colvar * variables(int i) const { return colvars[i]; } /// Retrieve colvar values and calculate their biasing forces /// Return bias energy virtual int update(); /// \brief Compute the energy of the bias with alternative values of the /// collective variables (suitable for bias exchange) - virtual int calc_energy(std::vector const &values = + virtual int calc_energy(std::vector const &values = std::vector(0)) { cvm::error("Error: calc_energy() not implemented.\n", COLVARS_NOT_IMPLEMENTED); return COLVARS_NOT_IMPLEMENTED; } /// Send forces to the collective variables virtual void communicate_forces(); /// Load new configuration - force constant and/or centers only virtual int change_configuration(std::string const &conf); /// Calculate change in energy from using alternate configuration virtual cvm::real energy_difference(std::string const &conf); /// Give the total number of bins for a given bias. // FIXME this is currently 1D only virtual int bin_num(); /// Calculate the bin index for a given bias. // 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); //// Share information between replicas, whatever it may be. virtual int replica_share(); /// Perform analysis tasks virtual void analyze() {} /// \brief Constructor colvarbias(char const *key); /// \brief Parse config string and (re)initialize virtual int init(std::string const &conf); /// \brief Set to zero all mutable data virtual int reset(); private: /// Default constructor colvarbias(); /// Copy constructor colvarbias(colvarbias &); public: /// \brief Delete everything virtual int clear(); /// Destructor virtual ~colvarbias(); /// Write the values of specific mutable properties to a string virtual std::string const get_state_params() const; /// 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() virtual std::ostream & write_state_data(std::ostream &os) { return os; } /// Read all mutable data not already set by set_state_params() virtual std::istream & read_state_data(std::istream &is) { return is; } /// Read a keyword from the state data (typically a header) std::istream & read_state_data_key(std::istream &is, char const *key); /// Write the bias configuration to a restart file or other stream virtual std::ostream & write_state(std::ostream &os); /// Read the bias configuration from a restart file or other stream virtual std::istream & read_state(std::istream &is); /// Write a label to the trajectory file (comment line) virtual std::ostream & write_traj_label(std::ostream &os); /// Output quantities such as the bias energy to the trajectory file virtual std::ostream & write_traj(std::ostream &os); /// (Re)initialize the output files (does not write them yet) virtual int setup_output() { return COLVARS_OK; } /// Write any output files that this bias may have (e.g. PMF files) virtual int write_output_files() { return COLVARS_OK; } /// Use this prefix for all output files std::string output_prefix; /// If this bias is communicating with other replicas through files, send it to them virtual int write_state_to_replicas() { return COLVARS_OK; } inline cvm::real get_energy() { return bias_energy; } /// \brief Implementation of the feature list for colvarbias static std::vector cvb_features; /// \brief Implementation of the feature list accessor for colvarbias virtual std::vector &features() { return cvb_features; } protected: /// \brief Pointers to collective variables to which the bias is /// applied; current values and metric functions will be obtained /// through each colvar object std::vector colvars; /// \brief Current forces from this bias to the variables std::vector colvar_forces; /// \brief Current energy of this bias (colvar_forces should be obtained by deriving this) cvm::real bias_energy; /// Whether to write the current bias energy from this bias to the trajectory file bool b_output_energy; /// \brief Whether this bias has already accumulated information /// (for history-dependent biases) bool has_data; /// \brief Step number read from the last state file size_t state_file_step; }; #endif diff --git a/lib/colvars/colvarbias_abf.cpp b/lib/colvars/colvarbias_abf.cpp index d039004f0..a96fc21d6 100644 --- a/lib/colvars/colvarbias_abf.cpp +++ b/lib/colvars/colvarbias_abf.cpp @@ -1,662 +1,676 @@ // -*- 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 "colvar.h" #include "colvarbias_abf.h" colvarbias_abf::colvarbias_abf(char const *key) : colvarbias(key), system_force(NULL), gradients(NULL), samples(NULL), z_gradients(NULL), z_samples(NULL), czar_gradients(NULL), last_gradients(NULL), last_samples(NULL) { } int colvarbias_abf::init(std::string const &conf) { colvarbias::init(conf); enable(f_cvb_scalar_variables); enable(f_cvb_calc_pmf); // TODO relax this in case of VMD plugin if (cvm::temperature() == 0.0) cvm::log("WARNING: ABF should not be run without a thermostat or at 0 Kelvin!\n"); // ************* parsing general ABF options *********************** get_keyval_feature((colvarparse *)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"); } else { 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 get_keyval(conf, "inputPrefix", input_prefix, std::vector()); get_keyval(conf, "outputFreq", output_freq, cvm::restart_out_freq); get_keyval(conf, "historyFreq", history_freq, 0); b_history_files = (history_freq > 0); // shared ABF get_keyval(conf, "shared", shared_on, false); if (shared_on) { - if (!cvm::replica_enabled() || cvm::replica_num() <= 1) + if (!cvm::replica_enabled() || cvm::replica_num() <= 1) { cvm::error("Error: shared ABF requires more than one replica."); - else - cvm::log("shared ABF will be applied among "+ cvm::to_str(cvm::replica_num()) + " replicas.\n"); + return COLVARS_ERROR; + } + cvm::log("shared ABF will be applied among "+ cvm::to_str(cvm::replica_num()) + " 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); } // ************* checking the associated colvars ******************* if (colvars.size() == 0) { cvm::error("Error: no collective variables specified for the ABF bias.\n"); + return COLVARS_ERROR; } if (update_bias) { - // Request calculation of total force (which also checks for availability) - // TODO - change this to a dependency - needs ABF-specific features + // Request calculation of total force if(enable(f_cvb_get_total_force)) return cvm::get_error(); } bool b_extended = false; for (size_t i = 0; i < colvars.size(); i++) { if (colvars[i]->value().type() != colvarvalue::type_scalar) { cvm::error("Error: ABF bias can only use scalar-type variables.\n"); } colvars[i]->enable(f_cv_grid); if (hide_Jacobian) { colvars[i]->enable(f_cv_hide_Jacobian); } // If any colvar is extended-system, we need to collect the extended // system gradient if (colvars[i]->is_enabled(f_cv_extended_Lagrangian)) b_extended = true; + // Cannot mix and match coarse time steps with ABF because it gives + // wrong total force averages - total force needs to be averaged over + // every time step + if (colvars[i]->get_time_step_factor() != time_step_factor) { + cvm::error("Error: " + colvars[i]->description + " has a value of timeStepFactor (" + + cvm::to_str(colvars[i]->get_time_step_factor()) + ") different from that of " + + description + " (" + cvm::to_str(time_step_factor) + ").\n"); + return COLVARS_ERROR; + } + // Here we could check for orthogonality of the Cartesian coordinates // and make it just a warning if some parameter is set? } if (get_keyval(conf, "maxForce", max_force)) { if (max_force.size() != colvars.size()) { cvm::error("Error: Number of parameters to maxForce does not match number of colvars."); } for (size_t i = 0; i < colvars.size(); i++) { if (max_force[i] < 0.0) { cvm::error("Error: maxForce should be non-negative."); } } cap_force = true; } else { cap_force = false; } bin.assign(colvars.size(), 0); force_bin.assign(colvars.size(), 0); system_force = new cvm::real [colvars.size()]; // Construct empty grids based on the colvars if (cvm::debug()) { 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; // Data for eABF z-based estimator if (b_extended) { // CZAR output files for stratified eABF get_keyval(conf, "writeCZARwindowFile", b_czar_window_file, false, colvarparse::parse_silent); z_bin.assign(colvars.size(), 0); z_samples = new colvar_grid_count(colvars); z_samples->request_actual_value(); z_gradients = new colvar_grid_gradient(colvars); z_gradients->request_actual_value(); z_gradients->samples = z_samples; z_samples->has_parent_data = true; czar_gradients = new colvar_grid_gradient(colvars); } // 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 custom grids are provided, read them if ( input_prefix.size() > 0 ) { read_gradients_samples(); } cvm::log("Finished ABF setup.\n"); return COLVARS_OK; } /// Destructor colvarbias_abf::~colvarbias_abf() { if (samples) { delete samples; samples = NULL; } if (gradients) { delete gradients; gradients = 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; } // 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; } } /// Update the FE gradient, compute and apply biasing force /// also output data to disk if needed int colvarbias_abf::update() { if (cvm::debug()) cvm::log("Updating ABF bias " + this->name); if (cvm::step_relative() == 0) { // At first timestep, do only: // initialization stuff (file operations relying on n_abf_biases // compute current value of colvars for (size_t i = 0; i < colvars.size(); i++) { bin[i] = samples->current_bin_scalar(i); } } else { for (size_t i = 0; i < colvars.size(); i++) { bin[i] = samples->current_bin_scalar(i); } if ( update_bias && samples->index_ok(force_bin) ) { // Only if requested and within bounds of the grid... for (size_t i = 0; i < colvars.size(); 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 ( z_gradients && update_bias ) { for (size_t i = 0; i < colvars.size(); i++) { z_bin[i] = z_samples->current_bin_scalar(i); } if ( z_samples->index_ok(z_bin) ) { for (size_t i = 0; i < colvars.size(); i++) { // If we are outside the range of xi, the force has not been obtained above // the function is just an accessor, so cheap to call again anyway update_system_force(i); } z_gradients->acc_force(z_bin, system_force); } } } // save bin for next timestep force_bin = bin; // Reset biasing forces from previous timestep for (size_t i = 0; i < colvars.size(); i++) { colvar_forces[i].reset(); } // Compute and apply the new bias, if applicable if (is_enabled(f_cvb_apply_force) && samples->index_ok(bin)) { - size_t count = samples->value(bin); - cvm::real fact = 1.0; + size_t count = samples->value(bin); + cvm::real fact = 1.0; // Factor that ensures smooth introduction of the force if ( count < full_samples ) { - fact = ( count < min_samples) ? 0.0 : + fact = (count < min_samples) ? 0.0 : (cvm::real(count - min_samples)) / (cvm::real(full_samples - min_samples)); } const cvm::real * grad = &(gradients->value(bin)); if ( fact != 0.0 ) { if ( (colvars.size() == 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 colvar_forces[0].real_value = fact * (grad[0] / cvm::real(count) - gradients->average()); } else { for (size_t i = 0; i < colvars.size(); i++) { // subtracting the mean force (opposite of the FE gradient) means adding the gradient colvar_forces[i].real_value = fact * grad[i] / cvm::real(count); } } if (cap_force) { for (size_t i = 0; i < colvars.size(); 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]); } } } } } // update the output prefix; TODO: move later to setup_output() function if (cvm::num_biases_feature(colvardeps::f_cvb_calc_pmf) == 1) { // This is the only bias computing PMFs output_prefix = cvm::output_prefix(); } else { output_prefix = cvm::output_prefix() + "." + this->name; } if (output_freq && (cvm::step_absolute() % output_freq) == 0) { if (cvm::debug()) cvm::log("ABF bias trying to write gradients and samples to disk"); write_gradients_samples(output_prefix); } if (b_history_files && (cvm::step_absolute() % history_freq) == 0) { // file already exists iff cvm::step_relative() > 0 // otherwise, backup and replace write_gradients_samples(output_prefix + ".hist", (cvm::step_relative() > 0)); } 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())+"."); } return COLVARS_OK; } int colvarbias_abf::replica_share() { int p; if ( !cvm::replica_enabled() ) { cvm::error("Error: shared ABF: No replicas.\n"); return COLVARS_ERROR; } // We must have stored the last_gradients and last_samples. if (shared_last_step < 0 ) { cvm::error("Error: shared ABF: Tried to apply shared ABF before any sampling had occurred.\n"); return COLVARS_ERROR; } // Share gradients for shared ABF. cvm::log("shared ABF: Sharing gradient and samples among replicas at step "+cvm::to_str(cvm::step_absolute()) ); // 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; char* msg_data = new char[msg_total]; if (cvm::replica_index() == 0) { // Replica 0 collects the delta gradient and count from the others. for (p = 1; p < cvm::replica_num(); p++) { // Receive the deltas. cvm::replica_comm_recv(msg_data, msg_total, p); // Map the deltas from the others into the grids. 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 ); } // 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 < cvm::replica_num(); p++) { cvm::replica_comm_send(msg_data, msg_total, p); } } 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])); cvm::replica_comm_send(msg_data, msg_total, 0); // We now receive the combined gradient from Replica 0. cvm::replica_comm_recv(msg_data, msg_total, 0); // 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])); } // Without a barrier it's possible that one replica starts // share 2 when other replicas haven't finished share 1. cvm::replica_comm_barrier(); // Done syncing the replicas. delete[] msg_data; // 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(); return COLVARS_OK; } void colvarbias_abf::write_gradients_samples(const std::string &prefix, bool append) { std::string samples_out_name = prefix + ".count"; std::string gradients_out_name = prefix + ".grad"; std::ios::openmode mode = (append ? std::ios::app : std::ios::out); - cvm::ofstream samples_os; - cvm::ofstream gradients_os; - - if (!append) cvm::backup_file(samples_out_name.c_str()); - samples_os.open(samples_out_name.c_str(), mode); - if (!samples_os.is_open()) { + std::ostream *samples_os = + cvm::proxy->output_stream(samples_out_name, mode); + if (!samples_os) { cvm::error("Error opening ABF samples file " + samples_out_name + " for writing"); } - samples->write_multicol(samples_os); - samples_os.close(); + samples->write_multicol(*samples_os); + cvm::proxy->close_output_stream(samples_out_name); - if (!append) cvm::backup_file(gradients_out_name.c_str()); - gradients_os.open(gradients_out_name.c_str(), mode); - if (!gradients_os.is_open()) { + std::ostream *gradients_os = + cvm::proxy->output_stream(gradients_out_name, mode); + if (!gradients_os) { cvm::error("Error opening ABF gradient file " + gradients_out_name + " for writing"); } - gradients->write_multicol(gradients_os); - gradients_os.close(); + gradients->write_multicol(*gradients_os); + cvm::proxy->close_output_stream(gradients_out_name); if (colvars.size() == 1) { - std::string pmf_out_name = prefix + ".pmf"; - if (!append) cvm::backup_file(pmf_out_name.c_str()); - cvm::ofstream pmf_os; // Do numerical integration and output a PMF - pmf_os.open(pmf_out_name.c_str(), mode); - if (!pmf_os.is_open()) cvm::error("Error opening pmf file " + pmf_out_name + " for writing"); - gradients->write_1D_integral(pmf_os); - pmf_os << std::endl; - pmf_os.close(); + std::string pmf_out_name = prefix + ".pmf"; + std::ostream *pmf_os = cvm::proxy->output_stream(pmf_out_name, mode); + if (!pmf_os) { + cvm::error("Error opening pmf file " + pmf_out_name + " for writing"); + } + gradients->write_1D_integral(*pmf_os); + *pmf_os << std::endl; + cvm::proxy->close_output_stream(pmf_out_name); } if (z_gradients) { // Write eABF-related quantities std::string z_samples_out_name = prefix + ".zcount"; - cvm::ofstream z_samples_os; - if (!append) cvm::backup_file(z_samples_out_name.c_str()); - z_samples_os.open(z_samples_out_name.c_str(), mode); - if (!z_samples_os.is_open()) { + std::ostream *z_samples_os = + cvm::proxy->output_stream(z_samples_out_name, mode); + if (!z_samples_os) { cvm::error("Error opening eABF z-histogram file " + z_samples_out_name + " for writing"); } - z_samples->write_multicol(z_samples_os); - z_samples_os.close(); + z_samples->write_multicol(*z_samples_os); + cvm::proxy->close_output_stream(z_samples_out_name); if (b_czar_window_file) { std::string z_gradients_out_name = prefix + ".zgrad"; - cvm::ofstream z_gradients_os; - if (!append) cvm::backup_file(z_gradients_out_name.c_str()); - z_gradients_os.open(z_gradients_out_name.c_str(), mode); - if (!z_gradients_os.is_open()) { + std::ostream *z_gradients_os = + cvm::proxy->output_stream(z_gradients_out_name, mode); + if (!z_gradients_os) { cvm::error("Error opening eABF z-gradient file " + z_gradients_out_name + " for writing"); } - z_gradients->write_multicol(z_gradients_os); - z_gradients_os.close(); + z_gradients->write_multicol(*z_gradients_os); + cvm::proxy->close_output_stream(z_gradients_out_name); } // 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) - cvm::temperature() * cvm::boltzmann() * z_samples->log_gradient_finite_diff(ix, n), n); } } std::string czar_gradients_out_name = prefix + ".czar.grad"; - cvm::ofstream czar_gradients_os; - if (!append) cvm::backup_file(czar_gradients_out_name.c_str()); - czar_gradients_os.open(czar_gradients_out_name.c_str(), mode); - if (!czar_gradients_os.is_open()) { + std::ostream *czar_gradients_os = + cvm::proxy->output_stream(czar_gradients_out_name, mode); + if (!czar_gradients_os) { cvm::error("Error opening CZAR gradient file " + czar_gradients_out_name + " for writing"); } - czar_gradients->write_multicol(czar_gradients_os); - czar_gradients_os.close(); + czar_gradients->write_multicol(*czar_gradients_os); + cvm::proxy->close_output_stream(czar_gradients_out_name); if (colvars.size() == 1) { - std::string czar_pmf_out_name = prefix + ".czar.pmf"; - if (!append) cvm::backup_file(czar_pmf_out_name.c_str()); - cvm::ofstream czar_pmf_os; // Do numerical integration and output a PMF - czar_pmf_os.open(czar_pmf_out_name.c_str(), mode); - if (!czar_pmf_os.is_open()) cvm::error("Error opening CZAR pmf file " + czar_pmf_out_name + " for writing"); - czar_gradients->write_1D_integral(czar_pmf_os); - czar_pmf_os << std::endl; - czar_pmf_os.close(); + std::string czar_pmf_out_name = prefix + ".czar.pmf"; + std::ostream *czar_pmf_os = + cvm::proxy->output_stream(czar_pmf_out_name, mode); + if (!czar_pmf_os) cvm::error("Error opening CZAR pmf file " + czar_pmf_out_name + " for writing"); + czar_gradients->write_1D_integral(*czar_pmf_os); + *czar_pmf_os << std::endl; + cvm::proxy->close_output_stream(czar_pmf_out_name); } } return; } // 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); } /// Calculate the bin index for a given bias. int colvarbias_abf::current_bin() { return samples->current_bin_scalar(0); } /// 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); } void colvarbias_abf::read_gradients_samples() { std::string samples_in_name, gradients_in_name, z_samples_in_name, z_gradients_in_name; 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"; // For user-provided files, the per-bias naming scheme may not apply std::ifstream is; cvm::log("Reading sample count from " + samples_in_name + " and gradient from " + gradients_in_name); is.open(samples_in_name.c_str()); if (!is.is_open()) cvm::error("Error opening ABF samples file " + samples_in_name + " for reading"); samples->read_multicol(is, true); is.close(); is.clear(); is.open(gradients_in_name.c_str()); - if (!is.is_open()) cvm::error("Error opening ABF gradient file " + gradients_in_name + " for reading"); - gradients->read_multicol(is, true); - is.close(); + if (!is.is_open()) { + cvm::error("Error opening ABF gradient file " + + gradients_in_name + " for reading", INPUT_ERROR); + } else { + gradients->read_multicol(is, true); + is.close(); + } if (z_gradients) { // 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); is.clear(); is.open(z_samples_in_name.c_str()); if (!is.is_open()) cvm::error("Error opening eABF z-histogram file " + z_samples_in_name + " for reading"); z_samples->read_multicol(is, true); is.close(); is.clear(); is.open(z_gradients_in_name.c_str()); if (!is.is_open()) cvm::error("Error opening eABF z-gradient file " + z_gradients_in_name + " for reading"); z_gradients->read_multicol(is, true); is.close(); } } return; } std::ostream & colvarbias_abf::write_state_data(std::ostream& os) { std::ios::fmtflags 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"; gradients->write_raw(os, 8); if (z_gradients) { os.setf(std::ios::fmtflags(0), std::ios::floatfield); // default floating-point format os << "\nz_samples\n"; z_samples->write_raw(os, 8); os.flags(flags); os << "\nz_gradient\n"; z_gradients->write_raw(os, 8); } os.flags(flags); return os; } std::istream & colvarbias_abf::read_state_data(std::istream& is) { if ( input_prefix.size() > 0 ) { cvm::error("ERROR: cannot provide both inputPrefix and a colvars state file.\n", INPUT_ERROR); } if (! read_state_data_key(is, "samples")) { return is; } if (! samples->read_raw(is)) { return is; } if (! read_state_data_key(is, "gradient")) { return is; } if (! gradients->read_raw(is)) { return is; } if (z_gradients) { if (! read_state_data_key(is, "z_samples")) { return is; } if (! z_samples->read_raw(is)) { return is; } if (! read_state_data_key(is, "z_gradient")) { return is; } if (! z_gradients->read_raw(is)) { return is; } } return is; } diff --git a/lib/colvars/colvarbias_alb.cpp b/lib/colvars/colvarbias_alb.cpp index d096ac3da..124a15c5d 100644 --- a/lib/colvars/colvarbias_alb.cpp +++ b/lib/colvars/colvarbias_alb.cpp @@ -1,420 +1,420 @@ // -*- 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 "colvarmodule.h" #include "colvarbias_alb.h" #include "colvarbias.h" #ifdef _MSC_VER #if _MSC_VER <= 1700 #define copysign(A,B) _copysign(A,B) double fmax(double A, double B) { return ( A > B ? A : B ); } double fmin(double A, double B) { return ( A < B ? A : B ); } #endif #endif /* Note about nomenclature. Force constant is called a coupling * constant here to emphasize its changing in the code. Outwards, * everything is called a force constant to keep it consistent with * the rest of colvars. * */ colvarbias_alb::colvarbias_alb(char const *key) : colvarbias(key), update_calls(0), b_equilibration(true) { } int colvarbias_alb::init(std::string const &conf) { colvarbias::init(conf); enable(f_cvb_scalar_variables); size_t i; // get the initial restraint centers colvar_centers.resize(colvars.size()); means.resize(colvars.size()); ssd.resize(colvars.size()); //sum of squares of differences from mean //setup force vectors max_coupling_range.resize(colvars.size()); max_coupling_rate.resize(colvars.size()); coupling_accum.resize(colvars.size()); set_coupling.resize(colvars.size()); current_coupling.resize(colvars.size()); coupling_rate.resize(colvars.size()); enable(f_cvb_apply_force); for (i = 0; i < colvars.size(); i++) { colvar_centers[i].type(colvars[i]->value()); //zero moments means[i] = ssd[i] = 0; //zero force some of the force vectors that aren't initialized coupling_accum[i] = current_coupling[i] = 0; } if (get_keyval(conf, "centers", colvar_centers, colvar_centers)) { for (i = 0; i < colvars.size(); i++) { colvar_centers[i].apply_constraints(); } } else { colvar_centers.clear(); cvm::fatal_error("Error: must define the initial centers of adaptive linear bias .\n"); } if (colvar_centers.size() != colvars.size()) cvm::fatal_error("Error: number of centers does not match " "that of collective variables.\n"); if (!get_keyval(conf, "UpdateFrequency", update_freq, 0)) cvm::fatal_error("Error: must set updateFrequency for adaptive linear bias.\n"); //we split the time between updating and equilibrating update_freq /= 2; if (update_freq <= 1) cvm::fatal_error("Error: must set updateFrequency to greater than 2.\n"); enable(f_cvb_history_dependent); get_keyval(conf, "outputCenters", b_output_centers, false); get_keyval(conf, "outputGradient", b_output_grad, false); get_keyval(conf, "outputCoupling", b_output_coupling, true); get_keyval(conf, "hardForceRange", b_hard_coupling_range, true); //initial guess if (!get_keyval(conf, "forceConstant", set_coupling, set_coupling)) for (i =0 ; i < colvars.size(); i++) set_coupling[i] = 0.; //how we're going to increase to that point for (i = 0; i < colvars.size(); i++) coupling_rate[i] = (set_coupling[i] - current_coupling[i]) / update_freq; if (!get_keyval(conf, "forceRange", max_coupling_range, max_coupling_range)) { //set to default for (i = 0; i < colvars.size(); i++) { if (cvm::temperature() > 0) max_coupling_range[i] = 3 * cvm::temperature() * cvm::boltzmann(); else max_coupling_range[i] = 3 * cvm::boltzmann(); } } if (!get_keyval(conf, "rateMax", max_coupling_rate, max_coupling_rate)) { //set to default for (i = 0; i < colvars.size(); i++) { max_coupling_rate[i] = max_coupling_range[i] / (10 * update_freq); } } if (cvm::debug()) cvm::log(" bias.\n"); return COLVARS_OK; } colvarbias_alb::~colvarbias_alb() { } int colvarbias_alb::update() { bias_energy = 0.0; update_calls++; if (cvm::debug()) cvm::log("Updating the adaptive linear bias \""+this->name+"\".\n"); //log the moments of the CVs // Force and energy calculation bool finished_equil_flag = 1; cvm::real delta; for (size_t i = 0; i < colvars.size(); i++) { colvar_forces[i] = -1.0 * restraint_force(restraint_convert_k(current_coupling[i], colvars[i]->width), colvars[i], colvar_centers[i]); bias_energy += restraint_potential(restraint_convert_k(current_coupling[i], colvars[i]->width), - colvars[i], - colvar_centers[i]); + colvars[i], + colvar_centers[i]); if (!b_equilibration) { //Welford, West, and Hanso online variance method delta = static_cast(colvars[i]->value()) - means[i]; means[i] += delta / update_calls; ssd[i] += delta * (static_cast(colvars[i]->value()) - means[i]); } else { //check if we've reached the setpoint if (coupling_rate[i] == 0 || pow(current_coupling[i] - set_coupling[i],2) < pow(coupling_rate[i],2)) { - finished_equil_flag &= 1; //we continue equilibrating as long as we haven't reached all the set points + finished_equil_flag &= 1; //we continue equilibrating as long as we haven't reached all the set points } else { - current_coupling[i] += coupling_rate[i]; - finished_equil_flag = 0; + current_coupling[i] += coupling_rate[i]; + finished_equil_flag = 0; } //update max_coupling_range if (!b_hard_coupling_range && fabs(current_coupling[i]) > max_coupling_range[i]) { - std::ostringstream logStream; - logStream << "Coupling constant for " - << colvars[i]->name - << " has exceeded coupling range of " - << max_coupling_range[i] - << ".\n"; - - max_coupling_range[i] *= 1.25; - logStream << "Expanding coupling range to " << max_coupling_range[i] << ".\n"; - cvm::log(logStream.str()); + std::ostringstream logStream; + logStream << "Coupling constant for " + << colvars[i]->name + << " has exceeded coupling range of " + << max_coupling_range[i] + << ".\n"; + + max_coupling_range[i] *= 1.25; + logStream << "Expanding coupling range to " << max_coupling_range[i] << ".\n"; + cvm::log(logStream.str()); } } } if (b_equilibration && finished_equil_flag) { b_equilibration = false; update_calls = 0; } //now we update coupling constant, if necessary if (!b_equilibration && update_calls == update_freq) { //use estimated variance to take a step cvm::real step_size = 0; cvm::real temp; //reset means and sum of squares of differences for (size_t i = 0; i < colvars.size(); i++) { temp = 2. * (means[i] / (static_cast (colvar_centers[i])) - 1) * ssd[i] / (update_calls - 1); if (cvm::temperature() > 0) - step_size = temp / (cvm::temperature() * cvm::boltzmann()); + step_size = temp / (cvm::temperature() * cvm::boltzmann()); else - step_size = temp / cvm::boltzmann(); + step_size = temp / cvm::boltzmann(); means[i] = 0; ssd[i] = 0; //stochastic if we do that update or not if (colvars.size() == 1 || rand() < RAND_MAX / ((int) colvars.size())) { - coupling_accum[i] += step_size * step_size; - current_coupling[i] = set_coupling[i]; - set_coupling[i] += max_coupling_range[i] / sqrt(coupling_accum[i]) * step_size; - coupling_rate[i] = (set_coupling[i] - current_coupling[i]) / update_freq; - //set to the minimum rate and then put the sign back on it - coupling_rate[i] = copysign(fmin(fabs(coupling_rate[i]), max_coupling_rate[i]), coupling_rate[i]); + coupling_accum[i] += step_size * step_size; + current_coupling[i] = set_coupling[i]; + set_coupling[i] += max_coupling_range[i] / sqrt(coupling_accum[i]) * step_size; + coupling_rate[i] = (set_coupling[i] - current_coupling[i]) / update_freq; + //set to the minimum rate and then put the sign back on it + coupling_rate[i] = copysign(fmin(fabs(coupling_rate[i]), max_coupling_rate[i]), coupling_rate[i]); } else { - coupling_rate[i] = 0; + coupling_rate[i] = 0; } } update_calls = 0; b_equilibration = true; } return COLVARS_OK; } int colvarbias_alb::set_state_params(std::string const &conf) { if (!get_keyval(conf, "setCoupling", set_coupling)) cvm::fatal_error("Error: current setCoupling is missing from the restart.\n"); if (!get_keyval(conf, "currentCoupling", current_coupling)) cvm::fatal_error("Error: current setCoupling is missing from the restart.\n"); if (!get_keyval(conf, "maxCouplingRange", max_coupling_range)) cvm::fatal_error("Error: maxCouplingRange is missing from the restart.\n"); if (!get_keyval(conf, "couplingRate", coupling_rate)) cvm::fatal_error("Error: current setCoupling is missing from the restart.\n"); if (!get_keyval(conf, "couplingAccum", coupling_accum)) cvm::fatal_error("Error: couplingAccum is missing from the restart.\n"); if (!get_keyval(conf, "mean", means)) cvm::fatal_error("Error: current mean is missing from the restart.\n"); if (!get_keyval(conf, "ssd", ssd)) cvm::fatal_error("Error: current ssd is missing from the restart.\n"); if (!get_keyval(conf, "updateCalls", update_calls)) cvm::fatal_error("Error: current updateCalls is missing from the restart.\n"); if (!get_keyval(conf, "b_equilibration", b_equilibration)) cvm::fatal_error("Error: current updateCalls is missing from the restart.\n"); return COLVARS_OK; } std::string const colvarbias_alb::get_state_params() const { std::ostringstream os; os << " setCoupling "; size_t i; for (i = 0; i < colvars.size(); i++) { os << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) << set_coupling[i] << "\n"; } os << " currentCoupling "; for (i = 0; i < colvars.size(); i++) { os << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) << current_coupling[i] << "\n"; } os << " maxCouplingRange "; for (i = 0; i < colvars.size(); i++) { os << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) << max_coupling_range[i] << "\n"; } os << " couplingRate "; for (i = 0; i < colvars.size(); i++) { os << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) << coupling_rate[i] << "\n"; } os << " couplingAccum "; for (i = 0; i < colvars.size(); i++) { os << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) << coupling_accum[i] << "\n"; } os << " mean "; for (i = 0; i < colvars.size(); i++) { os << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) << means[i] << "\n"; } os << " ssd "; for (i = 0; i < colvars.size(); i++) { os << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) << ssd[i] << "\n"; } os << " updateCalls " << update_calls << "\n"; if (b_equilibration) os << " b_equilibration yes\n"; else os << " b_equilibration no\n"; return os.str(); } std::ostream & colvarbias_alb::write_traj_label(std::ostream &os) { os << " "; if (b_output_energy) os << " E_" << cvm::wrap_string(this->name, cvm::en_width-2); if (b_output_coupling) for (size_t i = 0; i < current_coupling.size(); i++) { os << " ForceConst_" << i - <name, cvm::cv_width - 4); + << cvm::wrap_string(colvars[i]->name, cvm::cv_width - 4); } if (b_output_centers) for (size_t i = 0; i < colvars.size(); i++) { size_t const this_cv_width = (colvars[i]->value()).output_width(cvm::cv_width); os << " x0_" << cvm::wrap_string(colvars[i]->name, this_cv_width-3); } return os; } std::ostream & colvarbias_alb::write_traj(std::ostream &os) { os << " "; if (b_output_energy) os << " " << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) << bias_energy; if (b_output_coupling) for (size_t i = 0; i < current_coupling.size(); i++) { os << " " - << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) - << current_coupling[i]; + << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) + << current_coupling[i]; } if (b_output_centers) for (size_t i = 0; i < colvars.size(); i++) { os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << colvar_centers[i]; } if (b_output_grad) for (size_t i = 0; i < means.size(); i++) { os << " " - << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) - << -2. * (means[i] / (static_cast (colvar_centers[i])) - 1) * ssd[i] / (fmax(update_calls,2) - 1); + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << -2. * (means[i] / (static_cast (colvar_centers[i])) - 1) * ssd[i] / (fmax(update_calls,2) - 1); } return os; } cvm::real colvarbias_alb::restraint_potential(cvm::real k, colvar const *x, colvarvalue const &xcenter) const { return k * (x->value() - xcenter); } colvarvalue colvarbias_alb::restraint_force(cvm::real k, colvar const *x, colvarvalue const &xcenter) const { return k; } cvm::real colvarbias_alb::restraint_convert_k(cvm::real k, cvm::real dist_measure) const { return k / dist_measure; } diff --git a/lib/colvars/colvarbias_histogram.cpp b/lib/colvars/colvarbias_histogram.cpp index 502a7455b..0722e6384 100644 --- a/lib/colvars/colvarbias_histogram.cpp +++ b/lib/colvars/colvarbias_histogram.cpp @@ -1,224 +1,226 @@ // -*- 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 "colvar.h" #include "colvarbias_histogram.h" /// Histogram "bias" constructor colvarbias_histogram::colvarbias_histogram(char const *key) : colvarbias(key), grid(NULL), out_name("") { } int colvarbias_histogram::init(std::string const &conf) { colvarbias::init(conf); enable(f_cvb_scalar_variables); enable(f_cvb_history_dependent); size_t i; get_keyval(conf, "outputFile", out_name, std::string("")); get_keyval(conf, "outputFileDX", out_name_dx, std::string("")); get_keyval(conf, "outputFreq", output_freq, cvm::restart_out_freq); /// with VMD, this may not be an error // if ( output_freq == 0 ) { // cvm::error("User required histogram with zero output frequency"); // } colvar_array_size = 0; { bool colvar_array = false; get_keyval(conf, "gatherVectorColvars", colvar_array, colvar_array); if (colvar_array) { for (i = 0; i < colvars.size(); i++) { // should be all vector if (colvars[i]->value().type() != colvarvalue::type_vector) { cvm::error("Error: used gatherVectorColvars with non-vector colvar.\n", INPUT_ERROR); return INPUT_ERROR; } if (i == 0) { colvar_array_size = colvars[i]->value().size(); if (colvar_array_size < 1) { cvm::error("Error: vector variable has dimension less than one.\n", INPUT_ERROR); return INPUT_ERROR; } } else { if (colvar_array_size != colvars[i]->value().size()) { cvm::error("Error: trying to combine vector colvars of different lengths.\n", INPUT_ERROR); return INPUT_ERROR; } } } } else { for (i = 0; i < colvars.size(); i++) { // should be all scalar if (colvars[i]->value().type() != colvarvalue::type_scalar) { cvm::error("Error: only scalar colvars are supported when gatherVectorColvars is off.\n", INPUT_ERROR); return INPUT_ERROR; } } } } if (colvar_array_size > 0) { weights.assign(colvar_array_size, 1.0); get_keyval(conf, "weights", weights, weights); } for (i = 0; i < colvars.size(); i++) { colvars[i]->enable(f_cv_grid); } grid = new colvar_grid_scalar(); grid->init_from_colvars(colvars); { std::string grid_conf; - if (key_lookup(conf, "histogramGrid", grid_conf)) { + if (key_lookup(conf, "histogramGrid", &grid_conf)) { grid->parse_params(grid_conf); + grid->check_keywords(grid_conf, "histogramGrid"); } } return COLVARS_OK; } colvarbias_histogram::~colvarbias_histogram() { if (grid) { delete grid; grid = NULL; } } int colvarbias_histogram::update() { int error_code = COLVARS_OK; // update base class error_code |= colvarbias::update(); if (cvm::debug()) { cvm::log("Updating histogram bias " + this->name); } // assign a valid bin size bin.assign(colvars.size(), 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 + "\""); } } 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 + "\""); } } if (colvar_array_size == 0) { // update indices for scalar values size_t i; for (i = 0; i < colvars.size(); i++) { bin[i] = grid->current_bin_scalar(i); } if (grid->index_ok(bin)) { grid->acc_value(bin, 1.0); } } else { // update indices for vector/array values size_t iv, i; for (iv = 0; iv < colvar_array_size; iv++) { for (i = 0; i < colvars.size(); i++) { bin[i] = grid->current_bin_scalar(i, iv); } if (grid->index_ok(bin)) { grid->acc_value(bin, weights[iv]); } } } if (output_freq && (cvm::step_absolute() % output_freq) == 0) { write_output_files(); } error_code |= cvm::get_error(); return error_code; } int colvarbias_histogram::write_output_files() { if (!has_data) { // nothing to write return COLVARS_OK; } if (out_name.size()) { cvm::log("Writing the histogram file \""+out_name+"\".\n"); cvm::backup_file(out_name.c_str()); - cvm::ofstream grid_os(out_name.c_str()); - if (!grid_os.is_open()) { - cvm::error("Error opening histogram file " + out_name + " for writing.\n", FILE_ERROR); + std::ostream *grid_os = cvm::proxy->output_stream(out_name); + if (!grid_os) { + return cvm::error("Error opening histogram file "+out_name+ + " for writing.\n", FILE_ERROR); } - // TODO add return code here - grid->write_multicol(grid_os); - grid_os.close(); + grid->write_multicol(*grid_os); + cvm::proxy->close_output_stream(out_name); } if (out_name_dx.size()) { cvm::log("Writing the histogram file \""+out_name_dx+"\".\n"); cvm::backup_file(out_name_dx.c_str()); - cvm::ofstream grid_os(out_name_dx.c_str()); - if (!grid_os.is_open()) { - cvm::error("Error opening histogram file " + out_name_dx + " for writing.\n", FILE_ERROR); + std::ostream *grid_os = cvm::proxy->output_stream(out_name_dx); + if (!grid_os) { + return cvm::error("Error opening histogram file "+out_name_dx+ + " for writing.\n", FILE_ERROR); } - // TODO add return code here - grid->write_opendx(grid_os); - grid_os.close(); + grid->write_opendx(*grid_os); + cvm::proxy->close_output_stream(out_name_dx); } + return COLVARS_OK; } 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; } return is; } 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"; grid->write_raw(os, 8); os.flags(flags); return os; } diff --git a/lib/colvars/colvarbias_meta.cpp b/lib/colvars/colvarbias_meta.cpp index b0acfe974..66806fc9f 100644 --- a/lib/colvars/colvarbias_meta.cpp +++ b/lib/colvars/colvarbias_meta.cpp @@ -1,1853 +1,1862 @@ // -*- 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 // used to set the absolute path of a replica file #if defined(WIN32) && !defined(__CYGWIN__) #include #define CHDIR ::_chdir #define GETCWD ::_getcwd #define PATHSEP "\\" #else #include #define CHDIR ::chdir #define GETCWD ::getcwd #define PATHSEP "/" #endif #include "colvar.h" #include "colvarbias_meta.h" colvarbias_meta::colvarbias_meta(char const *key) : colvarbias(key) { new_hills_begin = hills.end(); + hills_traj_os = NULL; + replica_hills_os = NULL; } int colvarbias_meta::init(std::string const &conf) { colvarbias::init(conf); enable(f_cvb_calc_pmf); get_keyval(conf, "hillWeight", hill_weight, 0.0); if (hill_weight > 0.0) { enable(f_cvb_apply_force); } else { cvm::error("Error: hillWeight must be provided, and a positive number.\n", INPUT_ERROR); } get_keyval(conf, "newHillFrequency", new_hill_freq, 1000); if (new_hill_freq > 0) { enable(f_cvb_history_dependent); } get_keyval(conf, "hillWidth", hill_width, std::sqrt(2.0 * PI) / 2.0); cvm::log("Half-widths of the Gaussian hills (sigma's):\n"); for (size_t i = 0; i < num_variables(); i++) { cvm::log(variables(i)->name+std::string(": ")+ cvm::to_str(0.5 * variables(i)->width * hill_width)); } { bool b_replicas = false; get_keyval(conf, "multipleReplicas", b_replicas, false); if (b_replicas) comm = multiple_replicas; else comm = single_replica; } // in all cases, the first replica is this bias itself if (replicas.size() == 0) { replicas.push_back(this); } get_keyval(conf, "useGrids", use_grids, true); if (use_grids) { get_keyval(conf, "gridsUpdateFrequency", grids_freq, new_hill_freq); get_keyval(conf, "rebinGrids", rebin_grids, false); expand_grids = false; size_t i; for (i = 0; i < num_variables(); i++) { variables(i)->enable(f_cv_grid); if (variables(i)->expand_boundaries) { expand_grids = true; cvm::log("Metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ ": Will expand grids when the colvar \""+ variables(i)->name+"\" approaches its boundaries.\n"); } } get_keyval(conf, "keepHills", keep_hills, false); if (! get_keyval(conf, "writeFreeEnergyFile", dump_fes, true)) get_keyval(conf, "dumpFreeEnergyFile", dump_fes, true, colvarparse::parse_silent); if (get_keyval(conf, "saveFreeEnergyFile", dump_fes_save, false, colvarparse::parse_silent)) { cvm::log("Option \"saveFreeEnergyFile\" is deprecated, " "please use \"keepFreeEnergyFile\" instead."); } get_keyval(conf, "keepFreeEnergyFiles", dump_fes_save, dump_fes_save); hills_energy = new colvar_grid_scalar(colvars); hills_energy_gradients = new colvar_grid_gradient(colvars); } else { rebin_grids = false; keep_hills = false; dump_fes = false; dump_fes_save = false; dump_replica_fes = false; hills_energy = NULL; hills_energy_gradients = NULL; } if (comm != single_replica) { if (expand_grids) cvm::fatal_error("Error: expandBoundaries is not supported when " "using more than one replicas; please allocate " "wide enough boundaries for each colvar" "ahead of time.\n"); if (get_keyval(conf, "dumpPartialFreeEnergyFile", dump_replica_fes, false)) { if (dump_replica_fes && (! dump_fes)) { cvm::log("Enabling \"dumpFreeEnergyFile\".\n"); } } get_keyval(conf, "replicaID", replica_id, std::string("")); if (!replica_id.size()) cvm::error("Error: replicaID must be defined " "when using more than one replica.\n", INPUT_ERROR); get_keyval(conf, "replicasRegistry", replicas_registry_file, (this->name+".replicas.registry.txt")); get_keyval(conf, "replicaUpdateFrequency", replica_update_freq, new_hill_freq); if (keep_hills) cvm::log("Warning: in metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ ": keepHills with more than one replica can lead to a very " "large amount of input/output and slow down your calculations. " "Please consider disabling it.\n"); } get_keyval(conf, "writeHillsTrajectory", b_hills_traj, false); init_well_tempered_params(conf); init_ebmeta_params(conf); if (cvm::debug()) cvm::log("Done initializing the metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+".\n"); - save_delimiters = false; return COLVARS_OK; } int colvarbias_meta::init_well_tempered_params(std::string const &conf) { // for well-tempered metadynamics get_keyval(conf, "wellTempered", well_tempered, false); get_keyval(conf, "biasTemperature", bias_temperature, -1.0); if ((bias_temperature == -1.0) && well_tempered) { cvm::fatal_error("Error: biasTemperature is not set.\n"); } if (well_tempered) { cvm::log("Well-tempered metadynamics is used.\n"); cvm::log("The bias temperature is "+cvm::to_str(bias_temperature)+".\n"); } return COLVARS_OK; } int colvarbias_meta::init_ebmeta_params(std::string const &conf) { // for ebmeta target_dist = NULL; get_keyval(conf, "ebMeta", ebmeta, false); if(ebmeta){ if (use_grids && expand_grids) { cvm::fatal_error("Error: expandBoundaries is not supported with " "ebMeta please allocate wide enough boundaries for " "each colvar ahead of time and set targetdistfile " "accordingly. \n"); } target_dist = new colvar_grid_scalar(); target_dist->init_from_colvars(colvars); get_keyval(conf, "targetdistfile", target_dist_file); std::ifstream targetdiststream(target_dist_file.c_str()); target_dist->read_multicol(targetdiststream); cvm::real min_val = target_dist->minimum_value(); if(min_val<0){ cvm::error("Error: Target distribution of ebMeta " "has negative values!.\n", INPUT_ERROR); } cvm::real min_pos_val = target_dist->minimum_pos_value(); if(min_pos_val<=0){ cvm::error("Error: Target distribution of ebMeta has negative " "or zero minimum positive value!.\n", INPUT_ERROR); } if(min_val==0){ cvm::log("WARNING: Target distribution has zero values.\n"); cvm::log("Zeros will be converted to the minimum positive value.\n"); target_dist->remove_zeros(min_pos_val); } // normalize target distribution and multiply by effective volume = exp(differential entropy) target_dist->multiply_constant(1.0/target_dist->integral()); cvm::real volume = std::exp(target_dist->entropy()); target_dist->multiply_constant(volume); get_keyval(conf, "ebMetaEquilSteps", ebmeta_equil_steps, 0); } return COLVARS_OK; } colvarbias_meta::~colvarbias_meta() { if (hills_energy) { delete hills_energy; hills_energy = NULL; } if (hills_energy_gradients) { delete hills_energy_gradients; hills_energy_gradients = NULL; } - if (replica_hills_os.is_open()) - replica_hills_os.close(); + if (replica_hills_os) { + cvm::proxy->close_output_stream(replica_hills_file); + replica_hills_os = NULL; + } - if (hills_traj_os.is_open()) - hills_traj_os.close(); + if (hills_traj_os) { + cvm::proxy->close_output_stream(hills_traj_file_name()); + hills_traj_os = NULL; + } if(target_dist) { delete target_dist; target_dist = NULL; } } // ********************************************************************** // Hill management member functions // ********************************************************************** std::list::const_iterator colvarbias_meta::create_hill(colvarbias_meta::hill const &h) { hill_iter const hills_end = hills.end(); hills.push_back(h); if (new_hills_begin == hills_end) { // if new_hills_begin is unset, set it for the first time new_hills_begin = hills.end(); new_hills_begin--; } if (use_grids) { // also add it to the list of hills that are off-grid, which may // need to be computed analytically when the colvar returns // off-grid cvm::real const min_dist = hills_energy->bin_distance_from_boundaries(h.centers, true); if (min_dist < (3.0 * std::floor(hill_width)) + 1.0) { hills_off_grid.push_back(h); } } // output to trajectory (if specified) - if (hills_traj_os.is_open()) { - hills_traj_os << (hills.back()).output_traj(); - hills_traj_os.flush(); + if (hills_traj_os) { + *hills_traj_os << (hills.back()).output_traj(); + cvm::proxy->flush_output_stream(hills_traj_os); } has_data = true; return hills.end(); } std::list::const_iterator colvarbias_meta::delete_hill(hill_iter &h) { if (cvm::debug()) { cvm::log("Deleting hill from the metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ ", with step number "+ cvm::to_str(h->it)+(h->replica.size() ? ", replica id \""+h->replica : "")+".\n"); } if (use_grids && !hills_off_grid.empty()) { for (hill_iter hoff = hills_off_grid.begin(); hoff != hills_off_grid.end(); hoff++) { if (*h == *hoff) { hills_off_grid.erase(hoff); break; } } } - if (hills_traj_os.is_open()) { + if (hills_traj_os) { // output to the trajectory - hills_traj_os << "# DELETED this hill: " - << (hills.back()).output_traj() - << "\n"; - hills_traj_os.flush(); + *hills_traj_os << "# DELETED this hill: " + << (hills.back()).output_traj() + << "\n"; + cvm::proxy->flush_output_stream(hills_traj_os); } return hills.erase(h); } int colvarbias_meta::update() { int error_code = COLVARS_OK; // update base class error_code |= colvarbias::update(); // update grid definition, if needed error_code |= update_grid_params(); // add new biasing energy/forces error_code |= update_bias(); // update grid content to reflect new bias error_code |= update_grid_data(); if (comm != single_replica && (cvm::step_absolute() % replica_update_freq) == 0) { // sync with the other replicas (if needed) error_code |= replica_share(); } error_code |= calc_energy(); error_code |= calc_forces(); return error_code; } int colvarbias_meta::update_grid_params() { if (use_grids) { std::vector curr_bin = hills_energy->get_colvars_index(); if (cvm::debug()) { cvm::log("Metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ ": current coordinates on the grid: "+ cvm::to_str(curr_bin)+".\n"); } if (expand_grids) { // first of all, expand the grids, if specified bool changed_grids = false; int const min_buffer = (3 * (size_t) std::floor(hill_width)) + 1; std::vector new_sizes(hills_energy->sizes()); std::vector new_lower_boundaries(hills_energy->lower_boundaries); std::vector new_upper_boundaries(hills_energy->upper_boundaries); for (size_t i = 0; i < num_variables(); i++) { if (! variables(i)->expand_boundaries) continue; cvm::real &new_lb = new_lower_boundaries[i].real_value; cvm::real &new_ub = new_upper_boundaries[i].real_value; int &new_size = new_sizes[i]; bool changed_lb = false, changed_ub = false; if (!variables(i)->hard_lower_boundary) if (curr_bin[i] < min_buffer) { int const extra_points = (min_buffer - curr_bin[i]); new_lb -= extra_points * variables(i)->width; new_size += extra_points; // changed offset in this direction => the pointer needs to // be changed, too curr_bin[i] += extra_points; changed_lb = true; cvm::log("Metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ ": new lower boundary for colvar \""+ variables(i)->name+"\", at "+ cvm::to_str(new_lower_boundaries[i])+".\n"); } if (!variables(i)->hard_upper_boundary) if (curr_bin[i] > new_size - min_buffer - 1) { int const extra_points = (curr_bin[i] - (new_size - 1) + min_buffer); new_ub += extra_points * variables(i)->width; new_size += extra_points; changed_ub = true; cvm::log("Metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ ": new upper boundary for colvar \""+ variables(i)->name+"\", at "+ cvm::to_str(new_upper_boundaries[i])+".\n"); } if (changed_lb || changed_ub) changed_grids = true; } if (changed_grids) { // map everything into new grids colvar_grid_scalar *new_hills_energy = new colvar_grid_scalar(*hills_energy); colvar_grid_gradient *new_hills_energy_gradients = new colvar_grid_gradient(*hills_energy_gradients); // supply new boundaries to the new grids new_hills_energy->lower_boundaries = new_lower_boundaries; new_hills_energy->upper_boundaries = new_upper_boundaries; new_hills_energy->setup(new_sizes, 0.0, 1); new_hills_energy_gradients->lower_boundaries = new_lower_boundaries; new_hills_energy_gradients->upper_boundaries = new_upper_boundaries; new_hills_energy_gradients->setup(new_sizes, 0.0, num_variables()); new_hills_energy->map_grid(*hills_energy); new_hills_energy_gradients->map_grid(*hills_energy_gradients); delete hills_energy; delete hills_energy_gradients; hills_energy = new_hills_energy; hills_energy_gradients = new_hills_energy_gradients; curr_bin = hills_energy->get_colvars_index(); if (cvm::debug()) cvm::log("Coordinates on the new grid: "+ cvm::to_str(curr_bin)+".\n"); } } } return COLVARS_OK; } int colvarbias_meta::update_bias() { // add a new hill if the required time interval has passed if ((cvm::step_absolute() % new_hill_freq) == 0 && is_enabled(f_cvb_history_dependent)) { if (cvm::debug()) { cvm::log("Metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ ": adding a new hill at step "+cvm::to_str(cvm::step_absolute())+".\n"); } cvm::real hills_scale=1.0; if (ebmeta) { hills_scale *= 1.0/target_dist->value(target_dist->get_colvars_index()); if(cvm::step_absolute() <= long(ebmeta_equil_steps)) { cvm::real const hills_lambda = (cvm::real(long(ebmeta_equil_steps) - cvm::step_absolute())) / (cvm::real(ebmeta_equil_steps)); hills_scale = hills_lambda + (1-hills_lambda)*hills_scale; } } if (well_tempered) { cvm::real hills_energy_sum_here = 0.0; if (use_grids) { std::vector curr_bin = hills_energy->get_colvars_index(); hills_energy_sum_here = hills_energy->value(curr_bin); } else { calc_hills(new_hills_begin, hills.end(), hills_energy_sum_here); } hills_scale *= std::exp(-1.0*hills_energy_sum_here/(bias_temperature*cvm::boltzmann())); } switch (comm) { case single_replica: create_hill(hill(hill_weight*hills_scale, colvars, hill_width)); break; case multiple_replicas: create_hill(hill(hill_weight*hills_scale, colvars, hill_width, replica_id)); - if (replica_hills_os.is_open()) { - replica_hills_os << hills.back(); + if (replica_hills_os) { + *replica_hills_os << hills.back(); } else { - cvm::fatal_error("Error: in metadynamics bias \""+this->name+"\""+ - ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ - " while writing hills for the other replicas.\n"); + return cvm::error("Error: in metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + " while writing hills for the other replicas.\n", FILE_ERROR); } break; } } return COLVARS_OK; } int colvarbias_meta::update_grid_data() { if ((cvm::step_absolute() % grids_freq) == 0) { // map the most recent gaussians to the grids project_hills(new_hills_begin, hills.end(), hills_energy, hills_energy_gradients); new_hills_begin = hills.end(); // TODO: we may want to condense all into one replicas array, // including "this" as the first element if (comm == multiple_replicas) { for (size_t ir = 0; ir < replicas.size(); ir++) { replicas[ir]->project_hills(replicas[ir]->new_hills_begin, replicas[ir]->hills.end(), replicas[ir]->hills_energy, replicas[ir]->hills_energy_gradients); replicas[ir]->new_hills_begin = replicas[ir]->hills.end(); } } } return COLVARS_OK; } int colvarbias_meta::calc_energy(std::vector const &values) { size_t ir = 0; for (ir = 0; ir < replicas.size(); ir++) { replicas[ir]->bias_energy = 0.0; } std::vector const curr_bin = values.size() ? hills_energy->get_colvars_index(values) : hills_energy->get_colvars_index(); if (hills_energy->index_ok(curr_bin)) { // index is within the grid: get the energy from there for (ir = 0; ir < replicas.size(); ir++) { bias_energy += replicas[ir]->hills_energy->value(curr_bin); if (cvm::debug()) { cvm::log("Metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ ": current coordinates on the grid: "+ cvm::to_str(curr_bin)+".\n"); cvm::log("Grid energy = "+cvm::to_str(bias_energy)+".\n"); } } } else { // off the grid: compute analytically only the hills at the grid's edges for (ir = 0; ir < replicas.size(); ir++) { calc_hills(replicas[ir]->hills_off_grid.begin(), replicas[ir]->hills_off_grid.end(), bias_energy, values); } } // now include the hills that have not been binned yet (starting // from new_hills_begin) for (ir = 0; ir < replicas.size(); ir++) { calc_hills(replicas[ir]->new_hills_begin, replicas[ir]->hills.end(), bias_energy); if (cvm::debug()) { cvm::log("Hills energy = "+cvm::to_str(bias_energy)+".\n"); } } return COLVARS_OK; } int colvarbias_meta::calc_forces(std::vector const &values) { size_t ir = 0, ic = 0; for (ir = 0; ir < replicas.size(); ir++) { for (ic = 0; ic < num_variables(); ic++) { replicas[ir]->colvar_forces[ic].reset(); } } std::vector const curr_bin = values.size() ? hills_energy->get_colvars_index(values) : hills_energy->get_colvars_index(); if (hills_energy->index_ok(curr_bin)) { for (ir = 0; ir < replicas.size(); ir++) { cvm::real const *f = &(replicas[ir]->hills_energy_gradients->value(curr_bin)); for (ic = 0; ic < num_variables(); ic++) { // the gradients are stored, not the forces colvar_forces[ic].real_value += -1.0 * f[ic]; } } } else { // off the grid: compute analytically only the hills at the grid's edges for (ir = 0; ir < replicas.size(); ir++) { for (ic = 0; ic < num_variables(); ic++) { calc_hills_force(ic, replicas[ir]->hills_off_grid.begin(), replicas[ir]->hills_off_grid.end(), colvar_forces, values); } } } // now include the hills that have not been binned yet (starting // from new_hills_begin) if (cvm::debug()) { cvm::log("Metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ ": adding the forces from the other replicas.\n"); } for (ir = 0; ir < replicas.size(); ir++) { for (ic = 0; ic < num_variables(); ic++) { calc_hills_force(ic, replicas[ir]->new_hills_begin, replicas[ir]->hills.end(), colvar_forces, values); if (cvm::debug()) { cvm::log("Hills forces = "+cvm::to_str(colvar_forces)+".\n"); } } } return COLVARS_OK; } void colvarbias_meta::calc_hills(colvarbias_meta::hill_iter h_first, colvarbias_meta::hill_iter h_last, cvm::real &energy, std::vector const &colvar_values) { size_t i = 0; std::vector curr_values(num_variables()); for (i = 0; i < num_variables(); i++) { curr_values[i].type(variables(i)->value()); } if (colvar_values.size()) { for (i = 0; i < num_variables(); i++) { curr_values[i] = colvar_values[i]; } } else { for (i = 0; i < num_variables(); i++) { curr_values[i] = variables(i)->value(); } } for (hill_iter h = h_first; h != h_last; h++) { // compute the gaussian exponent cvm::real cv_sqdev = 0.0; for (i = 0; i < num_variables(); i++) { colvarvalue const &x = curr_values[i]; colvarvalue const ¢er = h->centers[i]; cvm::real const half_width = 0.5 * h->widths[i]; cv_sqdev += (variables(i)->dist2(x, center)) / (half_width*half_width); } // compute the gaussian if (cv_sqdev > 23.0) { // set it to zero if the exponent is more negative than log(1.0E-05) h->value(0.0); } else { h->value(std::exp(-0.5*cv_sqdev)); } energy += h->energy(); } } void colvarbias_meta::calc_hills_force(size_t const &i, colvarbias_meta::hill_iter h_first, colvarbias_meta::hill_iter h_last, std::vector &forces, std::vector const &values) { // Retrieve the value of the colvar colvarvalue const x(values.size() ? values[i] : variables(i)->value()); // do the type check only once (all colvarvalues in the hills series // were already saved with their types matching those in the // colvars) hill_iter h; switch (variables(i)->value().type()) { case colvarvalue::type_scalar: for (h = h_first; h != h_last; h++) { if (h->value() == 0.0) continue; colvarvalue const ¢er = h->centers[i]; cvm::real const half_width = 0.5 * h->widths[i]; forces[i].real_value += ( h->weight() * h->value() * (0.5 / (half_width*half_width)) * (variables(i)->dist2_lgrad(x, center)).real_value ); } break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: for (h = h_first; h != h_last; h++) { if (h->value() == 0.0) continue; colvarvalue const ¢er = h->centers[i]; cvm::real const half_width = 0.5 * h->widths[i]; forces[i].rvector_value += ( h->weight() * h->value() * (0.5 / (half_width*half_width)) * (variables(i)->dist2_lgrad(x, center)).rvector_value ); } break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: for (h = h_first; h != h_last; h++) { if (h->value() == 0.0) continue; colvarvalue const ¢er = h->centers[i]; cvm::real const half_width = 0.5 * h->widths[i]; forces[i].quaternion_value += ( h->weight() * h->value() * (0.5 / (half_width*half_width)) * (variables(i)->dist2_lgrad(x, center)).quaternion_value ); } break; case colvarvalue::type_vector: for (h = h_first; h != h_last; h++) { if (h->value() == 0.0) continue; colvarvalue const ¢er = h->centers[i]; cvm::real const half_width = 0.5 * h->widths[i]; forces[i].vector1d_value += ( h->weight() * h->value() * (0.5 / (half_width*half_width)) * (variables(i)->dist2_lgrad(x, center)).vector1d_value ); } break; case colvarvalue::type_notset: case colvarvalue::type_all: default: break; } } // ********************************************************************** // grid management functions // ********************************************************************** void colvarbias_meta::project_hills(colvarbias_meta::hill_iter h_first, colvarbias_meta::hill_iter h_last, colvar_grid_scalar *he, colvar_grid_gradient *hg, bool print_progress) { if (cvm::debug()) cvm::log("Metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ ": projecting hills.\n"); // TODO: improve it by looping over a small subgrid instead of the whole grid std::vector colvar_values(num_variables()); std::vector colvar_forces_scalar(num_variables()); std::vector he_ix = he->new_index(); std::vector hg_ix = (hg != NULL) ? hg->new_index() : std::vector (0); cvm::real hills_energy_here = 0.0; std::vector hills_forces_here(num_variables(), 0.0); size_t count = 0; size_t const print_frequency = ((hills.size() >= 1000000) ? 1 : (1000000/(hills.size()+1))); if (hg != NULL) { // loop over the points of the grid for ( ; (he->index_ok(he_ix)) && (hg->index_ok(hg_ix)); count++) { size_t i; for (i = 0; i < num_variables(); i++) { colvar_values[i] = hills_energy->bin_to_value_scalar(he_ix[i], i); } // loop over the hills and increment the energy grid locally hills_energy_here = 0.0; calc_hills(h_first, h_last, hills_energy_here, colvar_values); he->acc_value(he_ix, hills_energy_here); for (i = 0; i < num_variables(); i++) { hills_forces_here[i].reset(); calc_hills_force(i, h_first, h_last, hills_forces_here, colvar_values); colvar_forces_scalar[i] = hills_forces_here[i].real_value; } hg->acc_force(hg_ix, &(colvar_forces_scalar.front())); he->incr(he_ix); hg->incr(hg_ix); if ((count % print_frequency) == 0) { if (print_progress) { cvm::real const progress = cvm::real(count) / cvm::real(hg->number_of_points()); std::ostringstream os; os.setf(std::ios::fixed, std::ios::floatfield); os << std::setw(6) << std::setprecision(2) << 100.0 * progress << "% done."; cvm::log(os.str()); } } } } else { // simpler version, with just the energy for ( ; (he->index_ok(he_ix)); ) { for (size_t i = 0; i < num_variables(); i++) { colvar_values[i] = hills_energy->bin_to_value_scalar(he_ix[i], i); } hills_energy_here = 0.0; calc_hills(h_first, h_last, hills_energy_here, colvar_values); he->acc_value(he_ix, hills_energy_here); he->incr(he_ix); count++; if ((count % print_frequency) == 0) { if (print_progress) { cvm::real const progress = cvm::real(count) / cvm::real(he->number_of_points()); std::ostringstream os; os.setf(std::ios::fixed, std::ios::floatfield); os << std::setw(6) << std::setprecision(2) << 100.0 * progress << "% done."; cvm::log(os.str()); } } } } if (print_progress) { cvm::log("100.00% done."); } if (! keep_hills) { hills.erase(hills.begin(), hills.end()); } } void colvarbias_meta::recount_hills_off_grid(colvarbias_meta::hill_iter h_first, colvarbias_meta::hill_iter h_last, colvar_grid_scalar *he) { hills_off_grid.clear(); for (hill_iter h = h_first; h != h_last; h++) { cvm::real const min_dist = hills_energy->bin_distance_from_boundaries(h->centers, true); if (min_dist < (3.0 * std::floor(hill_width)) + 1.0) { hills_off_grid.push_back(*h); } } } // ********************************************************************** // multiple replicas functions // ********************************************************************** int colvarbias_meta::replica_share() { // sync with the other replicas (if needed) if (comm == multiple_replicas) { // reread the replicas registry update_replicas_registry(); // empty the output buffer - if (replica_hills_os.is_open()) - replica_hills_os.flush(); + if (replica_hills_os) { + cvm::proxy->flush_output_stream(replica_hills_os); + } read_replica_files(); } return COLVARS_OK; } void colvarbias_meta::update_replicas_registry() { if (cvm::debug()) cvm::log("Metadynamics bias \""+this->name+"\""+ ": updating the list of replicas, currently containing "+ cvm::to_str(replicas.size())+" elements.\n"); { // copy the whole file into a string for convenience std::string line(""); std::ifstream reg_file(replicas_registry_file.c_str()); if (reg_file.is_open()) { replicas_registry.clear(); while (colvarparse::getline_nocomments(reg_file, line)) replicas_registry.append(line+"\n"); } else { cvm::error("Error: failed to open file \""+replicas_registry_file+ "\" for reading.\n", FILE_ERROR); } } // now parse it std::istringstream reg_is(replicas_registry); if (reg_is.good()) { std::string new_replica(""); std::string new_replica_file(""); while ((reg_is >> new_replica) && new_replica.size() && (reg_is >> new_replica_file) && new_replica_file.size()) { if (new_replica == this->replica_id) { // this is the record for this same replica, skip it if (cvm::debug()) cvm::log("Metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ ": skipping this replica's own record: \""+ new_replica+"\", \""+new_replica_file+"\"\n"); new_replica_file.clear(); new_replica.clear(); continue; } bool already_loaded = false; for (size_t ir = 0; ir < replicas.size(); ir++) { if (new_replica == (replicas[ir])->replica_id) { // this replica was already added if (cvm::debug()) cvm::log("Metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ ": skipping a replica already loaded, \""+ (replicas[ir])->replica_id+"\".\n"); already_loaded = true; break; } } if (!already_loaded) { // add this replica to the registry cvm::log("Metadynamics bias \""+this->name+"\""+ ": accessing replica \""+new_replica+"\".\n"); replicas.push_back(new colvarbias_meta("metadynamics")); (replicas.back())->replica_id = new_replica; (replicas.back())->replica_list_file = new_replica_file; (replicas.back())->replica_state_file = ""; (replicas.back())->replica_state_file_in_sync = false; // Note: the following could become a copy constructor? (replicas.back())->name = this->name; (replicas.back())->colvars = colvars; (replicas.back())->use_grids = use_grids; (replicas.back())->dump_fes = false; (replicas.back())->expand_grids = false; (replicas.back())->rebin_grids = false; (replicas.back())->keep_hills = false; (replicas.back())->colvar_forces = colvar_forces; (replicas.back())->comm = multiple_replicas; if (use_grids) { (replicas.back())->hills_energy = new colvar_grid_scalar(colvars); (replicas.back())->hills_energy_gradients = new colvar_grid_gradient(colvars); } } } } else { cvm::fatal_error("Error: cannot read the replicas registry file \""+ replicas_registry+"\".\n"); } // now (re)read the list file of each replica for (size_t ir = 0; ir < replicas.size(); ir++) { if (cvm::debug()) cvm::log("Metadynamics bias \""+this->name+"\""+ ": reading the list file for replica \""+ (replicas[ir])->replica_id+"\".\n"); std::ifstream list_is((replicas[ir])->replica_list_file.c_str()); std::string key; std::string new_state_file, new_hills_file; if (!(list_is >> key) || !(key == std::string("stateFile")) || !(list_is >> new_state_file) || !(list_is >> key) || !(key == std::string("hillsFile")) || !(list_is >> new_hills_file)) { cvm::log("Metadynamics bias \""+this->name+"\""+ ": failed to read the file \""+ (replicas[ir])->replica_list_file+"\": will try again after "+ cvm::to_str(replica_update_freq)+" steps.\n"); (replicas[ir])->update_status++; } else { (replicas[ir])->update_status = 0; if (new_state_file != (replicas[ir])->replica_state_file) { cvm::log("Metadynamics bias \""+this->name+"\""+ ": replica \""+(replicas[ir])->replica_id+ "\" has supplied a new state file, \""+new_state_file+ "\".\n"); (replicas[ir])->replica_state_file_in_sync = false; (replicas[ir])->replica_state_file = new_state_file; (replicas[ir])->replica_hills_file = new_hills_file; } } } if (cvm::debug()) cvm::log("Metadynamics bias \""+this->name+"\": the list of replicas contains "+ cvm::to_str(replicas.size())+" elements.\n"); } void colvarbias_meta::read_replica_files() { // Note: we start from the 2nd replica. for (size_t ir = 1; ir < replicas.size(); ir++) { if (! (replicas[ir])->replica_state_file_in_sync) { // if a new state file is being read, the hills file is also new (replicas[ir])->replica_hills_file_pos = 0; } // (re)read the state file if necessary if ( (! (replicas[ir])->has_data) || (! (replicas[ir])->replica_state_file_in_sync) ) { cvm::log("Metadynamics bias \""+this->name+"\""+ ": reading the state of replica \""+ (replicas[ir])->replica_id+"\" from file \""+ (replicas[ir])->replica_state_file+"\".\n"); std::ifstream is((replicas[ir])->replica_state_file.c_str()); if (! (replicas[ir])->read_state(is)) { cvm::log("Reading from file \""+(replicas[ir])->replica_state_file+ "\" failed or incomplete: will try again in "+ cvm::to_str(replica_update_freq)+" steps.\n"); } else { // state file has been read successfully (replicas[ir])->replica_state_file_in_sync = true; (replicas[ir])->update_status = 0; } is.close(); } // now read the hills added after writing the state file if ((replicas[ir])->replica_hills_file.size()) { if (cvm::debug()) cvm::log("Metadynamics bias \""+this->name+"\""+ ": checking for new hills from replica \""+ (replicas[ir])->replica_id+"\" in the file \""+ (replicas[ir])->replica_hills_file+"\".\n"); // read hills from the other replicas' files; for each file, resume // the position recorded previously std::ifstream is((replicas[ir])->replica_hills_file.c_str()); if (is.is_open()) { // try to resume the previous position is.seekg((replicas[ir])->replica_hills_file_pos, std::ios::beg); if (!is.is_open()){ // if fail (the file may have been overwritten), reset this // position is.clear(); is.seekg(0, std::ios::beg); // reset the counter (replicas[ir])->replica_hills_file_pos = 0; // schedule to reread the state file (replicas[ir])->replica_state_file_in_sync = false; // and record the failure (replicas[ir])->update_status++; cvm::log("Failed to read the file \""+(replicas[ir])->replica_hills_file+ "\" at the previous position: will try again in "+ cvm::to_str(replica_update_freq)+" steps.\n"); } else { while ((replicas[ir])->read_hill(is)) { // if (cvm::debug()) cvm::log("Metadynamics bias \""+this->name+"\""+ ": received a hill from replica \""+ (replicas[ir])->replica_id+ "\" at step "+ cvm::to_str(((replicas[ir])->hills.back()).it)+".\n"); } is.clear(); // store the position for the next read (replicas[ir])->replica_hills_file_pos = is.tellg(); if (cvm::debug()) cvm::log("Metadynamics bias \""+this->name+"\""+ ": stopped reading file \""+(replicas[ir])->replica_hills_file+ "\" at position "+ cvm::to_str((replicas[ir])->replica_hills_file_pos)+".\n"); // test whether this is the end of the file is.seekg(0, std::ios::end); if (is.tellg() > (replicas[ir])->replica_hills_file_pos+1) { (replicas[ir])->update_status++; } else { (replicas[ir])->update_status = 0; } } } else { cvm::log("Failed to read the file \""+(replicas[ir])->replica_hills_file+ "\": will try again in "+ cvm::to_str(replica_update_freq)+" steps.\n"); (replicas[ir])->update_status++; // cvm::fatal_error ("Error: cannot read from file \""+ // (replicas[ir])->replica_hills_file+"\".\n"); } is.close(); } size_t const n_flush = (replica_update_freq/new_hill_freq + 1); if ((replicas[ir])->update_status > 3*n_flush) { // TODO: suspend the calculation? cvm::log("WARNING: in metadynamics bias \""+this->name+"\""+ " failed to read completely the output of replica \""+ (replicas[ir])->replica_id+ "\" after more than "+ cvm::to_str((replicas[ir])->update_status * replica_update_freq)+ " steps. Ensure that it is still running.\n"); } } } int colvarbias_meta::set_state_params(std::string const &state_conf) { std::string new_replica = ""; if (colvarparse::get_keyval(state_conf, "replicaID", new_replica, std::string(""), colvarparse::parse_silent) && (new_replica != this->replica_id)) { cvm::error("Error: in the state file, the " "\"metadynamics\" block has a different replicaID: different system?\n", INPUT_ERROR); return INPUT_ERROR; } return COLVARS_OK; } std::istream & colvarbias_meta::read_state_data(std::istream& is) { bool grids_from_restart_file = use_grids; 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; if (has_data) { if (cvm::debug()) cvm::log("Backupping grids for metadynamics bias \""+ this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+".\n"); hills_energy_backup = hills_energy; hills_energy_gradients_backup = hills_energy_gradients; hills_energy = new colvar_grid_scalar(colvars); hills_energy_gradients = new colvar_grid_gradient(colvars); } size_t 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; } is.clear(); is.seekg(hills_energy_pos, std::ios::beg); is.setstate(std::ios::failbit); return is; } else if (!(key == std::string("hills_energy")) || !(hills_energy->read_restart(is))) { is.clear(); is.seekg(hills_energy_pos, std::ios::beg); grids_from_restart_file = false; if (!rebin_grids) { if (hills_energy_backup == NULL) cvm::fatal_error("Error: couldn't read the free 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 { if (comm == single_replica) cvm::log("Error: couldn't read the free energy grid for metadynamics bias \""+ this->name+"\".\n"); 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; } } } size_t 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); grids_from_restart_file = false; if (!rebin_grids) { if (hills_energy_backup == NULL) cvm::fatal_error("Error: couldn't read the free energy 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 { if (comm == single_replica) cvm::log("Error: couldn't read the free energy gradients grid for metadynamics bias \""+ this->name+"\".\n"); 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"); 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; } } bool const existing_hills = !hills.empty(); size_t const old_hills_size = hills.size(); hill_iter old_hills_end = hills.end(); hill_iter old_hills_off_grid_end = hills_off_grid.end(); // read the hills explicitly written (if there are any) 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"); } is.clear(); new_hills_begin = hills.end(); if (grids_from_restart_file) { if (hills.size() > old_hills_size) cvm::log("Read "+cvm::to_str(hills.size())+ " hills in addition to the grids.\n"); } else { if (!hills.empty()) cvm::log("Read "+cvm::to_str(hills.size())+" hills.\n"); } if (rebin_grids) { // allocate new grids (based on the new boundaries and widths just // read from the configuration file), and project onto them the // grids just read from the restart file colvar_grid_scalar *new_hills_energy = new colvar_grid_scalar(colvars); colvar_grid_gradient *new_hills_energy_gradients = new colvar_grid_gradient(colvars); if (!grids_from_restart_file || (keep_hills && !hills.empty())) { // if there are hills, recompute the new grids from them cvm::log("Rebinning the energy and forces grids from "+ cvm::to_str(hills.size())+" hills (this may take a while)...\n"); project_hills(hills.begin(), hills.end(), new_hills_energy, new_hills_energy_gradients, true); cvm::log("rebinning done.\n"); } else { // otherwise, use the grids in the restart file cvm::log("Rebinning the energy and forces grids " "from the grids in the restart file.\n"); new_hills_energy->map_grid(*hills_energy); new_hills_energy_gradients->map_grid(*hills_energy_gradients); } delete hills_energy; delete hills_energy_gradients; hills_energy = new_hills_energy; hills_energy_gradients = new_hills_energy_gradients; // assuming that some boundaries have expanded, eliminate those // off-grid hills that aren't necessary any more 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"); } } if (cvm::debug()) cvm::log("colvarbias_meta::read_restart() done\n"); if (existing_hills) { hills.erase(hills.begin(), old_hills_end); hills_off_grid.erase(hills_off_grid.begin(), old_hills_off_grid_end); } has_data = true; if (comm != single_replica) { read_replica_files(); } return is; } std::istream & colvarbias_meta::read_hill(std::istream &is) { if (!is) return is; // do nothing if failbit is set size_t const start_pos = is.tellg(); std::string data; if ( !(is >> read_block("hill", data)) ) { is.clear(); is.seekg(start_pos, std::ios::beg); is.setstate(std::ios::failbit); return is; } size_t h_it; get_keyval(data, "step", h_it, 0, parse_silent); if (h_it <= state_file_step) { 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; } cvm::real h_weight; get_keyval(data, "weight", h_weight, hill_weight, parse_silent); std::vector h_centers(num_variables()); for (size_t i = 0; i < num_variables(); i++) { h_centers[i].type(variables(i)->value()); } { // it is safer to read colvarvalue objects one at a time; // TODO: change this it later std::string centers_input; - key_lookup(data, "centers", centers_input); + key_lookup(data, "centers", ¢ers_input); std::istringstream centers_is(centers_input); for (size_t i = 0; i < num_variables(); i++) { centers_is >> h_centers[i]; } } std::vector h_widths(num_variables()); get_keyval(data, "widths", h_widths, std::vector(num_variables(), (std::sqrt(2.0 * PI) / 2.0)), parse_silent); std::string h_replica = ""; if (comm != single_replica) { get_keyval(data, "replicaID", h_replica, replica_id, parse_silent); if (h_replica != replica_id) cvm::fatal_error("Error: trying to read a hill created by replica \""+h_replica+ "\" for replica \""+replica_id+ "\"; did you swap output files?\n"); } hill_iter const hills_end = hills.end(); hills.push_back(hill(h_it, h_weight, h_centers, h_widths, h_replica)); if (new_hills_begin == hills_end) { // if new_hills_begin is unset, set it for the first time new_hills_begin = hills.end(); new_hills_begin--; } if (use_grids) { // 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); if (min_dist < (3.0 * std::floor(hill_width)) + 1.0) { hills_off_grid.push_back(hills.back()); } } has_data = true; return is; } int colvarbias_meta::setup_output() { output_prefix = cvm::output_prefix(); if (cvm::num_biases_feature(colvardeps::f_cvb_calc_pmf) > 1) { // if this is not the only free energy integrator, append // this bias's name, to distinguish it from the output of the other // biases producing a .pmf file output_prefix += ("."+this->name); } if (comm == multiple_replicas) { // TODO: one may want to specify the path manually for intricated filesystems? char *pwd = new char[3001]; if (GETCWD(pwd, 3000) == NULL) cvm::fatal_error("Error: cannot get the path of the current working directory.\n"); replica_list_file = (std::string(pwd)+std::string(PATHSEP)+ this->name+"."+replica_id+".files.txt"); // replica_hills_file and replica_state_file are those written // by the current replica; within the mirror biases, they are // those by another replica replica_hills_file = (std::string(pwd)+std::string(PATHSEP)+ cvm::output_prefix()+".colvars."+this->name+"."+replica_id+".hills"); replica_state_file = (std::string(pwd)+std::string(PATHSEP)+ cvm::output_prefix()+".colvars."+this->name+"."+replica_id+".state"); delete[] pwd; // now register this replica // first check that it isn't already there bool registered_replica = false; std::ifstream reg_is(replicas_registry_file.c_str()); if (reg_is.is_open()) { // the file may not be there yet std::string existing_replica(""); std::string existing_replica_file(""); while ((reg_is >> existing_replica) && existing_replica.size() && (reg_is >> existing_replica_file) && existing_replica_file.size()) { if (existing_replica == replica_id) { // this replica was already registered replica_list_file = existing_replica_file; reg_is.close(); registered_replica = true; break; } } reg_is.close(); } // if this replica was not included yet, we should generate a // new record for it: but first, we write this replica's files, // for the others to read // open the "hills" buffer file - if (!replica_hills_os.is_open()) { - cvm::backup_file(replica_hills_file.c_str()); - replica_hills_os.open(replica_hills_file.c_str()); - if (!replica_hills_os.is_open()) - cvm::error("Error: in opening file \""+ - replica_hills_file+"\" for writing.\n", FILE_ERROR); - replica_hills_os.setf(std::ios::scientific, std::ios::floatfield); + if (!replica_hills_os) { + cvm::proxy->backup_file(replica_hills_file); + replica_hills_os = cvm::proxy->output_stream(replica_hills_file); + if (!replica_hills_os) return cvm::get_error(); + replica_hills_os->setf(std::ios::scientific, std::ios::floatfield); } // write the state file (so that there is always one available) write_replica_state_file(); // schedule to read the state files of the other replicas for (size_t ir = 0; ir < replicas.size(); ir++) { (replicas[ir])->replica_state_file_in_sync = false; } // 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::ofstream list_os(replica_list_file.c_str(), - (use_grids ? std::ios::trunc : std::ios::app)); - if (! list_os.is_open()) - cvm::fatal_error("Error: in opening file \""+ - replica_list_file+"\" for writing.\n"); - list_os << "stateFile " << replica_state_file << "\n"; - list_os << "hillsFile " << replica_hills_file << "\n"; - list_os.close(); - - // finally, if add a new record for this replica to the registry + std::ostream *list_os = + cvm::proxy->output_stream(replica_list_file, + (use_grids ? std::ios_base::trunc : + std::ios_base::app)); + if (!list_os) { + return cvm::get_error(); + } + *list_os << "stateFile " << replica_state_file << "\n"; + *list_os << "hillsFile " << replica_hills_file << "\n"; + cvm::proxy->close_output_stream(replica_list_file); + + // finally, add a new record for this replica to the registry if (! registered_replica) { - std::ofstream reg_os(replicas_registry_file.c_str(), std::ios::app); - if (! reg_os.is_open()) - cvm::error("Error: in opening file \""+ - replicas_registry_file+"\" for writing.\n", FILE_ERROR); - reg_os << replica_id << " " << replica_list_file << "\n"; - reg_os.close(); + std::ostream *reg_os = + cvm::proxy->output_stream(replicas_registry_file, + std::ios::app); + if (!reg_os) { + return cvm::get_error(); + } + *reg_os << replica_id << " " << replica_list_file << "\n"; + cvm::proxy->close_output_stream(replicas_registry_file); } } if (b_hills_traj) { - std::string const traj_file_name(cvm::output_prefix()+ - ".colvars."+this->name+ - ( (comm != single_replica) ? - ("."+replica_id) : - ("") )+ - ".hills.traj"); - if (!hills_traj_os.is_open()) { - cvm::backup_file(traj_file_name.c_str()); - hills_traj_os.open(traj_file_name.c_str()); + if (!hills_traj_os) { + hills_traj_os = cvm::proxy->output_stream(hills_traj_file_name()); + if (!hills_traj_os) return cvm::get_error(); } - if (!hills_traj_os.is_open()) - cvm::error("Error: in opening hills output file \"" + - traj_file_name+"\".\n", FILE_ERROR); } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } +std::string const colvarbias_meta::hills_traj_file_name() const +{ + return std::string(cvm::output_prefix()+ + ".colvars."+this->name+ + ( (comm != single_replica) ? + ("."+replica_id) : + ("") )+ + ".hills.traj"); +} + + std::string const colvarbias_meta::get_state_params() const { std::ostringstream os; if (this->comm != single_replica) os << "replicaID " << this->replica_id << "\n"; return (colvarbias::get_state_params() + os.str()); } std::ostream & colvarbias_meta::write_state_data(std::ostream& 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); new_hills_begin = hills.end(); // write down the grids to the restart file os << " hills_energy\n"; hills_energy->write_restart(os); os << " hills_energy_gradients\n"; hills_energy_gradients->write_restart(os); } 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; } } 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; } } return os; } int colvarbias_meta::write_state_to_replicas() { if (comm != single_replica) { write_replica_state_file(); // schedule to reread the state files of the other replicas (they // have also rewritten them) for (size_t ir = 0; ir < replicas.size(); ir++) { (replicas[ir])->replica_state_file_in_sync = false; } } return COLVARS_OK; } int colvarbias_meta::write_output_files() { if (dump_fes) { write_pmf(); } return COLVARS_OK; } void colvarbias_meta::write_pmf() { // allocate a new grid to store the pmf colvar_grid_scalar *pmf = new colvar_grid_scalar(*hills_energy); pmf->setup(); if ((comm == single_replica) || (dump_replica_fes)) { // output the PMF from this instance or replica pmf->reset(); pmf->add_grid(*hills_energy); cvm::real const max = pmf->maximum_value(); pmf->add_constant(-1.0 * max); pmf->multiply_constant(-1.0); if (well_tempered) { cvm::real const well_temper_scale = (bias_temperature + cvm::temperature()) / bias_temperature; pmf->multiply_constant(well_temper_scale); } { std::string const fes_file_name(this->output_prefix + ((comm != single_replica) ? ".partial" : "") + (dump_fes_save ? "."+cvm::to_str(cvm::step_absolute()) : "") + ".pmf"); - cvm::backup_file(fes_file_name.c_str()); - cvm::ofstream fes_os(fes_file_name.c_str()); - pmf->write_multicol(fes_os); - fes_os.close(); + cvm::proxy->backup_file(fes_file_name); + std::ostream *fes_os = cvm::proxy->output_stream(fes_file_name); + pmf->write_multicol(*fes_os); + cvm::proxy->close_output_stream(fes_file_name); } } + if (comm != single_replica) { // output the combined PMF from all replicas pmf->reset(); pmf->add_grid(*hills_energy); for (size_t ir = 0; ir < replicas.size(); ir++) { pmf->add_grid(*(replicas[ir]->hills_energy)); } cvm::real const max = pmf->maximum_value(); pmf->add_constant(-1.0 * max); pmf->multiply_constant(-1.0); if (well_tempered) { cvm::real const well_temper_scale = (bias_temperature + cvm::temperature()) / bias_temperature; pmf->multiply_constant(well_temper_scale); } std::string const fes_file_name(this->output_prefix + (dump_fes_save ? "."+cvm::to_str(cvm::step_absolute()) : "") + ".pmf"); - cvm::backup_file(fes_file_name.c_str()); - cvm::ofstream fes_os(fes_file_name.c_str()); - pmf->write_multicol(fes_os); - fes_os.close(); + cvm::proxy->backup_file(fes_file_name); + std::ostream *fes_os = cvm::proxy->output_stream(fes_file_name); + pmf->write_multicol(*fes_os); + cvm::proxy->close_output_stream(fes_file_name); } delete pmf; } int colvarbias_meta::write_replica_state_file() { if (cvm::debug()) { cvm::log("Writing replica state file for bias \""+name+"\"\n"); } // write down also the restart for the other replicas cvm::backup_file(replica_state_file.c_str()); std::ostream *rep_state_os = cvm::proxy->output_stream(replica_state_file); if (rep_state_os == NULL) { cvm::error("Error: in opening file \""+ replica_state_file+"\" for writing.\n", FILE_ERROR); return FILE_ERROR; } rep_state_os->setf(std::ios::scientific, std::ios::floatfield); if (!write_state(*rep_state_os)) { cvm::error("Error: in writing to file \""+ replica_state_file+"\".\n", FILE_ERROR); cvm::proxy->close_output_stream(replica_state_file); return FILE_ERROR; } cvm::proxy->close_output_stream(replica_state_file); // rep_state_os.setf(std::ios::scientific, std::ios::floatfield); // rep_state_os << "\n" // << "metadynamics {\n" // << " configuration {\n" // << " name " << this->name << "\n" // << " step " << cvm::step_absolute() << "\n"; // if (this->comm != single_replica) { // rep_state_os << " replicaID " << this->replica_id << "\n"; // } // rep_state_os << " }\n\n"; // rep_state_os << " hills_energy\n"; // rep_state_os << std::setprecision(cvm::cv_prec) // << std::setw(cvm::cv_width); // hills_energy->write_restart(rep_state_os); // rep_state_os << " hills_energy_gradients\n"; // rep_state_os << std::setprecision(cvm::cv_prec) // << std::setw(cvm::cv_width); // hills_energy_gradients->write_restart(rep_state_os); // 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++) { // rep_state_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++) { // rep_state_os << *h; // } // } // rep_state_os << "}\n\n"; // rep_state_os.close(); // reopen the hills file - replica_hills_os.close(); - cvm::backup_file(replica_hills_file.c_str()); - replica_hills_os.open(replica_hills_file.c_str()); - if (!replica_hills_os.is_open()) - cvm::fatal_error("Error: in opening file \""+ - replica_hills_file+"\" for writing.\n"); - replica_hills_os.setf(std::ios::scientific, std::ios::floatfield); + cvm::proxy->close_output_stream(replica_hills_file); + cvm::proxy->backup_file(replica_hills_file); + replica_hills_os = cvm::proxy->output_stream(replica_hills_file); + if (!replica_hills_os) return cvm::get_error(); + replica_hills_os->setf(std::ios::scientific, std::ios::floatfield); return COLVARS_OK; } std::string colvarbias_meta::hill::output_traj() { std::ostringstream os; os.setf(std::ios::fixed, std::ios::floatfield); os << std::setw(cvm::it_width) << it << " "; os.setf(std::ios::scientific, std::ios::floatfield); size_t i; os << " "; for (i = 0; i < centers.size(); i++) { os << " "; os << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << centers[i]; } os << " "; for (i = 0; i < widths.size(); i++) { os << " "; os << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << widths[i]; } os << " "; os << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) << W << "\n"; return os.str(); } 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"; os << " widths "; for (i = 0; i < (h.widths).size(); i++) { os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << h.widths[i]; } os << "\n"; os << "}\n"; return os; } diff --git a/lib/colvars/colvarbias_meta.h b/lib/colvars/colvarbias_meta.h index 01921eaf6..249f7342b 100644 --- a/lib/colvars/colvarbias_meta.h +++ b/lib/colvars/colvarbias_meta.h @@ -1,439 +1,442 @@ // -*- 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_META_H #define COLVARBIAS_META_H #include #include #include #include #include "colvarbias.h" #include "colvargrid.h" /// Metadynamics bias (implementation of \link colvarbias \endlink) class colvarbias_meta : public colvarbias { public: /// Communication between different replicas enum Communication { /// One replica (default) single_replica, /// Hills added concurrently by several replicas multiple_replicas }; /// Communication between different replicas Communication comm; colvarbias_meta(char const *key); virtual int init(std::string const &conf); virtual int init_well_tempered_params(std::string const &conf); virtual int init_ebmeta_params(std::string const &conf); virtual ~colvarbias_meta(); virtual int update(); virtual int update_grid_params(); virtual int update_bias(); virtual int update_grid_data(); virtual int replica_share(); virtual int calc_energy(std::vector const &values = std::vector(0)); virtual int calc_forces(std::vector const &values = std::vector(0)); 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 int setup_output(); virtual int write_output_files(); virtual void write_pmf(); virtual int write_state_to_replicas(); class hill; typedef std::list::iterator hill_iter; protected: /// \brief width of a hill /// /// The local width of each collective variable, multiplied by this /// number, provides the hill width along that direction cvm::real hill_width; /// \brief Number of simulation steps between two hills size_t new_hill_freq; /// Write the hill logfile bool b_hills_traj; /// Logfile of hill management (creation and deletion) - cvm::ofstream hills_traj_os; + std::ostream *hills_traj_os; + + /// Name of the hill logfile + std::string const hills_traj_file_name() const; /// \brief List of hills used on this bias (total); if a grid is /// employed, these don't need to be updated at every time step std::list hills; /// \brief Iterator to the first of the "newest" hills (when using /// grids, those who haven't been mapped yet) hill_iter new_hills_begin; /// \brief List of hills used on this bias that are on the boundary /// edges; these are updated regardless of whether hills are used std::list hills_off_grid; /// \brief Same as new_hills_begin, but for the off-grid ones hill_iter new_hills_off_grid_begin; /// Regenerate the hills_off_grid list void recount_hills_off_grid(hill_iter h_first, hill_iter h_last, colvar_grid_scalar *ge); /// Read a hill from a file std::istream & read_hill(std::istream &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 virtual std::list::const_iterator create_hill(hill const &h); /// \brief Remove a previously saved hill (returns an iterator for /// the next hill in the list) virtual std::list::const_iterator delete_hill(hill_iter &h); /// \brief Calculate the values of the hills, incrementing /// bias_energy virtual void calc_hills(hill_iter h_first, hill_iter h_last, cvm::real &energy, std::vector const &values = std::vector(0)); /// \brief Calculate the forces acting on the i-th colvar, /// incrementing colvar_forces[i]; must be called after calc_hills /// each time the values of the colvars are changed virtual void calc_hills_force(size_t const &i, hill_iter h_first, hill_iter h_last, std::vector &forces, std::vector const &values = std::vector(0)); /// Height of new hills cvm::real hill_weight; /// \brief Bin the hills on grids of energy and forces, and use them /// to force the colvars (as opposed to deriving the hills analytically) bool use_grids; /// \brief Rebin the hills upon restarting bool rebin_grids; /// \brief Should the grids be expanded if necessary? bool expand_grids; /// \brief How often the hills should be projected onto the grids size_t grids_freq; /// \brief Whether to keep the hills in the restart file (e.g. to do /// meaningful accurate rebinning afterwards) bool keep_hills; /// \brief Dump the free energy surface (.pmf file) every restartFrequency bool dump_fes; /// \brief Dump the free energy surface (.pmf file) every restartFrequency /// using only the hills from this replica (only applicable to more than one replica) bool dump_replica_fes; /// \brief Dump the free energy surface files at different /// time steps, appending the step number to each file bool dump_fes_save; /// \brief Whether to use well-tempered metadynamics bool well_tempered; /// \brief Biasing temperature in well-tempered metadynamics cvm::real bias_temperature; // EBmeta parameters bool ebmeta; colvar_grid_scalar* target_dist; std::string target_dist_file; cvm::real target_dist_volume; size_t ebmeta_equil_steps; /// \brief Try to read the restart information by allocating new /// grids before replacing the current ones (used e.g. in /// multiple_replicas) bool safely_read_restart; /// Hill energy, cached on a grid colvar_grid_scalar *hills_energy; /// Hill forces, cached on a grid colvar_grid_gradient *hills_energy_gradients; /// \brief Project the selected hills onto grids void project_hills(hill_iter h_first, hill_iter h_last, colvar_grid_scalar *ge, colvar_grid_gradient *gf, bool print_progress = false); // Multiple Replicas variables and functions /// \brief Identifier for this replica std::string replica_id; /// \brief File containing the paths to the output files from this replica std::string replica_file_name; /// \brief Read the existing replicas on registry virtual void update_replicas_registry(); /// \brief Read new data from replicas' files virtual void read_replica_files(); /// \brief Write data to other replicas virtual int write_replica_state_file(); /// \brief Additional, "mirror" metadynamics biases, to collect info /// from the other replicas /// /// These are supposed to be synchronized by reading data from the /// other replicas, and not be modified by the "local" replica std::vector replicas; /// \brief Frequency at which data the "mirror" biases are updated size_t replica_update_freq; /// List of replicas (and their output list files): contents are /// copied into replicas_registry for convenience std::string replicas_registry_file; /// List of replicas (and their output list files) std::string replicas_registry; /// List of files written by this replica std::string replica_list_file; /// Hills energy and gradients written specifically for other /// replica (in addition to its own restart file) std::string replica_state_file; /// Whether a mirror bias has read the latest version of its state file bool replica_state_file_in_sync; /// If there was a failure reading one of the files (because they /// are not complete), this counter is incremented size_t update_status; /// Explicit hills communicated between replicas /// /// This file becomes empty after replica_state_file is rewritten std::string replica_hills_file; /// \brief Output stream corresponding to replica_hills_file - cvm::ofstream replica_hills_os; + std::ostream *replica_hills_os; /// Position within replica_hills_file (when reading it) int replica_hills_file_pos; }; /// \brief A hill for the metadynamics bias class colvarbias_meta::hill { protected: /// Value of the hill function (ranges between 0 and 1) cvm::real hill_value; /// Scale factor, which could be modified at runtime (default: 1) cvm::real sW; /// Maximum height in energy of the hill cvm::real W; /// Center of the hill in the collective variable space std::vector centers; /// Widths of the hill in the collective variable space std::vector widths; public: friend class colvarbias_meta; /// Time step at which this hill was added size_t it; /// Identity of the replica who added this hill (only in multiple replica simulations) std::string replica; /// \brief Runtime constructor: data are read directly from /// collective variables \param weight Weight of the hill \param /// cv Pointer to the array of collective variables involved \param /// replica (optional) Identity of the replica which creates the /// hill inline hill(cvm::real const &W_in, std::vector &cv, cvm::real const &hill_width, std::string const &replica_in = "") : sW(1.0), W(W_in), centers(cv.size()), widths(cv.size()), it(cvm::step_absolute()), replica(replica_in) { for (size_t i = 0; i < cv.size(); i++) { centers[i].type(cv[i]->value()); centers[i] = cv[i]->value(); widths[i] = cv[i]->width * hill_width; } if (cvm::debug()) cvm::log("New hill, applied to "+cvm::to_str(cv.size())+ " collective variables, with centers "+ cvm::to_str(centers)+", widths "+ cvm::to_str(widths)+" and weight "+ cvm::to_str(W)+".\n"); } /// \brief General constructor: all data are explicitly passed as /// arguments (used for instance when reading hills saved on a /// file) \param it Time step of creation of the hill \param /// weight Weight of the hill \param centers Center of the hill /// \param widths Width of the hill around centers \param replica /// (optional) Identity of the replica which creates the hill inline hill(size_t const &it_in, cvm::real const &W_in, std::vector const ¢ers_in, std::vector const &widths_in, std::string const &replica_in = "") : sW(1.0), W(W_in), centers(centers_in), widths(widths_in), it(it_in), replica(replica_in) {} /// Copy constructor inline hill(colvarbias_meta::hill const &h) : sW(1.0), W(h.W), centers(h.centers), widths(h.widths), it(h.it), replica(h.replica) {} /// Destructor inline ~hill() {} /// Get the energy inline cvm::real energy() { return W * sW * hill_value; } /// Get the energy using another hill weight inline cvm::real energy(cvm::real const &new_weight) { return new_weight * sW * hill_value; } /// Get the current hill value inline cvm::real const &value() { return hill_value; } /// Set the hill value as specified inline void value(cvm::real const &new_value) { hill_value = new_value; } /// Get the weight inline cvm::real weight() { return W * sW; } /// Scale the weight with this factor (by default 1.0 is used) inline void scale(cvm::real const &new_scale_fac) { sW = new_scale_fac; } /// Get the center of the hill inline std::vector & center() { return centers; } /// Get the i-th component of the center inline colvarvalue & center(size_t const &i) { return centers[i]; } /// Comparison operator inline friend bool operator < (hill const &h1, hill const &h2) { if (h1.it < h2.it) return true; else return false; } /// Comparison operator inline friend bool operator <= (hill const &h1, hill const &h2) { if (h1.it <= h2.it) return true; else return false; } /// Comparison operator inline friend bool operator > (hill const &h1, hill const &h2) { if (h1.it > h2.it) return true; else return false; } /// Comparison operator inline friend bool operator >= (hill const &h1, hill const &h2) { if (h1.it >= h2.it) return true; else return false; } /// Comparison operator inline friend bool operator == (hill const &h1, hill const &h2) { if ( (h1.it >= h2.it) && (h1.replica == h2.replica) ) return true; else return false; } /// Represent the hill ina string suitable for a trajectory file std::string output_traj(); /// Write the hill to an output stream inline friend std::ostream & operator << (std::ostream &os, hill const &h); }; #endif diff --git a/lib/colvars/colvarbias_restraint.cpp b/lib/colvars/colvarbias_restraint.cpp index 159d9eae6..bb6d6164e 100644 --- a/lib/colvars/colvarbias_restraint.cpp +++ b/lib/colvars/colvarbias_restraint.cpp @@ -1,1433 +1,1449 @@ // -*- 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 "colvarbias_restraint.h" colvarbias_restraint::colvarbias_restraint(char const *key) : colvarbias(key) { } int colvarbias_restraint::init(std::string const &conf) { colvarbias::init(conf); enable(f_cvb_apply_force); if (cvm::debug()) cvm::log("Initializing a new restraint bias.\n"); return COLVARS_OK; } int colvarbias_restraint::update() { // Update base class (bias_energy and colvar_forces are zeroed there) colvarbias::update(); // Force and energy calculation for (size_t i = 0; i < num_variables(); i++) { bias_energy += restraint_potential(i); colvar_forces[i].type(variables(i)->value()); colvar_forces[i].is_derivative(); colvar_forces[i] = restraint_force(i); } if (cvm::debug()) cvm::log("Done updating the restraint bias \""+this->name+"\".\n"); if (cvm::debug()) cvm::log("Current forces for the restraint bias \""+ this->name+"\": "+cvm::to_str(colvar_forces)+".\n"); return COLVARS_OK; } colvarbias_restraint::~colvarbias_restraint() { } std::string const colvarbias_restraint::get_state_params() const { return colvarbias::get_state_params(); } int colvarbias_restraint::set_state_params(std::string const &conf) { return colvarbias::set_state_params(conf); } std::ostream & colvarbias_restraint::write_traj_label(std::ostream &os) { return colvarbias::write_traj_label(os); } std::ostream & colvarbias_restraint::write_traj(std::ostream &os) { return colvarbias::write_traj(os); } colvarbias_restraint_centers::colvarbias_restraint_centers(char const *key) : colvarbias(key), colvarbias_restraint(key) { } int colvarbias_restraint_centers::init(std::string const &conf) { size_t i; bool null_centers = (colvar_centers.size() == 0); if (null_centers) { // try to initialize the restraint centers for the first time colvar_centers.resize(num_variables()); colvar_centers_raw.resize(num_variables()); for (i = 0; i < num_variables(); i++) { colvar_centers[i].type(variables(i)->value()); colvar_centers[i].reset(); colvar_centers_raw[i].type(variables(i)->value()); colvar_centers_raw[i].reset(); } } if (get_keyval(conf, "centers", colvar_centers, colvar_centers)) { for (i = 0; i < num_variables(); i++) { if (cvm::debug()) { cvm::log("colvarbias_restraint: parsing initial centers, i = "+cvm::to_str(i)+".\n"); } colvar_centers_raw[i] = colvar_centers[i]; colvar_centers[i].apply_constraints(); } null_centers = false; } if (null_centers) { colvar_centers.clear(); cvm::error("Error: must define the initial centers of the restraints.\n", INPUT_ERROR); return INPUT_ERROR; } if (colvar_centers.size() != num_variables()) { cvm::error("Error: number of centers does not match " "that of collective variables.\n", INPUT_ERROR); return INPUT_ERROR; } return COLVARS_OK; } int colvarbias_restraint_centers::change_configuration(std::string const &conf) { if (get_keyval(conf, "centers", colvar_centers, colvar_centers)) { for (size_t i = 0; i < num_variables(); i++) { colvar_centers[i].type(variables(i)->value()); colvar_centers[i].apply_constraints(); colvar_centers_raw[i].type(variables(i)->value()); colvar_centers_raw[i] = colvar_centers[i]; } } return COLVARS_OK; } colvarbias_restraint_k::colvarbias_restraint_k(char const *key) : colvarbias(key), colvarbias_restraint(key) { force_k = -1.0; } int colvarbias_restraint_k::init(std::string const &conf) { get_keyval(conf, "forceConstant", force_k, (force_k > 0.0 ? force_k : 1.0)); if (force_k < 0.0) { cvm::error("Error: undefined or invalid force constant.\n", INPUT_ERROR); return INPUT_ERROR; } return COLVARS_OK; } int colvarbias_restraint_k::change_configuration(std::string const &conf) { get_keyval(conf, "forceConstant", force_k, force_k); return COLVARS_OK; } colvarbias_restraint_moving::colvarbias_restraint_moving(char const *key) { target_nstages = 0; target_nsteps = 0; stage = 0; b_chg_centers = false; b_chg_force_k = false; } int colvarbias_restraint_moving::init(std::string const &conf) { if (b_chg_centers && b_chg_force_k) { cvm::error("Error: cannot specify both targetCenters and targetForceConstant.\n", INPUT_ERROR); return INPUT_ERROR; } if (b_chg_centers || b_chg_force_k) { get_keyval(conf, "targetNumSteps", target_nsteps, target_nsteps); if (!target_nsteps) { cvm::error("Error: targetNumSteps must be non-zero.\n", INPUT_ERROR); return cvm::get_error(); } if (get_keyval(conf, "targetNumStages", target_nstages, target_nstages) && lambda_schedule.size()) { cvm::error("Error: targetNumStages and lambdaSchedule are incompatible.\n", INPUT_ERROR); return cvm::get_error(); } } return COLVARS_OK; } 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 if (target_nstages) { os << "stage " << std::setw(cvm::it_width) << stage << "\n"; } } return os.str(); } int colvarbias_restraint_moving::set_state_params(std::string const &conf) { if (b_chg_centers || b_chg_force_k) { if (target_nstages) { // cvm::log ("Reading current stage from the restart.\n"); if (!get_keyval(conf, "stage", stage)) cvm::error("Error: current stage is missing from the restart.\n"); } } return COLVARS_OK; } colvarbias_restraint_centers_moving::colvarbias_restraint_centers_moving(char const *key) : colvarbias(key), colvarbias_restraint(key), colvarbias_restraint_centers(key), colvarbias_restraint_moving(key) { b_chg_centers = false; b_output_centers = false; b_output_acc_work = false; acc_work = 0.0; } int colvarbias_restraint_centers_moving::init(std::string const &conf) { colvarbias_restraint_centers::init(conf); if (cvm::debug()) { cvm::log("colvarbias_restraint: parsing target centers.\n"); } size_t i; if (get_keyval(conf, "targetCenters", target_centers, colvar_centers)) { if (colvar_centers.size() != num_variables()) { cvm::error("Error: number of target centers does not match " "that of collective variables.\n"); } b_chg_centers = true; for (i = 0; i < target_centers.size(); i++) { target_centers[i].apply_constraints(); } } if (b_chg_centers) { // parse moving restraint options colvarbias_restraint_moving::init(conf); } else { target_centers.clear(); return COLVARS_OK; } get_keyval(conf, "outputCenters", b_output_centers, b_output_centers); get_keyval(conf, "outputAccumulatedWork", b_output_acc_work, b_output_acc_work); return COLVARS_OK; } int colvarbias_restraint_centers_moving::update() { if (b_chg_centers) { if (cvm::debug()) { cvm::log("Updating centers for the restraint bias \""+ this->name+"\": "+cvm::to_str(colvar_centers)+".\n"); } if (!centers_incr.size()) { // if this is the first calculation, calculate the advancement // at each simulation step (or stage, if applicable) // (take current stage into account: it can be non-zero // if we are restarting a staged calculation) centers_incr.resize(num_variables()); for (size_t i = 0; i < num_variables(); i++) { centers_incr[i].type(variables(i)->value()); centers_incr[i] = (target_centers[i] - colvar_centers_raw[i]) / cvm::real( target_nstages ? (target_nstages - stage) : (target_nsteps - cvm::step_absolute())); } if (cvm::debug()) { cvm::log("Center increment for the restraint bias \""+ this->name+"\": "+cvm::to_str(centers_incr)+" at stage "+cvm::to_str(stage)+ ".\n"); } } if (target_nstages) { if ((cvm::step_relative() > 0) && (cvm::step_absolute() % target_nsteps) == 0 && stage < target_nstages) { for (size_t i = 0; i < num_variables(); i++) { colvar_centers_raw[i] += centers_incr[i]; colvar_centers[i] = colvar_centers_raw[i]; variables(i)->wrap(colvar_centers[i]); colvar_centers[i].apply_constraints(); } stage++; cvm::log("Moving restraint \"" + this->name + "\" stage " + cvm::to_str(stage) + " : setting centers to " + cvm::to_str(colvar_centers) + " at step " + cvm::to_str(cvm::step_absolute())); } } else if ((cvm::step_relative() > 0) && (cvm::step_absolute() <= target_nsteps)) { // move the restraint centers in the direction of the targets // (slow growth) for (size_t i = 0; i < num_variables(); i++) { colvar_centers_raw[i] += centers_incr[i]; colvar_centers[i] = colvar_centers_raw[i]; variables(i)->wrap(colvar_centers[i]); colvar_centers[i].apply_constraints(); } } if (cvm::debug()) { cvm::log("New centers for the restraint bias \""+ this->name+"\": "+cvm::to_str(colvar_centers)+".\n"); } } return COLVARS_OK; } int colvarbias_restraint_centers_moving::update_acc_work() { if (b_output_acc_work) { if ((cvm::step_relative() > 0) || (cvm::step_absolute() == 0)) { for (size_t i = 0; i < num_variables(); i++) { // project forces on the calculated increments at this step acc_work += colvar_forces[i] * centers_incr[i]; } } } return COLVARS_OK; } std::string const colvarbias_restraint_centers_moving::get_state_params() const { std::ostringstream os; os.setf(std::ios::scientific, std::ios::floatfield); if (b_chg_centers) { size_t i; os << "centers "; for (i = 0; i < num_variables(); i++) { os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << colvar_centers[i]; } os << "\n"; os << "centers_raw "; for (i = 0; i < num_variables(); i++) { os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << colvar_centers_raw[i]; } os << "\n"; if (b_output_acc_work) { os << "accumulatedWork " << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) << acc_work << "\n"; } } return colvarbias_restraint_moving::get_state_params() + os.str(); } int colvarbias_restraint_centers_moving::set_state_params(std::string const &conf) { colvarbias_restraint::set_state_params(conf); if (b_chg_centers) { // cvm::log ("Reading the updated restraint centers from the restart.\n"); if (!get_keyval(conf, "centers", colvar_centers)) cvm::error("Error: restraint centers are missing from the restart.\n"); if (!get_keyval(conf, "centers_raw", colvar_centers_raw)) cvm::error("Error: \"raw\" restraint centers are missing from the restart.\n"); if (b_output_acc_work) { if (!get_keyval(conf, "accumulatedWork", acc_work)) cvm::error("Error: accumulatedWork is missing from the restart.\n"); } } return COLVARS_OK; } std::ostream & colvarbias_restraint_centers_moving::write_traj_label(std::ostream &os) { if (b_output_centers) { for (size_t i = 0; i < num_variables(); i++) { size_t const this_cv_width = (variables(i)->value()).output_width(cvm::cv_width); os << " x0_" << cvm::wrap_string(variables(i)->name, this_cv_width-3); } } if (b_output_acc_work) { os << " W_" << cvm::wrap_string(this->name, cvm::en_width-2); } return os; } std::ostream & colvarbias_restraint_centers_moving::write_traj(std::ostream &os) { if (b_output_centers) { for (size_t i = 0; i < num_variables(); i++) { os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << colvar_centers[i]; } } if (b_output_acc_work) { os << " " << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) << acc_work; } return os; } colvarbias_restraint_k_moving::colvarbias_restraint_k_moving(char const *key) : colvarbias(key), colvarbias_restraint(key), colvarbias_restraint_k(key), colvarbias_restraint_moving(key) { b_chg_force_k = false; target_equil_steps = 0; target_force_k = 0.0; starting_force_k = 0.0; force_k_exp = 1.0; restraint_FE = 0.0; } int colvarbias_restraint_k_moving::init(std::string const &conf) { colvarbias_restraint_k::init(conf); if (get_keyval(conf, "targetForceConstant", target_force_k, target_force_k)) { starting_force_k = force_k; b_chg_force_k = true; } if (b_chg_force_k) { // parse moving restraint options colvarbias_restraint_moving::init(conf); } else { return COLVARS_OK; } get_keyval(conf, "targetEquilSteps", target_equil_steps, target_equil_steps); if (get_keyval(conf, "lambdaSchedule", lambda_schedule, lambda_schedule) && target_nstages > 0) { cvm::error("Error: targetNumStages and lambdaSchedule are incompatible.\n", INPUT_ERROR); return cvm::get_error(); } if (lambda_schedule.size()) { // There is one more lambda-point than stages target_nstages = lambda_schedule.size() - 1; } if (get_keyval(conf, "targetForceExponent", force_k_exp, force_k_exp)) { if (! b_chg_force_k) cvm::log("Warning: not changing force constant: targetForceExponent will be ignored\n"); } if (force_k_exp < 1.0) { cvm::log("Warning: for all practical purposes, targetForceExponent should be 1.0 or greater.\n"); } return COLVARS_OK; } int colvarbias_restraint_k_moving::update() { if (b_chg_force_k) { cvm::real lambda; if (target_nstages) { if (cvm::step_absolute() == 0) { // Setup first stage of staged variable force constant calculation if (lambda_schedule.size()) { lambda = lambda_schedule[0]; } else { lambda = 0.0; } force_k = starting_force_k + (target_force_k - starting_force_k) * std::pow(lambda, force_k_exp); cvm::log("Restraint " + this->name + ", stage " + cvm::to_str(stage) + " : lambda = " + cvm::to_str(lambda) + ", k = " + cvm::to_str(force_k)); } // TI calculation: estimate free energy derivative // need current lambda if (lambda_schedule.size()) { lambda = lambda_schedule[stage]; } else { lambda = cvm::real(stage) / cvm::real(target_nstages); } if (target_equil_steps == 0 || cvm::step_absolute() % target_nsteps >= target_equil_steps) { // Start averaging after equilibration period, if requested // Square distance normalized by square colvar width cvm::real dist_sq = 0.0; for (size_t i = 0; i < num_variables(); i++) { dist_sq += d_restraint_potential_dk(i); } restraint_FE += 0.5 * force_k_exp * std::pow(lambda, force_k_exp - 1.0) * (target_force_k - starting_force_k) * dist_sq; } // Finish current stage... if (cvm::step_absolute() % target_nsteps == 0 && cvm::step_absolute() > 0) { cvm::log("Restraint " + this->name + " Lambda= " + cvm::to_str(lambda) + " dA/dLambda= " + cvm::to_str(restraint_FE / cvm::real(target_nsteps - target_equil_steps))); // ...and move on to the next one if (stage < target_nstages) { restraint_FE = 0.0; stage++; if (lambda_schedule.size()) { lambda = lambda_schedule[stage]; } else { lambda = cvm::real(stage) / cvm::real(target_nstages); } force_k = starting_force_k + (target_force_k - starting_force_k) * std::pow(lambda, force_k_exp); cvm::log("Restraint " + this->name + ", stage " + cvm::to_str(stage) + " : lambda = " + cvm::to_str(lambda) + ", k = " + cvm::to_str(force_k)); } } } else if (cvm::step_absolute() <= target_nsteps) { // update force constant (slow growth) lambda = cvm::real(cvm::step_absolute()) / cvm::real(target_nsteps); force_k = starting_force_k + (target_force_k - starting_force_k) * std::pow(lambda, force_k_exp); } } return COLVARS_OK; } std::string const colvarbias_restraint_k_moving::get_state_params() const { std::ostringstream os; os.setf(std::ios::scientific, std::ios::floatfield); if (b_chg_force_k) { os << "forceConstant " << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) << force_k << "\n"; } return colvarbias_restraint_moving::get_state_params() + os.str(); } int colvarbias_restraint_k_moving::set_state_params(std::string const &conf) { colvarbias_restraint::set_state_params(conf); if (b_chg_force_k) { // cvm::log ("Reading the updated force constant from the restart.\n"); if (!get_keyval(conf, "forceConstant", force_k, force_k)) cvm::error("Error: force constant is missing from the restart.\n"); } return COLVARS_OK; } std::ostream & colvarbias_restraint_k_moving::write_traj_label(std::ostream &os) { return os; } std::ostream & colvarbias_restraint_k_moving::write_traj(std::ostream &os) { return os; } // redefined due to legacy state file keyword "harmonic" std::istream & colvarbias_restraint::read_state(std::istream &is) { size_t const start_pos = is.tellg(); std::string key, brace, conf; if ( !(is >> key) || !(key == "restraint" || key == "harmonic") || !(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(is.tellg())+" in stream.\n", INPUT_ERROR); is.clear(); is.seekg(start_pos, std::ios::beg); is.setstate(std::ios::failbit); return is; } if (!read_state_data(is)) { cvm::error("Error: in reading state data for \""+bias_type+"\" bias \""+ this->name+"\" at position "+ cvm::to_str(is.tellg())+" in stream.\n", INPUT_ERROR); is.clear(); is.seekg(start_pos, std::ios::beg); is.setstate(std::ios::failbit); } is >> brace; if (brace != "}") { cvm::log("brace = "+brace+"\n"); cvm::error("Error: corrupt restart information for \""+bias_type+"\" bias \""+ this->name+"\": no matching brace at position "+ cvm::to_str(is.tellg())+" in stream.\n"); is.setstate(std::ios::failbit); } return is; } std::ostream & colvarbias_restraint::write_state(std::ostream &os) { os.setf(std::ios::scientific, std::ios::floatfield); os << "restraint {\n" << " configuration {\n"; std::istringstream is(get_state_params()); std::string line; while (std::getline(is, line)) { os << " " << line << "\n"; } os << " }\n"; write_state_data(os); os << "}\n\n"; return os; } colvarbias_restraint_harmonic::colvarbias_restraint_harmonic(char const *key) : colvarbias(key), colvarbias_restraint(key), colvarbias_restraint_centers(key), colvarbias_restraint_moving(key), colvarbias_restraint_k(key), colvarbias_restraint_centers_moving(key), colvarbias_restraint_k_moving(key) { } int colvarbias_restraint_harmonic::init(std::string const &conf) { colvarbias_restraint::init(conf); colvarbias_restraint_moving::init(conf); colvarbias_restraint_centers_moving::init(conf); colvarbias_restraint_k_moving::init(conf); for (size_t i = 0; i < num_variables(); i++) { if (variables(i)->width != 1.0) cvm::log("The force constant for colvar \""+variables(i)->name+ "\" will be rescaled to "+ cvm::to_str(force_k / (variables(i)->width * variables(i)->width))+ " according to the specified width.\n"); } return COLVARS_OK; } int colvarbias_restraint_harmonic::update() { // update parameters (centers or force constant) colvarbias_restraint_centers_moving::update(); colvarbias_restraint_k_moving::update(); // update restraint energy and forces colvarbias_restraint::update(); // update accumulated work using the current forces colvarbias_restraint_centers_moving::update_acc_work(); return COLVARS_OK; } cvm::real colvarbias_restraint_harmonic::restraint_potential(size_t i) const { return 0.5 * force_k / (variables(i)->width * variables(i)->width) * variables(i)->dist2(variables(i)->value(), colvar_centers[i]); } colvarvalue const colvarbias_restraint_harmonic::restraint_force(size_t i) const { return -0.5 * force_k / (variables(i)->width * variables(i)->width) * variables(i)->dist2_lgrad(variables(i)->value(), colvar_centers[i]); } cvm::real colvarbias_restraint_harmonic::d_restraint_potential_dk(size_t i) const { return 0.5 / (variables(i)->width * variables(i)->width) * variables(i)->dist2(variables(i)->value(), colvar_centers[i]); } std::string const colvarbias_restraint_harmonic::get_state_params() const { return colvarbias_restraint::get_state_params() + colvarbias_restraint_centers_moving::get_state_params() + colvarbias_restraint_k_moving::get_state_params(); } int colvarbias_restraint_harmonic::set_state_params(std::string const &conf) { int error_code = COLVARS_OK; error_code |= colvarbias_restraint::set_state_params(conf); error_code |= colvarbias_restraint_centers_moving::set_state_params(conf); error_code |= colvarbias_restraint_k_moving::set_state_params(conf); return error_code; } std::ostream & colvarbias_restraint_harmonic::write_traj_label(std::ostream &os) { colvarbias_restraint::write_traj_label(os); colvarbias_restraint_centers_moving::write_traj_label(os); colvarbias_restraint_k_moving::write_traj_label(os); return os; } std::ostream & colvarbias_restraint_harmonic::write_traj(std::ostream &os) { colvarbias_restraint::write_traj(os); colvarbias_restraint_centers_moving::write_traj(os); colvarbias_restraint_k_moving::write_traj(os); return os; } int colvarbias_restraint_harmonic::change_configuration(std::string const &conf) { return colvarbias_restraint_centers::change_configuration(conf) | colvarbias_restraint_k::change_configuration(conf); } cvm::real colvarbias_restraint_harmonic::energy_difference(std::string const &conf) { cvm::real const old_bias_energy = bias_energy; cvm::real const old_force_k = force_k; std::vector const old_centers = colvar_centers; change_configuration(conf); update(); cvm::real const result = (bias_energy - old_bias_energy); bias_energy = old_bias_energy; force_k = old_force_k; colvar_centers = old_centers; return result; } colvarbias_restraint_harmonic_walls::colvarbias_restraint_harmonic_walls(char const *key) : colvarbias(key), colvarbias_restraint(key), colvarbias_restraint_k(key), colvarbias_restraint_moving(key), colvarbias_restraint_k_moving(key) { lower_wall_k = 0.0; upper_wall_k = 0.0; } int colvarbias_restraint_harmonic_walls::init(std::string const &conf) { colvarbias_restraint::init(conf); colvarbias_restraint_moving::init(conf); colvarbias_restraint_k_moving::init(conf); get_keyval(conf, "lowerWallConstant", lower_wall_k, (lower_wall_k > 0.0) ? lower_wall_k : force_k); get_keyval(conf, "upperWallConstant", upper_wall_k, (upper_wall_k > 0.0) ? upper_wall_k : force_k); + if (lower_wall_k * upper_wall_k > 0.0) { + for (size_t i = 0; i < num_variables(); i++) { + if (variables(i)->width != 1.0) + cvm::log("The lower and upper wall force constants for colvar \""+ + variables(i)->name+ + "\" will be rescaled to "+ + cvm::to_str(lower_wall_k / + (variables(i)->width * variables(i)->width))+ + " and "+ + cvm::to_str(upper_wall_k / + (variables(i)->width * variables(i)->width))+ + " according to the specified width.\n"); + } + } + enable(f_cvb_scalar_variables); size_t i; bool b_null_lower_walls = false; if (lower_walls.size() == 0) { b_null_lower_walls = true; lower_walls.resize(num_variables()); for (i = 0; i < num_variables(); i++) { lower_walls[i].type(variables(i)->value()); lower_walls[i].reset(); } } if (!get_keyval(conf, "lowerWalls", lower_walls, lower_walls) && b_null_lower_walls) { cvm::log("Lower walls were not provided.\n"); - lower_walls.resize(0); + lower_walls.clear(); } bool b_null_upper_walls = false; if (upper_walls.size() == 0) { b_null_upper_walls = true; upper_walls.resize(num_variables()); for (i = 0; i < num_variables(); i++) { upper_walls[i].type(variables(i)->value()); upper_walls[i].reset(); } } if (!get_keyval(conf, "upperWalls", upper_walls, upper_walls) && b_null_upper_walls) { cvm::log("Upper walls were not provided.\n"); - upper_walls.resize(0); + upper_walls.clear(); } if ((lower_walls.size() == 0) && (upper_walls.size() == 0)) { cvm::error("Error: no walls provided.\n", INPUT_ERROR); return INPUT_ERROR; } if ((lower_walls.size() == 0) || (upper_walls.size() == 0)) { for (i = 0; i < num_variables(); i++) { if (variables(i)->is_enabled(f_cv_periodic)) { cvm::error("Error: at least one variable is periodic, " "both walls must be provided.\n", INPUT_ERROR); return INPUT_ERROR; } } } if ((lower_walls.size() > 0) && (upper_walls.size() > 0)) { for (i = 0; i < num_variables(); i++) { if (lower_walls[i] >= upper_walls[i]) { cvm::error("Error: one upper wall, "+ cvm::to_str(upper_walls[i])+ ", is not higher than the lower wall, "+ cvm::to_str(lower_walls[i])+".\n", INPUT_ERROR); } } if (lower_wall_k * upper_wall_k == 0.0) { cvm::error("Error: lowerWallConstant and upperWallConstant, " "when defined, must both be positive.\n", INPUT_ERROR); return INPUT_ERROR; } force_k = lower_wall_k * upper_wall_k; // transform the two constants to relative values // (allow changing both via force_k) lower_wall_k /= force_k; upper_wall_k /= force_k; } for (i = 0; i < num_variables(); i++) { if (variables(i)->width != 1.0) cvm::log("The force constant for colvar \""+variables(i)->name+ "\" will be rescaled to "+ cvm::to_str(force_k / (variables(i)->width * variables(i)->width))+ " according to the specified width.\n"); } return COLVARS_OK; } int colvarbias_restraint_harmonic_walls::update() { colvarbias_restraint_k_moving::update(); colvarbias_restraint::update(); return COLVARS_OK; } void colvarbias_restraint_harmonic_walls::communicate_forces() { for (size_t i = 0; i < num_variables(); i++) { if (cvm::debug()) { cvm::log("Communicating a force to colvar \""+ variables(i)->name+"\".\n"); } - variables(i)->add_bias_force_actual_value(colvar_forces[i]); + // Impulse-style multiple timestep + variables(i)->add_bias_force_actual_value(cvm::real(time_step_factor) * colvar_forces[i]); } } cvm::real colvarbias_restraint_harmonic_walls::colvar_distance(size_t i) const { colvar *cv = variables(i); colvarvalue const &cvv = variables(i)->actual_value(); // For a periodic colvar, both walls may be applicable at the same time // in which case we pick the closer one if (cv->is_enabled(f_cv_periodic)) { cvm::real const lower_wall_dist2 = cv->dist2(cvv, lower_walls[i]); cvm::real const upper_wall_dist2 = cv->dist2(cvv, upper_walls[i]); if (lower_wall_dist2 < upper_wall_dist2) { cvm::real const grad = cv->dist2_lgrad(cvv, lower_walls[i]); if (grad < 0.0) { return 0.5 * grad; } } else { cvm::real const grad = cv->dist2_lgrad(cvv, upper_walls[i]); if (grad > 0.0) { return 0.5 * grad; } } return 0.0; } if (lower_walls.size() > 0) { cvm::real const grad = cv->dist2_lgrad(cvv, lower_walls[i]); if (grad < 0.0) { return 0.5 * grad; } } if (upper_walls.size() > 0) { cvm::real const grad = cv->dist2_lgrad(cvv, upper_walls[i]); if (grad > 0.0) { return 0.5 * grad; } } return 0.0; } cvm::real colvarbias_restraint_harmonic_walls::restraint_potential(size_t i) const { cvm::real const dist = colvar_distance(i); cvm::real const scale = dist > 0.0 ? upper_wall_k : lower_wall_k; return 0.5 * force_k * scale / (variables(i)->width * variables(i)->width) * dist * dist; } colvarvalue const colvarbias_restraint_harmonic_walls::restraint_force(size_t i) const { cvm::real const dist = colvar_distance(i); cvm::real const scale = dist > 0.0 ? upper_wall_k : lower_wall_k; return - force_k * scale / (variables(i)->width * variables(i)->width) * dist; } cvm::real colvarbias_restraint_harmonic_walls::d_restraint_potential_dk(size_t i) const { cvm::real const dist = colvar_distance(i); cvm::real const scale = dist > 0.0 ? upper_wall_k : lower_wall_k; return 0.5 * scale / (variables(i)->width * variables(i)->width) * dist * dist; } std::string const colvarbias_restraint_harmonic_walls::get_state_params() const { return colvarbias_restraint::get_state_params() + colvarbias_restraint_k_moving::get_state_params(); } int colvarbias_restraint_harmonic_walls::set_state_params(std::string const &conf) { int error_code = COLVARS_OK; error_code |= colvarbias_restraint::set_state_params(conf); error_code |= colvarbias_restraint_k_moving::set_state_params(conf); return error_code; } std::ostream & colvarbias_restraint_harmonic_walls::write_traj_label(std::ostream &os) { colvarbias_restraint::write_traj_label(os); colvarbias_restraint_k_moving::write_traj_label(os); return os; } std::ostream & colvarbias_restraint_harmonic_walls::write_traj(std::ostream &os) { colvarbias_restraint::write_traj(os); colvarbias_restraint_k_moving::write_traj(os); return os; } colvarbias_restraint_linear::colvarbias_restraint_linear(char const *key) : colvarbias(key), colvarbias_restraint(key), colvarbias_restraint_centers(key), colvarbias_restraint_moving(key), colvarbias_restraint_k(key), colvarbias_restraint_centers_moving(key), colvarbias_restraint_k_moving(key) { } int colvarbias_restraint_linear::init(std::string const &conf) { colvarbias_restraint::init(conf); colvarbias_restraint_moving::init(conf); colvarbias_restraint_centers_moving::init(conf); colvarbias_restraint_k_moving::init(conf); for (size_t i = 0; i < num_variables(); i++) { if (variables(i)->is_enabled(f_cv_periodic)) { cvm::error("Error: linear biases cannot be applied to periodic variables.\n", INPUT_ERROR); return INPUT_ERROR; } if (variables(i)->width != 1.0) cvm::log("The force constant for colvar \""+variables(i)->name+ "\" will be rescaled to "+ cvm::to_str(force_k / variables(i)->width)+ " according to the specified width.\n"); } return COLVARS_OK; } int colvarbias_restraint_linear::update() { // update parameters (centers or force constant) colvarbias_restraint_centers_moving::update(); colvarbias_restraint_k_moving::update(); // update restraint energy and forces colvarbias_restraint::update(); // update accumulated work using the current forces colvarbias_restraint_centers_moving::update_acc_work(); return COLVARS_OK; } int colvarbias_restraint_linear::change_configuration(std::string const &conf) { // Only makes sense to change the force constant return colvarbias_restraint_k::change_configuration(conf); } cvm::real colvarbias_restraint_linear::energy_difference(std::string const &conf) { cvm::real const old_bias_energy = bias_energy; cvm::real const old_force_k = force_k; change_configuration(conf); update(); cvm::real const result = (bias_energy - old_bias_energy); bias_energy = old_bias_energy; force_k = old_force_k; return result; } cvm::real colvarbias_restraint_linear::restraint_potential(size_t i) const { return force_k / variables(i)->width * (variables(i)->value() - colvar_centers[i]); } colvarvalue const colvarbias_restraint_linear::restraint_force(size_t i) const { return -1.0 * force_k / variables(i)->width; } cvm::real colvarbias_restraint_linear::d_restraint_potential_dk(size_t i) const { return 1.0 / variables(i)->width * (variables(i)->value() - colvar_centers[i]); } std::string const colvarbias_restraint_linear::get_state_params() const { return colvarbias_restraint::get_state_params() + colvarbias_restraint_centers_moving::get_state_params() + colvarbias_restraint_k_moving::get_state_params(); } int colvarbias_restraint_linear::set_state_params(std::string const &conf) { int error_code = COLVARS_OK; error_code |= colvarbias_restraint::set_state_params(conf); error_code |= colvarbias_restraint_centers_moving::set_state_params(conf); error_code |= colvarbias_restraint_k_moving::set_state_params(conf); return error_code; } std::ostream & colvarbias_restraint_linear::write_traj_label(std::ostream &os) { colvarbias_restraint::write_traj_label(os); colvarbias_restraint_centers_moving::write_traj_label(os); colvarbias_restraint_k_moving::write_traj_label(os); return os; } std::ostream & colvarbias_restraint_linear::write_traj(std::ostream &os) { colvarbias_restraint::write_traj(os); colvarbias_restraint_centers_moving::write_traj(os); colvarbias_restraint_k_moving::write_traj(os); return os; } colvarbias_restraint_histogram::colvarbias_restraint_histogram(char const *key) : colvarbias(key) { lower_boundary = 0.0; upper_boundary = 0.0; width = 0.0; gaussian_width = 0.0; } int colvarbias_restraint_histogram::init(std::string const &conf) { colvarbias::init(conf); get_keyval(conf, "lowerBoundary", lower_boundary, lower_boundary); get_keyval(conf, "upperBoundary", upper_boundary, upper_boundary); get_keyval(conf, "width", width, width); if (width <= 0.0) { cvm::error("Error: \"width\" must be positive.\n", INPUT_ERROR); } get_keyval(conf, "gaussianWidth", gaussian_width, 2.0 * width, colvarparse::parse_silent); get_keyval(conf, "gaussianSigma", gaussian_width, 2.0 * width); if (lower_boundary >= upper_boundary) { cvm::error("Error: the upper boundary, "+ cvm::to_str(upper_boundary)+ ", is not higher than the lower boundary, "+ cvm::to_str(lower_boundary)+".\n", INPUT_ERROR); } cvm::real const nbins = (upper_boundary - lower_boundary) / width; int const nbins_round = (int)(nbins); if (std::fabs(nbins - cvm::real(nbins_round)) > 1.0E-10) { cvm::log("Warning: grid interval ("+ cvm::to_str(lower_boundary, cvm::cv_width, cvm::cv_prec)+" - "+ cvm::to_str(upper_boundary, cvm::cv_width, cvm::cv_prec)+ ") is not commensurate to its bin width ("+ cvm::to_str(width, cvm::cv_width, cvm::cv_prec)+").\n"); } p.resize(nbins_round); ref_p.resize(nbins_round); p_diff.resize(nbins_round); bool const inline_ref_p = get_keyval(conf, "refHistogram", ref_p.data_array(), ref_p.data_array()); std::string ref_p_file; get_keyval(conf, "refHistogramFile", ref_p_file, std::string("")); if (ref_p_file.size()) { if (inline_ref_p) { cvm::error("Error: cannot specify both refHistogram and refHistogramFile at the same time.\n", INPUT_ERROR); } else { std::ifstream is(ref_p_file.c_str()); std::string data_s = ""; std::string line; while (getline_nocomments(is, line)) { data_s.append(line+"\n"); } if (data_s.size() == 0) { cvm::error("Error: file \""+ref_p_file+"\" empty or unreadable.\n", FILE_ERROR); } is.close(); cvm::vector1d data; if (data.from_simple_string(data_s) != 0) { cvm::error("Error: could not read histogram from file \""+ref_p_file+"\".\n"); } if (data.size() == 2*ref_p.size()) { // file contains both x and p(x) size_t i; for (i = 0; i < ref_p.size(); i++) { ref_p[i] = data[2*i+1]; } } else if (data.size() == ref_p.size()) { ref_p = data; } else { cvm::error("Error: file \""+ref_p_file+"\" contains a histogram of different length.\n", INPUT_ERROR); } } } cvm::real const ref_integral = ref_p.sum() * width; if (std::fabs(ref_integral - 1.0) > 1.0e-03) { cvm::log("Reference distribution not normalized, normalizing to unity.\n"); ref_p /= ref_integral; } get_keyval(conf, "writeHistogram", b_write_histogram, false); get_keyval(conf, "forceConstant", force_k, 1.0); return COLVARS_OK; } colvarbias_restraint_histogram::~colvarbias_restraint_histogram() { - p.resize(0); - ref_p.resize(0); - p_diff.resize(0); + p.clear(); + ref_p.clear(); + p_diff.clear(); } int colvarbias_restraint_histogram::update() { if (cvm::debug()) cvm::log("Updating the histogram restraint bias \""+this->name+"\".\n"); size_t vector_size = 0; size_t icv; for (icv = 0; icv < num_variables(); icv++) { vector_size += variables(icv)->value().size(); } cvm::real const norm = 1.0/(std::sqrt(2.0*PI)*gaussian_width*vector_size); // calculate the histogram p.reset(); for (icv = 0; icv < num_variables(); icv++) { colvarvalue const &cv = variables(icv)->value(); if (cv.type() == colvarvalue::type_scalar) { cvm::real const cv_value = cv.real_value; size_t igrid; for (igrid = 0; igrid < p.size(); igrid++) { cvm::real const x_grid = (lower_boundary + (igrid+0.5)*width); p[igrid] += norm * std::exp(-1.0 * (x_grid - cv_value) * (x_grid - cv_value) / (2.0 * gaussian_width * gaussian_width)); } } else if (cv.type() == colvarvalue::type_vector) { size_t idim; for (idim = 0; idim < cv.vector1d_value.size(); idim++) { cvm::real const cv_value = cv.vector1d_value[idim]; size_t igrid; for (igrid = 0; igrid < p.size(); igrid++) { cvm::real const x_grid = (lower_boundary + (igrid+0.5)*width); p[igrid] += norm * std::exp(-1.0 * (x_grid - cv_value) * (x_grid - cv_value) / (2.0 * gaussian_width * gaussian_width)); } } } else { cvm::error("Error: unsupported type for variable "+variables(icv)->name+".\n", COLVARS_NOT_IMPLEMENTED); return COLVARS_NOT_IMPLEMENTED; } } cvm::real const force_k_cv = force_k * vector_size; // calculate the difference between current and reference p_diff = p - ref_p; bias_energy = 0.5 * force_k_cv * p_diff * p_diff; // calculate the forces for (icv = 0; icv < num_variables(); icv++) { colvarvalue const &cv = variables(icv)->value(); colvarvalue &cv_force = colvar_forces[icv]; cv_force.type(cv); cv_force.reset(); if (cv.type() == colvarvalue::type_scalar) { cvm::real const cv_value = cv.real_value; cvm::real &force = cv_force.real_value; size_t igrid; for (igrid = 0; igrid < p.size(); igrid++) { cvm::real const x_grid = (lower_boundary + (igrid+0.5)*width); force += force_k_cv * p_diff[igrid] * norm * std::exp(-1.0 * (x_grid - cv_value) * (x_grid - cv_value) / (2.0 * gaussian_width * gaussian_width)) * (-1.0 * (x_grid - cv_value) / (gaussian_width * gaussian_width)); } } else if (cv.type() == colvarvalue::type_vector) { size_t idim; for (idim = 0; idim < cv.vector1d_value.size(); idim++) { cvm::real const cv_value = cv.vector1d_value[idim]; cvm::real &force = cv_force.vector1d_value[idim]; size_t igrid; for (igrid = 0; igrid < p.size(); igrid++) { cvm::real const x_grid = (lower_boundary + (igrid+0.5)*width); force += force_k_cv * p_diff[igrid] * norm * std::exp(-1.0 * (x_grid - cv_value) * (x_grid - cv_value) / (2.0 * gaussian_width * gaussian_width)) * (-1.0 * (x_grid - cv_value) / (gaussian_width * gaussian_width)); } } } else { // TODO } } return COLVARS_OK; } std::ostream & colvarbias_restraint_histogram::write_restart(std::ostream &os) { if (b_write_histogram) { std::string file_name(cvm::output_prefix()+"."+this->name+".hist.dat"); - std::ofstream os(file_name.c_str()); - os << "# " << cvm::wrap_string(variables(0)->name, cvm::cv_width) - << " " << "p(" << cvm::wrap_string(variables(0)->name, cvm::cv_width-3) - << ")\n"; + std::ostream *os = cvm::proxy->output_stream(file_name); + *os << "# " << cvm::wrap_string(variables(0)->name, cvm::cv_width) + << " " << "p(" << cvm::wrap_string(variables(0)->name, cvm::cv_width-3) + << ")\n"; size_t igrid; for (igrid = 0; igrid < p.size(); igrid++) { cvm::real const x_grid = (lower_boundary + (igrid+1)*width); - os << " " - << std::setprecision(cvm::cv_prec) - << std::setw(cvm::cv_width) - << x_grid - << " " - << std::setprecision(cvm::cv_prec) - << std::setw(cvm::cv_width) - << p[igrid] << "\n"; + *os << " " + << std::setprecision(cvm::cv_prec) + << std::setw(cvm::cv_width) + << x_grid + << " " + << std::setprecision(cvm::cv_prec) + << std::setw(cvm::cv_width) + << p[igrid] << "\n"; } - os.close(); + cvm::proxy->close_output_stream(file_name); } return os; } std::istream & colvarbias_restraint_histogram::read_restart(std::istream &is) { return is; } std::ostream & colvarbias_restraint_histogram::write_traj_label(std::ostream &os) { os << " "; if (b_output_energy) { os << " E_" << cvm::wrap_string(this->name, cvm::en_width-2); } return os; } std::ostream & colvarbias_restraint_histogram::write_traj(std::ostream &os) { os << " "; if (b_output_energy) { os << " " << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) << bias_energy; } return os; } diff --git a/lib/colvars/colvarcomp.cpp b/lib/colvars/colvarcomp.cpp index 786bc032d..589de1d32 100644 --- a/lib/colvars/colvarcomp.cpp +++ b/lib/colvars/colvarcomp.cpp @@ -1,334 +1,366 @@ // -*- 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 "colvar.h" #include "colvarcomp.h" colvar::cvc::cvc() : sup_coeff(1.0), sup_np(1), b_periodic(false), b_try_scalable(true) { init_cvc_requires(); } colvar::cvc::cvc(std::string const &conf) : sup_coeff(1.0), sup_np(1), b_periodic(false), b_try_scalable(true) { if (cvm::debug()) cvm::log("Initializing cvc base object.\n"); init_cvc_requires(); if (get_keyval(conf, "name", this->name, std::string(""), parse_silent)) { // Temporary description until child object is initialized description = "cvc " + name; } else { description = "uninitialized cvc"; } get_keyval(conf, "componentCoeff", sup_coeff, 1.0); get_keyval(conf, "componentExp", sup_np, 1); get_keyval(conf, "period", period, 0.0); get_keyval(conf, "wrapAround", wrap_center, 0.0); get_keyval_feature((colvarparse *)this, conf, "debugGradients", f_cvc_debug_gradient, false, parse_silent); + { + bool b_no_PBC = false; + get_keyval(conf, "forceNoPBC", b_no_PBC, false); + if (b_no_PBC) { + disable(f_cvc_pbc_minimum_image); + } else { + enable(f_cvc_pbc_minimum_image); + } + // this does not use get_keyval_feature() only for backward compatibility + } + // Attempt scalable calculations when in parallel? (By default yes, if available) get_keyval(conf, "scalable", b_try_scalable, true); if (cvm::debug()) cvm::log("Done initializing cvc base object.\n"); } int colvar::cvc::init_total_force_params(std::string const &conf) { if (cvm::get_error()) return COLVARS_ERROR; if (get_keyval_feature(this, conf, "oneSiteSystemForce", f_cvc_one_site_total_force, is_enabled(f_cvc_one_site_total_force))) { cvm::log("Warning: keyword \"oneSiteSystemForce\" is deprecated: " "please use \"oneSiteTotalForce\" instead.\n"); } if (get_keyval_feature(this, conf, "oneSiteTotalForce", f_cvc_one_site_total_force, is_enabled(f_cvc_one_site_total_force))) { cvm::log("Computing total force on group 1 only"); } if (! is_enabled(f_cvc_one_site_total_force)) { // check whether any of the other atom groups is dummy std::vector::iterator agi = atom_groups.begin(); agi++; for ( ; agi != atom_groups.end(); agi++) { if ((*agi)->b_dummy) { provide(f_cvc_inv_gradient, false); provide(f_cvc_Jacobian, false); } } } return COLVARS_OK; } cvm::atom_group *colvar::cvc::parse_group(std::string const &conf, char const *group_key, bool optional) { cvm::atom_group *group = NULL; + std::string group_conf; - if (key_lookup(conf, group_key)) { - group = new cvm::atom_group; - group->key = group_key; + if (key_lookup(conf, group_key, &group_conf)) { + group = new cvm::atom_group(group_key); if (b_try_scalable) { - if (is_available(f_cvc_scalable_com) && is_enabled(f_cvc_com_based)) { + if (is_available(f_cvc_scalable_com) + && is_enabled(f_cvc_com_based) + && !is_enabled(f_cvc_debug_gradient)) { enable(f_cvc_scalable_com); enable(f_cvc_scalable); // The CVC makes the feature available; // the atom group will enable it unless it needs to compute a rotational fit group->provide(f_ag_scalable_com); } // TODO check for other types of parallelism here } - if (group->parse(conf) == COLVARS_OK) { - atom_groups.push_back(group); - } else { + if (group_conf.size() == 0) { + cvm::error("Error: atom group \""+group->key+ + "\" is set, but has no definition.\n", + INPUT_ERROR); + return group; + } + + cvm::increase_depth(); + if (group->parse(group_conf) == COLVARS_OK) { + 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)+"\".\n"); + std::string(group_key)+"\"\n.", INPUT_ERROR); } + cvm::decrease_depth(); + } else { if (! optional) { cvm::error("Error: definition for atom group \""+ - std::string(group_key)+"\" not found.\n"); + std::string(group_key)+"\" not found.\n"); } } + return group; } int colvar::cvc::setup() { - size_t i; description = "cvc " + name; - - for (i = 0; i < atom_groups.size(); i++) { - add_child((colvardeps *) atom_groups[i]); - } - return COLVARS_OK; } colvar::cvc::~cvc() { + free_children_deps(); remove_all_children(); for (size_t i = 0; i < atom_groups.size(); i++) { if (atom_groups[i] != NULL) delete atom_groups[i]; } } - 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 } //// 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(); // } // } // } } void colvar::cvc::calc_force_invgrads() { cvm::error("Error: calculation of inverse gradients is not implemented " "for colvar components of type \""+function_type+"\".\n", COLVARS_NOT_IMPLEMENTED); } void colvar::cvc::calc_Jacobian_derivative() { cvm::error("Error: calculation of inverse gradients is not implemented " "for colvar components of type \""+function_type+"\".\n", COLVARS_NOT_IMPLEMENTED); } -void colvar::cvc::debug_gradients(cvm::atom_group *group) +void colvar::cvc::calc_fit_gradients() +{ + for (size_t ig = 0; ig < atom_groups.size(); ig++) { + atom_groups[ig]->calc_fit_gradients(); + } +} + + +void colvar::cvc::debug_gradients() { - // this function should work for any scalar variable: + // this function should work for any scalar cvc: // the only difference will be the name of the atom group (here, "group") // NOTE: this assumes that groups for this cvc are non-overlapping, // since atom coordinates are modified only within the current group - if (group->b_dummy) return; + cvm::log("Debugging gradients for " + description); - cvm::rotation const rot_0 = group->rot; - cvm::rotation const rot_inv = group->rot.inverse(); + for (size_t ig = 0; ig < atom_groups.size(); ig++) { + cvm::atom_group *group = atom_groups[ig]; + if (group->b_dummy) continue; - cvm::real x_0 = x.real_value; - if ((x.type() == colvarvalue::type_vector) && (x.size() == 1)) x_0 = x[0]; + cvm::rotation const rot_0 = group->rot; + cvm::rotation const rot_inv = group->rot.inverse(); - // cvm::log("gradients = "+cvm::to_str (gradients)+"\n"); + cvm::real x_0 = x.real_value; + if ((x.type() == colvarvalue::type_vector) && (x.size() == 1)) x_0 = x[0]; - cvm::atom_group *group_for_fit = group->fitting_group ? group->fitting_group : group; - cvm::atom_pos fit_gradient_sum, gradient_sum; + // cvm::log("gradients = "+cvm::to_str (gradients)+"\n"); - // print the values of the fit gradients - if (group->b_rotate || group->b_center) { - if (group->b_fit_gradients) { - size_t j; + cvm::atom_group *group_for_fit = group->fitting_group ? group->fitting_group : group; + cvm::atom_pos fit_gradient_sum, gradient_sum; - // fit_gradients are in the simulation frame: we should print them in the rotated frame - cvm::log("Fit gradients:\n"); - for (j = 0; j < group_for_fit->fit_gradients.size(); j++) { - cvm::log((group->fitting_group ? std::string("refPosGroup") : group->key) + - "[" + cvm::to_str(j) + "] = " + - (group->b_rotate ? - cvm::to_str(rot_0.rotate(group_for_fit->fit_gradients[j])) : - cvm::to_str(group_for_fit->fit_gradients[j]))); - } - } - } - - // debug the gradients - for (size_t ia = 0; ia < group->size(); ia++) { - - // tests are best conducted in the unrotated (simulation) frame - cvm::rvector const atom_grad = (group->b_rotate ? - rot_inv.rotate((*group)[ia].grad) : - (*group)[ia].grad); - gradient_sum += atom_grad; + // print the values of the fit gradients + if (group->b_rotate || group->b_center) { + if (group->is_enabled(f_ag_fit_gradients)) { + size_t j; - for (size_t id = 0; id < 3; id++) { - // (re)read original positions - group->read_positions(); - // change one coordinate - (*group)[ia].pos[id] += cvm::debug_gradients_step_size; - group->calc_required_properties(); - calc_value(); - cvm::real x_1 = x.real_value; - if ((x.type() == colvarvalue::type_vector) && (x.size() == 1)) x_1 = x[0]; - cvm::log("Atom "+cvm::to_str(ia)+", component "+cvm::to_str(id)+":\n"); - cvm::log("dx(actual) = "+cvm::to_str(x_1 - x_0, - 21, 14)+"\n"); - cvm::real const dx_pred = (group->fit_gradients.size()) ? - (cvm::debug_gradients_step_size * (atom_grad[id] + group->fit_gradients[ia][id])) : - (cvm::debug_gradients_step_size * atom_grad[id]); - cvm::log("dx(interp) = "+cvm::to_str(dx_pred, - 21, 14)+"\n"); - cvm::log("|dx(actual) - dx(interp)|/|dx(actual)| = "+ - cvm::to_str(std::fabs(x_1 - x_0 - dx_pred) / - std::fabs(x_1 - x_0), 12, 5)+"\n"); + // fit_gradients are in the simulation frame: we should print them in the rotated frame + cvm::log("Fit gradients:\n"); + for (j = 0; j < group_for_fit->fit_gradients.size(); j++) { + cvm::log((group->fitting_group ? std::string("refPosGroup") : group->key) + + "[" + cvm::to_str(j) + "] = " + + (group->b_rotate ? + cvm::to_str(rot_0.rotate(group_for_fit->fit_gradients[j])) : + cvm::to_str(group_for_fit->fit_gradients[j]))); + } + } } - } - if ((group->b_fit_gradients) && (group->fitting_group != NULL)) { - cvm::atom_group *ref_group = group->fitting_group; - group->read_positions(); - group->calc_required_properties(); + // debug the gradients + for (size_t ia = 0; ia < group->size(); ia++) { - for (size_t ia = 0; ia < ref_group->size(); ia++) { - - // fit gradients are in the unrotated (simulation) frame - cvm::rvector const atom_grad = ref_group->fit_gradients[ia]; - fit_gradient_sum += atom_grad; + // tests are best conducted in the unrotated (simulation) frame + cvm::rvector const atom_grad = (group->b_rotate ? + rot_inv.rotate((*group)[ia].grad) : + (*group)[ia].grad); + gradient_sum += atom_grad; for (size_t id = 0; id < 3; id++) { // (re)read original positions group->read_positions(); - ref_group->read_positions(); // change one coordinate - (*ref_group)[ia].pos[id] += cvm::debug_gradients_step_size; + (*group)[ia].pos[id] += cvm::debug_gradients_step_size; group->calc_required_properties(); calc_value(); + cvm::real x_1 = x.real_value; + if ((x.type() == colvarvalue::type_vector) && (x.size() == 1)) x_1 = x[0]; + cvm::log("Atom "+cvm::to_str(ia)+", component "+cvm::to_str(id)+":\n"); + cvm::log("dx(actual) = "+cvm::to_str(x_1 - x_0, + 21, 14)+"\n"); + cvm::real const dx_pred = (group->fit_gradients.size()) ? + (cvm::debug_gradients_step_size * (atom_grad[id] + group->fit_gradients[ia][id])) : + (cvm::debug_gradients_step_size * atom_grad[id]); + cvm::log("dx(interp) = "+cvm::to_str(dx_pred, + 21, 14)+"\n"); + cvm::log("|dx(actual) - dx(interp)|/|dx(actual)| = "+ + cvm::to_str(std::fabs(x_1 - x_0 - dx_pred) / + std::fabs(x_1 - x_0), 12, 5)+"\n"); + } + } - cvm::real const x_1 = x.real_value; - cvm::log("refPosGroup atom "+cvm::to_str(ia)+", component "+cvm::to_str (id)+":\n"); - cvm::log("dx(actual) = "+cvm::to_str (x_1 - x_0, - 21, 14)+"\n"); - - cvm::real const dx_pred = cvm::debug_gradients_step_size * atom_grad[id]; + if ((group->is_enabled(f_ag_fit_gradients)) && (group->fitting_group != NULL)) { + cvm::atom_group *ref_group = group->fitting_group; + group->read_positions(); + group->calc_required_properties(); - cvm::log("dx(interp) = "+cvm::to_str (dx_pred, - 21, 14)+"\n"); - cvm::log ("|dx(actual) - dx(interp)|/|dx(actual)| = "+ - cvm::to_str(std::fabs (x_1 - x_0 - dx_pred) / - std::fabs (x_1 - x_0), - 12, 5)+ - ".\n"); + for (size_t ia = 0; ia < ref_group->size(); ia++) { + + // fit gradients are in the unrotated (simulation) frame + cvm::rvector const atom_grad = ref_group->fit_gradients[ia]; + fit_gradient_sum += atom_grad; + + for (size_t id = 0; id < 3; id++) { + // (re)read original positions + group->read_positions(); + ref_group->read_positions(); + // change one coordinate + (*ref_group)[ia].pos[id] += cvm::debug_gradients_step_size; + group->calc_required_properties(); + calc_value(); + + cvm::real const x_1 = x.real_value; + cvm::log("refPosGroup atom "+cvm::to_str(ia)+", component "+cvm::to_str (id)+":\n"); + cvm::log("dx(actual) = "+cvm::to_str (x_1 - x_0, + 21, 14)+"\n"); + + cvm::real const dx_pred = cvm::debug_gradients_step_size * atom_grad[id]; + + cvm::log("dx(interp) = "+cvm::to_str (dx_pred, + 21, 14)+"\n"); + cvm::log ("|dx(actual) - dx(interp)|/|dx(actual)| = "+ + cvm::to_str(std::fabs (x_1 - x_0 - dx_pred) / + std::fabs (x_1 - x_0), + 12, 5)+ + ".\n"); + } } } - } - - cvm::log("Gradient sum: " + cvm::to_str(gradient_sum) + - " Fit gradient sum: " + cvm::to_str(fit_gradient_sum) + - " Total " + cvm::to_str(gradient_sum + fit_gradient_sum)); + cvm::log("Gradient sum: " + cvm::to_str(gradient_sum) + + " Fit gradient sum: " + cvm::to_str(fit_gradient_sum) + + " Total " + cvm::to_str(gradient_sum + fit_gradient_sum)); + } return; } cvm::real colvar::cvc::dist2(colvarvalue const &x1, colvarvalue const &x2) const { return x1.dist2(x2); } colvarvalue colvar::cvc::dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const { return x1.dist2_grad(x2); } colvarvalue colvar::cvc::dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const { return x2.dist2_grad(x1); } void colvar::cvc::wrap(colvarvalue &x) const { return; } // Static members std::vector colvar::cvc::cvc_features; diff --git a/lib/colvars/colvarcomp.h b/lib/colvars/colvarcomp.h index ec215cbad..2c865a166 100644 --- a/lib/colvars/colvarcomp.h +++ b/lib/colvars/colvarcomp.h @@ -1,1286 +1,1357 @@ // -*- 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 COLVARCOMP_H #define COLVARCOMP_H // Declaration of colvar::cvc base class and derived ones. // // Future cvc's could be declared on additional header files. // After the declaration of a new derived class, its metric // functions must be reimplemented as well. // If the new cvc has no symmetry or periodicity, // this can be done straightforwardly by using the macro: // simple_scalar_dist_functions (derived_class) #include #include #include "colvarmodule.h" #include "colvar.h" #include "colvaratoms.h" /// \brief Colvar component (base class); most implementations of /// \link cvc \endlink utilize one or more \link /// colvarmodule::atom \endlink or \link colvarmodule::atom_group /// \endlink objects to access atoms. /// /// A \link cvc \endlink object (or an object of a /// cvc-derived class) specifies how to calculate a collective /// variable, its gradients and other related physical quantities /// which do not depend only on the numeric value (the \link colvar /// \endlink class already serves this purpose). /// /// No restriction is set to what kind of calculation a \link /// cvc \endlink object performs (usually calculate an /// analytical function of atomic coordinates). The only constraint /// is that the value calculated is implemented as a \link colvarvalue /// \endlink object. This serves to provide a unique way to calculate /// scalar and non-scalar collective variables, and specify if and how /// they can be combined together by the parent \link colvar \endlink /// object. /// /// If you wish to implement a new collective variable component, you /// should write your own class by inheriting directly from \link /// cvc \endlink, or one of its derived classes (for instance, /// \link distance \endlink is frequently used, because it provides /// useful data and function members for any colvar based on two /// atom groups). The steps are: \par /// 1. add the name of this class under colvar.h \par /// 2. add a call to the parser in colvar.C, within the function colvar::colvar() \par /// 3. declare the class in colvarcomp.h \par /// 4. implement the class in one of the files colvarcomp_*.C /// /// /// The cvm::atom and cvm::atom_group classes are available to /// transparently communicate with the simulation program. However, /// they are not strictly needed, as long as all the degrees of /// freedom associated to the cvc are properly evolved by a simple /// call to e.g. apply_force(). class colvar::cvc : public colvarparse, public colvardeps { public: /// \brief The name of the object (helps to identify this /// 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 cvc \endlink /// objects are initialized; therefore the main "config string" /// constructor does not need to define it. If a \link cvc /// \endlink is initialized and/or a different constructor is used, /// this variable definition should be set within the constructor. std::string function_type; /// \brief Coefficient in the polynomial combination (default: 1.0) cvm::real sup_coeff; /// \brief Exponent in the polynomial combination (default: 1) int sup_np; /// \brief Is this a periodic component? bool b_periodic; /// \brief Period of this cvc value, (default: 0.0, non periodic) cvm::real period; /// \brief If the component is periodic, wrap around this value (default: 0.0) cvm::real wrap_center; /// \brief Constructor /// /// At least one constructor which reads a string should be defined /// for every class inheriting from cvc \param conf Contents /// of the configuration file pertaining to this \link cvc /// \endlink cvc(std::string const &conf); /// \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 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 std::vector &features() { return cvc_features; } /// \brief Obtain data needed for the calculation for the backend virtual void read_data(); /// \brief Calculate the variable virtual void calc_value() = 0; /// \brief Calculate the atomic gradients, to be reused later in /// order to apply forces virtual void calc_gradients() = 0; + /// \brief Calculate the atomic fit gradients + void calc_fit_gradients(); + /// \brief Calculate finite-difference gradients alongside the analytical ones, for each Cartesian component - virtual void debug_gradients(cvm::atom_group *group); + virtual void debug_gradients(); /// \brief Calculate the total force from the system using the /// inverse atomic gradients virtual void calc_force_invgrads(); /// \brief Calculate the divergence of the inverse atomic gradients virtual void calc_Jacobian_derivative(); /// \brief Return the previously calculated value colvarvalue const & value() const; /// \brief Return the previously calculated total force colvarvalue const & total_force() const; /// \brief Return the previously calculated divergence of the /// inverse atomic gradients colvarvalue const & Jacobian_derivative() const; /// \brief Apply the collective variable force, by communicating the /// atomic forces to the simulation program (\b Note: the \link ft /// \endlink member is not altered by this function) /// /// Note: multiple calls to this function within the same simulation /// step will add the forces altogether \param cvforce The /// 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; /// \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. /// /// If symmetries or periodicities are present, the /// colvar::cvc::dist2() should be redefined to return the /// "closest distance" value and colvar::cvc::dist2_lgrad(), /// colvar::cvc::dist2_rgrad() to return its gradients. /// /// If constraints are present (and not already implemented by any /// of the \link colvarvalue \endlink types), the /// colvar::cvc::dist2_lgrad() and /// colvar::cvc::dist2_rgrad() functions should be redefined /// to provide a gradient which is compatible with the constraint, /// 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; /// \brief Gradient(with respect to x1) of the square distance (can /// be redefined to transparently implement constraints, symmetries /// and periodicities) virtual colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const; /// \brief Gradient(with respect to x2) of the square distance (can /// be redefined to transparently implement constraints, symmetries /// and periodicities) virtual colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const; /// \brief Wrapp value (for periodic/symmetric cvcs) virtual void wrap(colvarvalue &x) const; /// \brief Pointers to all atom groups, to let colvars collect info /// e.g. atomic gradients std::vector atom_groups; + /// \brief Store a pointer to new atom group, and list as child for dependencies + inline void register_atom_group(cvm::atom_group *ag) { + atom_groups.push_back(ag); + add_child((colvardeps *) ag); + } + /// \brief Whether or not this CVC will be computed in parallel whenever possible bool b_try_scalable; protected: /// \brief Cached value colvarvalue x; /// \brief Value at the previous step colvarvalue x_old; /// \brief Calculated total force (\b Note: this is calculated from /// the total atomic forces read from the program, subtracting fromt /// the "internal" forces of the system the "external" forces from /// the colvar biases) colvarvalue ft; /// \brief Calculated Jacobian derivative (divergence of the inverse /// gradients): serves to calculate the phase space correction colvarvalue jd; }; inline colvarvalue const & colvar::cvc::value() const { return x; } inline colvarvalue const & colvar::cvc::total_force() const { return ft; } inline colvarvalue const & colvar::cvc::Jacobian_derivative() const { return jd; } /// \brief Colvar component: distance between the centers of mass of /// two groups (colvarvalue::type_scalar type, range [0:*)) class colvar::distance : public colvar::cvc { protected: /// First atom group cvm::atom_group *group1; /// Second atom group cvm::atom_group *group2; /// Vector distance, cached to be recycled cvm::rvector dist_v; /// Use absolute positions, ignoring PBCs when present bool b_no_PBC; public: distance(std::string const &conf); distance(); virtual ~distance() {} 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 Colvar component: distance vector between centers of mass // of two groups (\link colvarvalue::type_3vector \endlink type, // range (-*:*)x(-*:*)x(-*:*)) 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; }; /// \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]) 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; }; /// \brief Colvar component: projection of the distance vector along /// an axis(colvarvalue::type_scalar type, range (-*:*)) class colvar::distance_z : public colvar::cvc { protected: /// Main atom group cvm::atom_group *main; /// Reference atom group cvm::atom_group *ref1; /// Optional, second ref atom group cvm::atom_group *ref2; /// Use absolute positions, ignoring PBCs when present bool b_no_PBC; /// Vector on which the distance vector is projected cvm::rvector axis; /// Norm of the axis cvm::real axis_norm; /// Vector distance, cached to be recycled cvm::rvector dist_v; /// Flag: using a fixed axis vector? bool fixed_axis; public: distance_z(std::string const &conf); distance_z(); virtual ~distance_z() {} 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) const; }; /// \brief Colvar component: projection of the distance vector on a /// plane (colvarvalue::type_scalar type, range [0:*)) class colvar::distance_xy : public colvar::distance_z { protected: /// Components of the distance vector orthogonal to the axis cvm::rvector dist_v_ortho; /// 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; }; +/// \brief Colvar component: polar coordinate phi of a group +/// (colvarvalue::type_scalar type, range [-180:180]) +class colvar::polar_phi + : public colvar::cvc +{ +public: + polar_phi(std::string const &conf); + polar_phi(); + virtual ~polar_phi() {} +protected: + cvm::atom_group *atoms; + cvm::real r, theta, phi; +public: + 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) const; +}; + + +/// \brief Colvar component: polar coordinate theta of a group +/// (colvarvalue::type_scalar type, range [0:180]) +class colvar::polar_theta + : public colvar::cvc +{ +public: + polar_theta(std::string const &conf); + polar_theta(); + virtual ~polar_theta() {} +protected: + cvm::atom_group *atoms; + 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 - : public colvar::distance + : public colvar::cvc { protected: + /// First atom group + cvm::atom_group *group1; + /// Second atom group + cvm::atom_group *group2; /// Components of the distance vector orthogonal to the axis int exponent; + /// Use absolute positions, ignoring PBCs when present + bool b_no_PBC; public: distance_inv(std::string const &conf); distance_inv(); virtual ~distance_inv() {} 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: N1xN2 vector of pairwise distances /// (colvarvalue::type_vector type, range (0:*) for each component) class colvar::distance_pairs : public colvar::cvc { protected: /// First atom group cvm::atom_group *group1; /// Second atom group cvm::atom_group *group2; /// Use absolute positions, ignoring PBCs when present bool b_no_PBC; public: distance_pairs(std::string const &conf); distance_pairs(); virtual ~distance_pairs() {} virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); }; /// \brief Colvar component: Radius of gyration of an atom group /// (colvarvalue::type_scalar type, range [0:*)) class colvar::gyration : public colvar::cvc { protected: /// Atoms involved cvm::atom_group *atoms; public: /// Constructor gyration(std::string const &conf); gyration(); virtual ~gyration() {} 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 Colvar component: moment of inertia of an atom group /// (colvarvalue::type_scalar type, range [0:*)) 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; }; /// \brief Colvar component: moment of inertia of an atom group /// around a user-defined axis (colvarvalue::type_scalar type, range [0:*)) class colvar::inertia_z : public colvar::inertia { 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 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: projection of 3N coordinates onto an /// eigenvector(colvarvalue::type_scalar type, range (-*:*)) class colvar::eigenvector : public colvar::cvc { protected: /// Atom group cvm::atom_group * atoms; /// Reference coordinates std::vector ref_pos; /// Geometric center of the reference coordinates cvm::atom_pos ref_pos_center; /// Eigenvector (of a normal or essential mode): will always have zero center std::vector eigenvec; /// Inverse square norm of the eigenvector cvm::real eigenvec_invnorm2; public: /// Constructor eigenvector(std::string const &conf); virtual ~eigenvector() {} 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 Colvar component: angle between the centers of mass of /// three groups (colvarvalue::type_scalar type, range [0:PI]) class colvar::angle : public colvar::cvc { protected: /// Atom group cvm::atom_group *group1; /// Atom group cvm::atom_group *group2; /// Atom group cvm::atom_group *group3; /// Inter site vectors cvm::rvector r21, r23; /// Inter site vector norms cvm::real r21l, r23l; /// Derivatives wrt group centers of mass cvm::rvector dxdr1, dxdr3; /// 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; public: /// Initialize by parsing the configuration angle(std::string const &conf); /// \brief Initialize the three groups after three atoms angle(cvm::atom const &a1, cvm::atom const &a2, cvm::atom const &a3); angle(); virtual ~angle() {} 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 Colvar component: angle between the dipole of a molecule and an axis /// formed by two groups of atoms(colvarvalue::type_scalar type, range [0:PI]) class colvar::dipole_angle : public colvar::cvc { protected: /// Dipole atom group cvm::atom_group *group1; /// Atom group cvm::atom_group *group2; /// Atom group cvm::atom_group *group3; /// Inter site vectors cvm::rvector r21, r23; /// Inter site vector norms cvm::real r21l, r23l; /// Derivatives wrt group centers of mass cvm::rvector dxdr1, dxdr3; /// 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; 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 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: dihedral between the centers of mass of /// four groups (colvarvalue::type_scalar type, range [-PI:PI]) class colvar::dihedral : public colvar::cvc { protected: /// Atom group cvm::atom_group *group1; /// Atom group cvm::atom_group *group2; /// Atom group cvm::atom_group *group3; /// Atom group cvm::atom_group *group4; /// 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; 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 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) const; }; /// \brief Colvar component: coordination number between two groups /// (colvarvalue::type_scalar type, range [0:N1*N2]) class colvar::coordnum : public colvar::cvc { protected: /// First atom group cvm::atom_group *group1; /// Second atom group cvm::atom_group *group2; /// \brief "Cutoff" for isotropic calculation (default) cvm::real r0; /// \brief "Cutoff vector" for anisotropic calculation cvm::rvector r0_vec; /// \brief Wheter dist/r0 or \vec{dist}*\vec{1/r0_vec} should ne be /// used bool b_anisotropic; /// Integer exponent of the function numerator int en; /// Integer exponent of the function denominator int ed; /// \brief If true, group2 will be treated as a single atom /// (default: loop over all pairs of atoms in group1 and group2) bool b_group2_center_only; public: /// Constructor coordnum(std::string const &conf); coordnum(); virtual ~coordnum() {} virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); template /// \brief Calculate a coordination number through the function /// (1-x**n)/(1-x**m), x = |A1-A2|/r0 \param r0 "cutoff" for the /// coordination number \param exp_num \i n exponent \param exp_den /// \i m exponent \param A1 atom \param A2 atom static cvm::real switching_function(cvm::real const &r0, int const &exp_num, int const &exp_den, cvm::atom &A1, cvm::atom &A2); template /// \brief Calculate a coordination number through the function /// (1-x**n)/(1-x**m), x = |(A1-A2)*(r0_vec)^-|1 \param r0_vec /// vector of different cutoffs in the three directions \param /// exp_num \i n exponent \param exp_den \i m exponent \param A1 /// atom \param A2 atom static cvm::real switching_function(cvm::rvector const &r0_vec, int const &exp_num, int const &exp_den, cvm::atom &A1, cvm::atom &A2); 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: self-coordination number within a group /// (colvarvalue::type_scalar type, range [0:N*(N-1)/2]) class colvar::selfcoordnum : public colvar::cvc { protected: /// First atom group cvm::atom_group *group1; /// \brief "Cutoff" for isotropic calculation (default) cvm::real r0; /// Integer exponent of the function numerator int en; /// Integer exponent of the function denominator int ed; public: /// Constructor selfcoordnum(std::string const &conf); selfcoordnum(); virtual ~selfcoordnum() {} virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); template /// \brief Calculate a coordination number through the function /// (1-x**n)/(1-x**m), x = |A1-A2|/r0 \param r0 "cutoff" for the /// coordination number \param exp_num \i n exponent \param exp_den /// \i m exponent \param A1 atom \param A2 atom static cvm::real switching_function(cvm::real const &r0, int const &exp_num, int const &exp_den, cvm::atom &A1, cvm::atom &A2); 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: coordination number between two groups /// (colvarvalue::type_scalar type, range [0:N1*N2]) class colvar::groupcoordnum : public colvar::distance { protected: /// \brief "Cutoff" for isotropic calculation (default) cvm::real r0; /// \brief "Cutoff vector" for anisotropic calculation cvm::rvector r0_vec; /// \brief Wheter dist/r0 or \vec{dist}*\vec{1/r0_vec} should ne be /// used bool b_anisotropic; /// Integer exponent of the function numerator int en; /// Integer exponent of the function denominator int ed; public: /// Constructor groupcoordnum(std::string const &conf); groupcoordnum(); virtual ~groupcoordnum() {} virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); template /// \brief Calculate a coordination number through the function /// (1-x**n)/(1-x**m), x = |A1-A2|/r0 \param r0 "cutoff" for the /// coordination number \param exp_num \i n exponent \param exp_den /// \i m exponent \param A1 atom \param A2 atom static cvm::real switching_function(cvm::real const &r0, int const &exp_num, int const &exp_den, cvm::atom &A1, cvm::atom &A2); /* template /// \brief Calculate a coordination number through the function /// (1-x**n)/(1-x**m), x = |(A1-A2)*(r0_vec)^-|1 \param r0_vec /// vector of different cutoffs in the three directions \param /// exp_num \i n exponent \param exp_den \i m exponent \param A1 /// atom \param A2 atom static cvm::real switching_function(cvm::rvector const &r0_vec, int const &exp_num, int const &exp_den, cvm::atom &A1, cvm::atom &A2); */ 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: hydrogen bond, defined as the product of /// a colvar::coordnum and 1/2*(1-cos((180-ang)/ang_tol)) /// (colvarvalue::type_scalar type, range [0:1]) class colvar::h_bond : public colvar::cvc { protected: /// \brief "Cutoff" distance between acceptor and donor cvm::real r0; /// Integer exponent of the function numerator int en; /// Integer exponent of the function denominator int ed; 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 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 /// [0:1]) class colvar::alpha_angles : public colvar::cvc { protected: /// Reference Calpha-Calpha angle (default: 88 degrees) cvm::real theta_ref; /// Tolerance on the Calpha-Calpha angle cvm::real theta_tol; /// List of Calpha-Calpha angles std::vector theta; /// List of hydrogen bonds std::vector hb; /// Contribution of the hb terms cvm::real hb_coeff; public: alpha_angles(std::string const &conf); alpha_angles(); virtual ~alpha_angles(); void calc_value(); void calc_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; }; /// \brief Colvar component: dihedPC /// Projection of the config onto a dihedral principal component /// See e.g. Altis et al., J. Chem. Phys 126, 244111 (2007) /// Based on a set of 'dihedral' cvcs class colvar::dihedPC : public colvar::cvc { protected: std::vector theta; std::vector coeffs; public: dihedPC(std::string const &conf); dihedPC(); virtual ~dihedPC(); void calc_value(); void calc_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; }; /// \brief Colvar component: orientation in space of an atom group, /// with respect to a set of reference coordinates /// (colvarvalue::type_quaternion type, range /// [-1:1]x[-1:1]x[-1:1]x[-1:1]) class colvar::orientation : public colvar::cvc { protected: /// Atom group cvm::atom_group * atoms; /// Center of geometry of the group cvm::atom_pos atoms_cog; /// Reference coordinates std::vector ref_pos; /// Rotation object cvm::rotation rot; /// \brief This is used to remove jumps in the sign of the /// quaternion, which may be annoying in the colvars trajectory cvm::quaternion ref_quat; public: orientation(std::string const &conf); orientation(); 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; }; /// \brief Colvar component: angle of rotation with respect to a set /// of reference coordinates (colvarvalue::type_scalar type, range /// [0:PI)) class colvar::orientation_angle : public colvar::orientation { public: orientation_angle(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; }; /// \brief Colvar component: cosine of the angle of rotation with respect to a set /// of reference coordinates (colvarvalue::type_scalar type, range /// [-1:1]) class colvar::orientation_proj : public colvar::orientation { public: orientation_proj(std::string const &conf); orientation_proj(); 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; }; /// \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 { protected: cvm::rvector axis; public: tilt(std::string const &conf); tilt(); virtual ~tilt() {} 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: angle of rotation around a predefined /// axis (colvarvalue::type_scalar type, range [-PI:PI]) class colvar::spin_angle : public colvar::orientation { protected: cvm::rvector axis; public: spin_angle(std::string const &conf); spin_angle(); 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) const; }; /// \brief Colvar component: root mean square deviation (RMSD) of a /// group with respect to a set of reference coordinates; uses \link /// colvar::orientation \endlink to calculate the rotation matrix /// (colvarvalue::type_scalar type, range [0:*)) class colvar::rmsd : public colvar::cvc { protected: /// Atom group cvm::atom_group *atoms; /// Reference coordinates (for RMSD calculation only) std::vector ref_pos; public: /// Constructor rmsd(std::string const &conf); virtual ~rmsd() {} 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 Colvar component: flat vector of Cartesian coordinates // Mostly useful to compute scripted colvar values class colvar::cartesian : public colvar::cvc { protected: /// Atom group cvm::atom_group *atoms; /// Which Cartesian coordinates to include std::vector axes; public: cartesian(std::string const &conf); cartesian(); virtual ~cartesian() {} virtual void calc_value(); virtual void calc_gradients(); virtual void apply_force(colvarvalue const &force); }; // 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_angles.cpp b/lib/colvars/colvarcomp_angles.cpp index 0204f3b4b..9f879a4c4 100644 --- a/lib/colvars/colvarcomp_angles.cpp +++ b/lib/colvars/colvarcomp_angles.cpp @@ -1,512 +1,669 @@ // -*- 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 "colvar.h" #include "colvarcomp.h" #include colvar::angle::angle(std::string const &conf) : cvc(conf) { function_type = "angle"; provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); enable(f_cvc_com_based); group1 = parse_group(conf, "group1"); group2 = parse_group(conf, "group2"); group3 = parse_group(conf, "group3"); init_total_force_params(conf); x.type(colvarvalue::type_scalar); } colvar::angle::angle(cvm::atom const &a1, cvm::atom const &a2, cvm::atom const &a3) { function_type = "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)); - atom_groups.push_back(group1); - atom_groups.push_back(group2); - atom_groups.push_back(group3); + register_atom_group(group1); + register_atom_group(group2); + register_atom_group(group3); x.type(colvarvalue::type_scalar); } colvar::angle::angle() { function_type = "angle"; x.type(colvarvalue::type_scalar); } void colvar::angle::calc_value() { cvm::atom_pos const g1_pos = group1->center_of_mass(); cvm::atom_pos const g2_pos = group2->center_of_mass(); cvm::atom_pos const g3_pos = group3->center_of_mass(); - r21 = cvm::position_distance(g2_pos, g1_pos); + r21 = is_enabled(f_cvc_pbc_minimum_image) ? + cvm::position_distance(g2_pos, g1_pos) : + g1_pos - g2_pos; r21l = r21.norm(); - r23 = cvm::position_distance(g2_pos, g3_pos); + r23 = is_enabled(f_cvc_pbc_minimum_image) ? + cvm::position_distance(g2_pos, g3_pos) : + g3_pos - g2_pos; r23l = r23.norm(); - cvm::real const cos_theta = (r21*r23)/(r21l*r23l); + cvm::real const cos_theta = (r21*r23)/(r21l*r23l); x.real_value = (180.0/PI) * std::acos(cos_theta); } void colvar::angle::calc_gradients() { cvm::real const cos_theta = (r21*r23)/(r21l*r23l); cvm::real const dxdcos = -1.0 / std::sqrt(1.0 - cos_theta*cos_theta); dxdr1 = (180.0/PI) * dxdcos * (1.0/r21l) * ( r23/r23l + (-1.0) * cos_theta * r21/r21l ); dxdr3 = (180.0/PI) * dxdcos * (1.0/r23l) * ( r21/r21l + (-1.0) * cos_theta * r23/r23l ); group1->set_weighted_gradient(dxdr1); group2->set_weighted_gradient((dxdr1 + dxdr3) * (-1.0)); group3->set_weighted_gradient(dxdr3); } void colvar::angle::calc_force_invgrads() { // This uses a force measurement on groups 1 and 3 only // to keep in line with the implicit variable change used to // evaluate the Jacobian term (essentially polar coordinates // centered on group2, which means group2 is kept fixed // when propagating changes in the angle) if (is_enabled(f_cvc_one_site_total_force)) { group1->read_total_forces(); cvm::real norm_fact = 1.0 / dxdr1.norm2(); ft.real_value = norm_fact * dxdr1 * group1->total_force(); } else { group1->read_total_forces(); group3->read_total_forces(); cvm::real norm_fact = 1.0 / (dxdr1.norm2() + dxdr3.norm2()); ft.real_value = norm_fact * ( dxdr1 * group1->total_force() + dxdr3 * group3->total_force()); } return; } void colvar::angle::calc_Jacobian_derivative() { // det(J) = (2 pi) r^2 * sin(theta) // hence Jd = cot(theta) const cvm::real theta = x.real_value * PI / 180.0; jd = PI / 180.0 * (theta != 0.0 ? std::cos(theta) / std::sin(theta) : 0.0); } 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) { function_type = "dipole_angle"; group1 = parse_group(conf, "group1"); group2 = parse_group(conf, "group2"); group3 = parse_group(conf, "group3"); init_total_force_params(conf); x.type(colvarvalue::type_scalar); } colvar::dipole_angle::dipole_angle(cvm::atom const &a1, cvm::atom const &a2, cvm::atom const &a3) { function_type = "dipole_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)); - atom_groups.push_back(group1); - atom_groups.push_back(group2); - atom_groups.push_back(group3); + register_atom_group(group1); + register_atom_group(group2); + register_atom_group(group3); x.type(colvarvalue::type_scalar); } colvar::dipole_angle::dipole_angle() { function_type = "dipole_angle"; x.type(colvarvalue::type_scalar); } void colvar::dipole_angle::calc_value() { cvm::atom_pos const g1_pos = group1->center_of_mass(); cvm::atom_pos const g2_pos = group2->center_of_mass(); cvm::atom_pos const g3_pos = group3->center_of_mass(); group1->calc_dipole(g1_pos); r21 = group1->dipole(); r21l = r21.norm(); - r23 = cvm::position_distance(g2_pos, g3_pos); + r23 = is_enabled(f_cvc_pbc_minimum_image) ? + cvm::position_distance(g2_pos, g3_pos) : + g3_pos - g2_pos; r23l = r23.norm(); - cvm::real const cos_theta = (r21*r23)/(r21l*r23l); + cvm::real const cos_theta = (r21*r23)/(r21l*r23l); x.real_value = (180.0/PI) * std::acos(cos_theta); } //to be implemented //void colvar::dipole_angle::calc_force_invgrads(){} //void colvar::dipole_angle::calc_Jacobian_derivative(){} void colvar::dipole_angle::calc_gradients() { cvm::real const cos_theta = (r21*r23)/(r21l*r23l); cvm::real const dxdcos = -1.0 / std::sqrt(1.0 - cos_theta*cos_theta); dxdr1 = (180.0/PI) * dxdcos * (1.0/r21l)* (r23/r23l + (-1.0) * cos_theta * r21/r21l ); dxdr3 = (180.0/PI) * dxdcos * (1.0/r23l) * ( r21/r21l + (-1.0) * cos_theta * r23/r23l ); //this auxiliar variables are to avoid numerical errors inside "for" double aux1 = group1->total_charge/group1->total_mass; // double aux2 = group2->total_charge/group2->total_mass; // double aux3 = group3->total_charge/group3->total_mass; size_t i; for (i = 0; i < group1->size(); i++) { (*group1)[i].grad =((*group1)[i].charge + (-1)* (*group1)[i].mass * aux1) * (dxdr1); } for (i = 0; i < group2->size(); i++) { (*group2)[i].grad = ((*group2)[i].mass/group2->total_mass)* dxdr3 * (-1.0); } for (i = 0; i < group3->size(); i++) { (*group3)[i].grad =((*group3)[i].mass/group3->total_mass) * (dxdr3); } } 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) { function_type = "dihedral"; period = 360.0; b_periodic = true; provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); enable(f_cvc_com_based); 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); x.type(colvarvalue::type_scalar); } colvar::dihedral::dihedral(cvm::atom const &a1, cvm::atom const &a2, cvm::atom const &a3, cvm::atom const &a4) { if (cvm::debug()) cvm::log("Initializing dihedral object from atom groups.\n"); function_type = "dihedral"; period = 360.0; b_periodic = true; 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)); group2 = new cvm::atom_group(std::vector(1, a2)); group3 = new cvm::atom_group(std::vector(1, a3)); group4 = new cvm::atom_group(std::vector(1, a4)); - atom_groups.push_back(group1); - atom_groups.push_back(group2); - atom_groups.push_back(group3); - atom_groups.push_back(group4); + register_atom_group(group1); + register_atom_group(group2); + register_atom_group(group3); + register_atom_group(group4); x.type(colvarvalue::type_scalar); if (cvm::debug()) cvm::log("Done initializing dihedral object from atom groups.\n"); } colvar::dihedral::dihedral() { function_type = "dihedral"; period = 360.0; b_periodic = true; provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); x.type(colvarvalue::type_scalar); } void colvar::dihedral::calc_value() { cvm::atom_pos const g1_pos = group1->center_of_mass(); cvm::atom_pos const g2_pos = group2->center_of_mass(); cvm::atom_pos const g3_pos = group3->center_of_mass(); cvm::atom_pos const g4_pos = group4->center_of_mass(); // Usual sign convention: r12 = r2 - r1 - r12 = cvm::position_distance(g1_pos, g2_pos); - r23 = cvm::position_distance(g2_pos, g3_pos); - r34 = cvm::position_distance(g3_pos, g4_pos); + r12 = is_enabled(f_cvc_pbc_minimum_image) ? + cvm::position_distance(g1_pos, g2_pos) : + g2_pos - g1_pos; + r23 = is_enabled(f_cvc_pbc_minimum_image) ? + cvm::position_distance(g2_pos, g3_pos) : + g3_pos - g2_pos; + r34 = is_enabled(f_cvc_pbc_minimum_image) ? + cvm::position_distance(g3_pos, g4_pos) : + g4_pos - g3_pos; cvm::rvector const n1 = cvm::rvector::outer(r12, r23); cvm::rvector const n2 = cvm::rvector::outer(r23, r34); cvm::real const cos_phi = n1 * n2; cvm::real const sin_phi = n1 * r34 * r23.norm(); x.real_value = (180.0/PI) * std::atan2(sin_phi, cos_phi); this->wrap(x); } void colvar::dihedral::calc_gradients() { cvm::rvector A = cvm::rvector::outer(r12, r23); cvm::real rA = A.norm(); cvm::rvector B = cvm::rvector::outer(r23, r34); cvm::real rB = B.norm(); cvm::rvector C = cvm::rvector::outer(r23, A); cvm::real rC = C.norm(); cvm::real const cos_phi = (A*B)/(rA*rB); cvm::real const sin_phi = (C*B)/(rC*rB); cvm::rvector f1, f2, f3; rB = 1.0/rB; B *= rB; if (std::fabs(sin_phi) > 0.1) { rA = 1.0/rA; A *= rA; cvm::rvector const dcosdA = rA*(cos_phi*A-B); cvm::rvector const dcosdB = rB*(cos_phi*B-A); rA = 1.0; cvm::real const K = (1.0/sin_phi) * (180.0/PI); - f1 = K * cvm::rvector::outer(r23, dcosdA); - f3 = K * cvm::rvector::outer(dcosdB, r23); - f2 = K * (cvm::rvector::outer(dcosdA, r12) - + cvm::rvector::outer(r34, dcosdB)); + f1 = K * cvm::rvector::outer(r23, dcosdA); + f3 = K * cvm::rvector::outer(dcosdB, r23); + f2 = K * (cvm::rvector::outer(dcosdA, r12) + + cvm::rvector::outer(r34, dcosdB)); } else { rC = 1.0/rC; C *= rC; cvm::rvector const dsindC = rC*(sin_phi*C-B); cvm::rvector const dsindB = rB*(sin_phi*B-C); rC = 1.0; cvm::real const K = (-1.0/cos_phi) * (180.0/PI); f1.x = K*((r23.y*r23.y + r23.z*r23.z)*dsindC.x - r23.x*r23.y*dsindC.y - r23.x*r23.z*dsindC.z); f1.y = K*((r23.z*r23.z + r23.x*r23.x)*dsindC.y - r23.y*r23.z*dsindC.z - r23.y*r23.x*dsindC.x); f1.z = K*((r23.x*r23.x + r23.y*r23.y)*dsindC.z - r23.z*r23.x*dsindC.x - r23.z*r23.y*dsindC.y); f3 = cvm::rvector::outer(dsindB, r23); f3 *= K; f2.x = K*(-(r23.y*r12.y + r23.z*r12.z)*dsindC.x +(2.0*r23.x*r12.y - r12.x*r23.y)*dsindC.y +(2.0*r23.x*r12.z - r12.x*r23.z)*dsindC.z +dsindB.z*r34.y - dsindB.y*r34.z); f2.y = K*(-(r23.z*r12.z + r23.x*r12.x)*dsindC.y +(2.0*r23.y*r12.z - r12.y*r23.z)*dsindC.z +(2.0*r23.y*r12.x - r12.y*r23.x)*dsindC.x +dsindB.x*r34.z - dsindB.z*r34.x); f2.z = K*(-(r23.x*r12.x + r23.y*r12.y)*dsindC.z +(2.0*r23.z*r12.x - r12.z*r23.x)*dsindC.x +(2.0*r23.z*r12.y - r12.z*r23.y)*dsindC.y +dsindB.y*r34.x - dsindB.x*r34.y); } group1->set_weighted_gradient(-f1); group2->set_weighted_gradient(-f2 + f1); group3->set_weighted_gradient(-f3 + f2); group4->set_weighted_gradient(f3); } void colvar::dihedral::calc_force_invgrads() { cvm::rvector const u12 = r12.unit(); cvm::rvector const u23 = r23.unit(); cvm::rvector const u34 = r34.unit(); cvm::real const d12 = r12.norm(); cvm::real const d34 = r34.norm(); cvm::rvector const cross1 = (cvm::rvector::outer(u23, u12)).unit(); cvm::rvector const cross4 = (cvm::rvector::outer(u23, u34)).unit(); cvm::real const dot1 = u23 * u12; cvm::real const dot4 = u23 * u34; cvm::real const fact1 = d12 * std::sqrt(1.0 - dot1 * dot1); cvm::real const fact4 = d34 * std::sqrt(1.0 - dot4 * dot4); group1->read_total_forces(); if (is_enabled(f_cvc_one_site_total_force)) { // This is only measuring the force on group 1 ft.real_value = PI/180.0 * fact1 * (cross1 * group1->total_force()); } else { // Default case: use groups 1 and 4 group4->read_total_forces(); ft.real_value = PI/180.0 * 0.5 * (fact1 * (cross1 * group1->total_force()) - + fact4 * (cross4 * group4->total_force())); + + fact4 * (cross4 * group4->total_force())); } } void colvar::dihedral::calc_Jacobian_derivative() { // With this choice of inverse gradient ("internal coordinates"), Jacobian correction is 0 jd = 0.0; } 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) const { if ((x.real_value - wrap_center) >= 180.0) { x.real_value -= 360.0; return; } if ((x.real_value - wrap_center) < -180.0) { x.real_value += 360.0; return; } return; } + + +colvar::polar_theta::polar_theta(std::string const &conf) + : cvc(conf) +{ + function_type = "polar_theta"; + 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() +{ + function_type = "polar_theta"; + x.type(colvarvalue::type_scalar); +} + + +void colvar::polar_theta::calc_value() +{ + cvm::rvector pos = atoms->center_of_mass(); + r = atoms->center_of_mass().norm(); + // Internal values of theta and phi are radians + theta = (r > 0.) ? std::acos(pos.z / r) : 0.; + phi = std::atan2(pos.y, pos.x); + x.real_value = (180.0/PI) * theta; +} + + +void colvar::polar_theta::calc_gradients() +{ + if (r == 0.) + atoms->set_weighted_gradient(cvm::rvector(0., 0., 0.)); + else + atoms->set_weighted_gradient(cvm::rvector( + (180.0/PI) * std::cos(theta) * std::cos(phi) / r, + (180.0/PI) * std::cos(theta) * std::sin(phi) / r, + (180.0/PI) * -std::sin(theta) / r)); +} + + +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) +{ + function_type = "polar_phi"; + period = 360.0; + enable(f_cvc_com_based); + + atoms = parse_group(conf, "atoms"); + init_total_force_params(conf); + x.type(colvarvalue::type_scalar); +} + + +colvar::polar_phi::polar_phi() +{ + function_type = "polar_phi"; + period = 360.0; + x.type(colvarvalue::type_scalar); +} + + +void colvar::polar_phi::calc_value() +{ + cvm::rvector pos = atoms->center_of_mass(); + r = atoms->center_of_mass().norm(); + // Internal values of theta and phi are radians + theta = (r > 0.) ? std::acos(pos.z / r) : 0.; + phi = std::atan2(pos.y, pos.x); + x.real_value = (180.0/PI) * phi; +} + + +void colvar::polar_phi::calc_gradients() +{ + atoms->set_weighted_gradient(cvm::rvector( + (180.0/PI) * -std::sin(phi) / (r*std::sin(theta)), + (180.0/PI) * std::cos(phi) / (r*std::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) const +{ + if ((x.real_value - wrap_center) >= 180.0) { + x.real_value -= 360.0; + return; + } + + if ((x.real_value - wrap_center) < -180.0) { + x.real_value += 360.0; + return; + } + + return; +} diff --git a/lib/colvars/colvarcomp_coordnums.cpp b/lib/colvars/colvarcomp_coordnums.cpp index 987a16a81..369d489e2 100644 --- a/lib/colvars/colvarcomp_coordnums.cpp +++ b/lib/colvars/colvarcomp_coordnums.cpp @@ -1,506 +1,529 @@ // -*- 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 "colvarmodule.h" #include "colvarparse.h" #include "colvaratoms.h" #include "colvarvalue.h" #include "colvar.h" #include "colvarcomp.h" template cvm::real colvar::coordnum::switching_function(cvm::real const &r0, int const &en, int const &ed, cvm::atom &A1, cvm::atom &A2) { cvm::rvector const diff = cvm::position_distance(A1.pos, A2.pos); cvm::real const l2 = diff.norm2()/(r0*r0); // Assume en and ed are even integers, and avoid sqrt in the following int const en2 = en/2; int const ed2 = ed/2; cvm::real const xn = std::pow(l2, en2); cvm::real const xd = std::pow(l2, ed2); cvm::real const func = (1.0-xn)/(1.0-xd); if (calculate_gradients) { cvm::real const dFdl2 = (1.0/(1.0-xd))*(en2*(xn/l2) - func*ed2*(xd/l2))*(-1.0); cvm::rvector const dl2dx = (2.0/(r0*r0))*diff; A1.grad += (-1.0)*dFdl2*dl2dx; A2.grad += dFdl2*dl2dx; } return func; } template cvm::real colvar::coordnum::switching_function(cvm::rvector const &r0_vec, int const &en, int const &ed, cvm::atom &A1, cvm::atom &A2) { cvm::rvector const diff = cvm::position_distance(A1.pos, A2.pos); cvm::rvector const scal_diff(diff.x/r0_vec.x, diff.y/r0_vec.y, diff.z/r0_vec.z); cvm::real const l2 = scal_diff.norm2(); // Assume en and ed are even integers, and avoid sqrt in the following int const en2 = en/2; int const ed2 = ed/2; cvm::real const xn = std::pow(l2, en2); cvm::real const xd = std::pow(l2, ed2); cvm::real const func = (1.0-xn)/(1.0-xd); if (calculate_gradients) { cvm::real const dFdl2 = (1.0/(1.0-xd))*(en2*(xn/l2) - func*ed2*(xd/l2))*(-1.0); cvm::rvector const dl2dx((2.0/(r0_vec.x*r0_vec.x))*diff.x, (2.0/(r0_vec.y*r0_vec.y))*diff.y, (2.0/(r0_vec.z*r0_vec.z))*diff.z); A1.grad += (-1.0)*dFdl2*dl2dx; A2.grad += dFdl2*dl2dx; } return func; } colvar::coordnum::coordnum(std::string const &conf) : cvc(conf), b_anisotropic(false), b_group2_center_only(false) { function_type = "coordnum"; x.type(colvarvalue::type_scalar); group1 = parse_group(conf, "group1"); group2 = parse_group(conf, "group2"); - if (group1->b_dummy) - cvm::fatal_error("Error: only group2 is allowed to be a dummy atom\n"); + if (group1->b_dummy) { + cvm::error("Error: only group2 is allowed to be a dummy atom\n"); + return; + } bool const b_isotropic = get_keyval(conf, "cutoff", r0, cvm::real(4.0 * cvm::unit_angstrom())); if (get_keyval(conf, "cutoff3", r0_vec, cvm::rvector(4.0 * cvm::unit_angstrom(), 4.0 * cvm::unit_angstrom(), 4.0 * cvm::unit_angstrom()))) { if (b_isotropic) { cvm::error("Error: cannot specify \"cutoff\" and \"cutoff3\" at the same time.\n", INPUT_ERROR); + return; } b_anisotropic = true; // remove meaningless negative signs if (r0_vec.x < 0.0) r0_vec.x *= -1.0; if (r0_vec.y < 0.0) r0_vec.y *= -1.0; if (r0_vec.z < 0.0) r0_vec.z *= -1.0; } get_keyval(conf, "expNumer", en, int(6) ); get_keyval(conf, "expDenom", ed, int(12)); if ( (en%2) || (ed%2) ) { cvm::error("Error: odd exponents provided, can only use even ones.\n", 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, "group2CenterOnly", b_group2_center_only, group2->b_dummy); } colvar::coordnum::coordnum() : b_anisotropic(false), b_group2_center_only(false) { function_type = "coordnum"; x.type(colvarvalue::type_scalar); } void colvar::coordnum::calc_value() { x.real_value = 0.0; if (b_group2_center_only) { // create a fake atom to hold the group2 com coordinates cvm::atom group2_com_atom; group2_com_atom.pos = group2->center_of_mass(); if (b_anisotropic) { for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) x.real_value += switching_function(r0_vec, en, ed, *ai1, group2_com_atom); } else { for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) x.real_value += switching_function(r0, en, ed, *ai1, group2_com_atom); } } else { if (b_anisotropic) { for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { x.real_value += switching_function(r0_vec, en, ed, *ai1, *ai2); } } else { for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { x.real_value += switching_function(r0, en, ed, *ai1, *ai2); } } } } void colvar::coordnum::calc_gradients() { if (b_group2_center_only) { // create a fake atom to hold the group2 com coordinates cvm::atom group2_com_atom; group2_com_atom.pos = group2->center_of_mass(); if (b_anisotropic) { for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) switching_function(r0_vec, en, ed, *ai1, group2_com_atom); } else { for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) switching_function(r0, en, ed, *ai1, group2_com_atom); } group2->set_weighted_gradient(group2_com_atom.grad); } else { if (b_anisotropic) { for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { switching_function(r0_vec, en, ed, *ai1, *ai2); } } else { for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { switching_function(r0, en, ed, *ai1, *ai2); } } } } 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) { if (cvm::debug()) cvm::log("Initializing h_bond object.\n"); function_type = "h_bond"; x.type(colvarvalue::type_scalar); int a_num, d_num; get_keyval(conf, "acceptor", a_num, -1); get_keyval(conf, "donor", d_num, -1); if ( (a_num == -1) || (d_num == -1) ) { - cvm::fatal_error("Error: either acceptor or donor undefined.\n"); + cvm::error("Error: either acceptor or donor undefined.\n"); + return; } cvm::atom acceptor = cvm::atom(a_num); cvm::atom donor = cvm::atom(d_num); - atom_groups.push_back(new cvm::atom_group); + register_atom_group(new cvm::atom_group); atom_groups[0]->add_atom(acceptor); atom_groups[0]->add_atom(donor); get_keyval(conf, "cutoff", r0, (3.3 * cvm::unit_angstrom())); get_keyval(conf, "expNumer", en, 6); get_keyval(conf, "expDenom", ed, 8); if ( (en%2) || (ed%2) ) { - cvm::fatal_error("Error: odd exponents provided, can only use even ones.\n"); + cvm::error("Error: odd exponents provided, can only use even ones.\n"); + return; } if (cvm::debug()) cvm::log("Done initializing h_bond object.\n"); } 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) { function_type = "h_bond"; x.type(colvarvalue::type_scalar); - atom_groups.push_back(new cvm::atom_group); + register_atom_group(new cvm::atom_group); atom_groups[0]->add_atom(acceptor); atom_groups[0]->add_atom(donor); } colvar::h_bond::h_bond() : cvc() { function_type = "h_bond"; x.type(colvarvalue::type_scalar); } colvar::h_bond::~h_bond() { delete atom_groups[0]; } void colvar::h_bond::calc_value() { x.real_value = colvar::coordnum::switching_function(r0, en, ed, (*atom_groups[0])[0], (*atom_groups[0])[1]); } void colvar::h_bond::calc_gradients() { colvar::coordnum::switching_function(r0, en, ed, (*atom_groups[0])[0], (*atom_groups[0])[1]); } 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) { function_type = "selfcoordnum"; x.type(colvarvalue::type_scalar); group1 = parse_group(conf, "group1"); get_keyval(conf, "cutoff", r0, cvm::real(4.0 * cvm::unit_angstrom())); get_keyval(conf, "expNumer", en, int(6) ); get_keyval(conf, "expDenom", ed, int(12)); if ( (en%2) || (ed%2) ) { - cvm::fatal_error("Error: odd exponents provided, can only use even ones.\n"); + cvm::error("Error: odd exponents provided, can only use even ones.\n"); + return; + } + + if (!is_enabled(f_cvc_pbc_minimum_image)) { + cvm::log("Warning: only minimum-image distances are used by this variable.\n"); } } colvar::selfcoordnum::selfcoordnum() { function_type = "selfcoordnum"; x.type(colvarvalue::type_scalar); } void colvar::selfcoordnum::calc_value() { x.real_value = 0.0; for (size_t i = 0; i < group1->size() - 1; i++) { for (size_t j = i + 1; j < group1->size(); j++) { x.real_value += colvar::coordnum::switching_function(r0, en, ed, (*group1)[i], (*group1)[j]); } } } void colvar::selfcoordnum::calc_gradients() { for (size_t i = 0; i < group1->size() - 1; i++) { for (size_t j = i + 1; j < group1->size(); j++) { colvar::coordnum::switching_function(r0, en, ed, (*group1)[i], (*group1)[j]); } } } 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) { function_type = "groupcoordnum"; x.type(colvarvalue::type_scalar); // group1 and group2 are already initialized by distance() - if (group1->b_dummy || group2->b_dummy) - cvm::fatal_error("Error: neither group can be a dummy atom\n"); + if (group1->b_dummy || group2->b_dummy) { + cvm::error("Error: neither group can be a dummy atom\n"); + return; + } bool const b_scale = get_keyval(conf, "cutoff", r0, cvm::real(4.0 * cvm::unit_angstrom())); if (get_keyval(conf, "cutoff3", r0_vec, cvm::rvector(4.0, 4.0, 4.0), parse_silent)) { - if (b_scale) - cvm::fatal_error("Error: cannot specify \"scale\" and " + if (b_scale) { + cvm::error("Error: cannot specify \"scale\" and " "\"scale3\" at the same time.\n"); + return; + } b_anisotropic = true; // remove meaningless negative signs if (r0_vec.x < 0.0) r0_vec.x *= -1.0; if (r0_vec.y < 0.0) r0_vec.y *= -1.0; if (r0_vec.z < 0.0) r0_vec.z *= -1.0; } get_keyval(conf, "expNumer", en, int(6) ); get_keyval(conf, "expDenom", ed, int(12)); if ( (en%2) || (ed%2) ) { - cvm::fatal_error("Error: odd exponents provided, can only use even ones.\n"); + cvm::error("Error: odd exponents provided, can only use even ones.\n"); + return; + } + + if (!is_enabled(f_cvc_pbc_minimum_image)) { + cvm::log("Warning: only minimum-image distances are used by this variable.\n"); } } colvar::groupcoordnum::groupcoordnum() : b_anisotropic(false) { function_type = "groupcoordnum"; x.type(colvarvalue::type_scalar); } template cvm::real colvar::groupcoordnum::switching_function(cvm::real const &r0, int const &en, int const &ed, cvm::atom &A1, cvm::atom &A2) { cvm::rvector const diff = cvm::position_distance(A1.pos, A2.pos); cvm::real const l2 = diff.norm2()/(r0*r0); // Assume en and ed are even integers, and avoid sqrt in the following int const en2 = en/2; int const ed2 = ed/2; cvm::real const xn = std::pow(l2, en2); cvm::real const xd = std::pow(l2, ed2); cvm::real const func = (1.0-xn)/(1.0-xd); if (calculate_gradients) { cvm::real const dFdl2 = (1.0/(1.0-xd))*(en2*(xn/l2) - func*ed2*(xd/l2))*(-1.0); cvm::rvector const dl2dx = (2.0/(r0*r0))*diff; A1.grad += (-1.0)*dFdl2*dl2dx; A2.grad += dFdl2*dl2dx; } return func; } #if 0 // AMG: I don't think there's any reason to support anisotropic, // and I don't have those flags below in calc_value, but // if I need them, I'll also need to uncomment this method template cvm::real colvar::groupcoordnum::switching_function(cvm::rvector const &r0_vec, int const &en, int const &ed, cvm::atom &A1, cvm::atom &A2) { cvm::rvector const diff = cvm::position_distance(A1.pos, A2.pos); cvm::rvector const scal_diff(diff.x/r0_vec.x, diff.y/r0_vec.y, diff.z/r0_vec.z); cvm::real const l2 = scal_diff.norm2(); // Assume en and ed are even integers, and avoid sqrt in the following int const en2 = en/2; int const ed2 = ed/2; cvm::real const xn = std::pow(l2, en2); cvm::real const xd = std::pow(l2, ed2); cvm::real const func = (1.0-xn)/(1.0-xd); if (calculate_gradients) { cvm::real const dFdl2 = (1.0/(1.0-xd))*(en2*(xn/l2) - func*ed2*(xd/l2))*(-1.0); cvm::rvector const dl2dx((2.0/(r0_vec.x*r0_vec.x))*diff.x, (2.0/(r0_vec.y*r0_vec.y))*diff.y, (2.0/(r0_vec.z*r0_vec.z))*diff.z); A1.grad += (-1.0)*dFdl2*dl2dx; A2.grad += dFdl2*dl2dx; } return func; } #endif void colvar::groupcoordnum::calc_value() { // create fake atoms to hold the com coordinates cvm::atom group1_com_atom; cvm::atom group2_com_atom; group1_com_atom.pos = group1->center_of_mass(); group2_com_atom.pos = group2->center_of_mass(); x.real_value = coordnum::switching_function(r0, en, ed, group1_com_atom, group2_com_atom); } void colvar::groupcoordnum::calc_gradients() { cvm::atom group1_com_atom; cvm::atom group2_com_atom; group1_com_atom.pos = group1->center_of_mass(); group2_com_atom.pos = group2->center_of_mass(); coordnum::switching_function(r0, en, ed, group1_com_atom, group2_com_atom); 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 f46270246..18d154515 100644 --- a/lib/colvars/colvarcomp_distances.cpp +++ b/lib/colvars/colvarcomp_distances.cpp @@ -1,1463 +1,1472 @@ // -*- 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 "colvarmodule.h" #include "colvarvalue.h" #include "colvarparse.h" #include "colvar.h" #include "colvarcomp.h" colvar::distance::distance(std::string const &conf) : cvc(conf) { function_type = "distance"; provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); enable(f_cvc_com_based); group1 = parse_group(conf, "group1"); group2 = parse_group(conf, "group2"); - if (get_keyval(conf, "forceNoPBC", b_no_PBC, false)) { - cvm::log("Computing distance using absolute positions (not minimal-image)"); - } - init_total_force_params(conf); x.type(colvarvalue::type_scalar); } colvar::distance::distance() : cvc() { function_type = "distance"; provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); enable(f_cvc_com_based); - b_no_PBC = false; x.type(colvarvalue::type_scalar); } void colvar::distance::calc_value() { - if (b_no_PBC) { + if (!is_enabled(f_cvc_pbc_minimum_image)) { dist_v = group2->center_of_mass() - group1->center_of_mass(); } else { dist_v = cvm::position_distance(group1->center_of_mass(), - group2->center_of_mass()); + group2->center_of_mass()); } x.real_value = dist_v.norm(); } void colvar::distance::calc_gradients() { cvm::rvector const u = dist_v.unit(); group1->set_weighted_gradient(-1.0 * u); group2->set_weighted_gradient( u); } void colvar::distance::calc_force_invgrads() { group1->read_total_forces(); if (is_enabled(f_cvc_one_site_total_force)) { ft.real_value = -1.0 * (group1->total_force() * dist_v.unit()); } else { group2->read_total_forces(); ft.real_value = 0.5 * ((group2->total_force() - group1->total_force()) * dist_v.unit()); } } void colvar::distance::calc_Jacobian_derivative() { jd.real_value = x.real_value ? (2.0 / x.real_value) : 0.0; } 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) { function_type = "distance_vec"; enable(f_cvc_com_based); + enable(f_cvc_implicit_gradient); x.type(colvarvalue::type_3vector); } colvar::distance_vec::distance_vec() : distance() { function_type = "distance_vec"; enable(f_cvc_com_based); + enable(f_cvc_implicit_gradient); x.type(colvarvalue::type_3vector); } void colvar::distance_vec::calc_value() { - if (b_no_PBC) { + if (!is_enabled(f_cvc_pbc_minimum_image)) { x.rvector_value = group2->center_of_mass() - group1->center_of_mass(); } else { x.rvector_value = cvm::position_distance(group1->center_of_mass(), - group2->center_of_mass()); + group2->center_of_mass()); } } void colvar::distance_vec::calc_gradients() { // gradients are not stored: a 3x3 matrix for each atom would be // needed to store just the identity matrix } void colvar::distance_vec::apply_force(colvarvalue const &force) { if (!group1->noforce) group1->apply_force(-1.0 * force.rvector_value); if (!group2->noforce) group2->apply_force( force.rvector_value); } cvm::real colvar::distance_vec::dist2(colvarvalue const &x1, colvarvalue const &x2) const { return cvm::position_dist2(x1.rvector_value, x2.rvector_value); } 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); } 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); } colvar::distance_z::distance_z(std::string const &conf) : cvc(conf) { function_type = "distance_z"; provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); enable(f_cvc_com_based); 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) b_periodic = true; if ((wrap_center != 0.0) && (period == 0.0)) { cvm::error("Error: wrapAround was defined in a distanceZ component," " but its period has not been set.\n"); return; } main = parse_group(conf, "main"); ref1 = parse_group(conf, "ref"); // this group is optional ref2 = parse_group(conf, "ref2", true); if (ref2 && ref2->size()) { cvm::log("Using axis joining the centers of mass of groups \"ref\" and \"ref2\""); fixed_axis = false; if (key_lookup(conf, "axis")) cvm::log("Warning: explicit axis definition will be ignored!"); } 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; } if (axis.norm2() != 1.0) { axis = axis.unit(); cvm::log("The normalized axis is: "+cvm::to_str(axis)+".\n"); } } fixed_axis = true; } - if (get_keyval(conf, "forceNoPBC", b_no_PBC, false)) { - cvm::log("Computing distance using absolute positions (not minimal-image)"); - } - init_total_force_params(conf); } colvar::distance_z::distance_z() { function_type = "distance_z"; provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); enable(f_cvc_com_based); x.type(colvarvalue::type_scalar); } void colvar::distance_z::calc_value() { if (fixed_axis) { - if (b_no_PBC) { + if (!is_enabled(f_cvc_pbc_minimum_image)) { dist_v = main->center_of_mass() - ref1->center_of_mass(); } else { dist_v = cvm::position_distance(ref1->center_of_mass(), - main->center_of_mass()); + main->center_of_mass()); } } else { - if (b_no_PBC) { + if (!is_enabled(f_cvc_pbc_minimum_image)) { dist_v = main->center_of_mass() - (0.5 * (ref1->center_of_mass() + ref2->center_of_mass())); axis = ref2->center_of_mass() - ref1->center_of_mass(); } else { dist_v = cvm::position_distance(0.5 * (ref1->center_of_mass() + - ref2->center_of_mass()), main->center_of_mass()); - axis = cvm::position_distance(ref1->center_of_mass(), ref2->center_of_mass()); + ref2->center_of_mass()), + main->center_of_mass()); + axis = cvm::position_distance(ref1->center_of_mass(), + ref2->center_of_mass()); } axis_norm = axis.norm(); axis = axis.unit(); } x.real_value = axis * dist_v; this->wrap(x); } void colvar::distance_z::calc_gradients() { main->set_weighted_gradient( axis ); if (fixed_axis) { ref1->set_weighted_gradient(-1.0 * axis); } else { - if (b_no_PBC) { - ref1->set_weighted_gradient( 1.0 / axis_norm * (main->center_of_mass() - ref2->center_of_mass() - + if (!is_enabled(f_cvc_pbc_minimum_image)) { + ref1->set_weighted_gradient( 1.0 / axis_norm * + (main->center_of_mass() - ref2->center_of_mass() - x.real_value * axis )); - ref2->set_weighted_gradient( 1.0 / axis_norm * (ref1->center_of_mass() - main->center_of_mass() + + ref2->set_weighted_gradient( 1.0 / axis_norm * + (ref1->center_of_mass() - main->center_of_mass() + x.real_value * axis )); } else { ref1->set_weighted_gradient( 1.0 / axis_norm * ( - cvm::position_distance(ref2->center_of_mass(), main->center_of_mass()) - x.real_value * axis )); + cvm::position_distance(ref2->center_of_mass(), + main->center_of_mass()) - x.real_value * axis )); ref2->set_weighted_gradient( 1.0 / axis_norm * ( - cvm::position_distance(main->center_of_mass(), ref1->center_of_mass()) + x.real_value * axis )); + cvm::position_distance(main->center_of_mass(), + ref1->center_of_mass()) + x.real_value * axis )); } } } void colvar::distance_z::calc_force_invgrads() { main->read_total_forces(); if (fixed_axis && !is_enabled(f_cvc_one_site_total_force)) { ref1->read_total_forces(); ft.real_value = 0.5 * ((main->total_force() - ref1->total_force()) * axis); } else { ft.real_value = main->total_force() * axis; } } void colvar::distance_z::calc_Jacobian_derivative() { jd.real_value = 0.0; } void colvar::distance_z::apply_force(colvarvalue const &force) { if (!ref1->noforce) ref1->apply_colvar_force(force.real_value); if (ref2 && ref2->size() && !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 (b_periodic) { cvm::real shift = std::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 (b_periodic) { cvm::real shift = std::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 (b_periodic) { cvm::real shift = std::floor(diff/period + 0.5); diff -= shift * period; } return (-2.0) * diff; } void colvar::distance_z::wrap(colvarvalue &x) const { if (!b_periodic) { // don't wrap if the period has not been set return; } cvm::real shift = std::floor((x.real_value - wrap_center) / period + 0.5); x.real_value -= shift * period; return; } colvar::distance_xy::distance_xy(std::string const &conf) : distance_z(conf) { function_type = "distance_xy"; provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); enable(f_cvc_com_based); x.type(colvarvalue::type_scalar); } colvar::distance_xy::distance_xy() : distance_z() { function_type = "distance_xy"; provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); enable(f_cvc_com_based); x.type(colvarvalue::type_scalar); } void colvar::distance_xy::calc_value() { - if (b_no_PBC) { + if (!is_enabled(f_cvc_pbc_minimum_image)) { dist_v = main->center_of_mass() - ref1->center_of_mass(); } else { dist_v = cvm::position_distance(ref1->center_of_mass(), - main->center_of_mass()); + main->center_of_mass()); } if (!fixed_axis) { - if (b_no_PBC) { + if (!is_enabled(f_cvc_pbc_minimum_image)) { v12 = ref2->center_of_mass() - ref1->center_of_mass(); } else { - v12 = cvm::position_distance(ref1->center_of_mass(), ref2->center_of_mass()); + v12 = cvm::position_distance(ref1->center_of_mass(), + ref2->center_of_mass()); } axis_norm = v12.norm(); axis = v12.unit(); } dist_v_ortho = dist_v - (dist_v * axis) * axis; x.real_value = dist_v_ortho.norm(); } void colvar::distance_xy::calc_gradients() { // Intermediate quantity (r_P3 / r_12 where P is the projection // of 3(main) on the plane orthogonal to 12, containing 1 (ref1)) cvm::real A; cvm::real x_inv; if (x.real_value == 0.0) return; x_inv = 1.0 / x.real_value; if (fixed_axis) { ref1->set_weighted_gradient(-1.0 * x_inv * dist_v_ortho); main->set_weighted_gradient( x_inv * dist_v_ortho); } else { - if (b_no_PBC) { + if (!is_enabled(f_cvc_pbc_minimum_image)) { v13 = main->center_of_mass() - ref1->center_of_mass(); } else { - v13 = cvm::position_distance(ref1->center_of_mass(), main->center_of_mass()); + v13 = cvm::position_distance(ref1->center_of_mass(), + main->center_of_mass()); } A = (dist_v * axis) / axis_norm; ref1->set_weighted_gradient( (A - 1.0) * x_inv * dist_v_ortho); ref2->set_weighted_gradient( -A * x_inv * dist_v_ortho); main->set_weighted_gradient( 1.0 * x_inv * dist_v_ortho); } } void colvar::distance_xy::calc_force_invgrads() { main->read_total_forces(); if (fixed_axis && !is_enabled(f_cvc_one_site_total_force)) { ref1->read_total_forces(); ft.real_value = 0.5 / x.real_value * ((main->total_force() - ref1->total_force()) * dist_v_ortho); } else { ft.real_value = 1.0 / x.real_value * main->total_force() * dist_v_ortho; } } void colvar::distance_xy::calc_Jacobian_derivative() { jd.real_value = x.real_value ? (1.0 / x.real_value) : 0.0; } void colvar::distance_xy::apply_force(colvarvalue const &force) { if (!ref1->noforce) ref1->apply_colvar_force(force.real_value); if (ref2 && ref2->size() && !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) { function_type = "distance_dir"; enable(f_cvc_com_based); + enable(f_cvc_implicit_gradient); x.type(colvarvalue::type_unit3vector); } colvar::distance_dir::distance_dir() : distance() { function_type = "distance_dir"; enable(f_cvc_com_based); + enable(f_cvc_implicit_gradient); x.type(colvarvalue::type_unit3vector); } void colvar::distance_dir::calc_value() { - if (b_no_PBC) { + if (!is_enabled(f_cvc_pbc_minimum_image)) { dist_v = group2->center_of_mass() - group1->center_of_mass(); } else { dist_v = cvm::position_distance(group1->center_of_mass(), group2->center_of_mass()); } x.rvector_value = dist_v.unit(); } void colvar::distance_dir::calc_gradients() { // gradients are computed on the fly within apply_force() // Note: could be a problem if a future bias relies on gradient // calculations... // TODO in new deps system: remove dependency of biasing force to gradient? // That way we could tell apart an explicit gradient dependency } void colvar::distance_dir::apply_force(colvarvalue const &force) { // remove the radial force component cvm::real const iprod = force.rvector_value * x.rvector_value; cvm::rvector const force_tang = force.rvector_value - iprod * x.rvector_value; if (!group1->noforce) group1->apply_force(-1.0 * force_tang); if (!group2->noforce) group2->apply_force( force_tang); } cvm::real colvar::distance_dir::dist2(colvarvalue const &x1, colvarvalue const &x2) const { return (x1.rvector_value - x2.rvector_value).norm2(); } colvarvalue colvar::distance_dir::dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const { - return colvarvalue((x1.rvector_value - x2.rvector_value), colvarvalue::type_unit3vector); + return colvarvalue((x1.rvector_value - x2.rvector_value), colvarvalue::type_unit3vectorderiv); } colvarvalue colvar::distance_dir::dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const { - return colvarvalue((x2.rvector_value - x1.rvector_value), colvarvalue::type_unit3vector); + return colvarvalue((x2.rvector_value - x1.rvector_value), colvarvalue::type_unit3vectorderiv); } colvar::distance_inv::distance_inv(std::string const &conf) - : distance(conf) + : cvc(conf) { function_type = "distance_inv"; + + 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; } if (exponent <= 0) { cvm::error("Error: negative or zero exponent provided.\n"); return; } 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; } } } x.type(colvarvalue::type_scalar); } colvar::distance_inv::distance_inv() { function_type = "distance_inv"; exponent = 6; x.type(colvarvalue::type_scalar); } void colvar::distance_inv::calc_value() { x.real_value = 0.0; - if (b_no_PBC) { + if (!is_enabled(f_cvc_pbc_minimum_image)) { for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) { for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { cvm::rvector const dv = ai2->pos - ai1->pos; cvm::real const d2 = dv.norm2(); cvm::real dinv = 1.0; for (int ne = 0; ne < exponent/2; ne++) dinv *= 1.0/d2; x.real_value += dinv; cvm::rvector const dsumddv = -(cvm::real(exponent)) * dinv/d2 * dv; ai1->grad += -1.0 * dsumddv; ai2->grad += dsumddv; } } } else { for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) { for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { cvm::rvector const dv = cvm::position_distance(ai1->pos, ai2->pos); cvm::real const d2 = dv.norm2(); cvm::real dinv = 1.0; for (int ne = 0; ne < exponent/2; ne++) dinv *= 1.0/d2; x.real_value += dinv; cvm::rvector const dsumddv = -(cvm::real(exponent)) * dinv/d2 * dv; ai1->grad += -1.0 * dsumddv; ai2->grad += dsumddv; } } } x.real_value *= 1.0 / cvm::real(group1->size() * group2->size()); x.real_value = std::pow(x.real_value, -1.0/(cvm::real(exponent))); } void colvar::distance_inv::calc_gradients() { cvm::real const dxdsum = (-1.0/(cvm::real(exponent))) * std::pow(x.real_value, exponent+1) / cvm::real(group1->size() * group2->size()); for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) { ai1->grad *= dxdsum; } for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { ai2->grad *= dxdsum; } } 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) { function_type = "distance_pairs"; - if (get_keyval(conf, "forceNoPBC", b_no_PBC, false)) { - cvm::log("Computing distance using absolute positions (not minimal-image)"); - } - group1 = parse_group(conf, "group1"); group2 = parse_group(conf, "group2"); x.type(colvarvalue::type_vector); + enable(f_cvc_implicit_gradient); x.vector1d_value.resize(group1->size() * group2->size()); } colvar::distance_pairs::distance_pairs() { function_type = "distance_pairs"; + enable(f_cvc_implicit_gradient); x.type(colvarvalue::type_vector); } void colvar::distance_pairs::calc_value() { x.vector1d_value.resize(group1->size() * group2->size()); - if (b_no_PBC) { + if (!is_enabled(f_cvc_pbc_minimum_image)) { size_t i1, i2; for (i1 = 0; i1 < group1->size(); i1++) { for (i2 = 0; i2 < group2->size(); i2++) { cvm::rvector const dv = (*group2)[i2].pos - (*group1)[i1].pos; cvm::real const d = dv.norm(); x.vector1d_value[i1*group2->size() + i2] = d; (*group1)[i1].grad = -1.0 * dv.unit(); (*group2)[i2].grad = dv.unit(); } } } else { size_t i1, i2; for (i1 = 0; i1 < group1->size(); i1++) { for (i2 = 0; i2 < group2->size(); i2++) { - cvm::rvector const dv = cvm::position_distance((*group1)[i1].pos, (*group2)[i2].pos); + cvm::rvector const dv = cvm::position_distance((*group1)[i1].pos, + (*group2)[i2].pos); cvm::real const d = dv.norm(); x.vector1d_value[i1*group2->size() + i2] = d; (*group1)[i1].grad = -1.0 * dv.unit(); (*group2)[i2].grad = dv.unit(); } } } } void colvar::distance_pairs::calc_gradients() { // will be calculated on the fly in apply_force() } void colvar::distance_pairs::apply_force(colvarvalue const &force) { - if (b_no_PBC) { + if (!is_enabled(f_cvc_pbc_minimum_image)) { size_t i1, i2; for (i1 = 0; i1 < group1->size(); i1++) { for (i2 = 0; i2 < group2->size(); i2++) { cvm::rvector const dv = (*group2)[i2].pos - (*group1)[i1].pos; (*group1)[i1].apply_force(force[i1*group2->size() + i2] * (-1.0) * dv.unit()); (*group2)[i2].apply_force(force[i1*group2->size() + i2] * dv.unit()); } } } else { size_t i1, i2; for (i1 = 0; i1 < group1->size(); i1++) { for (i2 = 0; i2 < group2->size(); i2++) { - cvm::rvector const dv = cvm::position_distance((*group1)[i1].pos, (*group2)[i2].pos); + cvm::rvector const dv = cvm::position_distance((*group1)[i1].pos, + (*group2)[i2].pos); (*group1)[i1].apply_force(force[i1*group2->size() + i2] * (-1.0) * dv.unit()); (*group2)[i2].apply_force(force[i1*group2->size() + i2] * dv.unit()); } } } } colvar::gyration::gyration(std::string const &conf) : cvc(conf) { function_type = "gyration"; provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); atoms = parse_group(conf, "atoms"); if (atoms->b_user_defined_fit) { cvm::log("WARNING: explicit fitting parameters were provided for atom group \"atoms\"."); } else { atoms->b_center = true; 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)); } x.type(colvarvalue::type_scalar); } colvar::gyration::gyration() { function_type = "gyration"; provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); x.type(colvarvalue::type_scalar); } void colvar::gyration::calc_value() { x.real_value = 0.0; for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { x.real_value += (ai->pos).norm2(); } x.real_value = std::sqrt(x.real_value / cvm::real(atoms->size())); } void colvar::gyration::calc_gradients() { cvm::real const drdx = 1.0/(cvm::real(atoms->size()) * x.real_value); for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { ai->grad = drdx * ai->pos; } } void colvar::gyration::calc_force_invgrads() { atoms->read_total_forces(); cvm::real const dxdr = 1.0/x.real_value; ft.real_value = 0.0; for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { ft.real_value += dxdr * ai->pos * ai->total_force; } } void colvar::gyration::calc_Jacobian_derivative() { jd = x.real_value ? (3.0 * cvm::real(atoms->size()) - 4.0) / x.real_value : 0.0; } 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) { function_type = "inertia"; x.type(colvarvalue::type_scalar); } colvar::inertia::inertia() { function_type = "inertia"; x.type(colvarvalue::type_scalar); } void colvar::inertia::calc_value() { x.real_value = 0.0; for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { x.real_value += (ai->pos).norm2(); } } void colvar::inertia::calc_gradients() { for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { ai->grad = 2.0 * ai->pos; } } void colvar::inertia::apply_force(colvarvalue const &force) { if (!atoms->noforce) atoms->apply_colvar_force(force.real_value); } simple_scalar_dist_functions(inertia_z) colvar::inertia_z::inertia_z(std::string const &conf) : inertia(conf) { function_type = "inertia_z"; 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; } if (axis.norm2() != 1.0) { axis = axis.unit(); cvm::log("The normalized axis is: "+cvm::to_str(axis)+".\n"); } } x.type(colvarvalue::type_scalar); } colvar::inertia_z::inertia_z() { function_type = "inertia_z"; x.type(colvarvalue::type_scalar); } void colvar::inertia_z::calc_value() { x.real_value = 0.0; for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { cvm::real const iprod = ai->pos * axis; x.real_value += iprod * iprod; } } void colvar::inertia_z::calc_gradients() { for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { ai->grad = 2.0 * (ai->pos * axis) * axis; } } 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) { provide(f_cvc_inv_gradient); function_type = "rmsd"; x.type(colvarvalue::type_scalar); atoms = parse_group(conf, "atoms"); if (!atoms || atoms->size() == 0) { cvm::error("Error: \"atoms\" must contain at least 1 atom to compute RMSD."); return; } bool b_Jacobian_derivative = true; if (atoms->fitting_group != NULL && b_Jacobian_derivative) { cvm::log("The option \"refPositionsGroup\" (alternative group for fitting) was enabled: " "Jacobian derivatives of the RMSD will not be calculated.\n"); b_Jacobian_derivative = false; } if (b_Jacobian_derivative) provide(f_cvc_Jacobian); // the following is a simplified version of the corresponding atom group options; // we need this because the reference coordinates defined inside the atom group // may be used only for fitting, and even more so if fitting_group is used 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; } } { 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; } std::string ref_pos_col; double ref_pos_col_value=0.0; if (get_keyval(conf, "refPositionsCol", ref_pos_col, std::string(""))) { // 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; } } else { // if not, rely on existing atom indices for the group atoms->create_sorted_ids(); ref_pos.resize(atoms->size()); } cvm::load_coords(ref_pos_file.c_str(), ref_pos, atoms->sorted_ids, ref_pos_col, ref_pos_col_value); } } if (ref_pos.size() != atoms->size()) { cvm::error("Error: found " + cvm::to_str(ref_pos.size()) + " reference positions; expected " + cvm::to_str(atoms->size())); return; } if (atoms->b_user_defined_fit) { cvm::log("WARNING: explicit fitting parameters were provided for atom group \"atoms\"."); } else { // Default: fit everything cvm::log("Enabling \"centerReference\" and \"rotateReference\", to minimize RMSD before calculating it as a variable: " "if this is not the desired behavior, disable them explicitly within the \"atoms\" block.\n"); atoms->b_center = true; atoms->b_rotate = true; // default case: reference positions for calculating the rmsd are also those used // for fitting atoms->ref_pos = ref_pos; atoms->center_ref_pos(); 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->b_fit_gradients = false; + 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()); } } void colvar::rmsd::calc_value() { // rotational-translational fit is handled by the atom group x.real_value = 0.0; for (size_t ia = 0; ia < atoms->size(); ia++) { x.real_value += ((*atoms)[ia].pos - ref_pos[ia]).norm2(); } x.real_value /= cvm::real(atoms->size()); // MSD x.real_value = std::sqrt(x.real_value); } void colvar::rmsd::calc_gradients() { cvm::real const drmsddx2 = (x.real_value > 0.0) ? 0.5 / (x.real_value * cvm::real(atoms->size())) : 0.0; for (size_t ia = 0; ia < atoms->size(); ia++) { (*atoms)[ia].grad = (drmsddx2 * 2.0 * ((*atoms)[ia].pos - ref_pos[ia])); } } 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(); ft.real_value = 0.0; // Note: gradient square norm is 1/N_atoms for (size_t ia = 0; ia < atoms->size(); ia++) { ft.real_value += (*atoms)[ia].grad * (*atoms)[ia].total_force; } ft.real_value *= atoms->size(); } void colvar::rmsd::calc_Jacobian_derivative() { // divergence of the rotated coordinates (including only derivatives of the rotation matrix) cvm::real divergence = 0.0; if (atoms->b_rotate) { // gradient of the rotation matrix 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; 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]; g11 = 2.0 * (atoms->rot.q)[1]*dq[1]; g22 = 2.0 * (atoms->rot.q)[2]*dq[2]; g33 = 2.0 * (atoms->rot.q)[3]*dq[3]; g01 = (atoms->rot.q)[0]*dq[1] + (atoms->rot.q)[1]*dq[0]; g02 = (atoms->rot.q)[0]*dq[2] + (atoms->rot.q)[2]*dq[0]; g03 = (atoms->rot.q)[0]*dq[3] + (atoms->rot.q)[3]*dq[0]; g12 = (atoms->rot.q)[1]*dq[2] + (atoms->rot.q)[2]*dq[1]; g13 = (atoms->rot.q)[1]*dq[3] + (atoms->rot.q)[3]*dq[1]; g23 = (atoms->rot.q)[2]*dq[3] + (atoms->rot.q)[3]*dq[2]; // Gradient of the rotation matrix wrt current Cartesian position grad_rot_mat[0][0] = -2.0 * (g22 + g33); grad_rot_mat[1][0] = 2.0 * (g12 + g03); grad_rot_mat[2][0] = 2.0 * (g13 - g02); grad_rot_mat[0][1] = 2.0 * (g12 - g03); grad_rot_mat[1][1] = -2.0 * (g11 + g33); grad_rot_mat[2][1] = 2.0 * (g01 + g23); grad_rot_mat[0][2] = 2.0 * (g02 + g13); grad_rot_mat[1][2] = 2.0 * (g23 - g01); grad_rot_mat[2][2] = -2.0 * (g11 + g22); cvm::atom_pos &y = ref_pos[ia]; for (size_t alpha = 0; alpha < 3; alpha++) { for (size_t beta = 0; beta < 3; beta++) { divergence += grad_rot_mat[beta][alpha][alpha] * y[beta]; // Note: equation was derived for inverse rotation (see colvars paper) // so here the matrix is transposed // (eq would give divergence += grad_rot_mat[alpha][beta][alpha] * y[beta];) } } } } jd.real_value = x.real_value > 0.0 ? (3.0 * atoms->size() - 4.0 - divergence) / x.real_value : 0.0; } simple_scalar_dist_functions(rmsd) colvar::eigenvector::eigenvector(std::string const &conf) : cvc(conf) { provide(f_cvc_inv_gradient); provide(f_cvc_Jacobian); function_type = "eigenvector"; x.type(colvarvalue::type_scalar); atoms = parse_group(conf, "atoms"); { bool const b_inline = get_keyval(conf, "refPositions", ref_pos, ref_pos); 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; } } std::string file_name; 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; } 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; } } else { // if not, use atom indices atoms->create_sorted_ids(); } ref_pos.resize(atoms->size()); cvm::load_coords(file_name.c_str(), ref_pos, atoms->sorted_ids, file_col, file_col_value); } } if (ref_pos.size() == 0) { cvm::error("Error: reference positions were not provided.\n", INPUT_ERROR); return; } if (ref_pos.size() != atoms->size()) { cvm::error("Error: reference positions do not " "match the number of requested atoms.\n", INPUT_ERROR); return; } // save for later the geometric center of the provided positions (may not be the origin) cvm::rvector ref_pos_center(0.0, 0.0, 0.0); for (size_t i = 0; i < atoms->size(); i++) { ref_pos_center += ref_pos[i]; } ref_pos_center *= 1.0 / atoms->size(); if (atoms->b_user_defined_fit) { cvm::log("WARNING: explicit fitting parameters were provided for atom group \"atoms\".\n"); } else { // default: fit everything cvm::log("Enabling \"centerReference\" and \"rotateReference\", to minimize RMSD before calculating the vector projection: " "if this is not the desired behavior, disable them explicitly within the \"atoms\" block.\n"); atoms->b_center = true; atoms->b_rotate = true; atoms->ref_pos = ref_pos; atoms->center_ref_pos(); - atoms->b_fit_gradients = false; // cancel out if group is fitted on itself - // and cvc is translationally invariant + 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()); } { bool const b_inline = get_keyval(conf, "vector", eigenvec, eigenvec); // now load the eigenvector if (b_inline) { cvm::log("Using vector components from input file.\n"); if (eigenvec.size() != atoms->size()) { - cvm::fatal_error("Error: vector components do not " + cvm::error("Error: vector components do not " "match the number of requested atoms->\n"); + return; } } std::string file_name; 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; } std::string file_col; double file_col_value=0.0; if (get_keyval(conf, "vectorCol", file_col, std::string(""))) { // 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; } } else { // if not, use atom indices atoms->create_sorted_ids(); } eigenvec.resize(atoms->size()); cvm::load_coords(file_name.c_str(), eigenvec, atoms->sorted_ids, file_col, file_col_value); } } if (!ref_pos.size() || !eigenvec.size()) { cvm::error("Error: both reference coordinates" "and eigenvector must be defined.\n"); return; } cvm::atom_pos eig_center(0.0, 0.0, 0.0); for (size_t eil = 0; eil < atoms->size(); eil++) { eig_center += eigenvec[eil]; } eig_center *= 1.0 / atoms->size(); cvm::log("Geometric center of the provided vector: "+cvm::to_str(eig_center)+"\n"); bool b_difference_vector = false; get_keyval(conf, "differenceVector", b_difference_vector, false); if (b_difference_vector) { if (atoms->b_center) { // both sets should be centered on the origin for fitting for (size_t i = 0; i < atoms->size(); i++) { eigenvec[i] -= eig_center; ref_pos[i] -= ref_pos_center; } } if (atoms->b_rotate) { atoms->rot.calc_optimal_rotation(eigenvec, ref_pos); for (size_t i = 0; i < atoms->size(); i++) { eigenvec[i] = atoms->rot.rotate(eigenvec[i]); } } cvm::log("\"differenceVector\" is on: subtracting the reference positions from the provided vector: v = v - x0.\n"); for (size_t i = 0; i < atoms->size(); i++) { eigenvec[i] -= ref_pos[i]; } if (atoms->b_center) { // bring back the ref positions to where they were for (size_t i = 0; i < atoms->size(); i++) { ref_pos[i] += ref_pos_center; } } } else { cvm::log("Centering the provided vector to zero.\n"); for (size_t i = 0; i < atoms->size(); i++) { eigenvec[i] -= eig_center; } } // cvm::log("The first three components(v1x, v1y, v1z) of the resulting vector are: "+cvm::to_str (eigenvec[0])+".\n"); // for inverse gradients eigenvec_invnorm2 = 0.0; for (size_t ein = 0; ein < atoms->size(); ein++) { eigenvec_invnorm2 += eigenvec[ein].norm2(); } eigenvec_invnorm2 = 1.0 / eigenvec_invnorm2; if (b_difference_vector) { cvm::log("\"differenceVector\" is on: normalizing the vector.\n"); for (size_t i = 0; i < atoms->size(); i++) { eigenvec[i] *= eigenvec_invnorm2; } } else { cvm::log("The norm of the vector is |v| = "+cvm::to_str(eigenvec_invnorm2)+".\n"); } } void colvar::eigenvector::calc_value() { x.real_value = 0.0; for (size_t i = 0; i < atoms->size(); i++) { x.real_value += ((*atoms)[i].pos - ref_pos[i]) * eigenvec[i]; } } void colvar::eigenvector::calc_gradients() { for (size_t ia = 0; ia < atoms->size(); ia++) { (*atoms)[ia].grad = eigenvec[ia]; } } 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(); ft.real_value = 0.0; for (size_t ia = 0; ia < atoms->size(); ia++) { ft.real_value += eigenvec_invnorm2 * (*atoms)[ia].grad * (*atoms)[ia].total_force; } } void colvar::eigenvector::calc_Jacobian_derivative() { // gradient of the rotation matrix cvm::matrix2d grad_rot_mat(3, 3); cvm::quaternion &quat0 = atoms->rot.q; // gradients of products of 2 quaternion components cvm::rvector g11, g22, g33, g01, g02, g03, g12, g13, g23; cvm::real sum = 0.0; 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]; g11 = 2.0 * quat0[1]*dq_1[1]; g22 = 2.0 * quat0[2]*dq_1[2]; g33 = 2.0 * quat0[3]*dq_1[3]; g01 = quat0[0]*dq_1[1] + quat0[1]*dq_1[0]; g02 = quat0[0]*dq_1[2] + quat0[2]*dq_1[0]; g03 = quat0[0]*dq_1[3] + quat0[3]*dq_1[0]; g12 = quat0[1]*dq_1[2] + quat0[2]*dq_1[1]; g13 = quat0[1]*dq_1[3] + quat0[3]*dq_1[1]; g23 = quat0[2]*dq_1[3] + quat0[3]*dq_1[2]; // Gradient of the inverse rotation matrix wrt current Cartesian position // (transpose of the gradient of the direct rotation) grad_rot_mat[0][0] = -2.0 * (g22 + g33); grad_rot_mat[0][1] = 2.0 * (g12 + g03); grad_rot_mat[0][2] = 2.0 * (g13 - g02); grad_rot_mat[1][0] = 2.0 * (g12 - g03); grad_rot_mat[1][1] = -2.0 * (g11 + g33); grad_rot_mat[1][2] = 2.0 * (g01 + g23); grad_rot_mat[2][0] = 2.0 * (g02 + g13); grad_rot_mat[2][1] = 2.0 * (g23 - g01); grad_rot_mat[2][2] = -2.0 * (g11 + g22); for (size_t i = 0; i < 3; i++) { for (size_t j = 0; j < 3; j++) { sum += grad_rot_mat[i][j][i] * eigenvec[ia][j]; } } } jd.real_value = sum * std::sqrt(eigenvec_invnorm2); } simple_scalar_dist_functions(eigenvector) colvar::cartesian::cartesian(std::string const &conf) : cvc(conf) { function_type = "cartesian"; atoms = parse_group(conf, "atoms"); bool use_x, use_y, use_z; get_keyval(conf, "useX", use_x, true); get_keyval(conf, "useY", use_y, true); get_keyval(conf, "useZ", use_z, true); axes.clear(); if (use_x) axes.push_back(0); if (use_y) axes.push_back(1); 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; } x.type(colvarvalue::type_vector); + enable(f_cvc_implicit_gradient); x.vector1d_value.resize(atoms->size() * axes.size()); } void colvar::cartesian::calc_value() { size_t const dim = axes.size(); size_t ia, j; for (ia = 0; ia < atoms->size(); ia++) { for (j = 0; j < dim; j++) { x.vector1d_value[dim*ia + j] = (*atoms)[ia].pos[axes[j]]; } } } void colvar::cartesian::calc_gradients() { // we're not using the "grad" member of each // atom object, because it only can represent the gradient of a // scalar colvar } void colvar::cartesian::apply_force(colvarvalue const &force) { size_t const dim = axes.size(); size_t ia, j; if (!atoms->noforce) { cvm::rvector f; for (ia = 0; ia < atoms->size(); ia++) { for (j = 0; j < dim; j++) { f[axes[j]] = force.vector1d_value[dim*ia + j]; } (*atoms)[ia].apply_force(f); } } } diff --git a/lib/colvars/colvarcomp_protein.cpp b/lib/colvars/colvarcomp_protein.cpp index 393c7dcf9..b8fc96cfa 100644 --- a/lib/colvars/colvarcomp_protein.cpp +++ b/lib/colvars/colvarcomp_protein.cpp @@ -1,426 +1,422 @@ // -*- 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 "colvarmodule.h" #include "colvarvalue.h" #include "colvarparse.h" #include "colvar.h" #include "colvarcomp.h" ////////////////////////////////////////////////////////////////////// // alpha component ////////////////////////////////////////////////////////////////////// - // FIXME: this will not make collect_gradients work - // because gradients in individual atom groups - // are those of the sub-cvcs (angle, hb), not those - // of this cvc (alpha) - // This is true of all cvcs with sub-cvcs, and those - // that do not calculate explicit gradients - // SO: we need a flag giving the availability of - // atomic gradients - colvar::alpha_angles::alpha_angles(std::string const &conf) : cvc(conf) { if (cvm::debug()) cvm::log("Initializing alpha_angles object.\n"); function_type = "alpha_angles"; + enable(f_cvc_implicit_gradient); x.type(colvarvalue::type_scalar); std::string segment_id; get_keyval(conf, "psfSegID", segment_id, std::string("MAIN")); std::vector residues; { std::string residues_conf = ""; - key_lookup(conf, "residueRange", residues_conf); + key_lookup(conf, "residueRange", &residues_conf); if (residues_conf.size()) { std::istringstream is(residues_conf); int initial, final; char dash; if ( (is >> initial) && (initial > 0) && (is >> dash) && (dash == '-') && (is >> final) && (final > 0) ) { for (int rnum = initial; rnum <= final; rnum++) { residues.push_back(rnum); } } } else { - cvm::fatal_error("Error: no residues defined in \"residueRange\".\n"); + cvm::error("Error: no residues defined in \"residueRange\".\n"); + return; } } if (residues.size() < 5) { - cvm::fatal_error("Error: not enough residues defined in \"residueRange\".\n"); + cvm::error("Error: not enough residues defined in \"residueRange\".\n"); + return; } 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::fatal_error("Error: hBondCoeff must be defined between 0 and 1.\n"); + cvm::error("Error: hBondCoeff must be defined between 0 and 1.\n"); + return; } get_keyval(conf, "angleRef", theta_ref, 88.0); get_keyval(conf, "angleTol", theta_tol, 15.0); if (hb_coeff < 1.0) { for (size_t i = 0; i < residues.size()-2; i++) { theta.push_back(new colvar::angle(cvm::atom(r[i ], "CA", sid), cvm::atom(r[i+1], "CA", sid), cvm::atom(r[i+2], "CA", sid))); - atom_groups.push_back(theta.back()->atom_groups[0]); - atom_groups.push_back(theta.back()->atom_groups[1]); - atom_groups.push_back(theta.back()->atom_groups[2]); + register_atom_group(theta.back()->atom_groups[0]); + register_atom_group(theta.back()->atom_groups[1]); + register_atom_group(theta.back()->atom_groups[2]); } } else { cvm::log("The hBondCoeff specified will disable the Calpha-Calpha-Calpha angle terms.\n"); } { cvm::real r0; size_t en, ed; get_keyval(conf, "hBondCutoff", r0, (3.3 * cvm::unit_angstrom())); get_keyval(conf, "hBondExpNumer", en, 6); get_keyval(conf, "hBondExpDenom", ed, 8); if (hb_coeff > 0.0) { for (size_t i = 0; i < residues.size()-4; i++) { hb.push_back(new colvar::h_bond(cvm::atom(r[i ], "O", sid), cvm::atom(r[i+4], "N", sid), r0, en, ed)); - atom_groups.push_back(hb.back()->atom_groups[0]); + register_atom_group(hb.back()->atom_groups[0]); } } else { cvm::log("The hBondCoeff specified will disable the hydrogen bond terms.\n"); } } if (cvm::debug()) cvm::log("Done initializing alpha_angles object.\n"); } colvar::alpha_angles::alpha_angles() : cvc() { function_type = "alpha_angles"; + enable(f_cvc_implicit_gradient); x.type(colvarvalue::type_scalar); } colvar::alpha_angles::~alpha_angles() { while (theta.size() != 0) { delete theta.back(); theta.pop_back(); } while (hb.size() != 0) { delete hb.back(); hb.pop_back(); } } void colvar::alpha_angles::calc_value() { x.real_value = 0.0; if (theta.size()) { cvm::real const theta_norm = (1.0-hb_coeff) / cvm::real(theta.size()); for (size_t i = 0; i < theta.size(); i++) { (theta[i])->calc_value(); cvm::real const t = ((theta[i])->value().real_value-theta_ref)/theta_tol; cvm::real const f = ( (1.0 - std::pow(t, (int) 2)) / (1.0 - std::pow(t, (int) 4)) ); x.real_value += theta_norm * f; if (cvm::debug()) cvm::log("Calpha-Calpha angle no. "+cvm::to_str(i+1)+" in \""+ this->name+"\" has a value of "+ (cvm::to_str((theta[i])->value().real_value))+ " degrees, f = "+cvm::to_str(f)+".\n"); } } if (hb.size()) { cvm::real const hb_norm = hb_coeff / cvm::real(hb.size()); for (size_t i = 0; i < hb.size(); i++) { (hb[i])->calc_value(); x.real_value += hb_norm * (hb[i])->value().real_value; if (cvm::debug()) cvm::log("Hydrogen bond no. "+cvm::to_str(i+1)+" in \""+ this->name+"\" has a value of "+ (cvm::to_str((hb[i])->value().real_value))+".\n"); } } } void colvar::alpha_angles::calc_gradients() { size_t i; for (i = 0; i < theta.size(); i++) (theta[i])->calc_gradients(); for (i = 0; i < hb.size(); i++) (hb[i])->calc_gradients(); } void colvar::alpha_angles::apply_force(colvarvalue const &force) { if (theta.size()) { cvm::real const theta_norm = (1.0-hb_coeff) / cvm::real(theta.size()); for (size_t i = 0; i < theta.size(); i++) { cvm::real const t = ((theta[i])->value().real_value-theta_ref)/theta_tol; cvm::real const f = ( (1.0 - std::pow(t, (int) 2)) / (1.0 - std::pow(t, (int) 4)) ); cvm::real const dfdt = 1.0/(1.0 - std::pow(t, (int) 4)) * ( (-2.0 * t) + (-1.0*f)*(-4.0 * std::pow(t, (int) 3)) ); (theta[i])->apply_force(theta_norm * dfdt * (1.0/theta_tol) * force.real_value ); } } if (hb.size()) { cvm::real const hb_norm = hb_coeff / cvm::real(hb.size()); for (size_t i = 0; i < hb.size(); i++) { (hb[i])->apply_force(0.5 * hb_norm * force.real_value); } } } simple_scalar_dist_functions(alpha_angles) ////////////////////////////////////////////////////////////////////// // dihedral principal component ////////////////////////////////////////////////////////////////////// - // FIXME: this will not make collect_gradients work - // because gradients in individual atom groups - // are those of the sub-cvcs (dihedral), not those - // of this cvc - // This is true of all cvcs with sub-cvcs, and those - // that do not calculate explicit gradients - // SO: we need a flag giving the availability of - // atomic gradients - colvar::dihedPC::dihedPC(std::string const &conf) : cvc(conf) { if (cvm::debug()) cvm::log("Initializing dihedral PC object.\n"); function_type = "dihedPC"; + enable(f_cvc_implicit_gradient); x.type(colvarvalue::type_scalar); std::string segment_id; get_keyval(conf, "psfSegID", segment_id, std::string("MAIN")); std::vector residues; { std::string residues_conf = ""; - key_lookup(conf, "residueRange", residues_conf); + key_lookup(conf, "residueRange", &residues_conf); if (residues_conf.size()) { std::istringstream is(residues_conf); int initial, final; char dash; if ( (is >> initial) && (initial > 0) && (is >> dash) && (dash == '-') && (is >> final) && (final > 0) ) { for (int rnum = initial; rnum <= final; rnum++) { residues.push_back(rnum); } } } else { - cvm::fatal_error("Error: no residues defined in \"residueRange\".\n"); + cvm::error("Error: no residues defined in \"residueRange\".\n"); + return; } } if (residues.size() < 2) { - cvm::fatal_error("Error: dihedralPC requires at least two residues.\n"); + cvm::error("Error: dihedralPC requires at least two residues.\n"); + return; } std::string const &sid = segment_id; std::vector const &r = residues; std::string vecFileName; int vecNumber; if (get_keyval(conf, "vectorFile", vecFileName, vecFileName)) { get_keyval(conf, "vectorNumber", vecNumber, 0); - if (vecNumber < 1) - cvm::fatal_error("A positive value of vectorNumber is required."); + if (vecNumber < 1) { + cvm::error("A positive value of vectorNumber is required."); + return; + } std::ifstream vecFile; vecFile.open(vecFileName.c_str()); - if (!vecFile.good()) - cvm::fatal_error("Error opening dihedral PCA vector file " + vecFileName + " for reading"); + if (!vecFile.good()) { + cvm::error("Error opening dihedral PCA vector file " + vecFileName + " for reading"); + } // TODO: adapt to different formats by setting this flag bool eigenvectors_as_columns = true; if (eigenvectors_as_columns) { // Carma-style dPCA file std::string line; cvm::real c; while (vecFile.good()) { getline(vecFile, line); if (line.length() < 2) break; std::istringstream ls(line); for (int i=0; i> c; coeffs.push_back(c); } } /* TODO Uncomment this when different formats are recognized else { // Eigenvectors as lines // Skip to the right line for (int i = 1; i> c; coeffs.push_back(c); } } */ vecFile.close(); } else { get_keyval(conf, "vector", coeffs, coeffs); } if ( coeffs.size() != 4 * (residues.size() - 1)) { - cvm::fatal_error("Error: wrong number of coefficients: " + + 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; } for (size_t i = 0; i < residues.size()-1; i++) { // Psi theta.push_back(new colvar::dihedral(cvm::atom(r[i ], "N", sid), cvm::atom(r[i ], "CA", sid), cvm::atom(r[i ], "C", sid), cvm::atom(r[i+1], "N", sid))); - atom_groups.push_back(theta.back()->atom_groups[0]); - atom_groups.push_back(theta.back()->atom_groups[1]); - atom_groups.push_back(theta.back()->atom_groups[2]); - atom_groups.push_back(theta.back()->atom_groups[3]); + register_atom_group(theta.back()->atom_groups[0]); + register_atom_group(theta.back()->atom_groups[1]); + register_atom_group(theta.back()->atom_groups[2]); + register_atom_group(theta.back()->atom_groups[3]); // Phi (next res) theta.push_back(new colvar::dihedral(cvm::atom(r[i ], "C", sid), cvm::atom(r[i+1], "N", sid), cvm::atom(r[i+1], "CA", sid), cvm::atom(r[i+1], "C", sid))); - atom_groups.push_back(theta.back()->atom_groups[0]); - atom_groups.push_back(theta.back()->atom_groups[1]); - atom_groups.push_back(theta.back()->atom_groups[2]); - atom_groups.push_back(theta.back()->atom_groups[3]); + register_atom_group(theta.back()->atom_groups[0]); + register_atom_group(theta.back()->atom_groups[1]); + register_atom_group(theta.back()->atom_groups[2]); + register_atom_group(theta.back()->atom_groups[3]); } if (cvm::debug()) cvm::log("Done initializing dihedPC object.\n"); } colvar::dihedPC::dihedPC() : cvc() { function_type = "dihedPC"; + enable(f_cvc_implicit_gradient); x.type(colvarvalue::type_scalar); } colvar::dihedPC::~dihedPC() { while (theta.size() != 0) { delete theta.back(); theta.pop_back(); } } void colvar::dihedPC::calc_value() { x.real_value = 0.0; for (size_t i = 0; i < theta.size(); i++) { theta[i]->calc_value(); cvm::real const t = (PI / 180.) * theta[i]->value().real_value; x.real_value += coeffs[2*i ] * std::cos(t) + coeffs[2*i+1] * std::sin(t); } } void colvar::dihedPC::calc_gradients() { for (size_t i = 0; i < theta.size(); i++) { theta[i]->calc_gradients(); } } void colvar::dihedPC::apply_force(colvarvalue const &force) { for (size_t i = 0; i < theta.size(); i++) { cvm::real const t = (PI / 180.) * theta[i]->value().real_value; cvm::real const dcosdt = - (PI / 180.) * std::sin(t); cvm::real const dsindt = (PI / 180.) * std::cos(t); theta[i]->apply_force((coeffs[2*i ] * dcosdt + 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 936e77016..2650a9fe1 100644 --- a/lib/colvars/colvarcomp_rotations.cpp +++ b/lib/colvars/colvarcomp_rotations.cpp @@ -1,421 +1,427 @@ // -*- 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 "colvarmodule.h" #include "colvarvalue.h" #include "colvarparse.h" #include "colvar.h" #include "colvarcomp.h" colvar::orientation::orientation(std::string const &conf) : cvc(conf) { function_type = "orientation"; atoms = parse_group(conf, "atoms"); + enable(f_cvc_implicit_gradient); x.type(colvarvalue::type_quaternion); ref_pos.reserve(atoms->size()); if (get_keyval(conf, "refPositions", ref_pos, ref_pos)) { cvm::log("Using reference positions from input file.\n"); if (ref_pos.size() != atoms->size()) { - cvm::fatal_error("Error: reference positions do not " + cvm::error("Error: reference positions do not " "match the number of requested atoms.\n"); + return; } } { std::string file_name; if (get_keyval(conf, "refPositionsFile", file_name)) { 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::fatal_error("Error: refPositionsColValue, " + if (found && file_col_value==0.0) { + cvm::error("Error: refPositionsColValue, " "if provided, must be non-zero.\n"); + return; + } } else { // if not, use atom indices atoms->create_sorted_ids(); } ref_pos.resize(atoms->size()); cvm::load_coords(file_name.c_str(), ref_pos, atoms->sorted_ids, file_col, file_col_value); } } if (!ref_pos.size()) { - cvm::fatal_error("Error: must define a set of " + cvm::error("Error: must define a set of " "reference coordinates.\n"); + return; } cvm::log("Centering the reference coordinates: it is " "assumed that each atom is the closest " "periodic image to the center of geometry.\n"); cvm::rvector ref_cog(0.0, 0.0, 0.0); size_t i; for (i = 0; i < ref_pos.size(); i++) { ref_cog += ref_pos[i]; } ref_cog /= cvm::real(ref_pos.size()); for (i = 0; i < ref_pos.size(); i++) { ref_pos[i] -= ref_cog; } 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()); } } colvar::orientation::orientation() : cvc() { function_type = "orientation"; + enable(f_cvc_implicit_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)); if ((rot.q).inner(ref_quat) >= 0.0) { x.quaternion_value = rot.q; } else { x.quaternion_value = -1.0 * rot.q; } } void colvar::orientation::calc_gradients() { // gradients have already been calculated and stored within the // member object "rot"; we're not using the "grad" member of each // atom object, because it only can represent the gradient of a // scalar colvar } void colvar::orientation::apply_force(colvarvalue const &force) { cvm::quaternion const &FQ = force.quaternion_value; if (!atoms->noforce) { for (size_t ia = 0; ia < atoms->size(); ia++) { for (size_t i = 0; i < 4; i++) { (*atoms)[ia].apply_force(FQ[i] * rot.dQ0_2[ia][i]); } } } } cvm::real colvar::orientation::dist2(colvarvalue const &x1, colvarvalue const &x2) const { return x1.quaternion_value.dist2(x2); } colvarvalue colvar::orientation::dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const { return x1.quaternion_value.dist2_grad(x2); } colvarvalue colvar::orientation::dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const { return x2.quaternion_value.dist2_grad(x1); } colvar::orientation_angle::orientation_angle(std::string const &conf) : orientation(conf) { function_type = "orientation_angle"; x.type(colvarvalue::type_scalar); } colvar::orientation_angle::orientation_angle() : orientation() { function_type = "orientation_angle"; x.type(colvarvalue::type_scalar); } 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)); if ((rot.q).q0 >= 0.0) { x.real_value = (180.0/PI) * 2.0 * std::acos((rot.q).q0); } else { x.real_value = (180.0/PI) * 2.0 * std::acos(-1.0 * (rot.q).q0); } } void colvar::orientation_angle::calc_gradients() { cvm::real const dxdq0 = ( ((rot.q).q0 * (rot.q).q0 < 1.0) ? ((180.0 / PI) * (-2.0) / std::sqrt(1.0 - ((rot.q).q0 * (rot.q).q0))) : 0.0 ); for (size_t ia = 0; ia < atoms->size(); ia++) { (*atoms)[ia].grad = (dxdq0 * (rot.dQ0_2[ia])[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); } } simple_scalar_dist_functions(orientation_angle) colvar::orientation_proj::orientation_proj(std::string const &conf) : orientation(conf) { function_type = "orientation_proj"; x.type(colvarvalue::type_scalar); } colvar::orientation_proj::orientation_proj() : orientation() { function_type = "orientation_proj"; x.type(colvarvalue::type_scalar); } 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)); x.real_value = 2.0 * (rot.q).q0 * (rot.q).q0 - 1.0; } void colvar::orientation_proj::calc_gradients() { cvm::real const dxdq0 = 2.0 * 2.0 * (rot.q).q0; for (size_t ia = 0; ia < atoms->size(); ia++) { (*atoms)[ia].grad = (dxdq0 * (rot.dQ0_2[ia])[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(conf) { function_type = "tilt"; 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"); } x.type(colvarvalue::type_scalar); } colvar::tilt::tilt() : orientation() { function_type = "tilt"; x.type(colvarvalue::type_scalar); } void colvar::tilt::calc_value() { atoms_cog = atoms->center_of_geometry(); rot.calc_optimal_rotation(ref_pos, atoms->positions_shifted(-1.0 * atoms_cog)); x.real_value = rot.cos_theta(axis); } void colvar::tilt::calc_gradients() { cvm::quaternion const dxdq = rot.dcos_theta_dq(axis); for (size_t ia = 0; ia < atoms->size(); ia++) { (*atoms)[ia].grad = cvm::rvector(0.0, 0.0, 0.0); for (size_t iq = 0; iq < 4; iq++) { (*atoms)[ia].grad += (dxdq[iq] * (rot.dQ0_2[ia])[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(conf) { function_type = "spin_angle"; 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"); } period = 360.0; b_periodic = true; x.type(colvarvalue::type_scalar); } colvar::spin_angle::spin_angle() : orientation() { function_type = "spin_angle"; period = 360.0; b_periodic = true; x.type(colvarvalue::type_scalar); } 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)); x.real_value = rot.spin_angle(axis); this->wrap(x); } void colvar::spin_angle::calc_gradients() { cvm::quaternion const dxdq = rot.dspin_angle_dq(axis); for (size_t ia = 0; ia < atoms->size(); ia++) { (*atoms)[ia].grad = cvm::rvector(0.0, 0.0, 0.0); for (size_t iq = 0; iq < 4; iq++) { (*atoms)[ia].grad += (dxdq[iq] * (rot.dQ0_2[ia])[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) const { if ((x.real_value - wrap_center) >= 180.0) { x.real_value -= 360.0; return; } if ((x.real_value - wrap_center) < -180.0) { x.real_value += 360.0; return; } return; } diff --git a/lib/colvars/colvardeps.cpp b/lib/colvars/colvardeps.cpp index 8252f77e6..5402836f5 100644 --- a/lib/colvars/colvardeps.cpp +++ b/lib/colvars/colvardeps.cpp @@ -1,569 +1,768 @@ // -*- 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 "colvardeps.h" +colvardeps::colvardeps() + : time_step_factor (1) {} colvardeps::~colvardeps() { size_t i; - // Do not delete features if it's static + // Protest if we are deleting an object while a parent object may still depend on it + if (parents.size()) { + cvm::log("Warning: destroying \"" + description + "\" before its parents objects:"); + for (i=0; idescription); + } + } + + // Do not delete features if it's a static object + // may change in the future though // for (i=0; idescription); + +void colvardeps::free_children_deps() { + // Dereference children requirements of all enabled features + // Useful when object is destroyed or set inactive + // CAUTION: when setting the parent object inactive, disable "active" first + // then call this, to avoid double-dereferencing the deps of "active" + + // Cannot be in the base class destructor because it needs the derived class features() + size_t i,j,fid; + + if (cvm::debug()) cvm::log("DEPS: freeing children deps for " + description); + + cvm::increase_depth(); + for (fid = 0; fid < feature_states.size(); fid++) { + if (is_enabled(fid)) { + for (i=0; irequires_children.size(); i++) { + int g = features()[fid]->requires_children[i]; + for (j=0; jfeatures()[g]->description); + children[j]->decr_ref_count(g); + } + } + } + } + cvm::decrease_depth(); +} + + +// re-enable children features (and increase ref count accordingly) +// So free_children_deps() can be called whenever an object becomes inactive +void colvardeps::restore_children_deps() { + size_t i,j,fid; + + cvm::increase_depth(); + for (fid = 0; fid < feature_states.size(); fid++) { + if (is_enabled(fid)) { + for (i=0; irequires_children.size(); i++) { + int g = features()[fid]->requires_children[i]; + for (j=0; jfeatures()[g]->description); + children[j]->enable(g, false, false); + } + } } } + cvm::decrease_depth(); } void colvardeps::provide(int feature_id, bool truefalse) { feature_states[feature_id].available = truefalse; } void colvardeps::set_enabled(int feature_id, bool truefalse) { -// if (!is_static(feature_id)) { -// cvm::error("Cannot set feature " + features()[feature_id]->description + " statically in " + description + ".\n"); -// return; -// } if (truefalse) { - // Resolve dependencies too enable(feature_id); } else { - feature_states[feature_id].enabled = false; + disable(feature_id); } } bool colvardeps::get_keyval_feature(colvarparse *cvp, std::string const &conf, char const *key, int feature_id, bool const &def_value, colvarparse::Parse_Mode const parse_mode) { if (!is_user(feature_id)) { - cvm::error("Cannot set feature " + features()[feature_id]->description + " from user input in " + description + ".\n"); + cvm::error("Cannot set feature \"" + features()[feature_id]->description + "\" from user input in \"" + description + "\".\n"); return false; } bool value; bool const found = cvp->get_keyval(conf, key, value, def_value, parse_mode); if (value) enable(feature_id); return found; } int colvardeps::enable(int feature_id, bool dry_run /* default: false */, // dry_run: fail silently, do not enable if available // flag is passed recursively to deps of this feature bool toplevel /* default: true */) // toplevel: false if this is called as part of a chain of dependency resolution // this is used to diagnose failed dependencies by displaying the full stack // only the toplevel dependency will throw a fatal error { int res; size_t i, j; bool ok; feature *f = features()[feature_id]; feature_state *fs = &feature_states[feature_id]; if (cvm::debug()) { cvm::log("DEPS: " + description + - (dry_run ? " testing " : " requiring ") + + (dry_run ? " testing " : " enabling ") + "\"" + f->description +"\""); } if (fs->enabled) { - // Do not try to solve deps if already enabled + if (!(dry_run || toplevel)) { + // This is a dependency + // Prevent disabling this feature as long + // as requirement is enabled + fs->ref_count++; + if (cvm::debug()) + cvm::log("DEPS: bumping ref_count to " + cvm::to_str(fs->ref_count)); + } + // Do not try to further resolve deps return COLVARS_OK; } + std::string feature_type_descr = is_static(feature_id) ? "Static" : + (is_dynamic(feature_id) ? "Dynamic" : "User-controlled"); + if (!fs->available) { if (!dry_run) { if (toplevel) { - cvm::error("Error: Feature unavailable: \"" + f->description + "\" in " + description + "."); + cvm::error("Error: " + feature_type_descr + " feature unavailable: \"" + + f->description + "\" in " + description + "."); } else { - cvm::log("Feature unavailable: \"" + f->description + "\" in " + description); + cvm::log(feature_type_descr + " feature unavailable: \"" + + f->description + "\" in " + description + "."); } } return COLVARS_ERROR; } if (!toplevel && !is_dynamic(feature_id)) { if (!dry_run) { - cvm::log("Non-dynamic feature : \"" + f->description - + "\" in " + description + " may not be enabled as a dependency.\n"); + cvm::log(feature_type_descr + " feature \"" + f->description + + "\" may not be enabled as a dependency in " + description + ".\n"); } return COLVARS_ERROR; } // 1) enforce exclusions + // reminder: exclusions must be mutual for this to work for (i=0; irequires_exclude.size(); i++) { feature *g = features()[f->requires_exclude[i]]; if (cvm::debug()) cvm::log(f->description + " requires exclude " + g->description); if (is_enabled(f->requires_exclude[i])) { if (!dry_run) { - cvm::log("Features \"" + f->description + "\" is incompatible with \"" - + g->description + "\" in " + description); + cvm::log("Feature \"" + f->description + "\" is incompatible with \"" + + g->description + "\" in " + description + "."); if (toplevel) { cvm::error("Error: Failed dependency in " + description + "."); } } return COLVARS_ERROR; } } // 2) solve internal deps (self) for (i=0; irequires_self.size(); i++) { if (cvm::debug()) cvm::log(f->description + " requires self " + features()[f->requires_self[i]]->description); res = enable(f->requires_self[i], dry_run, false); if (res != COLVARS_OK) { if (!dry_run) { cvm::log("...required by \"" + f->description + "\" in " + description); if (toplevel) { cvm::error("Error: Failed dependency in " + description + "."); } } return res; } } // 3) solve internal alternate deps for (i=0; irequires_alt.size(); i++) { // test if one is available; if yes, enable and exit w/ success ok = false; for (j=0; jrequires_alt[i].size(); j++) { int g = f->requires_alt[i][j]; if (cvm::debug()) cvm::log(f->description + " requires alt " + features()[g]->description); res = enable(g, true, false); // see if available if (res == COLVARS_OK) { ok = true; - if (!dry_run) enable(g, false, false); // Require again, for real + if (!dry_run) { + enable(g, false, false); // Require again, for real + fs->alternate_refs.push_back(g); // We remember we enabled this + // so we can free it if this feature gets disabled + } break; } } if (!ok) { if (!dry_run) { - cvm::log("No dependency satisfied among alternates:"); - cvm::log("-----------------------------------------"); + cvm::log("\"" + f->description + "\" in " + description + + " requires one of the following features, none of which can be enabled:\n"); + cvm::log("-----------------------------------------\n"); + cvm::increase_depth(); for (j=0; jrequires_alt[i].size(); j++) { int g = f->requires_alt[i][j]; cvm::log(cvm::to_str(j+1) + ". " + features()[g]->description); - cvm::increase_depth(); enable(g, false, false); // Just for printing error output - cvm::decrease_depth(); } + cvm::decrease_depth(); cvm::log("-----------------------------------------"); - cvm::log("for \"" + f->description + "\" in " + description); if (toplevel) { cvm::error("Error: Failed dependency in " + description + "."); } } return COLVARS_ERROR; } } // 4) solve deps in children + // if the object is inactive, we solve but do not enable: will be enabled + // when the object becomes active + cvm::increase_depth(); for (i=0; irequires_children.size(); i++) { int g = f->requires_children[i]; for (j=0; jenable(g, dry_run, false); - cvm::decrease_depth(); + res = children[j]->enable(g, dry_run || !is_enabled(), false); if (res != COLVARS_OK) { if (!dry_run) { cvm::log("...required by \"" + f->description + "\" in " + description); if (toplevel) { cvm::error("Error: Failed dependency in " + description + "."); } } return res; } } - // If we've just touched the features of child objects, refresh them - if (!dry_run && f->requires_children.size() != 0) { + } + cvm::decrease_depth(); + + // Actually enable feature only once everything checks out + if (!dry_run) { + fs->enabled = true; + // This should be the only reference + if (!toplevel) fs->ref_count = 1; + if (feature_id == 0) { + // Waking up this object, enable all deps in children + restore_children_deps(); + } + do_feature_side_effects(feature_id); + if (cvm::debug()) + cvm::log("DEPS: feature \"" + f->description + "\" in " + + description + " enabled, ref_count = 1."); + } + return COLVARS_OK; +} + + +int colvardeps::disable(int feature_id) { + size_t i, j; + feature *f = features()[feature_id]; + feature_state *fs = &feature_states[feature_id]; + + if (cvm::debug()) cvm::log("DEPS: disabling feature \"" + + f->description + "\" in " + description); + + if (fs->enabled == false) { + return COLVARS_OK; + } + + if (fs->ref_count > 1) { + cvm::error("Error: cannot disable feature \"" + f->description + + "\" in " + description + " because of " + cvm::to_str(fs->ref_count-1) + + " remaining references.\n" ); + return COLVARS_ERROR; + } + + // internal deps (self) + for (i=0; irequires_self.size(); i++) { + if (cvm::debug()) cvm::log("DEPS: dereferencing self " + + features()[f->requires_self[i]]->description); + decr_ref_count(f->requires_self[i]); + } + + // alternates + for (i=0; ialternate_refs.size(); i++) { + if (cvm::debug()) cvm::log("DEPS: dereferencing alt " + + features()[fs->alternate_refs[i]]->description); + decr_ref_count(fs->alternate_refs[i]); + } + // Forget these, now that they are dereferenced + fs->alternate_refs.clear(); + + // deps in children + // except if the object is inactive, then children dependencies + // have already been dereferenced by this function + // (or never referenced if feature was enabled while the object + // was inactive) + if (is_enabled()) { + cvm::increase_depth(); + for (i=0; irequires_children.size(); i++) { + int g = f->requires_children[i]; for (j=0; jrefresh_deps(); + if (cvm::debug()) cvm::log("DEPS: dereferencing children's " + + children[j]->features()[g]->description); + children[j]->decr_ref_count(g); } } + cvm::decrease_depth(); } - // Actually enable feature only once everything checks out - if (!dry_run) fs->enabled = true; + fs->enabled = false; + fs->ref_count = 0; + if (feature_id == 0) { + // Putting this object to sleep + free_children_deps(); + } return COLVARS_OK; } +int colvardeps::decr_ref_count(int feature_id) { + int &rc = feature_states[feature_id].ref_count; + feature *f = features()[feature_id]; + + if (cvm::debug()) + cvm::log("DEPS: decreasing reference count of \"" + f->description + + "\" in " + description + ".\n"); + + if (rc <= 0) { + cvm::error("Error: cannot decrease reference count of feature \"" + f->description + + "\" in " + description + ", which is " + cvm::to_str(rc) + ".\n"); + return COLVARS_ERROR; + } + + rc--; + if (rc == 0 && f->is_dynamic()) { + // we can auto-disable this feature + if (cvm::debug()) + cvm::log("DEPS will now auto-disable dynamic feature \"" + f->description + + "\" in " + description + ".\n"); + disable(feature_id); + } + return COLVARS_OK; +} -// disable() { -// -// // we need refs to parents to walk up the deps tree! -// // or refresh -// } void colvardeps::init_feature(int feature_id, const char *description, feature_type type) { features()[feature_id]->description = description; features()[feature_id]->type = type; } // Shorthand macros for describing dependencies #define f_req_self(f, g) features()[f]->requires_self.push_back(g) // This macro ensures that exclusions are symmetric #define f_req_exclude(f, g) features()[f]->requires_exclude.push_back(g); \ features()[g]->requires_exclude.push_back(f) #define f_req_children(f, g) features()[f]->requires_children.push_back(g) #define f_req_alt2(f, g, h) features()[f]->requires_alt.push_back(std::vector(2));\ features()[f]->requires_alt.back()[0] = g; \ features()[f]->requires_alt.back()[1] = h #define f_req_alt3(f, g, h, i) features()[f]->requires_alt.push_back(std::vector(3));\ features()[f]->requires_alt.back()[0] = g; \ features()[f]->requires_alt.back()[1] = h; \ features()[f]->requires_alt.back()[2] = i +#define f_req_alt4(f, g, h, i, j) features()[f]->requires_alt.push_back(std::vector(4));\ + features()[f]->requires_alt.back()[0] = g; \ + features()[f]->requires_alt.back()[1] = h; \ + features()[f]->requires_alt.back()[2] = i; \ + features()[f]->requires_alt.back()[3] = j void colvardeps::init_cvb_requires() { int i; if (features().size() == 0) { for (i = 0; i < f_cvb_ntot; i++) { features().push_back(new feature); } init_feature(f_cvb_active, "active", f_type_dynamic); f_req_children(f_cvb_active, f_cv_active); + init_feature(f_cvb_awake, "awake", f_type_static); + f_req_self(f_cvb_awake, f_cvb_active); + init_feature(f_cvb_apply_force, "apply force", f_type_user); f_req_children(f_cvb_apply_force, f_cv_gradient); init_feature(f_cvb_get_total_force, "obtain total force"); f_req_children(f_cvb_get_total_force, f_cv_total_force); init_feature(f_cvb_history_dependent, "history-dependent", f_type_static); init_feature(f_cvb_scalar_variables, "require scalar variables", f_type_static); f_req_children(f_cvb_scalar_variables, f_cv_scalar); init_feature(f_cvb_calc_pmf, "calculate a PMF", f_type_static); } // Initialize feature_states for each instance feature_states.reserve(f_cvb_ntot); for (i = 0; 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 } } void colvardeps::init_cv_requires() { size_t i; if (features().size() == 0) { for (i = 0; i < f_cv_ntot; i++) { features().push_back(new feature); } init_feature(f_cv_active, "active", f_type_dynamic); - f_req_children(f_cv_active, f_cvc_active); - // Colvars must be either a linear combination, or scalar (and polynomial) or scripted - f_req_alt3(f_cv_active, f_cv_scalar, f_cv_linear, f_cv_scripted); + // Do not require f_cvc_active in children, as some components may be disabled + // Colvars must be either a linear combination, or scalar (and polynomial) or scripted/custom + f_req_alt4(f_cv_active, f_cv_scalar, f_cv_linear, f_cv_scripted, f_cv_custom_function); + + init_feature(f_cv_awake, "awake", f_type_static); + f_req_self(f_cv_awake, f_cv_active); init_feature(f_cv_gradient, "gradient", f_type_dynamic); f_req_children(f_cv_gradient, f_cvc_gradient); init_feature(f_cv_collect_gradient, "collect gradient", f_type_dynamic); f_req_self(f_cv_collect_gradient, f_cv_gradient); f_req_self(f_cv_collect_gradient, f_cv_scalar); + // The following exlusion could be lifted by implementing the feature + f_req_exclude(f_cv_collect_gradient, f_cv_scripted); - init_feature(f_cv_fdiff_velocity, "fdiff_velocity", f_type_dynamic); + init_feature(f_cv_fdiff_velocity, "velocity from finite differences", f_type_dynamic); // System force: either trivial (spring force); through extended Lagrangian, or calculated explicitly init_feature(f_cv_total_force, "total force", f_type_dynamic); f_req_alt2(f_cv_total_force, f_cv_extended_Lagrangian, f_cv_total_force_calc); // Deps for explicit total force calculation init_feature(f_cv_total_force_calc, "total force calculation", f_type_dynamic); f_req_self(f_cv_total_force_calc, f_cv_scalar); f_req_self(f_cv_total_force_calc, f_cv_linear); f_req_children(f_cv_total_force_calc, f_cvc_inv_gradient); f_req_self(f_cv_total_force_calc, f_cv_Jacobian); init_feature(f_cv_Jacobian, "Jacobian derivative", f_type_dynamic); f_req_self(f_cv_Jacobian, f_cv_scalar); f_req_self(f_cv_Jacobian, f_cv_linear); f_req_children(f_cv_Jacobian, f_cvc_Jacobian); init_feature(f_cv_hide_Jacobian, "hide Jacobian force", f_type_user); f_req_self(f_cv_hide_Jacobian, f_cv_Jacobian); // can only hide if calculated init_feature(f_cv_extended_Lagrangian, "extended Lagrangian", f_type_user); f_req_self(f_cv_extended_Lagrangian, f_cv_scalar); f_req_self(f_cv_extended_Lagrangian, f_cv_gradient); init_feature(f_cv_Langevin, "Langevin dynamics", f_type_user); f_req_self(f_cv_Langevin, f_cv_extended_Lagrangian); init_feature(f_cv_linear, "linear", f_type_static); init_feature(f_cv_scalar, "scalar", f_type_static); init_feature(f_cv_output_energy, "output energy", f_type_user); init_feature(f_cv_output_value, "output value", f_type_user); init_feature(f_cv_output_velocity, "output velocity", f_type_user); f_req_self(f_cv_output_velocity, f_cv_fdiff_velocity); init_feature(f_cv_output_applied_force, "output applied force", f_type_user); init_feature(f_cv_output_total_force, "output total force", f_type_user); f_req_self(f_cv_output_total_force, f_cv_total_force); init_feature(f_cv_subtract_applied_force, "subtract applied force from total force", f_type_user); f_req_self(f_cv_subtract_applied_force, f_cv_total_force); + // There is no well-defined way to implement f_cv_subtract_applied_force + // in the case of extended-Lagrangian colvars + f_req_exclude(f_cv_subtract_applied_force, f_cv_extended_Lagrangian); init_feature(f_cv_lower_boundary, "lower boundary", f_type_user); f_req_self(f_cv_lower_boundary, f_cv_scalar); init_feature(f_cv_upper_boundary, "upper boundary", f_type_user); f_req_self(f_cv_upper_boundary, f_cv_scalar); init_feature(f_cv_grid, "grid", f_type_user); f_req_self(f_cv_grid, f_cv_lower_boundary); f_req_self(f_cv_grid, f_cv_upper_boundary); init_feature(f_cv_runave, "running average", f_type_user); init_feature(f_cv_corrfunc, "correlation function", f_type_user); - init_feature(f_cv_scripted, "scripted", f_type_static); + init_feature(f_cv_scripted, "scripted", f_type_user); + + init_feature(f_cv_custom_function, "custom function", f_type_user); + f_req_exclude(f_cv_custom_function, f_cv_scripted); + init_feature(f_cv_periodic, "periodic", f_type_static); f_req_self(f_cv_periodic, f_cv_homogeneous); init_feature(f_cv_scalar, "scalar", f_type_static); init_feature(f_cv_linear, "linear", f_type_static); init_feature(f_cv_homogeneous, "homogeneous", f_type_static); + + // because total forces are obtained from the previous time step, + // we cannot (currently) have colvar values and total forces for the same timestep + init_feature(f_cv_multiple_ts, "multiple timestep colvar"); + f_req_exclude(f_cv_multiple_ts, f_cv_total_force_calc); } // Initialize feature_states for each instance feature_states.reserve(f_cv_ntot); for (i = 0; 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 } - -// // properties that may NOT be enabled as a dependency -// // This will be deprecated by feature types -// int unavailable_deps[] = { -// f_cv_lower_boundary, -// f_cv_upper_boundary, -// f_cv_extended_Lagrangian, -// f_cv_Langevin, -// f_cv_scripted, -// f_cv_periodic, -// f_cv_scalar, -// f_cv_linear, -// f_cv_homogeneous -// }; -// for (i = 0; i < sizeof(unavailable_deps) / sizeof(unavailable_deps[0]); i++) { -// feature_states[unavailable_deps[i]].available = false; -// } } void colvardeps::init_cvc_requires() { size_t i; // Initialize static array once and for all if (features().size() == 0) { for (i = 0; i < colvardeps::f_cvc_ntot; i++) { features().push_back(new feature); } init_feature(f_cvc_active, "active", f_type_dynamic); // The dependency below may become useful if we use dynamic atom groups // f_req_children(f_cvc_active, f_ag_active); init_feature(f_cvc_scalar, "scalar", f_type_static); init_feature(f_cvc_gradient, "gradient", f_type_dynamic); + init_feature(f_cvc_implicit_gradient, "implicit gradient", f_type_static); + f_req_children(f_cvc_implicit_gradient, f_ag_implicit_gradient); + init_feature(f_cvc_inv_gradient, "inverse gradient", f_type_dynamic); f_req_self(f_cvc_inv_gradient, f_cvc_gradient); init_feature(f_cvc_debug_gradient, "debug gradient", f_type_user); f_req_self(f_cvc_debug_gradient, f_cvc_gradient); + f_req_exclude(f_cvc_debug_gradient, f_cvc_implicit_gradient); init_feature(f_cvc_Jacobian, "Jacobian derivative", f_type_dynamic); f_req_self(f_cvc_Jacobian, f_cvc_inv_gradient); init_feature(f_cvc_com_based, "depends on group centers of mass", f_type_static); + // init_feature(f_cvc_pbc_minimum_image, "use minimum-image distances with PBCs", f_type_user); + // Compute total force on first site only to avoid unwanted // coupling to other colvars (see e.g. Ciccotti et al., 2005) - init_feature(f_cvc_one_site_total_force, "compute total collective force only from one group center", f_type_user); + init_feature(f_cvc_one_site_total_force, "compute total force from one group", f_type_user); f_req_self(f_cvc_one_site_total_force, f_cvc_com_based); init_feature(f_cvc_scalable, "scalable calculation", f_type_static); f_req_self(f_cvc_scalable, f_cvc_scalable_com); init_feature(f_cvc_scalable_com, "scalable calculation of centers of mass", f_type_static); f_req_self(f_cvc_scalable_com, f_cvc_com_based); // TODO only enable this when f_ag_scalable can be turned on for a pre-initialized group // f_req_children(f_cvc_scalable, f_ag_scalable); // f_req_children(f_cvc_scalable_com, f_ag_scalable_com); } // Initialize feature_states for each instance // 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++) { bool avail = is_dynamic(i) ? false : true; feature_states.push_back(feature_state(avail, false)); } + // CVCs are enabled from the start - get disabled based on flags + feature_states[f_cvc_active].enabled = true; + // Features that are implemented by all cvcs by default // Each cvc specifies what other features are available feature_states[f_cvc_active].available = true; feature_states[f_cvc_gradient].available = true; + // Use minimum-image distances by default + feature_states[f_cvc_pbc_minimum_image].enabled = true; + // Features that are implemented by default if their requirements are feature_states[f_cvc_one_site_total_force].available = true; // Features That are implemented only for certain simulation engine configurations feature_states[f_cvc_scalable_com].available = (cvm::proxy->scalable_group_coms() == COLVARS_OK); feature_states[f_cvc_scalable].available = feature_states[f_cvc_scalable_com].available; } void colvardeps::init_ag_requires() { size_t i; // Initialize static array once and for all if (features().size() == 0) { for (i = 0; i < f_ag_ntot; i++) { features().push_back(new feature); } init_feature(f_ag_active, "active", f_type_dynamic); init_feature(f_ag_center, "translational fit", f_type_static); init_feature(f_ag_rotate, "rotational fit", f_type_static); init_feature(f_ag_fitting_group, "reference positions group", f_type_static); - init_feature(f_ag_fit_gradient_group, "fit gradient for main group", f_type_static); - init_feature(f_ag_fit_gradient_ref, "fit gradient for reference group", f_type_static); + init_feature(f_ag_implicit_gradient, "implicit atom gradient", f_type_dynamic); + init_feature(f_ag_fit_gradients, "fit gradients", f_type_user); + f_req_exclude(f_ag_fit_gradients, f_ag_implicit_gradient); + init_feature(f_ag_atom_forces, "atomic forces", f_type_dynamic); // parallel calculation implies that we have at least a scalable center of mass, // but f_ag_scalable is kept as a separate feature to deal with future dependencies init_feature(f_ag_scalable, "scalable group calculation", f_type_static); init_feature(f_ag_scalable_com, "scalable group center of mass calculation", f_type_static); f_req_self(f_ag_scalable, f_ag_scalable_com); // init_feature(f_ag_min_msd_fit, "minimum MSD fit") // f_req_self(f_ag_min_msd_fit, f_ag_center) // f_req_self(f_ag_min_msd_fit, f_ag_rotate) // f_req_exclude(f_ag_min_msd_fit, f_ag_fitting_group) } // 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++) { feature_states.push_back(feature_state(false, false)); } // Features that are implemented (or not) by all atom groups feature_states[f_ag_active].available = true; // f_ag_scalable_com is provided by the CVC iff it is COM-based feature_states[f_ag_scalable_com].available = false; // TODO make f_ag_scalable depend on f_ag_scalable_com (or something else) feature_states[f_ag_scalable].available = true; + feature_states[f_ag_fit_gradients].available = true; + feature_states[f_ag_implicit_gradient].available = true; } void colvardeps::print_state() { size_t i; - cvm::log("Enabled features of " + description); + cvm::log("Enabled features of \"" + description + "\" (with reference count)"); for (i = 0; i < feature_states.size(); i++) { - if (feature_states[i].enabled) - cvm::log("- " + features()[i]->description); + if (is_enabled(i)) + cvm::log("- " + features()[i]->description + " (" + + cvm::to_str(feature_states[i].ref_count) + ")"); } + cvm::increase_depth(); for (i=0; iprint_state(); - cvm::decrease_depth(); } + cvm::decrease_depth(); } void colvardeps::add_child(colvardeps *child) { + children.push_back(child); child->parents.push_back((colvardeps *)this); + + // Solve dependencies of already enabled parent features + // in the new child + + size_t i, fid; + cvm::increase_depth(); + for (fid = 0; fid < feature_states.size(); fid++) { + if (is_enabled(fid)) { + for (i=0; irequires_children.size(); i++) { + int g = features()[fid]->requires_children[i]; + if (cvm::debug()) cvm::log("DEPS: re-enabling children's " + + child->features()[g]->description); + child->enable(g, false, false); + } + } + } + cvm::decrease_depth(); } void colvardeps::remove_child(colvardeps *child) { int i; bool found = false; for (i = children.size()-1; i>=0; --i) { if (children[i] == child) { children.erase(children.begin() + i); found = true; break; } } if (!found) { cvm::error("Trying to remove missing child reference from " + description + "\n"); } found = false; for (i = child->parents.size()-1; i>=0; --i) { if (child->parents[i] == this) { child->parents.erase(child->parents.begin() + i); found = true; break; } } if (!found) { cvm::error("Trying to remove missing parent reference from " + child->description + "\n"); } } void colvardeps::remove_all_children() { size_t i; int j; bool found; for (i = 0; i < children.size(); ++i) { found = false; for (j = children[i]->parents.size()-1; j>=0; --j) { if (children[i]->parents[j] == this) { children[i]->parents.erase(children[i]->parents.begin() + j); found = true; break; } } if (!found) { cvm::error("Trying to remove missing parent reference from " + children[i]->description + "\n"); } } children.clear(); } diff --git a/lib/colvars/colvardeps.h b/lib/colvars/colvardeps.h index fd07cb645..b810a5fca 100644 --- a/lib/colvars/colvardeps.h +++ b/lib/colvars/colvardeps.h @@ -1,310 +1,354 @@ // -*- 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 COLVARDEPS_H #define COLVARDEPS_H #include "colvarmodule.h" #include "colvarparse.h" /// \brief Parent class for a member object of a bias, cv or cvc etc. containing features and /// their dependencies, and handling dependency resolution /// /// There are 3 kinds of features: /// 1. Dynamic features are under the control of the dependency resolution /// system. They may be enabled or disabled depending on dependencies. /// 2. User features may be enabled based on user input (they may trigger a failure upon dependency resolution, though) /// 3. Static features are static properties of the object, determined /// programatically at initialization time. /// +/// In all classes, feature 0 is active. When an object is inactivated +/// all its children dependencies are dereferenced (free_children_deps) +/// While the object is inactive, no dependency solving is done on children +/// it is done when the object is activated back (restore_children_deps) class colvardeps { public: - colvardeps() {} + colvardeps(); virtual ~colvardeps(); // Subclasses should initialize the following members: std::string description; // reference to object name (cv, cvc etc.) /// This contains the current state of each feature for each object + // since the feature class only contains static properties struct feature_state { feature_state(bool a, bool e) - : available(a), enabled(e) {} + : available(a), enabled(e), ref_count(0) {} /// Feature may be enabled, subject to possible dependencies bool available; /// Currently enabled - this flag is subject to change dynamically /// TODO consider implications for dependency solving: anyone who disables /// it should trigger a refresh of parent objects bool enabled; // see if this should be private depending on implementation + // bool enabledOnce; // this should trigger an update when object is evaluated + + /// Number of features requiring this one as a dependency + /// When it falls to zero: + /// - a dynamic feature is disabled automatically + /// - other features may be disabled statically + int ref_count; + /// List of features that were enabled by this one + /// as part of an alternate requirement (for ref counting purposes) + /// This is necessary because we don't know which feature in the list + /// we enabled, otherwise + std::vector alternate_refs; }; +protected: + /// Time step multiplier (for coarse-timestep biases & colvars) + /// Biases and colvars will only be calculated at those times + /// (f_cvb_awake and f_cv_awake); a + /// Biases use this to apply "impulse" biasing forces at the outer timestep + /// Unused by lower-level objects (cvcs and atom groups) + int time_step_factor; private: /// List of the states of all features std::vector feature_states; /// Enum of possible feature types enum feature_type { f_type_not_set, f_type_dynamic, f_type_user, f_type_static }; public: + /// \brief returns time_step_factor + inline int get_time_step_factor() const {return time_step_factor;} + /// Pair a numerical feature ID with a description and type void init_feature(int feature_id, const char *description, feature_type type = f_type_not_set); - /// Describes a feature and its dependecies + /// Describes a feature and its dependencies /// used in a static array within each subclass class feature { public: feature() {} ~feature() {} std::string description; // Set by derived object initializer // features that this feature requires in the same object // NOTE: we have no safety mechanism against circular dependencies, however, they would have to be internal to an object (ie. requires_self or requires_alt) std::vector requires_self; // Features that are incompatible, ie. required disabled // if enabled, they will cause a dependency failure (they will not be disabled) // To enforce these dependencies regardless of the order in which they // are enabled, they are always set in a symmetric way, so whichever is enabled // second will cause the dependency to fail std::vector requires_exclude; // sets of features that are required in an alternate way // when parent feature is enabled, if none are enabled, the first one listed that is available will be enabled std::vector > requires_alt; // features that this feature requires in children std::vector requires_children; inline bool is_dynamic() { return type == f_type_dynamic; } inline bool is_static() { return type == f_type_static; } inline bool is_user() { return type == f_type_user; } /// Type of this feature, from the enum feature_type feature_type type; }; inline bool is_dynamic(int id) { return features()[id]->type == f_type_dynamic; } inline bool is_static(int id) { return features()[id]->type == f_type_static; } inline bool is_user(int id) { return features()[id]->type == f_type_user; } // Accessor to array of all features with deps, static in most derived classes // Subclasses with dynamic dependency trees may override this // with a non-static array // Intermediate classes (colvarbias and colvarcomp, which are also base classes) // implement this as virtual to allow overriding virtual std::vector&features() = 0; void add_child(colvardeps *child); void remove_child(colvardeps *child); /// Used before deleting an object, if not handled by that object's destructor /// (useful for cvcs because their children are member objects) void remove_all_children(); private: - // pointers to objects this object depends on - // list should be maintained by any code that modifies the object - // this could be secured by making lists of colvars / cvcs / atom groups private and modified through accessor functions + /// pointers to objects this object depends on + /// list should be maintained by any code that modifies the object + /// this could be secured by making lists of colvars / cvcs / atom groups private and modified through accessor functions std::vector children; - // pointers to objects that depend on this object - // the size of this array is in effect a reference counter + /// pointers to objects that depend on this object + /// the size of this array is in effect a reference counter std::vector parents; public: - // disabling a feature f: - // if parents depend on f, tell them to refresh / check that they are ok? - // if children provide features to satisfy f ONLY, disable that - - // When the state of this object has changed, recursively tell parents - // to enforce their dependencies -// void refresh_parents() { -// -// } - - // std::vector parents; // Needed to trigger a refresh if capabilities of this object change - - // End of members to be initialized by subclasses - // Checks whether given feature is enabled // Defaults to querying f_*_active inline bool is_enabled(int f = f_cv_active) const { return feature_states[f].enabled; } // Checks whether given feature is available // Defaults to querying f_*_active inline bool is_available(int f = f_cv_active) const { return feature_states[f].available; } /// Set the feature's available flag, without checking /// To be used for dynamic properties /// dependencies will be checked by enable() void provide(int feature_id, bool truefalse = true); - /// Set the feature's enabled flag, without dependency check or resolution - /// To be used for static properties only - /// Checking for availability is up to the caller + /// Enable or disable, depending on flag value void set_enabled(int feature_id, bool truefalse = true); protected: /// Parse a keyword and enable a feature accordingly bool get_keyval_feature(colvarparse *cvp, std::string const &conf, char const *key, int feature_id, bool const &def_value, colvarparse::Parse_Mode const parse_mode = colvarparse::parse_normal); public: - int enable(int f, bool dry_run = false, bool toplevel = true); // enable a feature and recursively solve its dependencies - // dry_run is set to true to recursively test if a feature is available, without enabling it -// int disable(int f); + /// enable a feature and recursively solve its dependencies + /// for proper reference counting, one should not add + /// spurious calls to enable() + /// dry_run is set to true to recursively test if a feature is available, without enabling it + int enable(int f, bool dry_run = false, bool toplevel = true); + + /// Disable a feature, decrease the reference count of its dependencies + /// and recursively disable them as applicable + int disable(int f); + + /// disable all enabled features to free their dependencies + /// to be done when deleting the object + /// Cannot be in the base class destructor because it needs the derived class features() + void free_children_deps(); + + /// re-enable children features (to be used when object becomes active) + void restore_children_deps(); + /// Decrement the reference count of a feature + /// disabling it if it's dynamic and count reaches zero + int decr_ref_count(int f); - /// This function is called whenever feature states are changed outside - /// of the object's control, that is, by parents - /// Eventually it may also be used when properties of children change - virtual int refresh_deps() { return COLVARS_OK; } + /// Implements possible actions to be carried out + /// when a given feature is enabled + /// Base function does nothing, can be overloaded + virtual void do_feature_side_effects(int id) {} // NOTE that all feature enums should start with f_*_active enum features_biases { /// \brief Bias is active f_cvb_active, - f_cvb_apply_force, // will apply forces - f_cvb_get_total_force, // requires total forces - f_cvb_history_dependent, // depends on simulation history - f_cvb_scalar_variables, // requires scalar colvars - f_cvb_calc_pmf, // whether this bias will compute a PMF + /// \brief Bias is awake (active on its own accord) this timestep + f_cvb_awake, + /// \brief will apply forces + f_cvb_apply_force, + /// \brief requires total forces + f_cvb_get_total_force, + /// \brief depends on simulation history + f_cvb_history_dependent, + /// \brief requires scalar colvars + f_cvb_scalar_variables, + /// \brief whether this bias will compute a PMF + f_cvb_calc_pmf, f_cvb_ntot }; enum features_colvar { /// \brief Calculate colvar f_cv_active, + /// \brief Colvar is awake (active on its own accord) this timestep + f_cv_awake, /// \brief Gradients are calculated and temporarily stored, so /// that external forces can be applied f_cv_gradient, /// \brief Collect atomic gradient data from all cvcs into vector /// atomic_gradient f_cv_collect_gradient, /// \brief Calculate the velocity with finite differences f_cv_fdiff_velocity, /// \brief The total force is calculated, projecting the atomic /// forces on the inverse gradient f_cv_total_force, /// \brief Calculate total force from atomic forces f_cv_total_force_calc, /// \brief Subtract the applied force from the total force f_cv_subtract_applied_force, /// \brief Estimate Jacobian derivative f_cv_Jacobian, /// \brief Do not report the Jacobian force as part of the total force /// instead, apply a correction internally to cancel it f_cv_hide_Jacobian, /// \brief The variable has a harmonic restraint around a moving /// center with fictitious mass; bias forces will be applied to /// the center f_cv_extended_Lagrangian, /// \brief The extended system coordinate undergoes Langevin dynamics f_cv_Langevin, /// \brief Output the potential and kinetic energies /// (for extended Lagrangian colvars only) f_cv_output_energy, /// \brief Output the value to the trajectory file (on by default) f_cv_output_value, /// \brief Output the velocity to the trajectory file f_cv_output_velocity, /// \brief Output the applied force to the trajectory file f_cv_output_applied_force, /// \brief Output the total force to the trajectory file f_cv_output_total_force, /// \brief A lower boundary is defined f_cv_lower_boundary, /// \brief An upper boundary is defined f_cv_upper_boundary, /// \brief Provide a discretization of the values of the colvar to /// be used by the biases or in analysis (needs lower and upper /// boundary) f_cv_grid, /// \brief Compute running average f_cv_runave, /// \brief Compute time correlation function f_cv_corrfunc, /// \brief Value and gradient computed by user script f_cv_scripted, + /// \brief Value and gradient computed by user function through Lepton + f_cv_custom_function, /// \brief Colvar is periodic f_cv_periodic, /// \brief is scalar f_cv_scalar, f_cv_linear, f_cv_homogeneous, + /// \brief multiple timestep through time_step_factor + f_cv_multiple_ts, /// \brief Number of colvar features f_cv_ntot }; enum features_cvc { f_cvc_active, f_cvc_scalar, f_cvc_gradient, + /// \brief CVC doesn't calculate and store explicit atom gradients + f_cvc_implicit_gradient, f_cvc_inv_gradient, /// \brief If enabled, calc_gradients() will call debug_gradients() for every group needed f_cvc_debug_gradient, f_cvc_Jacobian, + f_cvc_pbc_minimum_image, f_cvc_one_site_total_force, f_cvc_com_based, f_cvc_scalable, f_cvc_scalable_com, f_cvc_ntot }; enum features_atomgroup { f_ag_active, f_ag_center, f_ag_rotate, f_ag_fitting_group, /// Perform a standard minimum msd fit for given atoms /// ie. not using refpositionsgroup // f_ag_min_msd_fit, - f_ag_fit_gradient_group,// TODO check that these are sometimes needed separately - // maybe for minimum RMSD? - f_ag_fit_gradient_ref, + /// \brief Does not have explicit atom gradients from parent CVC + f_ag_implicit_gradient, + f_ag_fit_gradients, f_ag_atom_forces, f_ag_scalable, f_ag_scalable_com, f_ag_ntot }; void init_cvb_requires(); void init_cv_requires(); void init_cvc_requires(); void init_ag_requires(); /// \brief print all enabled features and those of children, for debugging void print_state(); }; #endif diff --git a/lib/colvars/colvargrid.cpp b/lib/colvars/colvargrid.cpp index 3b25acd2e..9016e2c23 100644 --- a/lib/colvars/colvargrid.cpp +++ b/lib/colvars/colvargrid.cpp @@ -1,193 +1,194 @@ // -*- 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 "colvargrid.h" colvar_grid_count::colvar_grid_count() : colvar_grid() { mult = 1; } colvar_grid_count::colvar_grid_count(std::vector const &nx_i, size_t const &def_count) : colvar_grid(nx_i, def_count, 1) {} colvar_grid_count::colvar_grid_count(std::vector &colvars, size_t const &def_count) : colvar_grid(colvars, def_count, 1) {} colvar_grid_scalar::colvar_grid_scalar() : colvar_grid(), samples(NULL), grad(NULL) {} colvar_grid_scalar::colvar_grid_scalar(colvar_grid_scalar const &g) : colvar_grid(g), samples(NULL), grad(NULL) { grad = new cvm::real[nd]; } colvar_grid_scalar::colvar_grid_scalar(std::vector const &nx_i) : colvar_grid(nx_i, 0.0, 1), samples(NULL), grad(NULL) { grad = new cvm::real[nd]; } colvar_grid_scalar::colvar_grid_scalar(std::vector &colvars, bool margin) : colvar_grid(colvars, 0.0, 1, margin), samples(NULL), grad(NULL) { grad = new cvm::real[nd]; } colvar_grid_scalar::~colvar_grid_scalar() { if (grad) { delete [] grad; grad = NULL; } } cvm::real colvar_grid_scalar::maximum_value() const { cvm::real max = data[0]; for (size_t i = 0; i < nt; i++) { if (data[i] > max) max = data[i]; } return max; } cvm::real colvar_grid_scalar::minimum_value() const { cvm::real min = data[0]; for (size_t i = 0; i < nt; i++) { if (data[i] < min) min = data[i]; } return min; } cvm::real colvar_grid_scalar::minimum_pos_value() const { cvm::real minpos = data[0]; size_t i; for (i = 0; i < nt; i++) { if(data[i] > 0) { minpos = data[i]; break; } } for (i = 0; i < nt; i++) { if (data[i] > 0 && data[i] < minpos) minpos = data[i]; } return minpos; } cvm::real colvar_grid_scalar::integral() const { cvm::real sum = 0.0; for (size_t i = 0; i < nt; i++) { sum += data[i]; } cvm::real bin_volume = 1.0; for (size_t id = 0; id < widths.size(); id++) { bin_volume *= widths[id]; } return bin_volume * sum; } cvm::real colvar_grid_scalar::entropy() const { cvm::real sum = 0.0; for (size_t i = 0; i < nt; i++) { sum += -1.0 * data[i] * std::log(data[i]); } cvm::real bin_volume = 1.0; for (size_t id = 0; id < widths.size(); id++) { bin_volume *= widths[id]; } return bin_volume * sum; } colvar_grid_gradient::colvar_grid_gradient() : colvar_grid(), samples(NULL) {} colvar_grid_gradient::colvar_grid_gradient(std::vector const &nx_i) : colvar_grid(nx_i, 0.0, nx_i.size()), samples(NULL) {} colvar_grid_gradient::colvar_grid_gradient(std::vector &colvars) : colvar_grid(colvars, 0.0, colvars.size()), samples(NULL) {} void colvar_grid_gradient::write_1D_integral(std::ostream &os) { cvm::real bin, min, integral; std::vector int_vals; os << "# xi A(xi)\n"; if ( cv.size() != 1 ) { - cvm::fatal_error("Cannot write integral for multi-dimensional gradient grids."); + cvm::error("Cannot write integral for multi-dimensional gradient grids."); + return; } integral = 0.0; int_vals.push_back( 0.0 ); min = 0.0; // correction for periodic colvars, so that the PMF is periodic cvm::real corr; if ( periodic[0] ) { corr = average(); } else { corr = 0.0; } for (std::vector ix = new_index(); index_ok(ix); incr(ix)) { if (samples) { size_t const samples_here = samples->value(ix); if (samples_here) integral += (value(ix) / cvm::real(samples_here) - corr) * cv[0]->width; } else { integral += (value(ix) - corr) * cv[0]->width; } if ( integral < min ) min = integral; int_vals.push_back( integral ); } bin = 0.0; for ( int i = 0; i < nx[0]; i++, bin += 1.0 ) { os << std::setw(10) << cv[0]->lower_boundary.real_value + cv[0]->width * bin << " " << std::setw(cvm::cv_width) << std::setprecision(cvm::cv_prec) << int_vals[i] - min << "\n"; } os << std::setw(10) << cv[0]->lower_boundary.real_value + cv[0]->width * bin << " " << std::setw(cvm::cv_width) << std::setprecision(cvm::cv_prec) << int_vals[nx[0]] - min << "\n"; return; } diff --git a/lib/colvars/colvargrid.h b/lib/colvars/colvargrid.h index d4b9295c6..6f06cb106 100644 --- a/lib/colvars/colvargrid.h +++ b/lib/colvars/colvargrid.h @@ -1,1497 +1,1493 @@ // -*- 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 COLVARGRID_H #define COLVARGRID_H #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: /// Number of dimensions size_t nd; /// Number of points along each dimension std::vector nx; /// Cumulative number of points along each dimension std::vector nxc; /// \brief Multiplicity of each datum (allow the binning of /// non-scalar types such as atomic gradients) size_t mult; /// Total number of grid points size_t nt; /// Low-level array of values std::vector data; /// Newly read data (used for count grids, when adding several grids read from disk) std::vector new_data; /// Colvars collected in this grid std::vector cv; /// Do we request actual value (for extended-system colvars)? std::vector actual_value; /// Get the low-level index corresponding to an index inline size_t address(std::vector const &ix) const { size_t addr = 0; for (size_t i = 0; i < nd; i++) { addr += ix[i]*nxc[i]; if (cvm::debug()) { if (ix[i] >= nx[i]) { cvm::error("Error: exceeding bounds in colvar_grid.\n", BUG_ERROR); return 0; } } } return addr; } public: /// Lower boundaries of the colvars in this grid std::vector lower_boundaries; /// Upper boundaries of the colvars in this grid std::vector upper_boundaries; /// Whether some colvars are periodic std::vector periodic; /// Whether some colvars have hard lower boundaries std::vector hard_lower_boundaries; /// Whether some colvars have hard upper boundaries std::vector hard_upper_boundaries; /// Widths of the colvars in this grid std::vector widths; /// True if this is a count grid related to another grid of data bool has_parent_data; /// Whether this grid has been filled with data or is still empty bool has_data; /// Return the number of colvars inline size_t number_of_colvars() const { return nd; } /// Return the number of points in the i-th direction, if provided, or /// the total number inline size_t number_of_points(int const icv = -1) const { if (icv < 0) { return nt; } else { return nx[icv]; } } /// Get the sizes in each direction inline std::vector const & sizes() const { return nx; } /// Set the sizes in each direction inline void set_sizes(std::vector const &new_sizes) { nx = new_sizes; } /// Return the multiplicity of the type used inline size_t multiplicity() const { return mult; } /// \brief Request grid to use actual values of extended coords inline void request_actual_value(bool b = true) { size_t i; for (i = 0; i < actual_value.size(); i++) { actual_value[i] = b; } } /// \brief Allocate data int setup(std::vector const &nx_i, T const &t = T(), size_t const &mult_i = 1) { if (cvm::debug()) { cvm::log("Allocating grid: multiplicity = "+cvm::to_str(mult_i)+ ", dimensions = "+cvm::to_str(nx_i)+".\n"); } mult = mult_i; data.clear(); nx = nx_i; nd = nx.size(); nxc.resize(nd); // setup dimensions nt = mult; for (int i = nd-1; i >= 0; i--) { if (nx[i] <= 0) { cvm::error("Error: providing an invalid number of grid points, "+ cvm::to_str(nx[i])+".\n", BUG_ERROR); return COLVARS_ERROR; } nxc[i] = nt; nt *= nx[i]; } if (cvm::debug()) { cvm::log("Total number of grid elements = "+cvm::to_str(nt)+".\n"); } data.reserve(nt); data.assign(nt, t); return COLVARS_OK; } /// \brief Allocate data (allow initialization also after construction) int setup() { return setup(this->nx, T(), this->mult); } /// \brief Reset data (in case the grid is being reused) void reset(T const &t = T()) { data.assign(nt, t); } /// Default constructor colvar_grid() : has_data(false) { - save_delimiters = false; nd = nt = 0; mult = 1; this->setup(); } /// Destructor virtual ~colvar_grid() {} /// \brief "Almost copy-constructor": only copies configuration /// parameters from another grid, but doesn't reallocate stuff; /// setup() must be called after that; colvar_grid(colvar_grid const &g) : nd(g.nd), nx(g.nx), mult(g.mult), data(), cv(g.cv), actual_value(g.actual_value), lower_boundaries(g.lower_boundaries), upper_boundaries(g.upper_boundaries), periodic(g.periodic), hard_lower_boundaries(g.hard_lower_boundaries), hard_upper_boundaries(g.hard_upper_boundaries), widths(g.widths), has_data(false) { - save_delimiters = false; } /// \brief Constructor from explicit grid sizes \param nx_i Number /// of grid points along each dimension \param t Initial value for /// the function at each point (optional) \param mult_i Multiplicity /// of each value colvar_grid(std::vector const &nx_i, T const &t = T(), size_t mult_i = 1) : has_data(false) { - save_delimiters = false; this->setup(nx_i, t, mult_i); } /// \brief Constructor from a vector of colvars colvar_grid(std::vector const &colvars, T const &t = T(), size_t mult_i = 1, bool margin = false) : has_data(false) { - save_delimiters = false; this->init_from_colvars(colvars, t, mult_i, margin); } int init_from_colvars(std::vector const &colvars, T const &t = T(), size_t mult_i = 1, bool margin = false) { if (cvm::debug()) { cvm::log("Reading grid configuration from collective variables.\n"); } cv = colvars; nd = colvars.size(); mult = mult_i; size_t i; if (cvm::debug()) { cvm::log("Allocating a grid for "+cvm::to_str(colvars.size())+ " collective variables, multiplicity = "+cvm::to_str(mult_i)+".\n"); } for (i = 0; i < cv.size(); i++) { if (cv[i]->value().type() != colvarvalue::type_scalar) { cvm::error("Colvar grids can only be automatically " "constructed for scalar variables. " "ABF and histogram can not be used; metadynamics " "can be used with useGrids disabled.\n", INPUT_ERROR); return COLVARS_ERROR; } if (cv[i]->width <= 0.0) { cvm::error("Tried to initialize a grid on a " "variable with negative or zero width.\n", INPUT_ERROR); return COLVARS_ERROR; } widths.push_back(cv[i]->width); hard_lower_boundaries.push_back(cv[i]->hard_lower_boundary); hard_upper_boundaries.push_back(cv[i]->hard_upper_boundary); periodic.push_back(cv[i]->periodic_boundaries()); // By default, get reported colvar value (for extended Lagrangian colvars) actual_value.push_back(false); // except if a colvar is specified twice in a row // then the first instance is the actual value // For histograms of extended-system coordinates if (i > 0 && cv[i-1] == cv[i]) { actual_value[i-1] = true; } if (margin) { if (periodic[i]) { // Shift the grid by half the bin width (values at edges instead of center of bins) lower_boundaries.push_back(cv[i]->lower_boundary.real_value - 0.5 * widths[i]); upper_boundaries.push_back(cv[i]->upper_boundary.real_value - 0.5 * widths[i]); } else { // Make this grid larger by one bin width lower_boundaries.push_back(cv[i]->lower_boundary.real_value - 0.5 * widths[i]); upper_boundaries.push_back(cv[i]->upper_boundary.real_value + 0.5 * widths[i]); } } else { lower_boundaries.push_back(cv[i]->lower_boundary); upper_boundaries.push_back(cv[i]->upper_boundary); } } this->init_from_boundaries(); return this->setup(); } int init_from_boundaries(T const &t = T(), size_t const &mult_i = 1) { if (cvm::debug()) { cvm::log("Configuring grid dimensions from colvars boundaries.\n"); } // these will have to be updated nx.clear(); nxc.clear(); nt = 0; for (size_t i = 0; i < lower_boundaries.size(); i++) { cvm::real nbins = ( upper_boundaries[i].real_value - lower_boundaries[i].real_value ) / widths[i]; int nbins_round = (int)(nbins+0.5); if (std::fabs(nbins - cvm::real(nbins_round)) > 1.0E-10) { cvm::log("Warning: grid interval("+ cvm::to_str(lower_boundaries[i], cvm::cv_width, cvm::cv_prec)+" - "+ cvm::to_str(upper_boundaries[i], cvm::cv_width, cvm::cv_prec)+ ") is not commensurate to its bin width("+ cvm::to_str(widths[i], cvm::cv_width, cvm::cv_prec)+").\n"); upper_boundaries[i].real_value = lower_boundaries[i].real_value + (nbins_round * widths[i]); } if (cvm::debug()) cvm::log("Number of points is "+cvm::to_str((int) nbins_round)+ " for the colvar no. "+cvm::to_str(i+1)+".\n"); nx.push_back(nbins_round); } return COLVARS_OK; } /// Wrap an index vector around periodic boundary conditions /// also checks validity of non-periodic indices inline void wrap(std::vector & ix) const { 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] < 0 || ix[i] >= nx[i]) { cvm::error("Trying to wrap illegal index vector (non-PBC) for a grid point: " + cvm::to_str(ix), BUG_ERROR); return; } } } } /// \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(actual_value[i] ? cv[i]->actual_value() : cv[i]->value(), i); } /// \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 { return value_to_bin_scalar_bound(actual_value[i] ? cv[i]->actual_value() : cv[i]->value(), i); } /// \brief Report the bin corresponding to the current value of item iv in variable i inline int current_bin_scalar(int const i, int const iv) const { return value_to_bin_scalar(actual_value[i] ? cv[i]->actual_value().vector1d_value[iv] : cv[i]->value().vector1d_value[iv], i); } /// \brief Use the lower boundary and the width to report which bin /// the provided value is in inline int value_to_bin_scalar(colvarvalue const &value, const int i) const { return (int) std::floor( (value.real_value - lower_boundaries[i].real_value) / widths[i] ); } /// \brief Use the lower boundary and the width to report which bin /// the provided value is in and assign first or last bin if out of boundaries inline int value_to_bin_scalar_bound(colvarvalue const &value, const int i) const { int bin_index = std::floor( (value.real_value - lower_boundaries[i].real_value) / widths[i] ); if (bin_index < 0) bin_index=0; if (bin_index >=int(nx[i])) bin_index=int(nx[i])-1; return (int) bin_index; } /// \brief Same as the standard version, but uses another grid definition inline int value_to_bin_scalar(colvarvalue const &value, colvarvalue const &new_offset, cvm::real const &new_width) const { return (int) std::floor( (value.real_value - new_offset.real_value) / new_width ); } /// \brief Use the two boundaries and the width to report the /// central value corresponding to a bin index inline colvarvalue bin_to_value_scalar(int const &i_bin, int const i) const { return lower_boundaries[i].real_value + widths[i] * (0.5 + i_bin); } /// \brief Same as the standard version, but uses different parameters inline colvarvalue bin_to_value_scalar(int const &i_bin, colvarvalue const &new_offset, cvm::real const &new_width) const { return new_offset.real_value + new_width * (0.5 + i_bin); } /// Set the value at the point with index ix inline void set_value(std::vector const &ix, T const &t, size_t const &imult = 0) { data[this->address(ix)+imult] = t; has_data = true; } /// \brief Get the change from this to other_grid /// and store the result in this. /// this_grid := other_grid - this_grid /// Grids must have the same dimensions. void delta_grid(colvar_grid const &other_grid) { if (other_grid.multiplicity() != this->multiplicity()) { cvm::error("Error: trying to subtract two grids with " "different multiplicity.\n"); return; } if (other_grid.data.size() != this->data.size()) { cvm::error("Error: trying to subtract two grids with " "different size.\n"); return; } for (size_t i = 0; i < data.size(); i++) { data[i] = other_grid.data[i] - data[i]; } has_data = true; } /// \brief Copy data from another grid of the same type, AND /// identical definition (boundaries, widths) /// Added for shared ABF. void copy_grid(colvar_grid const &other_grid) { if (other_grid.multiplicity() != this->multiplicity()) { cvm::error("Error: trying to copy two grids with " "different multiplicity.\n"); return; } if (other_grid.data.size() != this->data.size()) { cvm::error("Error: trying to copy two grids with " "different size.\n"); return; } for (size_t i = 0; i < data.size(); i++) { data[i] = other_grid.data[i]; } has_data = true; } /// \brief Extract the grid data as they are represented in memory. /// Put the results in "out_data". void raw_data_out(T* out_data) const { for (size_t i = 0; i < data.size(); i++) out_data[i] = data[i]; } /// \brief Input the data as they are represented in memory. void raw_data_in(const T* in_data) { for (size_t i = 0; i < data.size(); i++) data[i] = in_data[i]; has_data = true; } /// \brief Size of the data as they are represented in memory. size_t raw_data_num() const { return data.size(); } /// \brief Get the binned value indexed by ix, or the first of them /// if the multiplicity is larger than 1 inline T const & value(std::vector const &ix, size_t const &imult = 0) const { return data[this->address(ix) + imult]; } /// \brief Add a constant to all elements (fast loop) inline void add_constant(T const &t) { for (size_t i = 0; i < nt; i++) data[i] += t; has_data = true; } /// \brief Multiply all elements by a scalar constant (fast loop) inline void multiply_constant(cvm::real const &a) { for (size_t i = 0; i < nt; i++) data[i] *= a; } /// \brief Assign all zero elements a scalar constant (fast loop) inline void remove_zeros(cvm::real const &a) { for (size_t i = 0; i < nt; i++) if(data[i]==0) data[i] = a; } /// \brief Get the bin indices corresponding to the provided values of /// the colvars inline std::vector const get_colvars_index(std::vector const &values) const { std::vector index = new_index(); for (size_t i = 0; i < nd; i++) { index[i] = value_to_bin_scalar(values[i], i); } return index; } /// \brief Get the bin indices corresponding to the current values /// of the colvars inline std::vector const get_colvars_index() const { std::vector index = new_index(); for (size_t i = 0; i < nd; i++) { index[i] = current_bin_scalar(i); } return index; } /// \brief Get the bin indices corresponding to the provided values of /// the colvars and assign first or last bin if out of boundaries inline std::vector const get_colvars_index_bound() const { std::vector index = new_index(); for (size_t i = 0; i < nd; i++) { index[i] = current_bin_scalar_bound(i); } return index; } /// \brief Get the minimal distance (in number of bins) from the /// boundaries; a negative number is returned if the given point is /// off-grid inline cvm::real bin_distance_from_boundaries(std::vector const &values, bool skip_hard_boundaries = false) { cvm::real minimum = 1.0E+16; for (size_t i = 0; i < nd; i++) { if (periodic[i]) continue; cvm::real dl = std::sqrt(cv[i]->dist2(values[i], lower_boundaries[i])) / widths[i]; cvm::real du = std::sqrt(cv[i]->dist2(values[i], upper_boundaries[i])) / widths[i]; if (values[i].real_value < lower_boundaries[i]) dl *= -1.0; if (values[i].real_value > upper_boundaries[i]) du *= -1.0; if ( ((!skip_hard_boundaries) || (!hard_lower_boundaries[i])) && (dl < minimum)) minimum = dl; if ( ((!skip_hard_boundaries) || (!hard_upper_boundaries[i])) && (du < minimum)) minimum = du; } return minimum; } /// \brief Add data from another grid of the same type /// /// Note: this function maps other_grid inside this one regardless /// of whether it fits or not. void map_grid(colvar_grid const &other_grid) { if (other_grid.multiplicity() != this->multiplicity()) { cvm::error("Error: trying to merge two grids with values of " "different multiplicity.\n"); return; } std::vector const &gb = this->lower_boundaries; std::vector const &gw = this->widths; std::vector const &ogb = other_grid.lower_boundaries; std::vector const &ogw = other_grid.widths; std::vector ix = this->new_index(); std::vector oix = other_grid.new_index(); if (cvm::debug()) cvm::log("Remapping grid...\n"); for ( ; this->index_ok(ix); this->incr(ix)) { for (size_t i = 0; i < nd; i++) { oix[i] = value_to_bin_scalar(bin_to_value_scalar(ix[i], gb[i], gw[i]), ogb[i], ogw[i]); } if (! other_grid.index_ok(oix)) { continue; } for (size_t im = 0; im < mult; im++) { this->set_value(ix, other_grid.value(oix, im), im); } } has_data = true; if (cvm::debug()) cvm::log("Remapping done.\n"); } /// \brief Add data from another grid of the same type, AND /// identical definition (boundaries, widths) void add_grid(colvar_grid const &other_grid, cvm::real scale_factor = 1.0) { if (other_grid.multiplicity() != this->multiplicity()) { cvm::error("Error: trying to sum togetehr two grids with values of " "different multiplicity.\n"); return; } if (scale_factor != 1.0) for (size_t i = 0; i < data.size(); i++) { data[i] += scale_factor * other_grid.data[i]; } else // skip multiplication if possible for (size_t i = 0; i < data.size(); i++) { data[i] += other_grid.data[i]; } has_data = true; } /// \brief Return the value suitable for output purposes (so that it /// may be rescaled or manipulated without changing it permanently) virtual inline T value_output(std::vector const &ix, size_t const &imult = 0) { return value(ix, imult); } /// \brief Get the value from a formatted output and transform it /// into the internal representation (the two may be different, /// e.g. when using colvar_grid_count) virtual inline void value_input(std::vector const &ix, T const &t, size_t const &imult = 0, bool add = false) { if ( add ) data[address(ix) + imult] += t; else data[address(ix) + imult] = t; has_data = true; } // /// Get the pointer to the binned value indexed by ix // inline T const *value_p (std::vector const &ix) // { // return &(data[address (ix)]); // } /// \brief Get the index corresponding to the "first" bin, to be /// used as the initial value for an index in looping inline std::vector const new_index() const { return std::vector (nd, 0); } /// \brief Check that the index is within range in each of the /// dimensions inline bool index_ok(std::vector const &ix) const { for (size_t i = 0; i < nd; i++) { if ( (ix[i] < 0) || (ix[i] >= int(nx[i])) ) return false; } return true; } /// \brief Increment the index, in a way that will make it loop over /// the whole nd-dimensional array inline void incr(std::vector &ix) const { for (int i = ix.size()-1; i >= 0; i--) { ix[i]++; if (ix[i] >= nx[i]) { if (i > 0) { ix[i] = 0; continue; } else { // this is the last iteration, a non-valid index is being // set for the outer index, which will be caught by // index_ok() ix[0] = nx[0]; return; } } else { return; } } } /// \brief 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"; 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 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; { 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 (! actual_value.size()) actual_value.assign(nd, false); if (! periodic.size()) periodic.assign(nd, false); if (! widths.size()) widths.assign(nd, 1.0); bool new_params = false; if (old_nx.size()) { for (size_t i = 0; i < nd; i++) { if ( (old_nx[i] != nx[i]) || (std::sqrt(cv[i]->dist2(old_lb[i], lower_boundaries[i])) > 1.0E-10) ) { 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.resize(0); // no longer needed: setup calls clear() + // data.clear(); // no longer needed: setup calls clear() return this->setup(nx, T(), mult); } return COLVARS_OK; } /// \brief Check that the grid information inside (boundaries, /// widths, ...) is consistent with the current setting of the /// colvars void check_consistency() { for (size_t i = 0; i < nd; i++) { if ( (std::sqrt(cv[i]->dist2(cv[i]->lower_boundary, lower_boundaries[i])) > 1.0E-10) || (std::sqrt(cv[i]->dist2(cv[i]->upper_boundary, upper_boundaries[i])) > 1.0E-10) || (std::sqrt(cv[i]->dist2(cv[i]->width, widths[i])) > 1.0E-10) ) { cvm::error("Error: restart information for a grid is " "inconsistent with that of its colvars.\n"); return; } } } /// \brief Check that the grid information inside (boundaries, /// widths, ...) is consistent with that of another grid void check_consistency(colvar_grid const &other_grid) { for (size_t i = 0; i < nd; i++) { // we skip dist2(), because periodicities and the like should // matter: boundaries should be EXACTLY the same (otherwise, // map_grid() should be used) if ( (std::fabs(other_grid.lower_boundaries[i] - lower_boundaries[i]) > 1.0E-10) || (std::fabs(other_grid.upper_boundaries[i] - upper_boundaries[i]) > 1.0E-10) || (std::fabs(other_grid.widths[i] - widths[i]) > 1.0E-10) || (data.size() != other_grid.data.size()) ) { cvm::error("Error: inconsistency between " "two grids that are supposed to be equal, " "aside from the data stored.\n"); return; } } } /// \brief Read grid entry in restart file std::istream & read_restart(std::istream &is) { size_t 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; } /// \brief Write grid entry in restart file std::ostream & write_restart(std::ostream &os) { write_params(os); write_raw(os); return 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) { std::streamsize const w = os.width(); std::streamsize const p = os.precision(); 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"; return os; } /// \brief Read data written by colvar_grid::write_raw() std::istream & read_raw(std::istream &is) { size_t 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; } /// \brief Write the grid in a format which is both human readable /// and suitable for visualization e.g. with gnuplot void write_multicol(std::ostream &os) { std::streamsize const w = os.width(); std::streamsize const p = os.precision(); // Data in the header: nColvars, then for each // xiMin, dXi, nPoints, periodic os << std::setw(2) << "# " << nd << "\n"; for (size_t i = 0; i < nd; i++) { os << "# " << std::setw(10) << lower_boundaries[i] << std::setw(10) << widths[i] << std::setw(10) << nx[i] << " " << periodic[i] << "\n"; } for (std::vector ix = new_index(); index_ok(ix); incr(ix) ) { if (ix.back() == 0) { // if the last index is 0, add a new line to mark the new record os << "\n"; } for (size_t i = 0; i < nd; i++) { os << " " << std::setw(w) << std::setprecision(p) << bin_to_value_scalar(ix[i], i); } os << " "; for (size_t imult = 0; imult < mult; imult++) { os << " " << std::setw(w) << std::setprecision(p) << value_output(ix, imult); } os << "\n"; } } /// \brief Read a grid written by colvar_grid::write_multicol() /// Adding data if add is true, replacing if false std::istream & read_multicol(std::istream &is, bool add = false) { // Data in the header: nColvars, then for each // xiMin, dXi, nPoints, periodic std::string hash; cvm::real lower, width, x; size_t n, periodic; bool remap; std::vector new_value; std::vector nx_read; std::vector bin; if ( cv.size() != nd ) { cvm::error("Cannot read grid file: missing reference to colvars."); return is; } if ( !(is >> hash) || (hash != "#") ) { cvm::error("Error reading grid at position "+ cvm::to_str(is.tellg())+" in stream(read \"" + hash + "\")\n"); return is; } is >> n; if ( n != nd ) { cvm::error("Error reading grid: wrong number of collective variables.\n"); return is; } nx_read.resize(n); bin.resize(n); new_value.resize(mult); if (this->has_parent_data && add) { new_data.resize(data.size()); } remap = false; for (size_t i = 0; i < nd; i++ ) { if ( !(is >> hash) || (hash != "#") ) { cvm::error("Error reading grid at position "+ cvm::to_str(is.tellg())+" in stream(read \"" + hash + "\")\n"); return is; } is >> lower >> width >> nx_read[i] >> periodic; if ( (std::fabs(lower - lower_boundaries[i].real_value) > 1.0e-10) || (std::fabs(width - widths[i] ) > 1.0e-10) || (nx_read[i] != nx[i]) ) { cvm::log("Warning: reading from different grid definition (colvar " + cvm::to_str(i+1) + "); remapping data on new grid.\n"); remap = true; } } if ( remap ) { // re-grid data while (is.good()) { bool end_of_file = false; for (size_t i = 0; i < nd; i++ ) { if ( !(is >> x) ) end_of_file = true; bin[i] = value_to_bin_scalar(x, i); } if (end_of_file) break; for (size_t imult = 0; imult < mult; imult++) { is >> new_value[imult]; } if ( index_ok(bin) ) { for (size_t imult = 0; imult < mult; imult++) { value_input(bin, new_value[imult], imult, add); } } } } else { // do not re-grid the data but assume the same grid is used for (std::vector ix = new_index(); index_ok(ix); incr(ix) ) { for (size_t i = 0; i < nd; i++ ) { is >> x; } for (size_t imult = 0; imult < mult; imult++) { is >> new_value[imult]; value_input(ix, new_value[imult], imult, add); } } } has_data = true; return is; } /// \brief Write the grid data without labels, as they are /// represented in memory std::ostream & write_opendx(std::ostream &os) { // write the header os << "object 1 class gridpositions counts"; size_t icv; for (icv = 0; icv < number_of_colvars(); icv++) { os << " " << number_of_points(icv); } os << "\n"; os << "origin"; for (icv = 0; icv < number_of_colvars(); icv++) { os << " " << (lower_boundaries[icv].real_value + 0.5 * widths[icv]); } os << "\n"; for (icv = 0; icv < number_of_colvars(); icv++) { os << "delta"; for (size_t icv2 = 0; icv2 < number_of_colvars(); icv2++) { if (icv == icv2) os << " " << widths[icv]; else os << " " << 0.0; } os << "\n"; } os << "object 2 class gridconnections counts"; for (icv = 0; icv < number_of_colvars(); icv++) { os << " " << number_of_points(icv); } os << "\n"; os << "object 3 class array type double rank 0 items " << number_of_points() << " data follows\n"; write_raw(os); os << "object \"collective variables scalar field\" class field\n"; return os; } }; /// \brief Colvar_grid derived class to hold counters in discrete /// n-dim colvar space class colvar_grid_count : public colvar_grid { public: /// Default constructor colvar_grid_count(); /// Destructor virtual inline ~colvar_grid_count() {} /// Constructor colvar_grid_count(std::vector const &nx_i, size_t const &def_count = 0); /// Constructor from a vector of colvars colvar_grid_count(std::vector &colvars, size_t const &def_count = 0); /// Increment the counter at given position inline void incr_count(std::vector const &ix) { ++(data[this->address(ix)]); } /// \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) { return new_data[address(ix) + imult]; } /// \brief Get the value from a formatted output and transform it /// into the internal representation (it may have been rescaled or /// manipulated) virtual inline void value_input(std::vector const &ix, size_t const &t, size_t const &imult = 0, bool add = false) { if (add) { data[address(ix)] += t; if (this->has_parent_data) { // save newly read data for inputting parent grid new_data[address(ix)] = t; } } else { data[address(ix)] = t; } has_data = true; } /// \brief Return the log-gradient from finite differences /// on the *same* grid for dimension n inline cvm::real log_gradient_finite_diff(const std::vector &ix0, int n = 0) { int A0, A1, A2; std::vector ix = ix0; if (periodic[n]) { ix[n]--; wrap(ix); A0 = data[address(ix)]; ix = ix0; ix[n]++; wrap(ix); A1 = data[address(ix)]; if (A0 * A1 == 0) { return 0.; // can't handle empty bins } else { return (std::log((cvm::real)A1) - std::log((cvm::real)A0)) / (widths[n] * 2.); } } else if (ix[n] > 0 && ix[n] < nx[n]-1) { // not an edge ix[n]--; A0 = data[address(ix)]; ix = ix0; ix[n]++; A1 = data[address(ix)]; if (A0 * A1 == 0) { return 0.; // can't handle empty bins } else { return (std::log((cvm::real)A1) - std::log((cvm::real)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 = data[address(ix)]; ix[n] += increment; A1 = data[address(ix)]; ix[n] += increment; A2 = data[address(ix)]; if (A0 * A1 * A2 == 0) { return 0.; // can't handle empty bins } else { return (-1.5 * std::log((cvm::real)A0) + 2. * std::log((cvm::real)A1) - 0.5 * std::log((cvm::real)A2)) * increment / widths[n]; } } } }; /// Class for accumulating a scalar function on a grid class colvar_grid_scalar : public colvar_grid { public: /// \brief Provide the associated sample count by which each binned value /// should be divided colvar_grid_count *samples; /// Default constructor colvar_grid_scalar(); /// Copy constructor (needed because of the grad pointer) colvar_grid_scalar(colvar_grid_scalar const &g); /// Destructor ~colvar_grid_scalar(); /// Constructor from specific sizes arrays colvar_grid_scalar(std::vector const &nx_i); /// Constructor from a vector of colvars colvar_grid_scalar(std::vector &colvars, bool margin = 0); /// Accumulate the value inline void acc_value(std::vector const &ix, cvm::real const &new_value, size_t const &imult = 0) { // only legal value of imult here is 0 data[address(ix)] += new_value; if (samples) samples->incr_count(ix); has_data = true; } /// Return the gradient of the scalar field from finite differences inline const cvm::real * gradient_finite_diff( const std::vector &ix0 ) { cvm::real A0, A1; std::vector ix; if (nd != 2) { cvm::error("Finite differences available in dimension 2 only."); return grad; } for (unsigned int n = 0; n < nd; n++) { ix = ix0; A0 = data[address(ix)]; ix[n]++; wrap(ix); A1 = data[address(ix)]; ix[1-n]++; wrap(ix); A1 += data[address(ix)]; ix[n]--; wrap(ix); A0 += data[address(ix)]; grad[n] = 0.5 * (A1 - A0) / widths[n]; } return grad; } /// \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) { 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))) : 0.0; } else { return data[address(ix)]; } } /// \brief Get the value from a formatted output and transform it /// into the internal representation (it may have been rescaled or /// manipulated) virtual void value_input(std::vector const &ix, cvm::real const &new_value, size_t const &imult = 0, bool add = false) { if (imult > 0) { cvm::error("Error: trying to access a component " "larger than 1 in a scalar data grid.\n"); return; } if (add) { if (samples) data[address(ix)] += new_value * samples->new_count(ix); else data[address(ix)] += new_value; } else { if (samples) data[address(ix)] = new_value * samples->value(ix); else data[address(ix)] = new_value; } has_data = true; } /// \brief Return the highest value cvm::real maximum_value() const; /// \brief Return the lowest value cvm::real minimum_value() const; /// \brief Return the lowest positive value cvm::real minimum_pos_value() const; /// \brief Calculates the integral of the map (uses widths if they are defined) cvm::real integral() const; /// \brief Assuming that the map is a normalized probability density, /// calculates the entropy (uses widths if they are defined) cvm::real entropy() const; private: // gradient cvm::real * grad; }; /// Class for accumulating the gradient of a scalar function on a grid class colvar_grid_gradient : public colvar_grid { public: /// \brief Provide the sample count by which each binned value /// should be divided colvar_grid_count *samples; /// Default constructor colvar_grid_gradient(); /// Destructor virtual inline ~colvar_grid_gradient() {} /// Constructor from specific sizes arrays colvar_grid_gradient(std::vector const &nx_i); /// Constructor from a vector of colvars colvar_grid_gradient(std::vector &colvars); /// \brief Accumulate the gradient inline void acc_grad(std::vector const &ix, cvm::real const *grads) { for (size_t imult = 0; imult < mult; imult++) { data[address(ix) + imult] += grads[imult]; } if (samples) samples->incr_count(ix); } /// \brief Accumulate the gradient based on the force (i.e. sums the /// opposite of the force) inline void acc_force(std::vector const &ix, cvm::real const *forces) { for (size_t imult = 0; imult < mult; imult++) { data[address(ix) + imult] -= forces[imult]; } if (samples) samples->incr_count(ix); } /// \brief Return the value of the function at ix divided by its /// number of samples (if the count grid is defined) virtual inline cvm::real value_output(std::vector const &ix, size_t const &imult = 0) { if (samples) return (samples->value(ix) > 0) ? (data[address(ix) + imult] / cvm::real(samples->value(ix))) : 0.0; else return data[address(ix) + imult]; } /// \brief Get the value from a formatted output and transform it /// into the internal representation (it may have been rescaled or /// manipulated) virtual inline void value_input(std::vector const &ix, cvm::real const &new_value, size_t const &imult = 0, bool add = false) { if (add) { if (samples) data[address(ix) + imult] += new_value * samples->new_count(ix); else data[address(ix) + imult] += new_value; } else { if (samples) data[address(ix) + imult] = new_value * samples->value(ix); else data[address(ix) + imult] = new_value; } has_data = true; } /// Compute and return average value for a 1D gradient grid inline cvm::real average() { 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); } } return (sum / cvm::real(nx[0])); } /// \brief If the grid is 1-dimensional, integrate it and write the /// integral to a file void write_1D_integral(std::ostream &os); }; #endif diff --git a/lib/colvars/colvarmodule.cpp b/lib/colvars/colvarmodule.cpp index 10cd3c0e4..780dc28af 100644 --- a/lib/colvars/colvarmodule.cpp +++ b/lib/colvars/colvarmodule.cpp @@ -1,1718 +1,1760 @@ // -*- 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 "colvarmodule.h" #include "colvarparse.h" #include "colvarproxy.h" #include "colvar.h" #include "colvarbias.h" #include "colvarbias_abf.h" #include "colvarbias_alb.h" #include "colvarbias_histogram.h" #include "colvarbias_meta.h" #include "colvarbias_restraint.h" #include "colvarscript.h" +#include "colvaratoms.h" colvarmodule::colvarmodule(colvarproxy *proxy_in) { + depth_s = 0; + cv_traj_os = NULL; + // pointer to the proxy object if (proxy == NULL) { proxy = proxy_in; parse = new colvarparse(); } else { // TODO relax this error to handle multiple molecules in VMD // once the module is not static anymore cvm::error("Error: trying to allocate the collective " - "variable module twice.\n"); + "variable module twice.\n", BUG_ERROR); return; } - depth_s = 0; - cvm::log(cvm::line_marker); cvm::log("Initializing the collective variables module, version "+ cvm::to_str(COLVARS_VERSION)+".\n"); cvm::log("Please cite Fiorin et al, Mol Phys 2013:\n " "http://dx.doi.org/10.1080/00268976.2013.813594\n" "in any publication based on this calculation.\n"); if (proxy->smp_enabled() == COLVARS_OK) { cvm::log("SMP parallelism is available.\n"); } // set initial default values // "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; colvarmodule::it_restart_from_state_file = true; colvarmodule::use_scripted_forces = false; colvarmodule::b_analysis = false; colvarmodule::debug_gradients_step_size = 1.0e-07; colvarmodule::rotation::monitor_crossings = false; colvarmodule::rotation::crossing_threshold = 1.0e-02; colvarmodule::cv_traj_freq = 100; colvarmodule::restart_out_freq = proxy->restart_frequency(); // by default overwrite the existing trajectory file colvarmodule::cv_traj_append = false; } colvarmodule * colvarmodule::main() { return proxy->colvars; } std::vector *colvarmodule::variables() { return &colvars; } std::vector *colvarmodule::variables_active() { return &colvars_active; } std::vector *colvarmodule::variables_active_smp() { return &colvars_smp; } std::vector *colvarmodule::variables_active_smp_items() { return &colvars_smp_items; } std::vector *colvarmodule::biases_active() { return &(biases_active_); } size_t colvarmodule::size() const { return colvars.size() + biases.size(); } int colvarmodule::read_config_file(char const *config_filename) { cvm::log(cvm::line_marker); cvm::log("Reading new configuration from file \""+ std::string(config_filename)+"\":\n"); // open the configfile config_s.open(config_filename); if (!config_s.is_open()) { cvm::error("Error: in opening configuration file \""+ std::string(config_filename)+"\".\n", FILE_ERROR); return COLVARS_ERROR; } // read the config file into a string std::string conf = ""; std::string line; while (colvarparse::getline_nocomments(config_s, line)) { // Delete lines that contain only white space after removing comments if (line.find_first_not_of(colvarparse::white_space) != std::string::npos) conf.append(line+"\n"); } config_s.close(); return parse_config(conf); } int colvarmodule::read_config_string(std::string const &config_str) { cvm::log(cvm::line_marker); cvm::log("Reading new configuration:\n"); std::istringstream config_s(config_str); // strip the comments away std::string conf = ""; std::string line; while (colvarparse::getline_nocomments(config_s, line)) { // Delete lines that contain only white space after removing comments if (line.find_first_not_of(colvarparse::white_space) != std::string::npos) conf.append(line+"\n"); } return parse_config(conf); } std::istream & colvarmodule::getline(std::istream &is, std::string &line) { std::string l; if (std::getline(is, l)) { size_t const sz = l.size(); if (sz > 0) { if (l[sz-1] == '\r' ) { line = l.substr(0, sz-1); } else { line = l; } } else { line.clear(); } } return is; } int colvarmodule::parse_config(std::string &conf) { extra_conf.clear(); // parse global options if (catch_input_errors(parse_global_params(conf))) { return get_error(); } // parse the options for collective variables if (catch_input_errors(parse_colvars(conf))) { return get_error(); } // parse the options for biases if (catch_input_errors(parse_biases(conf))) { return get_error(); } // done parsing known keywords, check that all keywords found were valid ones if (catch_input_errors(parse->check_keywords(conf, "colvarmodule"))) { return get_error(); } if (extra_conf.size()) { catch_input_errors(parse_global_params(extra_conf)); catch_input_errors(parse_colvars(extra_conf)); catch_input_errors(parse_biases(extra_conf)); parse->check_keywords(extra_conf, "colvarmodule"); extra_conf.clear(); if (get_error() != COLVARS_OK) return get_error(); } cvm::log(cvm::line_marker); cvm::log("Collective variables module (re)initialized.\n"); cvm::log(cvm::line_marker); // update any necessary proxy data proxy->setup(); - if (cv_traj_os.is_open()) { + if (cv_traj_os != NULL) { // configuration might have changed, better redo the labels - write_traj_label(cv_traj_os); + write_traj_label(*cv_traj_os); } return get_error(); } int colvarmodule::append_new_config(std::string const &new_conf) { extra_conf += new_conf; return COLVARS_OK; } int colvarmodule::parse_global_params(std::string const &conf) { colvarmodule *cvm = cvm::main(); std::string index_file_name; if (parse->get_keyval(conf, "indexFile", index_file_name)) { cvm->read_index_file(index_file_name.c_str()); } if (parse->get_keyval(conf, "smp", proxy->b_smp_active, proxy->b_smp_active)) { if (proxy->b_smp_active == false) { cvm::log("SMP parallelism has been disabled.\n"); } } parse->get_keyval(conf, "analysis", b_analysis, b_analysis); parse->get_keyval(conf, "debugGradientsStepSize", debug_gradients_step_size, debug_gradients_step_size, colvarparse::parse_silent); parse->get_keyval(conf, "monitorEigenvalueCrossing", colvarmodule::rotation::monitor_crossings, colvarmodule::rotation::monitor_crossings, colvarparse::parse_silent); parse->get_keyval(conf, "eigenvalueCrossingThreshold", colvarmodule::rotation::crossing_threshold, colvarmodule::rotation::crossing_threshold, colvarparse::parse_silent); parse->get_keyval(conf, "colvarsTrajFrequency", cv_traj_freq, cv_traj_freq); parse->get_keyval(conf, "colvarsRestartFrequency", restart_out_freq, restart_out_freq); // if this is true when initializing, it means // we are continuing after a reset(): default to true parse->get_keyval(conf, "colvarsTrajAppend", cv_traj_append, cv_traj_append); parse->get_keyval(conf, "scriptedColvarForces", use_scripted_forces, false); parse->get_keyval(conf, "scriptingAfterBiases", scripting_after_biases, true); if (use_scripted_forces && !proxy->force_script_defined) { cvm::error("User script for scripted colvar forces not found.", INPUT_ERROR); } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } int colvarmodule::parse_colvars(std::string const &conf) { if (cvm::debug()) cvm::log("Initializing the collective variables.\n"); std::string colvar_conf = ""; size_t pos = 0; - while (parse->key_lookup(conf, "colvar", colvar_conf, pos)) { + while (parse->key_lookup(conf, "colvar", &colvar_conf, &pos)) { if (colvar_conf.size()) { cvm::log(cvm::line_marker); cvm::increase_depth(); colvars.push_back(new colvar()); if (((colvars.back())->init(colvar_conf) != COLVARS_OK) || ((colvars.back())->check_keywords(colvar_conf, "colvar") != COLVARS_OK)) { cvm::log("Error while constructing colvar number " + cvm::to_str(colvars.size()) + " : deleting."); delete colvars.back(); // the colvar destructor updates the colvars array return COLVARS_ERROR; } cvm::decrease_depth(); } else { cvm::error("Error: \"colvar\" keyword found without any configuration.\n", INPUT_ERROR); return COLVARS_ERROR; } cvm::decrease_depth(); colvar_conf = ""; } if (!colvars.size()) { cvm::log("Warning: no collective variables defined.\n"); } if (colvars.size()) cvm::log(cvm::line_marker); cvm::log("Collective variables initialized, "+ cvm::to_str(colvars.size())+ " in total.\n"); return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } bool colvarmodule::check_new_bias(std::string &conf, char const *key) { if (cvm::get_error() || (biases.back()->check_keywords(conf, key) != COLVARS_OK)) { cvm::log("Error while constructing bias number " + cvm::to_str(biases.size()) + " : deleting.\n"); delete biases.back(); // the bias destructor updates the biases array return true; } return false; } template int colvarmodule::parse_biases_type(std::string const &conf, char const *keyword) { std::string bias_conf = ""; size_t conf_saved_pos = 0; - while (parse->key_lookup(conf, keyword, bias_conf, conf_saved_pos)) { + while (parse->key_lookup(conf, keyword, &bias_conf, &conf_saved_pos)) { if (bias_conf.size()) { cvm::log(cvm::line_marker); cvm::increase_depth(); biases.push_back(new bias_type(keyword)); biases.back()->init(bias_conf); if (cvm::check_new_bias(bias_conf, keyword) != COLVARS_OK) { return COLVARS_ERROR; } cvm::decrease_depth(); } else { cvm::error("Error: keyword \""+std::string(keyword)+"\" found without configuration.\n", INPUT_ERROR); return COLVARS_ERROR; } bias_conf = ""; } return COLVARS_OK; } int colvarmodule::parse_biases(std::string const &conf) { if (cvm::debug()) cvm::log("Initializing the collective variables biases.\n"); /// initialize ABF instances parse_biases_type(conf, "abf"); /// initialize adaptive linear biases parse_biases_type(conf, "ALB"); /// initialize harmonic restraints parse_biases_type(conf, "harmonic"); /// initialize harmonic walls restraints parse_biases_type(conf, "harmonicWalls"); /// initialize histograms parse_biases_type(conf, "histogram"); /// initialize histogram restraints parse_biases_type(conf, "histogramRestraint"); /// initialize linear restraints parse_biases_type(conf, "linear"); /// initialize metadynamics instances parse_biases_type(conf, "metadynamics"); if (use_scripted_forces) { cvm::log(cvm::line_marker); cvm::increase_depth(); cvm::log("User forces script will be run at each bias update."); cvm::decrease_depth(); } size_t i; - for (i = 0; i < biases.size(); i++) { - biases[i]->enable(colvardeps::f_cvb_active); - if (cvm::debug()) - biases[i]->print_state(); - } - size_t n_hist_dep_biases = 0; std::vector hist_dep_biases_names; for (i = 0; i < biases.size(); i++) { if (biases[i]->is_enabled(colvardeps::f_cvb_apply_force) && biases[i]->is_enabled(colvardeps::f_cvb_history_dependent)) { n_hist_dep_biases++; hist_dep_biases_names.push_back(biases[i]->name); } } if (n_hist_dep_biases > 1) { cvm::log("WARNING: there are "+cvm::to_str(n_hist_dep_biases)+ " history-dependent biases with non-zero force parameters:\n"+ cvm::to_str(hist_dep_biases_names)+"\n"+ "Please make sure that their forces do not counteract each other.\n"); } if (biases.size() || use_scripted_forces) { cvm::log(cvm::line_marker); cvm::log("Collective variables biases initialized, "+ cvm::to_str(biases.size())+" in total.\n"); } else { if (!use_scripted_forces) { cvm::log("No collective variables biases were defined.\n"); } } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } int colvarmodule::num_biases_feature(int feature_id) { colvarmodule *cv = cvm::main(); size_t n = 0; for (std::vector::iterator bi = cv->biases.begin(); bi != cv->biases.end(); bi++) { if ((*bi)->is_enabled(feature_id)) { n++; } } return n; } int colvarmodule::num_biases_type(std::string const &type) { colvarmodule *cv = cvm::main(); size_t n = 0; for (std::vector::iterator bi = cv->biases.begin(); bi != cv->biases.end(); bi++) { if ((*bi)->bias_type == type) { n++; } } return n; } int colvarmodule::catch_input_errors(int result) { if (result != COLVARS_OK || get_error()) { set_error_bits(result); set_error_bits(INPUT_ERROR); parse->init(); return get_error(); } return COLVARS_OK; } -colvarbias * colvarmodule::bias_by_name(std::string const &name) { +colvarbias * colvarmodule::bias_by_name(std::string const &name) +{ colvarmodule *cv = cvm::main(); for (std::vector::iterator bi = cv->biases.begin(); bi != cv->biases.end(); bi++) { if ((*bi)->name == name) { return (*bi); } } return NULL; } -colvar *colvarmodule::colvar_by_name(std::string const &name) { +colvar *colvarmodule::colvar_by_name(std::string const &name) +{ colvarmodule *cv = cvm::main(); for (std::vector::iterator cvi = cv->colvars.begin(); cvi != cv->colvars.end(); cvi++) { if ((*cvi)->name == name) { return (*cvi); } } return NULL; } +cvm::atom_group *colvarmodule::atom_group_by_name(std::string const &name) +{ + colvarmodule *cv = cvm::main(); + for (std::vector::iterator agi = cv->named_atom_groups.begin(); + agi != cv->named_atom_groups.end(); + agi++) { + if ((*agi)->name == name) { + return (*agi); + } + } + return NULL; +} + + int colvarmodule::change_configuration(std::string const &bias_name, std::string const &conf) { // This is deprecated; supported strategy is to delete the bias // and parse the new config cvm::increase_depth(); colvarbias *b; b = bias_by_name(bias_name); - if (b == NULL) { cvm::error("Error: bias not found: " + bias_name); } + if (b == NULL) { + cvm::error("Error: bias not found: " + bias_name); + return COLVARS_ERROR; + } b->change_configuration(conf); cvm::decrease_depth(); return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } std::string colvarmodule::read_colvar(std::string const &name) { cvm::increase_depth(); colvar *c; std::stringstream ss; c = colvar_by_name(name); - if (c == NULL) { cvm::fatal_error("Error: colvar not found: " + name); } + if (c == NULL) { + cvm::error("Error: colvar not found: " + name); + return std::string(); + } ss << c->value(); cvm::decrease_depth(); return ss.str(); } cvm::real colvarmodule::energy_difference(std::string const &bias_name, std::string const &conf) { cvm::increase_depth(); colvarbias *b; cvm::real energy_diff = 0.; b = bias_by_name(bias_name); - if (b == NULL) { cvm::fatal_error("Error: bias not found: " + bias_name); } + if (b == NULL) { + cvm::error("Error: bias not found: " + bias_name); + return 0.; + } energy_diff = b->energy_difference(conf); cvm::decrease_depth(); return energy_diff; } int colvarmodule::bias_current_bin(std::string const &bias_name) { cvm::increase_depth(); int ret; colvarbias *b = bias_by_name(bias_name); if (b != NULL) { ret = b->current_bin(); } else { cvm::error("Error: bias not found.\n"); ret = COLVARS_ERROR; } cvm::decrease_depth(); return ret; } int colvarmodule::bias_bin_num(std::string const &bias_name) { cvm::increase_depth(); int ret; colvarbias *b = bias_by_name(bias_name); if (b != NULL) { ret = b->bin_num(); } else { cvm::error("Error: bias not found.\n"); ret = COLVARS_ERROR; } cvm::decrease_depth(); return ret; } int colvarmodule::bias_bin_count(std::string const &bias_name, size_t bin_index) { cvm::increase_depth(); int ret; colvarbias *b = bias_by_name(bias_name); if (b != NULL) { ret = b->bin_count(bin_index); } else { cvm::error("Error: bias not found.\n"); ret = COLVARS_ERROR; } cvm::decrease_depth(); return ret; } int colvarmodule::bias_share(std::string const &bias_name) { cvm::increase_depth(); int ret; colvarbias *b = bias_by_name(bias_name); if (b != NULL) { b->replica_share(); ret = COLVARS_OK; } else { cvm::error("Error: bias not found.\n"); ret = COLVARS_ERROR; } cvm::decrease_depth(); return ret; } int colvarmodule::calc() { int error_code = COLVARS_OK; if (cvm::debug()) { cvm::log(cvm::line_marker); cvm::log("Collective variables module, step no. "+ cvm::to_str(cvm::step_absolute())+"\n"); } error_code |= calc_colvars(); // set biasing forces to zero before biases are calculated and summed over for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); cvi++) { (*cvi)->reset_bias_force(); } error_code |= calc_biases(); error_code |= update_colvar_forces(); if (cvm::b_analysis) { error_code |= analyze(); } // write trajectory files, if needed if (cv_traj_freq && cv_traj_name.size()) { error_code |= write_traj_files(); } // write restart files, if needed if (restart_out_freq && restart_out_name.size()) { error_code |= write_restart_files(); } return error_code; } int colvarmodule::calc_colvars() { if (cvm::debug()) cvm::log("Calculating collective variables.\n"); // calculate collective variables and their gradients + // First, we need to decide which biases are awake + // so they can activate colvars as needed + std::vector::iterator bi; + for (bi = biases.begin(); bi != biases.end(); bi++) { + int tsf = (*bi)->get_time_step_factor(); + if (tsf > 0 && (step_absolute() % tsf == 0)) { + (*bi)->enable(colvardeps::f_cvb_awake); + } else { + (*bi)->disable(colvardeps::f_cvb_awake); + } + } + int error_code = COLVARS_OK; std::vector::iterator cvi; // Determine which colvars are active at this iteration - variables_active()->resize(0); + variables_active()->clear(); variables_active()->reserve(variables()->size()); for (cvi = variables()->begin(); cvi != variables()->end(); cvi++) { - // This is a dynamic feature - the next call should be to enable() - // or disable() when dynamic dependency resolution is fully implemented - (*cvi)->set_enabled(colvardeps::f_cv_active, - step_absolute() % (*cvi)->get_time_step_factor() == 0); - variables_active()->push_back(*cvi); + // Wake up or put to sleep variables + int tsf = (*cvi)->get_time_step_factor(); + if (tsf > 0 && (step_absolute() % tsf == 0)) { + (*cvi)->enable(colvardeps::f_cv_awake); + } else { + (*cvi)->disable(colvardeps::f_cv_awake); + } + + if ((*cvi)->is_enabled()) { + variables_active()->push_back(*cvi); + } } // if SMP support is available, split up the work if (proxy->smp_enabled() == COLVARS_OK) { // first, calculate how much work (currently, how many active CVCs) each colvar has - variables_active_smp()->resize(0); - variables_active_smp_items()->resize(0); + variables_active_smp()->clear(); + variables_active_smp_items()->clear(); variables_active_smp()->reserve(variables_active()->size()); variables_active_smp_items()->reserve(variables_active()->size()); // set up a vector containing all components cvm::increase_depth(); for (cvi = variables_active()->begin(); cvi != variables_active()->end(); cvi++) { error_code |= (*cvi)->update_cvc_flags(); size_t num_items = (*cvi)->num_active_cvcs(); variables_active_smp()->reserve(variables_active_smp()->size() + num_items); variables_active_smp_items()->reserve(variables_active_smp_items()->size() + num_items); for (size_t icvc = 0; icvc < num_items; icvc++) { variables_active_smp()->push_back(*cvi); variables_active_smp_items()->push_back(icvc); } } cvm::decrease_depth(); // calculate colvar components in parallel error_code |= proxy->smp_colvars_loop(); cvm::increase_depth(); for (cvi = variables_active()->begin(); cvi != variables_active()->end(); cvi++) { error_code |= (*cvi)->collect_cvc_data(); } cvm::decrease_depth(); } else { // calculate colvars one at a time cvm::increase_depth(); for (cvi = variables_active()->begin(); cvi != variables_active()->end(); cvi++) { error_code |= (*cvi)->calc(); if (cvm::get_error()) { return COLVARS_ERROR; } } cvm::decrease_depth(); } error_code |= cvm::get_error(); return error_code; } int colvarmodule::calc_biases() { // update the biases and communicate their forces to the collective // variables if (cvm::debug() && biases.size()) cvm::log("Updating collective variable biases.\n"); std::vector::iterator bi; int error_code = COLVARS_OK; // Total bias energy is reset before calling scripted biases total_bias_energy = 0.0; // update the list of active biases - biases_active()->resize(0); + // which may have changed based on f_cvb_awake in calc_colvars() + biases_active()->clear(); biases_active()->reserve(biases.size()); for (bi = biases.begin(); bi != biases.end(); bi++) { if ((*bi)->is_enabled()) { biases_active()->push_back(*bi); } } // if SMP support is available, split up the work if (proxy->smp_enabled() == COLVARS_OK) { if (use_scripted_forces && !scripting_after_biases) { // calculate biases and scripted forces in parallel error_code |= proxy->smp_biases_script_loop(); } else { // calculate biases in parallel error_code |= proxy->smp_biases_loop(); } } else { if (use_scripted_forces && !scripting_after_biases) { error_code |= calc_scripted_forces(); } cvm::increase_depth(); for (bi = biases_active()->begin(); bi != biases_active()->end(); bi++) { error_code |= (*bi)->update(); if (cvm::get_error()) { return error_code; } } cvm::decrease_depth(); } for (bi = biases_active()->begin(); bi != biases_active()->end(); bi++) { total_bias_energy += (*bi)->get_energy(); } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } int colvarmodule::update_colvar_forces() { int error_code = COLVARS_OK; std::vector::iterator cvi; std::vector::iterator bi; // sum the forces from all biases for each collective variable if (cvm::debug() && biases.size()) cvm::log("Collecting forces from all biases.\n"); cvm::increase_depth(); for (bi = biases_active()->begin(); bi != biases_active()->end(); bi++) { (*bi)->communicate_forces(); if (cvm::get_error()) { return COLVARS_ERROR; } } cvm::decrease_depth(); if (use_scripted_forces && scripting_after_biases) { error_code |= calc_scripted_forces(); } // Now we have collected energies from both built-in and scripted biases if (cvm::debug()) cvm::log("Adding total bias energy: " + cvm::to_str(total_bias_energy) + "\n"); proxy->add_energy(total_bias_energy); cvm::real total_colvar_energy = 0.0; // sum up the forces for each colvar, including wall forces // and integrate any internal // equation of motion (extended system) if (cvm::debug()) cvm::log("Updating the internal degrees of freedom " "of colvars (if they have any).\n"); cvm::increase_depth(); for (cvi = variables()->begin(); cvi != variables()->end(); cvi++) { - // Here we call even inactive colvars, so they accumulate biasing forces - // as well as update their extended-system dynamics + // Inactive colvars will only reset their forces and return 0 energy total_colvar_energy += (*cvi)->update_forces_energy(); if (cvm::get_error()) { return COLVARS_ERROR; } } cvm::decrease_depth(); if (cvm::debug()) cvm::log("Adding total colvar energy: " + cvm::to_str(total_colvar_energy) + "\n"); proxy->add_energy(total_colvar_energy); // make collective variables communicate their forces to their // coupled degrees of freedom (i.e. atoms) if (cvm::debug()) cvm::log("Communicating forces from the colvars to the atoms.\n"); cvm::increase_depth(); for (cvi = variables_active()->begin(); cvi != variables_active()->end(); cvi++) { if ((*cvi)->is_enabled(colvardeps::f_cv_gradient)) { (*cvi)->communicate_forces(); if (cvm::get_error()) { return COLVARS_ERROR; } } } cvm::decrease_depth(); return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } int colvarmodule::calc_scripted_forces() { // Run user force script, if provided, // potentially adding scripted forces to the colvars int res; res = proxy->run_force_callback(); if (res == COLVARS_NOT_IMPLEMENTED) { cvm::error("Colvar forces scripts are not implemented."); return COLVARS_NOT_IMPLEMENTED; } if (res != COLVARS_OK) { cvm::error("Error running user colvar forces script"); return COLVARS_ERROR; } return COLVARS_OK; } int colvarmodule::write_restart_files() { if ( (cvm::step_relative() > 0) && ((cvm::step_absolute() % restart_out_freq) == 0) ) { cvm::log("Writing the state file \""+ restart_out_name+"\".\n"); - proxy->backup_file(restart_out_name.c_str()); - restart_out_os.open(restart_out_name.c_str()); - if (!restart_out_os.is_open() || !write_restart(restart_out_os)) - cvm::error("Error: in writing restart file.\n"); - restart_out_os.close(); + proxy->backup_file(restart_out_name); + std::ostream *restart_out_os = proxy->output_stream(restart_out_name); + if (!restart_out_os) return cvm::get_error(); + if (!write_restart(*restart_out_os)) { + return cvm::error("Error: in writing restart file.\n", FILE_ERROR); + } + proxy->close_output_stream(restart_out_name); } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } int colvarmodule::write_traj_files() { - if (!cv_traj_os.is_open()) { + if (cv_traj_os == NULL) { open_traj_file(cv_traj_name); } // write labels in the traj file every 1000 lines and at first timestep if ((cvm::step_absolute() % (cv_traj_freq * 1000)) == 0 || cvm::step_relative() == 0) { - write_traj_label(cv_traj_os); + write_traj_label(*cv_traj_os); } if ((cvm::step_absolute() % cv_traj_freq) == 0) { - write_traj(cv_traj_os); + write_traj(*cv_traj_os); } - if (restart_out_freq && cv_traj_os.is_open()) { + if (restart_out_freq && (cv_traj_os != NULL)) { // flush the trajectory file if we are at the restart frequency if ( (cvm::step_relative() > 0) && ((cvm::step_absolute() % restart_out_freq) == 0) ) { cvm::log("Synchronizing (emptying the buffer of) trajectory file \""+ cv_traj_name+"\".\n"); - cv_traj_os.flush(); + proxy->flush_output_stream(cv_traj_os); } } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } int colvarmodule::analyze() { if (cvm::debug()) { cvm::log("colvarmodule::analyze(), step = "+cvm::to_str(it)+".\n"); } if (cvm::step_relative() == 0) cvm::log("Performing analysis.\n"); // perform colvar-specific analysis for (std::vector::iterator cvi = variables_active()->begin(); cvi != variables_active()->end(); cvi++) { cvm::increase_depth(); (*cvi)->analyze(); cvm::decrease_depth(); } // perform bias-specific analysis for (std::vector::iterator bi = biases.begin(); bi != biases.end(); bi++) { cvm::increase_depth(); (*bi)->analyze(); cvm::decrease_depth(); } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } int colvarmodule::setup() { if (this->size() == 0) return cvm::get_error(); // loop over all components of all colvars to reset masses of all groups for (std::vector::iterator cvi = variables()->begin(); cvi != variables()->end(); cvi++) { (*cvi)->setup(); } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } colvarmodule::~colvarmodule() { if ((proxy->smp_thread_id() == COLVARS_NOT_IMPLEMENTED) || (proxy->smp_thread_id() == 0)) { reset(); delete parse; parse = NULL; proxy = NULL; } } int colvarmodule::reset() { parse->init(); cvm::log("Resetting the Collective Variables Module.\n"); // 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 } biases.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 } colvars.clear(); index_groups.clear(); index_group_names.clear(); - if (cv_traj_os.is_open()) { + proxy->reset(); + + if (cv_traj_os != NULL) { // Do not close file here, as we might not be done with it yet. - cv_traj_os.flush(); + proxy->flush_output_stream(cv_traj_os); } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } int colvarmodule::setup_input() { if (this->size() == 0) return cvm::get_error(); std::string restart_in_name(""); // read the restart configuration, if available if (proxy->input_prefix().size()) { // read the restart file restart_in_name = proxy->input_prefix(); std::ifstream input_is(restart_in_name.c_str()); if (!input_is.good()) { // try by adding the suffix input_is.clear(); restart_in_name = restart_in_name+std::string(".colvars.state"); input_is.open(restart_in_name.c_str()); } if (!input_is.good()) { cvm::error("Error: in opening input file \""+ std::string(restart_in_name)+"\".\n", FILE_ERROR); return COLVARS_ERROR; } else { cvm::log(cvm::line_marker); cvm::log("Restarting from file \""+restart_in_name+"\".\n"); read_restart(input_is); if (cvm::get_error() != COLVARS_OK) { return COLVARS_ERROR; } else { proxy->input_prefix().clear(); } cvm::log(cvm::line_marker); } } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } int colvarmodule::setup_output() { if (this->size() == 0) return cvm::get_error(); int error_code = COLVARS_OK; // output state file (restart) restart_out_name = proxy->restart_output_prefix().size() ? std::string(proxy->restart_output_prefix()+".colvars.state") : std::string(""); if (restart_out_name.size()) { cvm::log("The restart output state file 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); } cv_traj_name = (output_prefix().size() ? std::string(output_prefix()+".colvars.traj") : std::string("")); if (cv_traj_freq && cv_traj_name.size()) { error_code |= open_traj_file(cv_traj_name); } 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(FILE_ERROR); } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } std::istream & colvarmodule::read_restart(std::istream &is) { bool warn_total_forces = false; { // read global restart information std::string restart_conf; if (is >> colvarparse::read_block("configuration", restart_conf)) { if (it_restart_from_state_file) { parse->get_keyval(restart_conf, "step", it_restart, (size_t) 0, colvarparse::parse_silent); it = it_restart; } std::string restart_version; parse->get_keyval(restart_conf, "version", restart_version, std::string(""), colvarparse::parse_silent); if (restart_version.size() && (restart_version != std::string(COLVARS_VERSION))) { cvm::log("This state file was generated with version "+restart_version+"\n"); } if ((restart_version.size() == 0) || (restart_version.compare(std::string(COLVARS_VERSION)) < 0)) { // check for total force change if (proxy->total_forces_enabled()) { warn_total_forces = true; } } } is.clear(); parse->clear_keyword_registry(); } // colvars restart cvm::increase_depth(); for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); cvi++) { if ( !((*cvi)->read_restart(is)) ) { cvm::error("Error: in reading restart configuration for collective variable \""+ (*cvi)->name+"\".\n", INPUT_ERROR); } } // biases restart for (std::vector::iterator bi = biases.begin(); bi != biases.end(); bi++) { if (!((*bi)->read_state(is))) { cvm::error("Error: in reading restart configuration for bias \""+ (*bi)->name+"\".\n", INPUT_ERROR); } } cvm::decrease_depth(); if (warn_total_forces) { cvm::log(cvm::line_marker); cvm::log("WARNING:\n"); std::string const warning("### CHANGES IN THE DEFINITION OF SYSTEM FORCES (NOW TOTAL FORCES)\n\ \n\ Starting from the version 2016-08-10 of the Colvars module, \n\ the role of system forces has been replaced by total forces.\n\ \n\ These include *all* forces acting on a collective variable, whether they\n\ come from the force field potential or from external terms\n\ (e.g. restraints), including forces applied by Colvars itself.\n\ \n\ In NAMD, forces applied by Colvars, IMD, SMD, TMD, symmetry\n\ restraints and tclForces are now all counted in the total force.\n\ \n\ In LAMMPS, forces applied by Colvars itself are now counted in the total\n\ force (all forces from other fixes were being counted already).\n\ \n\ \n\ ### WHEN IS THIS CHANGE RELEVANT\n\ \n\ This change affects results *only* when (1) outputSystemForce is\n\ requested or (2) the ABF bias is used. All other usage cases are\n\ *unaffected* (colvar restraints, metadynamics, etc).\n\ \n\ When system forces are reported (flag: outputSystemForce), their values\n\ in the output may change, but the physical trajectory is never affected.\n\ The physical results of ABF calculations may be affected in some cases.\n\ \n\ \n\ ### CHANGES TO ABF CALCULATIONS\n\ \n\ Compared to previous Colvars versions, the ABF method will now attempt\n\ to cancel external forces (for example, boundary walls) and it may be\n\ not possible to resume through a state file a simulation that was\n\ performed with a previous version.\n\ \n\ There are three possible scenarios:\n\ \n\ 1. No external forces are applied to the atoms used by ABF: results are\n\ unchanged.\n\ \n\ 2. Some of the atoms used by ABF experience external forces, but these\n\ forces are not applied directly to the variables used by ABF\n\ (e.g. another colvar that uses the same atoms, tclForces, etc): in this\n\ case, we recommend beginning a new simulation.\n\ \n\ 3. External forces are applied to one or more of the colvars used by\n\ ABF, but no other forces are applied to their atoms: you may use the\n\ subtractAppliedForce keyword inside the corresponding colvars to\n\ continue the previous simulation.\n\n"); cvm::log(warning); cvm::log(cvm::line_marker); // update this ahead of time in this special case output_prefix() = proxy->input_prefix(); cvm::log("All output files will now be saved with the prefix \""+output_prefix()+".tmp.*\".\n"); cvm::log(cvm::line_marker); cvm::log("Please review the important warning above. After that, you may rename:\n\ \""+output_prefix()+".tmp.colvars.state\"\n\ to:\n\ \""+ proxy->input_prefix()+".colvars.state\"\n"); output_prefix() = output_prefix()+".tmp"; write_output_files(); cvm::error("Exiting with error until issue is addressed.\n", FATAL_ERROR); } return is; } int colvarmodule::backup_file(char const *filename) { return proxy->backup_file(filename); } int colvarmodule::write_output_files() { // if this is a simulation run (i.e. not a postprocessing), output data // must be written to be able to restart the simulation std::string const out_name = (output_prefix().size() ? std::string(output_prefix()+".colvars.state") : std::string("colvars.state")); cvm::log("Saving collective variables state to \""+out_name+"\".\n"); std::ostream * os = proxy->output_stream(out_name); os->setf(std::ios::scientific, std::ios::floatfield); this->write_restart(*os); proxy->close_output_stream(out_name); cvm::increase_depth(); for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); cvi++) { (*cvi)->write_output_files(); } cvm::decrease_depth(); cvm::increase_depth(); for (std::vector::iterator bi = biases.begin(); bi != biases.end(); bi++) { (*bi)->write_output_files(); (*bi)->write_state_to_replicas(); } cvm::decrease_depth(); - if (cv_traj_os.is_open()) { - // do not close to avoid problems with multiple NAMD runs - cv_traj_os.flush(); + if (cv_traj_os != NULL) { + // do not close, there may be another run command + proxy->flush_output_stream(cv_traj_os); } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } int colvarmodule::read_traj(char const *traj_filename, long traj_read_begin, long traj_read_end) { cvm::log("Opening trajectory file \""+ std::string(traj_filename)+"\".\n"); std::ifstream traj_is(traj_filename); while (true) { while (true) { std::string line(""); do { if (!colvarparse::getline_nocomments(traj_is, line)) { cvm::log("End of file \""+std::string(traj_filename)+ "\" reached, or corrupted file.\n"); traj_is.close(); return false; } } while (line.find_first_not_of(colvarparse::white_space) == std::string::npos); std::istringstream is(line); if (!(is >> it)) return false; if ( (it < traj_read_begin) ) { if ((it % 1000) == 0) std::cerr << "Skipping trajectory step " << it << " \r"; continue; } else { if ((it % 1000) == 0) std::cerr << "Reading from trajectory, step = " << it << " \r"; if ( (traj_read_end > traj_read_begin) && (it > traj_read_end) ) { std::cerr << "\n"; cvm::error("Reached the end of the trajectory, " "read_end = "+cvm::to_str(traj_read_end)+"\n", FILE_ERROR); return COLVARS_ERROR; } for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); cvi++) { if (!(*cvi)->read_traj(is)) { cvm::error("Error: in reading colvar \""+(*cvi)->name+ "\" from trajectory file \""+ std::string(traj_filename)+"\".\n", FILE_ERROR); return COLVARS_ERROR; } } break; } } } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } std::ostream & colvarmodule::write_restart(std::ostream &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" << "}\n\n"; int error_code = COLVARS_OK; cvm::increase_depth(); for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); cvi++) { (*cvi)->write_restart(os); error_code |= (*cvi)->write_output_files(); } for (std::vector::iterator bi = biases.begin(); bi != biases.end(); bi++) { (*bi)->write_state(os); error_code |= (*bi)->write_state_to_replicas(); error_code |= (*bi)->write_output_files(); } cvm::decrease_depth(); if (error_code != COLVARS_OK) { // TODO make this function return an int instead os.setstate(std::ios::failbit); } return os; } + int colvarmodule::open_traj_file(std::string const &file_name) { - if (cv_traj_os.is_open()) { + if (cv_traj_os != NULL) { return COLVARS_OK; } // (re)open trajectory file if (cv_traj_append) { cvm::log("Appending to colvar trajectory file \""+file_name+ "\".\n"); - cv_traj_os.open(file_name.c_str(), std::ios::app); + cv_traj_os = (cvm::proxy)->output_stream(file_name, std::ios::app); } else { cvm::log("Writing to colvar trajectory file \""+file_name+ "\".\n"); proxy->backup_file(file_name.c_str()); - cv_traj_os.open(file_name.c_str()); + cv_traj_os = (cvm::proxy)->output_stream(file_name); } - if (!cv_traj_os.is_open()) { + if (cv_traj_os == NULL) { cvm::error("Error: cannot write to file \""+file_name+"\".\n", FILE_ERROR); } - return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); + return cvm::get_error(); } + int colvarmodule::close_traj_file() { - if (cv_traj_os.is_open()) { - cv_traj_os.close(); + if (cv_traj_os != NULL) { + proxy->close_output_stream(cv_traj_name); + cv_traj_os = NULL; } - return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); + return cvm::get_error(); } + std::ostream & colvarmodule::write_traj_label(std::ostream &os) { - if (!os.good()) { - cvm::error("Cannot write to trajectory file."); - return os; - } os.setf(std::ios::scientific, std::ios::floatfield); os << "# " << cvm::wrap_string("step", cvm::it_width-2) << " "; cvm::increase_depth(); for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); cvi++) { (*cvi)->write_traj_label(os); } for (std::vector::iterator bi = biases.begin(); bi != biases.end(); bi++) { (*bi)->write_traj_label(os); } os << "\n"; + if (cvm::debug()) { - os.flush(); + proxy->flush_output_stream(&os); } + cvm::decrease_depth(); return os; } + std::ostream & colvarmodule::write_traj(std::ostream &os) { os.setf(std::ios::scientific, std::ios::floatfield); os << std::setw(cvm::it_width) << it << " "; cvm::increase_depth(); for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); cvi++) { (*cvi)->write_traj(os); } for (std::vector::iterator bi = biases.begin(); bi != biases.end(); bi++) { (*bi)->write_traj(os); } os << "\n"; + if (cvm::debug()) { - os.flush(); + proxy->flush_output_stream(&os); } + cvm::decrease_depth(); return os; } void cvm::log(std::string const &message) { // allow logging when the module is not fully initialized size_t const d = (cvm::main() != NULL) ? depth() : 0; if (d > 0) proxy->log((std::string(2*d, ' '))+message); else proxy->log(message); } void cvm::increase_depth() { (depth())++; } void cvm::decrease_depth() { if (depth() > 0) { (depth())--; } } size_t & cvm::depth() { // NOTE: do not call log() or error() here, to avoid recursion colvarmodule *cv = cvm::main(); if (proxy->smp_enabled() == COLVARS_OK) { int const nt = proxy->smp_num_threads(); if (int(cv->depth_v.size()) != nt) { proxy->smp_lock(); // update array of depths if (cv->depth_v.size() > 0) { cv->depth_s = cv->depth_v[0]; } cv->depth_v.clear(); cv->depth_v.assign(nt, cv->depth_s); proxy->smp_unlock(); } return cv->depth_v[proxy->smp_thread_id()]; } return cv->depth_s; } void colvarmodule::set_error_bits(int code) { if (code < 0) { cvm::fatal_error("Error: set_error_bits() received negative error code.\n"); return; } proxy->smp_lock(); errorCode |= code | COLVARS_ERROR; proxy->smp_unlock(); } bool colvarmodule::get_error_bit(int code) { return bool(errorCode & code); } void colvarmodule::clear_error() { proxy->smp_lock(); errorCode = COLVARS_OK; proxy->smp_unlock(); } -void cvm::error(std::string const &message, int code) +int colvarmodule::error(std::string const &message, int code) { set_error_bits(code); proxy->error(message); + return get_error(); } -void cvm::fatal_error(std::string const &message) +int colvarmodule::fatal_error(std::string const &message) { - // TODO once all non-fatal errors have been set to be handled by error(), - // set DELETE_COLVARS here for VMD to handle it set_error_bits(FATAL_ERROR); proxy->fatal_error(message); -} - - -void cvm::exit(std::string const &message) -{ - proxy->exit(message); + return get_error(); } int cvm::read_index_file(char const *filename) { std::ifstream is(filename, std::ios::binary); if (!is.good()) { cvm::error("Error: in opening index file \""+ std::string(filename)+"\".\n", FILE_ERROR); } while (is.good()) { char open, close; std::string group_name; if ( (is >> open) && (open == '[') && (is >> group_name) && (is >> close) && (close == ']') ) { for (std::list::iterator names_i = index_group_names.begin(); names_i != index_group_names.end(); names_i++) { if (*names_i == group_name) { cvm::error("Error: the group name \""+group_name+ "\" appears in multiple index files.\n", FILE_ERROR); } } index_group_names.push_back(group_name); index_groups.push_back(std::vector()); } else { cvm::error("Error: in parsing index file \""+ std::string(filename)+"\".\n", INPUT_ERROR); } int atom_number = 1; size_t pos = is.tellg(); while ( (is >> atom_number) && (atom_number > 0) ) { (index_groups.back()).push_back(atom_number); pos = is.tellg(); } is.clear(); is.seekg(pos, std::ios::beg); std::string delim; if ( (is >> delim) && (delim == "[") ) { // new group is.clear(); is.seekg(pos, std::ios::beg); } else { break; } } cvm::log("The following index groups were read from the index file \""+ std::string(filename)+"\":\n"); std::list::iterator names_i = index_group_names.begin(); std::list >::iterator lists_i = index_groups.begin(); for ( ; names_i != index_group_names.end() ; names_i++, lists_i++) { cvm::log(" "+(*names_i)+" ("+cvm::to_str(lists_i->size())+" atoms).\n"); } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } int cvm::load_atoms(char const *file_name, cvm::atom_group &atoms, std::string const &pdb_field, double const 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, const std::vector &indices, std::string const &pdb_field, double const pdb_field_value) { // Differentiate between PDB and XYZ files // for XYZ files, use CVM internal parser // otherwise call proxy function for PDB std::string const ext(strlen(file_name) > 4 ? (file_name + (strlen(file_name) - 4)) : file_name); if (colvarparse::to_lower_cppstr(ext) == std::string(".xyz")) { if ( pdb_field.size() > 0 ) { cvm::error("Error: PDB column may not be specified for XYZ coordinate file.\n", INPUT_ERROR); return COLVARS_ERROR; } return cvm::load_coords_xyz(file_name, pos, indices); } else { return proxy->load_coords(file_name, pos, indices, pdb_field, pdb_field_value); } } int cvm::load_coords_xyz(char const *filename, std::vector &pos, const std::vector &indices) { std::ifstream xyz_is(filename); unsigned int natoms; char symbol[256]; std::string line; if ( ! (xyz_is >> natoms) ) { cvm::error("Error: cannot parse XYZ file " + std::string(filename) + ".\n", INPUT_ERROR); } // skip comment line cvm::getline(xyz_is, line); cvm::getline(xyz_is, line); xyz_is.width(255); std::vector::iterator pos_i = pos.begin(); if (pos.size() != natoms) { // Use specified indices int next = 0; // indices are zero-based std::vector::const_iterator index = indices.begin(); for ( ; pos_i != pos.end() ; pos_i++, index++) { while ( next < *index ) { cvm::getline(xyz_is, line); next++; } xyz_is >> symbol; xyz_is >> (*pos_i)[0] >> (*pos_i)[1] >> (*pos_i)[2]; } } else { // Use all positions for ( ; pos_i != pos.end() ; pos_i++) { xyz_is >> symbol; xyz_is >> (*pos_i)[0] >> (*pos_i)[1] >> (*pos_i)[2]; } } return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); } // shared pointer to the proxy object colvarproxy *colvarmodule::proxy = NULL; // static runtime data cvm::real colvarmodule::debug_gradients_step_size = 1.0e-07; int colvarmodule::errorCode = 0; long colvarmodule::it = 0; long colvarmodule::it_restart = 0; size_t colvarmodule::restart_out_freq = 0; size_t colvarmodule::cv_traj_freq = 0; bool colvarmodule::b_analysis = false; bool colvarmodule::use_scripted_forces = false; bool colvarmodule::scripting_after_biases = true; // i/o constants size_t const colvarmodule::it_width = 12; size_t const colvarmodule::cv_prec = 14; size_t const colvarmodule::cv_width = 21; size_t const colvarmodule::en_prec = 14; size_t const colvarmodule::en_width = 21; const char * const colvarmodule::line_marker = (const char *) "----------------------------------------------------------------------\n"; diff --git a/lib/colvars/colvarmodule.h b/lib/colvars/colvarmodule.h index b4f80e0b5..0f6efd14c 100644 --- a/lib/colvars/colvarmodule.h +++ b/lib/colvars/colvarmodule.h @@ -1,736 +1,702 @@ // -*- 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 COLVARMODULE_H #define COLVARMODULE_H -#ifndef COLVARS_VERSION -#define COLVARS_VERSION "2017-03-09" -#endif +#include "colvars_version.h" #ifndef COLVARS_DEBUG #define COLVARS_DEBUG false #endif /*! \mainpage Main page This is the Developer's documentation for the Collective Variables Module. You can browse the class hierarchy or the list of source files. */ /// \file colvarmodule.h /// \brief Collective variables main module /// /// This file declares the main class for defining and manipulating /// collective variables: there should be only one instance of this /// class, because several variables are made static (i.e. they are /// 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 INPUT_ERROR (1<<2) // out of bounds or inconsistent input #define BUG_ERROR (1<<3) // Inconsistent state indicating bug #define FILE_ERROR (1<<4) #define MEMORY_ERROR (1<<5) #define FATAL_ERROR (1<<6) // Should be set, or not, together with other bits #define DELETE_COLVARS (1<<7) // Instruct the caller to delete cvm #define COLVARS_NO_SUCH_FRAME (1<<8) // Cannot load the requested frame #include #include #include #include #include #include #include #include #include -#ifdef NAMD_VERSION -// use Lustre-friendly wrapper to POSIX write() -#include "fstream_namd.h" -#endif - class colvarparse; class colvar; class colvarbias; class colvarproxy; class colvarscript; /// \brief Collective variables module (main class) /// /// Class to control the collective variables calculation. An object /// (usually one) of this class is spawned from the MD program, /// containing all i/o routines and general interface. /// /// At initialization, the colvarmodule object creates a proxy object /// to provide a transparent interface between the MD program and the /// child objects class colvarmodule { private: /// Impossible to initialize the main object without arguments colvarmodule(); public: friend class colvarproxy; // TODO colvarscript should be unaware of colvarmodule's internals friend class colvarscript; /// Defining an abstract real number allows to switch precision typedef double real; /// Residue identifier typedef int residue_id; class rvector; template class vector1d; template class matrix2d; class quaternion; class rotation; /// \brief Atom position (different type name from rvector, to make /// possible future PBC-transparent implementations) typedef rvector atom_pos; /// \brief 3x3 matrix of real numbers class rmatrix; // 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; /// Module-wide error state /// see constants at the top of this file protected: static int errorCode; public: static void set_error_bits(int code); static bool get_error_bit(int code); static inline int get_error() { return errorCode; } static void clear_error(); /// Current step number static long it; /// Starting step number for this run static long it_restart; /// Return the current step number from the beginning of this run static inline long step_relative() { return it - it_restart; } /// Return the current step number from the beginning of the whole /// calculation static inline long step_absolute() { return it; } /// If true, get it_restart from the state file; if set to false, /// the MD program is providing it bool it_restart_from_state_file; /// \brief Finite difference step size (if there is no dynamics, or /// if gradients need to be tested independently from the size of /// dt) static real debug_gradients_step_size; private: /// Prefix for all output files for this run std::string cvm_output_prefix; public: /// Accessor for the above static inline std::string &output_prefix() { colvarmodule *cv = colvarmodule::main(); return cv->cvm_output_prefix; } private: /// Array of collective variables std::vector colvars; /// Array of collective variables std::vector colvars_active; /// Collective variables to be calculated on different threads; /// colvars with multple items (e.g. multiple active CVCs) are duplicated std::vector colvars_smp; /// Indexes of the items to calculate for each colvar std::vector colvars_smp_items; + /// Array of named atom groups + std::vector named_atom_groups; public: + /// Register a named atom group into named_atom_groups + inline void register_named_atom_group(atom_group * ag) { + named_atom_groups.push_back(ag); + } /// Array of collective variables std::vector *variables(); /* TODO: implement named CVCs /// Array of named (reusable) collective variable components static std::vector cvcs; /// Named cvcs register themselves at initialization time inline void register_cvc(cvc *p) { cvcs.push_back(p); } */ /// Collective variables with the active flag on std::vector *variables_active(); /// Collective variables to be calculated on different threads; /// colvars with multple items (e.g. multiple active CVCs) are duplicated std::vector *variables_active_smp(); /// Indexes of the items to calculate for each colvar std::vector *variables_active_smp_items(); /// Array of collective variable biases std::vector biases; /// Energy of built-in and scripted biases, summed per time-step real total_bias_energy; private: /// Array of active collective variable biases std::vector biases_active_; public: /// Array of active collective variable biases std::vector *biases_active(); /// \brief Whether debug output should be enabled (compile-time option) static inline bool debug() { return COLVARS_DEBUG; } /// \brief How many objects are configured yet? size_t size() const; /// \brief Constructor \param config_name Configuration file name /// \param restart_name (optional) Restart file name colvarmodule(colvarproxy *proxy); /// Destructor ~colvarmodule(); /// Actual function called by the destructor int reset(); /// Open a config file, load its contents, and pass it to config_string() int read_config_file(char const *config_file_name); /// \brief Parse a config string assuming it is a complete configuration /// (i.e. calling all parse functions) int read_config_string(std::string const &conf); /// \brief Parse a "clean" config string (no comments) int parse_config(std::string &conf); // Parse functions (setup internal data based on a string) /// Allow reading from Windows text files using using std::getline /// (which can still be used when the text is produced by Colvars itself) static std::istream & getline(std::istream &is, std::string &line); /// Parse the few module's global parameters int parse_global_params(std::string const &conf); /// Parse and initialize collective variables int parse_colvars(std::string const &conf); /// Parse and initialize collective variable biases int parse_biases(std::string const &conf); /// \brief Add new configuration during parsing (e.g. to implement /// back-compatibility); cannot be nested, i.e. conf should not contain /// anything that triggers another call int append_new_config(std::string const &conf); private: /// Auto-generated configuration during parsing (e.g. to implement /// back-compatibility) std::string extra_conf; /// Parse and initialize collective variable biases of a specific type template int parse_biases_type(std::string const &conf, char const *keyword); /// Test error condition and keyword parsing /// on error, delete new bias bool check_new_bias(std::string &conf, char const *key); public: /// Return how many biases have this feature enabled static int num_biases_feature(int feature_id); /// Return how many biases are defined with this type static int num_biases_type(std::string const &type); private: /// Useful wrapper to interrupt parsing if any error occurs int catch_input_errors(int result); public: // "Setup" functions (change internal data based on related data // from the proxy that may change during program execution) // No additional parsing is done within these functions /// (Re)initialize internal data (currently used by LAMMPS) /// Also calls setup() member functions of colvars and biases int setup(); /// (Re)initialize and (re)read the input state file calling read_restart() int setup_input(); /// (Re)initialize the output trajectory and state file (does not write it yet) int setup_output(); -#ifdef NAMD_VERSION - typedef ofstream_namd ofstream; -#else - typedef std::ofstream ofstream; -#endif - /// Read the input restart file std::istream & read_restart(std::istream &is); /// Write the output restart file std::ostream & write_restart(std::ostream &os); /// Open a trajectory file if requested (and leave it open) int open_traj_file(std::string const &file_name); - /// Close it + /// Close it (note: currently unused) int close_traj_file(); /// Write in the trajectory file std::ostream & write_traj(std::ostream &os); /// Write explanatory labels in the trajectory file std::ostream & write_traj_label(std::ostream &os); /// Write all trajectory files int write_traj_files(); /// Write all restart files int write_restart_files(); /// Write all FINAL output files int write_output_files(); /// Backup a file before writing it static int backup_file(char const *filename); /// Look up a bias by name; returns NULL if not found static colvarbias * bias_by_name(std::string const &name); /// Look up a colvar by name; returns NULL if not found static colvar * colvar_by_name(std::string const &name); + /// Look up a named atom group by name; returns NULL if not found + static atom_group * atom_group_by_name(std::string const &name); + /// Load new configuration for the given bias - /// currently works for harmonic (force constant and/or centers) int change_configuration(std::string const &bias_name, std::string const &conf); /// Read a colvar value std::string read_colvar(std::string const &name); /// Calculate change in energy from using alt. config. for the given bias - /// currently works for harmonic (force constant and/or centers) real energy_difference(std::string const &bias_name, std::string const &conf); /// Give the total number of bins for a given bias. int bias_bin_num(std::string const &bias_name); /// Calculate the bin index for a given bias. int bias_current_bin(std::string const &bias_name); //// Give the count at a given bin index. int bias_bin_count(std::string const &bias_name, size_t bin_index); //// Share among replicas. int bias_share(std::string const &bias_name); /// Main worker function int calc(); /// Calculate collective variables int calc_colvars(); /// Calculate biases int calc_biases(); /// Integrate bias and restraint forces, send colvar forces to atoms int update_colvar_forces(); /// Perform analysis int analyze(); /// \brief Read a collective variable trajectory (post-processing /// only, not called at runtime) int read_traj(char const *traj_filename, long traj_read_begin, long traj_read_end); /// Quick conversion of an object to a string template static std::string to_str(T const &x, size_t const &width = 0, size_t const &prec = 0); /// Quick conversion of a vector of objects to a string template static std::string to_str(std::vector const &x, size_t const &width = 0, size_t const &prec = 0); /// Reduce the number of characters in a string static inline std::string wrap_string(std::string const &s, size_t const &nchars) { if (!s.size()) return std::string(nchars, ' '); else return ( (s.size() <= size_t(nchars)) ? (s+std::string(nchars-s.size(), ' ')) : (std::string(s, 0, nchars)) ); } /// Number of characters to represent a time step static size_t const it_width; /// Number of digits to represent a collective variables value(s) static size_t const cv_prec; /// Number of characters to represent a collective variables value(s) static size_t const cv_width; /// Number of digits to represent the collective variables energy static size_t const en_prec; /// Number of characters to represent the collective variables energy static size_t const en_width; /// Line separator in the log output static const char * const line_marker; // proxy functions /// \brief Value of the unit for atomic coordinates with respect to /// angstroms (used by some variables for hard-coded default values) static real unit_angstrom(); /// \brief Boltmann constant static real boltzmann(); /// \brief Temperature of the simulation (K) static real temperature(); /// \brief Time step of MD integrator (fs) static real dt(); /// Request calculation of total force from MD engine static void request_total_force(); /// Print a message to the main log static void log(std::string const &message); /// Print a message to the main log and exit with error code - static void fatal_error(std::string const &message); + static int fatal_error(std::string const &message); /// Print a message to the main log and set global error code - static void error(std::string const &message, int code = COLVARS_ERROR); + static int error(std::string const &message, int code = COLVARS_ERROR); /// Print a message to the main log and exit normally static void exit(std::string const &message); // Replica exchange commands. static bool replica_enabled(); static int replica_index(); static int replica_num(); static void replica_comm_barrier(); static int replica_comm_recv(char* msg_data, int buf_len, int src_rep); static int replica_comm_send(char* msg_data, int msg_len, int dest_rep); /// \brief Get the distance between two atomic positions with pbcs handled /// correctly static rvector position_distance(atom_pos const &pos1, - atom_pos const &pos2); - + atom_pos const &pos2); /// \brief Get the square distance between two positions (with /// periodic boundary conditions handled transparently) /// /// Note: in the case of periodic boundary conditions, this provides /// an analytical square distance (while taking the square of /// position_distance() would produce leads to a cusp) static real position_dist2(atom_pos const &pos1, - atom_pos const &pos2); - - /// \brief Get the closest periodic image to a reference position - /// \param pos The position to look for the closest periodic image - /// \param ref_pos (optional) The reference position - static void select_closest_image(atom_pos &pos, - atom_pos const &ref_pos); - - /// \brief Perform select_closest_image() on a set of atomic positions - /// - /// After that, distance vectors can then be calculated directly, - /// without using position_distance() - static void select_closest_images(std::vector &pos, - atom_pos const &ref_pos); - + atom_pos const &pos2); /// \brief Names of groups from a Gromacs .ndx file to be read at startup std::list index_group_names; /// \brief Groups from a Gromacs .ndx file read at startup std::list > index_groups; /// \brief Read a Gromacs .ndx file int read_index_file(char const *filename); /// \brief Create atoms from a file \param filename name of the file /// (usually a PDB) \param atoms array of the atoms to be allocated /// \param pdb_field (optiona) if "filename" is a PDB file, use this /// field to determine which are the atoms to be set static int load_atoms(char const *filename, atom_group &atoms, std::string const &pdb_field, double const pdb_field_value = 0.0); /// \brief Load the coordinates for a group of atoms from a file /// (PDB or XYZ) static int load_coords(char const *filename, std::vector &pos, const std::vector &indices, std::string const &pdb_field, double const pdb_field_value = 0.0); /// \brief Load the coordinates for a group of atoms from an /// XYZ file static int load_coords_xyz(char const *filename, std::vector &pos, const std::vector &indices); /// Frequency for collective variables trajectory output static size_t cv_traj_freq; /// \brief True if only analysis is performed and not a run static bool b_analysis; /// Frequency for saving output restarts static size_t restart_out_freq; /// Output restart file name std::string restart_out_name; /// Pseudo-random number with Gaussian distribution static real rand_gaussian(void); protected: /// Configuration file std::ifstream config_s; /// Configuration file parser object colvarparse *parse; /// Name of the trajectory file std::string cv_traj_name; /// Collective variables output trajectory file - colvarmodule::ofstream cv_traj_os; + std::ostream *cv_traj_os; /// Appending to the existing trajectory file? bool cv_traj_append; - /// Output restart file - colvarmodule::ofstream restart_out_os; - private: /// Counter for the current depth in the object hierarchy (useg e.g. in output) size_t depth_s; /// Thread-specific depth std::vector depth_v; public: /// Get the current object depth in the hierarchy static size_t & depth(); /// Increase the depth (number of indentations in the output) static void increase_depth(); /// Decrease the depth (number of indentations in the output) static void decrease_depth(); static inline bool scripted_forces() { return use_scripted_forces; } /// Use scripted colvars forces? static bool use_scripted_forces; /// Wait for all biases before calculating scripted forces? static bool scripting_after_biases; /// Calculate the energy and forces of scripted biases int calc_scripted_forces(); /// \brief Pointer to the proxy object, used to retrieve atomic data /// from the hosting program; it is static in order to be accessible /// from static functions in the colvarmodule class static colvarproxy *proxy; /// \brief Accessor for the above static colvarmodule *main(); }; /// Shorthand for the frequently used type prefix typedef colvarmodule cvm; #include "colvartypes.h" std::ostream & operator << (std::ostream &os, cvm::rvector const &v); std::istream & operator >> (std::istream &is, cvm::rvector &v); template std::string cvm::to_str(T const &x, size_t const &width, size_t const &prec) { std::ostringstream os; if (width) os.width(width); if (prec) { os.setf(std::ios::scientific, std::ios::floatfield); os.precision(prec); } os << x; return os.str(); } template std::string cvm::to_str(std::vector const &x, size_t const &width, size_t const &prec) { if (!x.size()) return std::string(""); std::ostringstream os; if (prec) { os.setf(std::ios::scientific, std::ios::floatfield); } os << "{ "; if (width) os.width(width); if (prec) os.precision(prec); os << x[0]; for (size_t i = 1; i < x.size(); i++) { os << ", "; if (width) os.width(width); if (prec) os.precision(prec); os << x[i]; } os << " }"; return os.str(); } #include "colvarproxy.h" inline cvm::real cvm::unit_angstrom() { return proxy->unit_angstrom(); } inline cvm::real cvm::boltzmann() { return proxy->boltzmann(); } inline cvm::real cvm::temperature() { return proxy->temperature(); } inline cvm::real cvm::dt() { return proxy->dt(); } // Replica exchange commands inline bool cvm::replica_enabled() { return proxy->replica_enabled(); } inline int cvm::replica_index() { return proxy->replica_index(); } inline int cvm::replica_num() { return proxy->replica_num(); } inline void cvm::replica_comm_barrier() { return proxy->replica_comm_barrier(); } inline int cvm::replica_comm_recv(char* msg_data, int buf_len, int src_rep) { return proxy->replica_comm_recv(msg_data,buf_len,src_rep); } inline int cvm::replica_comm_send(char* msg_data, int msg_len, int dest_rep) { return proxy->replica_comm_send(msg_data,msg_len,dest_rep); } inline void cvm::request_total_force() { proxy->request_total_force(true); } -inline void cvm::select_closest_image(atom_pos &pos, - atom_pos const &ref_pos) -{ - proxy->select_closest_image(pos, ref_pos); -} - -inline void cvm::select_closest_images(std::vector &pos, - atom_pos const &ref_pos) -{ - proxy->select_closest_images(pos, ref_pos); -} - inline cvm::rvector cvm::position_distance(atom_pos const &pos1, atom_pos const &pos2) { return proxy->position_distance(pos1, pos2); } inline cvm::real cvm::position_dist2(cvm::atom_pos const &pos1, cvm::atom_pos const &pos2) { return proxy->position_dist2(pos1, pos2); } inline cvm::real cvm::rand_gaussian(void) { return proxy->rand_gaussian(); } #endif diff --git a/lib/colvars/colvarparse.cpp b/lib/colvars/colvarparse.cpp index 8055d925d..9f333b7b7 100644 --- a/lib/colvars/colvarparse.cpp +++ b/lib/colvars/colvarparse.cpp @@ -1,799 +1,798 @@ // -*- 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 "colvarmodule.h" #include "colvarvalue.h" #include "colvarparse.h" // space & tab -std::string const colvarparse::white_space = " \t"; - -std::string colvarparse::dummy_string = ""; -size_t colvarparse::dummy_pos = 0; +char const * const colvarparse::white_space = " \t"; // definition of single-value keyword parsers template bool colvarparse::_get_keyval_scalar_(std::string const &conf, char const *key, TYPE &value, TYPE const &def_value, Parse_Mode const parse_mode) { std::string data; bool b_found = false, b_found_any = false; size_t save_pos = 0, found_count = 0; do { std::string data_this = ""; - b_found = key_lookup(conf, key, data_this, save_pos); + b_found = key_lookup(conf, key, &data_this, &save_pos); if (b_found) { if (!b_found_any) b_found_any = true; found_count++; data = data_this; } } while (b_found); if (found_count > 1) cvm::log("Warning: found more than one instance of \""+ std::string(key)+"\".\n"); if (data.size()) { std::istringstream is(data); TYPE x(def_value); if (is >> x) { value = x; } else { cvm::error("Error: in parsing \""+ std::string(key)+"\".\n", INPUT_ERROR); } if (parse_mode != parse_silent) { cvm::log("# "+std::string(key)+" = "+ cvm::to_str(value)+"\n"); } } else { if (b_found_any) { cvm::error("Error: improper or missing value " "for \""+std::string(key)+"\".\n", INPUT_ERROR); } value = def_value; if (parse_mode != parse_silent) { cvm::log("# "+std::string(key)+" = "+ cvm::to_str(def_value)+" [default]\n"); } } return b_found_any; } bool colvarparse::_get_keyval_scalar_string_(std::string const &conf, char const *key, std::string &value, std::string const &def_value, Parse_Mode const parse_mode) { std::string data; bool b_found = false, b_found_any = false; size_t save_pos = 0, found_count = 0; do { std::string data_this = ""; - b_found = key_lookup(conf, key, data_this, save_pos); + b_found = key_lookup(conf, key, &data_this, &save_pos); if (b_found) { if (!b_found_any) b_found_any = true; found_count++; data = data_this; } } while (b_found); if (found_count > 1) cvm::log("Warning: found more than one instance of \""+ std::string(key)+"\".\n"); if (data.size()) { std::istringstream is(data); size_t data_count = 0; std::string x(def_value); while (is >> x) { value = x; data_count++; } if (data_count == 0) cvm::error("Error: in parsing \""+ std::string(key)+"\".\n", INPUT_ERROR); if (data_count > 1) { cvm::error("Error: multiple values " "are not allowed for keyword \""+ std::string(key)+"\".\n", INPUT_ERROR); } if (parse_mode != parse_silent) { cvm::log("# "+std::string(key)+" = \""+ cvm::to_str(value)+"\"\n"); } } else { if (b_found_any) { cvm::error("Error: improper or missing value " "for \""+std::string(key)+"\".\n", INPUT_ERROR); } value = def_value; if (parse_mode != parse_silent) { cvm::log("# "+std::string(key)+" = \""+ cvm::to_str(def_value)+"\" [default]\n"); } } return b_found_any; } // multiple-value keyword parsers template bool colvarparse::_get_keyval_vector_(std::string const &conf, char const *key, std::vector &values, std::vector const &def_values, Parse_Mode const parse_mode) { std::string data; bool b_found = false, b_found_any = false; size_t save_pos = 0, found_count = 0; do { std::string data_this = ""; - b_found = key_lookup(conf, key, data_this, save_pos); + b_found = key_lookup(conf, key, &data_this, &save_pos); if (b_found) { if (!b_found_any) b_found_any = true; found_count++; data = data_this; } } while (b_found); if (found_count > 1) cvm::log("Warning: found more than one instance of \""+ std::string(key)+"\".\n"); if (data.size()) { std::istringstream is(data); if (values.size() == 0) { std::vector x; if (def_values.size()) x = def_values; else x.assign(1, TYPE()); for (size_t i = 0; ( is >> x[ ((i> x) { values[i] = x; } else { cvm::error("Error: in parsing \""+ std::string(key)+"\".\n", INPUT_ERROR); } } } if (parse_mode != parse_silent) { cvm::log("# "+std::string(key)+" = "+ cvm::to_str(values)+"\n"); } } else { if (b_found_any) { cvm::error("Error: improper or missing values for \""+ std::string(key)+"\".\n", INPUT_ERROR); } for (size_t i = 0; i < values.size(); i++) values[i] = def_values[ (i > def_values.size()) ? 0 : i ]; if (parse_mode != parse_silent) { cvm::log("# "+std::string(key)+" = "+ cvm::to_str(def_values)+" [default]\n"); } } return b_found_any; } // single-value keyword parsers bool colvarparse::get_keyval(std::string const &conf, char const *key, int &value, int const &def_value, Parse_Mode const parse_mode) { return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, size_t &value, size_t const &def_value, Parse_Mode const parse_mode) { return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, long &value, long const &def_value, Parse_Mode const parse_mode) { return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, std::string &value, std::string const &def_value, Parse_Mode const parse_mode) { return _get_keyval_scalar_string_(conf, key, value, def_value, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, cvm::real &value, cvm::real const &def_value, Parse_Mode const parse_mode) { return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, cvm::rvector &value, cvm::rvector const &def_value, Parse_Mode const parse_mode) { return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, cvm::quaternion &value, cvm::quaternion const &def_value, Parse_Mode const parse_mode) { return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, colvarvalue &value, colvarvalue const &def_value, Parse_Mode const parse_mode) { return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, bool &value, bool const &def_value, Parse_Mode const parse_mode) { std::string data; bool b_found = false, b_found_any = false; size_t save_pos = 0, found_count = 0; do { std::string data_this = ""; - b_found = key_lookup(conf, key, data_this, save_pos); + b_found = key_lookup(conf, key, &data_this, &save_pos); if (b_found) { if (!b_found_any) b_found_any = true; found_count++; data = data_this; } } while (b_found); if (found_count > 1) cvm::log("Warning: found more than one instance of \""+ std::string(key)+"\".\n"); if (data.size()) { if ( (data == std::string("on")) || (data == std::string("yes")) || (data == std::string("true")) ) { value = true; } else if ( (data == std::string("off")) || (data == std::string("no")) || (data == std::string("false")) ) { value = false; } else cvm::error("Error: boolean values only are allowed " "for \""+std::string(key)+"\".\n", INPUT_ERROR); if (parse_mode != parse_silent) { cvm::log("# "+std::string(key)+" = "+ (value ? "on" : "off")+"\n"); } } else { if (b_found_any) { if (parse_mode != parse_silent) { cvm::log("# "+std::string(key)+" = on\n"); } value = true; } else { value = def_value; if (parse_mode != parse_silent) { cvm::log("# "+std::string(key)+" = "+ (def_value ? "on" : "off")+" [default]\n"); } } } return b_found_any; } // multiple-value keyword parsers bool colvarparse::get_keyval(std::string const &conf, char const *key, std::vector &values, std::vector const &def_values, Parse_Mode const parse_mode) { return _get_keyval_vector_(conf, key, values, def_values, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, std::vector &values, std::vector const &def_values, Parse_Mode const parse_mode) { return _get_keyval_vector_(conf, key, values, def_values, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, std::vector &values, std::vector const &def_values, Parse_Mode const parse_mode) { return _get_keyval_vector_(conf, key, values, def_values, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, std::vector &values, std::vector const &def_values, Parse_Mode const parse_mode) { return _get_keyval_vector_(conf, key, values, def_values, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, std::vector &values, std::vector const &def_values, Parse_Mode const parse_mode) { return _get_keyval_vector_(conf, key, values, def_values, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, std::vector &values, std::vector const &def_values, Parse_Mode const parse_mode) { return _get_keyval_vector_(conf, key, values, def_values, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, std::vector &values, std::vector const &def_values, Parse_Mode const parse_mode) { return _get_keyval_vector_(conf, key, values, def_values, parse_mode); } bool colvarparse::get_keyval(std::string const &conf, char const *key, std::vector &values, std::vector const &def_values, Parse_Mode const parse_mode) { return _get_keyval_vector_(conf, key, values, def_values, parse_mode); } void colvarparse::add_keyword(char const *key) { for (std::list::iterator ki = allowed_keywords.begin(); ki != allowed_keywords.end(); ki++) { if (to_lower_cppstr(std::string(key)) == *ki) return; } // not found in the list // if (cvm::debug()) // cvm::log("Registering a new keyword, \""+std::string (key)+"\".\n"); allowed_keywords.push_back(to_lower_cppstr(std::string(key))); } void colvarparse::strip_values(std::string &conf) { size_t offset = 0; data_begin_pos.sort(); data_end_pos.sort(); std::list::iterator data_begin = data_begin_pos.begin(); std::list::iterator data_end = data_end_pos.begin(); for ( ; (data_begin != data_begin_pos.end()) && (data_end != data_end_pos.end()) ; data_begin++, data_end++) { // std::cerr << "data_begin, data_end " // << *data_begin << ", " << *data_end // << "\n"; size_t const nchars = *data_end-*data_begin; // std::cerr << "conf[data_begin:data_end] = \"" // << std::string (conf, *data_begin - offset, nchars) // << "\"\n"; conf.erase(*data_begin - offset, nchars); offset += nchars; // std::cerr << ("Stripped config = \"\n"+conf+"\"\n"); } } void colvarparse::clear_keyword_registry() { allowed_keywords.clear(); data_begin_pos.clear(); data_end_pos.clear(); } int colvarparse::check_keywords(std::string &conf, char const *key) { if (cvm::debug()) cvm::log("Configuration string for \""+std::string(key)+ "\": \"\n"+conf+"\".\n"); strip_values(conf); // after stripping, the config string has either empty lines, or // lines beginning with a keyword std::string line; std::istringstream is(conf); while (cvm::getline(is, line)) { if (line.size() == 0) continue; if (line.find_first_not_of(white_space) == std::string::npos) continue; std::string uk; std::istringstream line_is(line); line_is >> uk; // if (cvm::debug()) // cvm::log ("Checking the validity of \""+uk+"\" from line:\n" + line); uk = to_lower_cppstr(uk); bool found_keyword = false; for (std::list::iterator ki = allowed_keywords.begin(); ki != allowed_keywords.end(); ki++) { if (uk == *ki) { found_keyword = true; break; } } if (!found_keyword) { cvm::error("Error: keyword \""+uk+"\" is not supported, " "or not recognized in this context.\n", INPUT_ERROR); return INPUT_ERROR; } } clear_keyword_registry(); return COLVARS_OK; } std::istream & colvarparse::getline_nocomments(std::istream &is, std::string &line) { cvm::getline(is, line); size_t const comment = line.find('#'); if (comment != std::string::npos) { line.erase(comment); } return is; } bool colvarparse::key_lookup(std::string const &conf, char const *key_in, - std::string &data, - size_t &save_pos) + std::string *data, + size_t *save_pos) { if (cvm::debug()) { cvm::log("Looking for the keyword \""+std::string(key_in)+"\" and its value.\n"); } // add this keyword to the register (in its camelCase version) add_keyword(key_in); // use the lowercase version from now on std::string const key(to_lower_cppstr(key_in)); // "conf_lower" is only used to lookup the keyword, but its value // will be read from "conf", in order not to mess up file names std::string const conf_lower(to_lower_cppstr(conf)); // by default, there is no value, unless we found one - data = ""; - - // when the function is invoked without save_pos, ensure that we - // start from zero - colvarparse::dummy_pos = 0; + if (data != NULL) { + data->clear(); + } // start from the first occurrence of key - size_t pos = conf_lower.find(key, save_pos); + size_t pos = conf_lower.find(key, (save_pos != NULL) ? *save_pos : 0); // iterate over all instances of the substring until it finds it as isolated keyword while (true) { if (pos == std::string::npos) { // no valid instance of the keyword has been found if (cvm::debug()) { cvm::log("Keyword \""+std::string(key_in)+"\" not found.\n"); } return false; } bool b_isolated_left = true, b_isolated_right = true; if (pos > 0) { - if ( std::string("\n"+white_space+ + if ( std::string("\n"+std::string(white_space)+ "}").find(conf[pos-1]) == std::string::npos ) { // none of the valid delimiting characters is on the left of key b_isolated_left = false; } } if (pos < conf.size()-key.size()-1) { - if ( std::string("\n"+white_space+ + if ( std::string("\n"+std::string(white_space)+ "{").find(conf[pos+key.size()]) == std::string::npos ) { // none of the valid delimiting characters is on the right of key b_isolated_right = false; } } // check that there are matching braces between here and the end of conf bool const b_not_within_block = brace_check(conf, pos); bool const b_isolated = (b_isolated_left && b_isolated_right && b_not_within_block); if (b_isolated) { // found it break; } else { // try the next occurrence of key pos = conf_lower.find(key, pos+key.size()); } } + if (save_pos != NULL) { // save the pointer for a future call (when iterating over multiple // valid instances of the same keyword) - save_pos = pos + key.size(); + *save_pos = pos + key.size(); + } // get the remainder of the line size_t pl = conf.rfind("\n", pos); size_t line_begin = (pl == std::string::npos) ? 0 : pos; size_t nl = conf.find("\n", pos); size_t line_end = (nl == std::string::npos) ? conf.size() : nl; std::string line(conf, line_begin, (line_end-line_begin)); size_t data_begin = (to_lower_cppstr(line)).find(key) + key.size(); data_begin = line.find_first_not_of(white_space, data_begin+1); if (data_begin != std::string::npos) { size_t data_end = line.find_last_not_of(white_space) + 1; data_end = (data_end == std::string::npos) ? line.size() : data_end; size_t brace = line.find('{', data_begin); // look for an opening brace size_t brace_last = brace; if (brace != std::string::npos) { // find the matching closing brace // if (cvm::debug()) { // cvm::log("Multi-line value, config is now \""+line+"\".\n"); // } int brace_count = 1; while (brace_count > 0) { brace = line.find_first_of("{}", brace_last+1); // find all braces within this line while (brace < std::string::npos) { brace_last = brace; 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; } if (brace == std::string::npos) { // add a new line if (line_end >= conf.size()) { cvm::error("Parse error: reached the end while " "looking for closing brace; until now " "the following was parsed: \"\n"+ line+"\".\n", INPUT_ERROR); return false; } line_begin = line_end; nl = conf.find('\n', line_begin+1); if (nl == std::string::npos) line_end = conf.size(); else line_end = nl; line.append(conf, line_begin, (line_end-line_begin)); // if (cvm::debug()) { // cvm::log("Added a new line, config is now \""+line+"\".\n"); // } } if (brace_count < 0) { cvm::error("Error: found closing brace without opening brace.\n", INPUT_ERROR); } } // strip the leading and trailing braces data_begin = line.find_first_of('{') + 1; data_begin = line.find_first_not_of(white_space, data_begin); data_end = line.find_last_of('}', line.size()) - 1; data_end = line.find_last_not_of(white_space, data_end) + 1; } - data.append(line, data_begin, (data_end-data_begin)); + if (data != NULL) { + data->append(line, data_begin, (data_end-data_begin)); - if (cvm::debug()) { - cvm::log("Keyword value = \""+data+"\".\n"); - } + if (cvm::debug()) { + cvm::log("Keyword value = \""+*data+"\".\n"); + } - if (data.size() && save_delimiters) { - data_begin_pos.push_back(conf.find(data, pos+key.size())); - data_end_pos.push_back(data_begin_pos.back()+data.size()); + if (data->size()) { + data_begin_pos.push_back(conf.find(*data, pos+key.size())); + data_end_pos.push_back(data_begin_pos.back()+data->size()); + } } } - save_pos = line_end; + if (save_pos != NULL) *save_pos = line_end; return true; } std::istream & operator>> (std::istream &is, colvarparse::read_block const &rb) { size_t start_pos = is.tellg(); std::string read_key, next; 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 is.clear(); is.seekg(start_pos, std::ios::beg); is.setstate(std::ios::failbit); return is; } if (next != "{") { (*rb.data) = next; return is; } size_t brace_count = 1; 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--; br_old = br; br++; } if (brace_count) (*rb.data).append(line + "\n"); else { (*rb.data).append(line, 0, br_old); 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); } return is; } bool colvarparse::brace_check(std::string const &conf, size_t const start_pos) { size_t brace_count = 0; size_t brace = start_pos; while ( (brace = conf.find_first_of("{}", brace)) != std::string::npos) { if (conf[brace] == '{') brace_count++; if (conf[brace] == '}') brace_count--; brace++; } if (brace_count != 0) return false; else return true; } diff --git a/lib/colvars/colvarparse.h b/lib/colvars/colvarparse.h index 9f116caaf..9389bc49d 100644 --- a/lib/colvars/colvarparse.h +++ b/lib/colvars/colvarparse.h @@ -1,316 +1,299 @@ // -*- 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 COLVARPARSE_H #define COLVARPARSE_H #include #include #include "colvarmodule.h" #include "colvarvalue.h" /// \file colvarparse.h Parsing functions for collective variables /// \brief Base class containing parsing functions; all objects which /// need to parse input inherit from this class colvarparse { -protected: +private: /// \brief List of legal keywords for this object: this is updated /// by each call to colvarparse::get_keyval() or /// colvarparse::key_lookup() std::list allowed_keywords; /// \brief List of delimiters for the values of each keyword in the /// configuration string; all keywords will be stripped of their /// values before the keyword check is performed std::list data_begin_pos; /// \brief List of delimiters for the values of each keyword in the /// configuration string; all keywords will be stripped of their /// values before the keyword check is performed std::list data_end_pos; - /// \brief Whether or not to accumulate data_begin_pos and - /// data_end_pos in key_lookup(); it may be useful to disable - /// this after the constructor is called, because other files may be - /// read (e.g. restart) that would mess up the registry; in any - /// case, nothing serious happens until check_keywords() is invoked - /// (which should happen only right after construction) - bool save_delimiters; - /// \brief Add a new valid keyword to the list void add_keyword(char const *key); /// \brief Remove all the values from the config string void strip_values(std::string &conf); /// \brief Configuration string of the object std::string config_string; public: inline colvarparse() - : save_delimiters(true) { init(); } /// Constructor that stores the object's config string inline colvarparse(const std::string& conf) - : save_delimiters(true) { init(conf); } /// Set the object ready to parse a new configuration string inline void init() { config_string.clear(); clear_keyword_registry(); } /// Set a new config string for this object inline void init(const std::string& conf) { if (! config_string.size()) { init(); config_string = conf; } } inline const std::string& get_config() { return config_string; } /// How a keyword is parsed in a string enum Parse_Mode { /// \brief(default) Read the first instance of a keyword (if /// any), report its value, and print a warning when there is more /// than one parse_normal, /// \brief Like parse_normal, but don't send any message to the log /// (useful e.g. in restart files when such messages are very /// numerous and redundant) parse_silent }; /// \brief Check that all the keywords within "conf" are in the list /// of allowed keywords; this will invoke strip_values() first and /// then loop over all words int check_keywords(std::string &conf, char const *key); /// \brief Use this after parsing a config string (note that check_keywords() calls it already) void clear_keyword_registry(); -public: - /// \fn get_keyval bool const get_keyval (std::string const &conf, /// char const *key, _type_ &value, _type_ const &def_value, /// Parse_Mode const parse_mode) \brief Helper function to parse /// keywords in the configuration and get their values /// /// In normal circumstances, you should use either version the /// get_keyval function. Both of them look for the C string "key" /// in the C++ string "conf", and assign the corresponding value (if /// available) to the variable "value" (first version), or assign as /// many values as found to the vector "values" (second version). /// /// If "key" is found but no value is associated to it, the default /// value is provided (either one copy or as many copies as the /// current length of the vector "values" specifies). A message /// will print, unless parse_mode is equal to parse_silent. The /// return value of both forms of get_keyval is true if "key" is /// found (with or without value), and false when "key" is absent in /// the string "conf". If there is more than one instance of the /// keyword, a warning will be raised; instead, to loop over /// multiple instances key_lookup() should be invoked directly. /// /// If you introduce a new data type, add two new instances of this /// functions, or insert this type in the \link colvarvalue \endlink /// wrapper class (colvarvalue.h). bool get_keyval(std::string const &conf, char const *key, int &value, int const &def_value = (int)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, Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, long &value, long const &def_value = 0, Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, std::string &value, std::string const &def_value = std::string(""), Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, cvm::real &value, cvm::real const &def_value = (cvm::real)0.0, Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, cvm::rvector &value, cvm::rvector const &def_value = cvm::rvector(), Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, cvm::quaternion &value, cvm::quaternion const &def_value = cvm::quaternion(), Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, colvarvalue &value, colvarvalue const &def_value = colvarvalue(colvarvalue::type_notset), Parse_Mode const parse_mode = parse_normal); bool get_keyval(std::string const &conf, char const *key, bool &value, bool const &def_value = false, 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, (int)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), 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), 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, std::string("")), 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, (cvm::real)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, cvm::rvector()), 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, cvm::quaternion()), 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, colvarvalue(colvarvalue::type_notset)), Parse_Mode const parse_mode = parse_normal); protected: // Templates template bool _get_keyval_scalar_(std::string const &conf, char const *key, TYPE &value, TYPE const &def_value, Parse_Mode const parse_mode); bool _get_keyval_scalar_string_(std::string const &conf, char const *key, std::string &value, std::string const &def_value, Parse_Mode const parse_mode); template bool _get_keyval_vector_(std::string const &conf, char const *key, std::vector &values, std::vector const &def_values, Parse_Mode const parse_mode); public: /// \brief Return a lowercased copy of the string static inline std::string to_lower_cppstr(std::string const &in) { std::string out = ""; for (size_t i = 0; i < in.size(); i++) { out.append(1, (char) ::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 /// /// 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 { std::string const key; std::string * const data; public: inline read_block(std::string const &key_in, std::string &data_in) : key(key_in), data(&data_in) {} inline ~read_block() {} friend std::istream & operator >> (std::istream &is, read_block const &rb); }; /// Accepted white space delimiters, used in key_lookup() - static std::string const white_space; + static const char * const white_space; /// \brief Low-level function for parsing configuration strings; /// automatically adds the requested keyword to the list of valid /// ones. \param conf the content of the configuration file or one /// of its blocks \param key the keyword to search within "conf" \param /// data (optional) holds the string provided after "key", if any /// \param save_pos (optional) stores the position of the keyword /// within "conf", useful when doing multiple calls bool key_lookup(std::string const &conf, char const *key, - std::string &data = dummy_string, - size_t &save_pos = dummy_pos); - - /// Used as a default argument by key_lookup - static std::string dummy_string; - /// Used as a default argument by key_lookup - static size_t dummy_pos; + std::string *data = NULL, + size_t *save_pos = NULL); /// \brief Works as std::getline() but also removes everything /// between a comment character and the following newline static std::istream & getline_nocomments(std::istream &is, std::string &s); /// Check if the content of the file has matching braces bool brace_check(std::string const &conf, size_t const start_pos = 0); }; #endif diff --git a/lib/colvars/colvarproxy.cpp b/lib/colvars/colvarproxy.cpp new file mode 100644 index 000000000..fa24091d5 --- /dev/null +++ b/lib/colvars/colvarproxy.cpp @@ -0,0 +1,492 @@ +// -*- 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 "colvarmodule.h" +#include "colvarproxy.h" +#include "colvarscript.h" +#include "colvaratoms.h" + + + +colvarproxy_system::colvarproxy_system() {} + + +colvarproxy_system::~colvarproxy_system() {} + + +void colvarproxy_system::add_energy(cvm::real energy) {} + + +void colvarproxy_system::request_total_force(bool yesno) +{ + if (yesno == true) + cvm::error("Error: total forces are currently not implemented.\n", + COLVARS_NOT_IMPLEMENTED); +} + + +bool colvarproxy_system::total_forces_enabled() const +{ + return false; +} + + +cvm::real colvarproxy_system::position_dist2(cvm::atom_pos const &pos1, + cvm::atom_pos const &pos2) +{ + return (position_distance(pos1, pos2)).norm2(); +} + + + +colvarproxy_atoms::colvarproxy_atoms() {} + + +colvarproxy_atoms::~colvarproxy_atoms() +{ + reset(); +} + + +int colvarproxy_atoms::reset() +{ + atoms_ids.clear(); + atoms_ncopies.clear(); + atoms_masses.clear(); + atoms_charges.clear(); + atoms_positions.clear(); + atoms_total_forces.clear(); + atoms_new_colvar_forces.clear(); + return COLVARS_OK; +} + + +int colvarproxy_atoms::add_atom_slot(int atom_id) +{ + atoms_ids.push_back(atom_id); + atoms_ncopies.push_back(1); + atoms_masses.push_back(1.0); + atoms_charges.push_back(0.0); + 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)); + return (atoms_ids.size() - 1); +} + + +int colvarproxy_atoms::init_atom(cvm::residue_id const &residue, + std::string const &atom_name, + std::string const &segment_id) +{ + cvm::error("Error: initializing an atom by name and residue number is currently not supported.\n", + COLVARS_NOT_IMPLEMENTED); + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_atoms::check_atom_id(cvm::residue_id const &residue, + std::string const &atom_name, + std::string const &segment_id) +{ + colvarproxy_atoms::init_atom(residue, atom_name, segment_id); + return COLVARS_NOT_IMPLEMENTED; +} + + +void colvarproxy_atoms::clear_atom(int index) +{ + if (((size_t) index) >= atoms_ids.size()) { + cvm::error("Error: trying to disable an atom that was not previously requested.\n", + INPUT_ERROR); + } + if (atoms_ncopies[index] > 0) { + atoms_ncopies[index] -= 1; + } +} + + +int colvarproxy_atoms::load_atoms(char const *filename, + cvm::atom_group &atoms, + std::string const &pdb_field, + double const) +{ + 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, + const std::vector &indices, + std::string const &pdb_field, + double const) +{ + return cvm::error("Error: loading atomic coordinates from a file " + "is currently not implemented.\n", + COLVARS_NOT_IMPLEMENTED); +} + + + +colvarproxy_atom_groups::colvarproxy_atom_groups() {} + + +colvarproxy_atom_groups::~colvarproxy_atom_groups() +{ + reset(); +} + + +int colvarproxy_atom_groups::reset() +{ + atom_groups_ids.clear(); + atom_groups_ncopies.clear(); + atom_groups_masses.clear(); + atom_groups_charges.clear(); + atom_groups_coms.clear(); + atom_groups_total_forces.clear(); + atom_groups_new_colvar_forces.clear(); + return COLVARS_OK; +} + + +int colvarproxy_atom_groups::add_atom_group_slot(int atom_group_id) +{ + atom_groups_ids.push_back(atom_group_id); + atom_groups_ncopies.push_back(1); + atom_groups_masses.push_back(1.0); + atom_groups_charges.push_back(0.0); + atom_groups_coms.push_back(cvm::rvector(0.0, 0.0, 0.0)); + atom_groups_total_forces.push_back(cvm::rvector(0.0, 0.0, 0.0)); + atom_groups_new_colvar_forces.push_back(cvm::rvector(0.0, 0.0, 0.0)); + return (atom_groups_ids.size() - 1); +} + + +int colvarproxy_atom_groups::scalable_group_coms() +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_atom_groups::init_atom_group(std::vector const &atoms_ids) +{ + cvm::error("Error: initializing a group outside of the Colvars module " + "is currently not supported.\n", + COLVARS_NOT_IMPLEMENTED); + return COLVARS_NOT_IMPLEMENTED; +} + + +void colvarproxy_atom_groups::clear_atom_group(int index) +{ + if (((size_t) index) >= atom_groups_ids.size()) { + cvm::error("Error: trying to disable an atom group " + "that was not previously requested.\n", + INPUT_ERROR); + } + if (atom_groups_ncopies[index] > 0) { + atom_groups_ncopies[index] -= 1; + } +} + + + +colvarproxy_smp::colvarproxy_smp() +{ + b_smp_active = true; +} + + +colvarproxy_smp::~colvarproxy_smp() {} + + +int colvarproxy_smp::smp_enabled() +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_smp::smp_colvars_loop() +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_smp::smp_biases_loop() +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_smp::smp_biases_script_loop() +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_smp::smp_thread_id() +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_smp::smp_num_threads() +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_smp::smp_lock() +{ + return COLVARS_OK; +} + + +int colvarproxy_smp::smp_trylock() +{ + return COLVARS_OK; +} + + +int colvarproxy_smp::smp_unlock() +{ + return COLVARS_OK; +} + + + + +colvarproxy_replicas::colvarproxy_replicas() {} + + +colvarproxy_replicas::~colvarproxy_replicas() {} + + +bool colvarproxy_replicas::replica_enabled() +{ + return false; +} + + +int colvarproxy_replicas::replica_index() +{ + return 0; +} + + +int colvarproxy_replicas::replica_num() +{ + return 1; +} + + +void colvarproxy_replicas::replica_comm_barrier() {} + + +int colvarproxy_replicas::replica_comm_recv(char* msg_data, + int buf_len, + int src_rep) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_replicas::replica_comm_send(char* msg_data, + int msg_len, + int dest_rep) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + + + +colvarproxy_script::colvarproxy_script() +{ + script = NULL; +} + + +colvarproxy_script::~colvarproxy_script() {} + + +char *colvarproxy_script::script_obj_to_str(unsigned char *obj) +{ + return reinterpret_cast(obj); +} + + +int colvarproxy_script::run_force_callback() +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_script::run_colvar_callback( + std::string const &name, + std::vector const &cvcs, + colvarvalue &value) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_script::run_colvar_gradient_callback( + std::string const &name, + std::vector const &cvcs, + std::vector > &gradient) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + + + +colvarproxy_io::colvarproxy_io() {} + + +colvarproxy_io::~colvarproxy_io() {} + + +int colvarproxy_io::get_frame(long int&) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_io::set_frame(long int) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +std::ostream * colvarproxy_io::output_stream(std::string const &output_name, + std::ios_base::openmode mode) +{ + if (cvm::debug()) { + cvm::log("Using colvarproxy::output_stream()\n"); + } + std::list::iterator osi = output_files.begin(); + std::list::iterator osni = output_stream_names.begin(); + for ( ; osi != output_files.end(); osi++, osni++) { + if (*osni == output_name) { + return *osi; + } + } + if (!(mode & (std::ios_base::app | std::ios_base::ate))) { + backup_file(output_name); + } + std::ofstream *os = new std::ofstream(output_name.c_str(), mode); + if (!os->is_open()) { + cvm::error("Error: cannot write to file/channel \""+output_name+"\".\n", + FILE_ERROR); + return NULL; + } + output_stream_names.push_back(output_name); + output_files.push_back(os); + return os; +} + + +int colvarproxy_io::flush_output_stream(std::ostream *os) +{ + std::list::iterator osi = output_files.begin(); + std::list::iterator osni = output_stream_names.begin(); + for ( ; osi != output_files.end(); osi++, osni++) { + if (*osi == os) { + ((std::ofstream *) (*osi))->flush(); + return COLVARS_OK; + } + } + return cvm::error("Error: trying to flush an output file/channel " + "that wasn't open.\n", BUG_ERROR); +} + + +int colvarproxy_io::close_output_stream(std::string const &output_name) +{ + std::list::iterator osi = output_files.begin(); + std::list::iterator osni = output_stream_names.begin(); + for ( ; osi != output_files.end(); osi++, osni++) { + if (*osni == output_name) { + ((std::ofstream *) (*osi))->close(); + output_files.erase(osi); + output_stream_names.erase(osni); + return COLVARS_OK; + } + } + return cvm::error("Error: trying to close an output file/channel " + "that wasn't open.\n", BUG_ERROR); +} + + +int colvarproxy_io::backup_file(char const *filename) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + + +colvarproxy::colvarproxy() +{ + colvars = NULL; + b_simulation_running = true; +} + + +colvarproxy::~colvarproxy() {} + + +int colvarproxy::reset() +{ + int error_code = COLVARS_OK; + error_code |= colvarproxy_atoms::reset(); + error_code |= colvarproxy_atom_groups::reset(); + return error_code; +} + + +int colvarproxy::setup() +{ + return COLVARS_OK; +} + + +int colvarproxy::update_input() +{ + return COLVARS_OK; +} + + +int colvarproxy::update_output() +{ + return COLVARS_OK; +} + + +size_t colvarproxy::restart_frequency() +{ + return 0; +} + + + + + + + + + + + diff --git a/lib/colvars/colvarproxy.h b/lib/colvars/colvarproxy.h index 5b216c9d4..95d13cd7e 100644 --- a/lib/colvars/colvarproxy.h +++ b/lib/colvars/colvarproxy.h @@ -1,647 +1,589 @@ // -*- 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 COLVARPROXY_H #define COLVARPROXY_H #include #include #include "colvarmodule.h" #include "colvarvalue.h" + +/// \file colvarproxy.h +/// \brief Colvars proxy classes +/// +/// This file declares the class for the object responsible for interfacing +/// Colvars with other codes (MD engines, VMD, Python). The \link colvarproxy +/// \endlink class is a derivative of multiple classes, each devoted to a +/// specific task (e.g. \link colvarproxy_atoms \endlink to access data for +/// individual atoms). +/// +/// To interface to a new MD engine, the simplest solution is to derive a new +/// class from \link colvarproxy \endlink. Currently implemented are: \link +/// colvarproxy_lammps, \endlink, \link colvarproxy_namd, \endlink, \link +/// colvarproxy_vmd, \endlink. + + // forward declarations class colvarscript; -/// \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. -/// Only pure virtual functions ("= 0") must be reimplemented to ensure baseline functionality. -class colvarproxy { +/// Methods for accessing the simulation system (PBCs, integrator, etc) +class colvarproxy_system { public: - /// Pointer to the main object - colvarmodule *colvars; - /// Constructor - colvarproxy() - { - colvars = NULL; - b_simulation_running = true; - b_smp_active = true; - script = NULL; - } + colvarproxy_system(); /// Destructor - virtual ~colvarproxy() - {} - - /// (Re)initialize required member data after construction - virtual int setup() - { - return COLVARS_OK; - } - - /// \brief Update data required by the colvars module (e.g. cache atom positions) - /// - /// TODO Break up colvarproxy_namd and colvarproxy_lammps function into these - virtual int update_input() - { - return COLVARS_OK; - } - - /// \brief Update data based from the results of a module update (e.g. send forces) - virtual int update_output() - { - return COLVARS_OK; - } - - // **************** SIMULATION PARAMETERS **************** + virtual ~colvarproxy_system(); /// \brief Value of the unit for atomic coordinates with respect to /// angstroms (used by some variables for hard-coded default values) virtual cvm::real unit_angstrom() = 0; /// \brief Boltzmann constant virtual cvm::real boltzmann() = 0; - /// \brief Temperature of the simulation (K) + /// \brief Target temperature of the simulation (K units) virtual cvm::real temperature() = 0; /// \brief Time step of the simulation (fs) virtual cvm::real dt() = 0; /// \brief Pseudo-random number with Gaussian distribution virtual cvm::real rand_gaussian(void) = 0; - /// \brief Get the current frame number - // Returns error code - virtual int get_frame(long int&) { return COLVARS_NOT_IMPLEMENTED; } - - /// \brief Set the current frame number (as well as colvarmodule::it) - // Returns error code - virtual int set_frame(long int) { return COLVARS_NOT_IMPLEMENTED; } + /// Pass restraint energy value for current timestep to MD engine + virtual void add_energy(cvm::real energy) = 0; - /// \brief Prefix to be used for input files (restarts, not - /// configuration) - std::string input_prefix_str, output_prefix_str, restart_output_prefix_str; + /// \brief Get the PBC-aware distance vector between two positions + virtual cvm::rvector position_distance(cvm::atom_pos const &pos1, + cvm::atom_pos const &pos2) = 0; - inline std::string & input_prefix() - { - return input_prefix_str; - } + /// \brief Get the PBC-aware square distance between two positions; + /// may need to be reimplemented independently from position_distance() for optimization purposes + virtual cvm::real position_dist2(cvm::atom_pos const &pos1, + cvm::atom_pos const &pos2); - /// \brief Prefix to be used for output restart files - inline std::string restart_output_prefix() - { - return restart_output_prefix_str; - } + /// Tell the proxy whether total forces are needed (may not always be available) + virtual void request_total_force(bool yesno); - /// \brief Prefix to be used for output files (final system - /// configuration) - inline std::string output_prefix() - { - return output_prefix_str; - } + /// Are total forces being used? + virtual bool total_forces_enabled() const; +}; - /// \brief Restarts will be written each time this number of steps has passed - virtual size_t restart_frequency() - { - return 0; - } -protected: - - /// Whether a simulation is running (and try to prevent irrecovarable errors) - bool b_simulation_running; +/// \brief Container of atomic data for processing by Colvars +class colvarproxy_atoms { public: - /// Whether a simulation is running (and try to prevent irrecovarable errors) - virtual bool simulation_running() const - { - return b_simulation_running; - } - -protected: + /// Constructor + colvarproxy_atoms(); - /// \brief Currently opened output files: by default, these are ofstream objects. - /// Allows redefinition to implement different output mechanisms - std::list output_files; - /// \brief Identifiers for output_stream objects: by default, these are the names of the files - std::list output_stream_names; + /// Destructor + virtual ~colvarproxy_atoms(); -public: + /// Prepare this atom for collective variables calculation, selecting it by + /// numeric index (1-based) + virtual int init_atom(int atom_number) = 0; - // ***************** SHARED-MEMORY PARALLELIZATION ***************** + /// Check that this atom number is valid, but do not initialize the + /// corresponding atom yet + virtual int check_atom_id(int atom_number) = 0; - /// Whether threaded parallelization is available (TODO: make this a cvm::deps feature) - virtual int smp_enabled() - { - return COLVARS_NOT_IMPLEMENTED; - } + /// 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. + virtual int init_atom(cvm::residue_id const &residue, + std::string const &atom_name, + std::string const &segment_id); - /// Whether threaded parallelization should be used (TODO: make this a cvm::deps feature) - bool b_smp_active; + /// Check that this atom is valid, but do not initialize it yet + virtual int check_atom_id(cvm::residue_id const &residue, + std::string const &atom_name, + std::string const &segment_id); - /// Distribute calculation of colvars (and their components) across threads - virtual int smp_colvars_loop() - { - return COLVARS_NOT_IMPLEMENTED; - } + /// \brief Used by the atom class destructor: rather than deleting the array slot + /// (costly) set the corresponding atoms_ncopies to zero + virtual void clear_atom(int index); - /// Distribute calculation of biases across threads - virtual int smp_biases_loop() - { - return COLVARS_NOT_IMPLEMENTED; - } + /// \brief Read atom identifiers from a file \param filename name of + /// the file (usually a PDB) \param atoms array to which atoms read + /// from "filename" will be appended \param pdb_field (optiona) if + /// "filename" is a PDB file, use this field to determine which are + /// the atoms to be set + virtual int load_atoms(char const *filename, + cvm::atom_group &atoms, + std::string const &pdb_field, + double const pdb_field_value = 0.0); - /// Distribute calculation of biases across threads 2nd through last, with all scripted biased on 1st thread - virtual int smp_biases_script_loop() - { - return COLVARS_NOT_IMPLEMENTED; - } + /// \brief Load the coordinates for a group of atoms from a file + /// (usually a PDB); if "pos" is already allocated, the number of its + /// elements must match the number of atoms in "filename" + virtual int load_coords(char const *filename, + std::vector &pos, + const std::vector &indices, + std::string const &pdb_field, + double const pdb_field_value = 0.0); - /// Index of this thread - virtual int smp_thread_id() - { - return COLVARS_NOT_IMPLEMENTED; - } + /// Clear atomic data + int reset(); - /// Number of threads sharing this address space - virtual int smp_num_threads() + /// Get the numeric ID of the given atom (for the program) + inline int get_atom_id(int index) const { - return COLVARS_NOT_IMPLEMENTED; + return atoms_ids[index]; } - /// Lock the proxy's shared data for access by a thread, if threads are implemented; if not implemented, does nothing - virtual int smp_lock() + /// Get the mass of the given atom + inline cvm::real get_atom_mass(int index) const { - return COLVARS_OK; + return atoms_masses[index]; } - /// Attempt to lock the proxy's shared data - virtual int smp_trylock() + /// Get the charge of the given atom + inline cvm::real get_atom_charge(int index) const { - return COLVARS_OK; + return atoms_charges[index]; } - /// Release the lock - virtual int smp_unlock() + /// Read the current position of the given atom + inline cvm::rvector get_atom_position(int index) const { - return COLVARS_OK; - } - - // **************** MULTIPLE REPLICAS COMMUNICATION **************** - - // Replica exchange commands: - - /// \brief Indicate if multi-replica support is available and active - virtual bool replica_enabled() { return false; } - - /// \brief Index of this replica - virtual int replica_index() { return 0; } - - /// \brief Total number of replica - virtual int replica_num() { return 1; } - - /// \brief Synchronize replica - virtual void replica_comm_barrier() {} - - /// \brief Receive data from other replica - virtual int replica_comm_recv(char* msg_data, int buf_len, int src_rep) { - return COLVARS_NOT_IMPLEMENTED; - } - - /// \brief Send data to other replica - virtual int replica_comm_send(char* msg_data, int msg_len, int dest_rep) { - return COLVARS_NOT_IMPLEMENTED; + return atoms_positions[index]; } - - // **************** SCRIPTING INTERFACE **************** - - /// Pointer to the scripting interface object - /// (does not need to be allocated in a new interface) - colvarscript *script; - - /// is a user force script defined? - bool force_script_defined; - - /// Do we have a scripting interface? - bool have_scripts; - - /// Run a user-defined colvar forces script - virtual int run_force_callback() { return COLVARS_NOT_IMPLEMENTED; } - - virtual int run_colvar_callback(std::string const &name, - std::vector const &cvcs, - colvarvalue &value) - { return COLVARS_NOT_IMPLEMENTED; } - - virtual int run_colvar_gradient_callback(std::string const &name, - std::vector const &cvcs, - std::vector > &gradient) - { return COLVARS_NOT_IMPLEMENTED; } - - - // **************** INPUT/OUTPUT **************** - - /// Print a message to the main log - virtual void log(std::string const &message) = 0; - - /// Print a message to the main log and let the rest of the program handle the error - virtual void error(std::string const &message) = 0; - - /// Print a message to the main log and exit with error code - virtual void fatal_error(std::string const &message) = 0; - - /// Print a message to the main log and exit normally - virtual void exit(std::string const &message) + /// Read the current total force of the given atom + inline cvm::rvector get_atom_total_force(int index) const { - cvm::error("Error: exiting without error is not implemented, returning error code.\n", - COLVARS_NOT_IMPLEMENTED); + return atoms_total_forces[index]; } - // TODO the following definitions may be moved to a .cpp file - - /// \brief Returns a reference to the given output channel; - /// if this is not open already, then open it - virtual std::ostream * output_stream(std::string const &output_name) + /// Request that this force is applied to the given atom + inline void apply_atom_force(int index, cvm::rvector const &new_force) { - std::list::iterator osi = output_files.begin(); - std::list::iterator osni = output_stream_names.begin(); - for ( ; osi != output_files.end(); osi++, osni++) { - if (*osni == output_name) { - return *osi; - } - } - output_stream_names.push_back(output_name); - std::ofstream * os = new std::ofstream(output_name.c_str()); - if (!os->is_open()) { - cvm::error("Error: cannot write to file \""+output_name+"\".\n", - FILE_ERROR); - } - output_files.push_back(os); - return os; + atoms_new_colvar_forces[index] += new_force; } - /// \brief Closes the given output channel - virtual int close_output_stream(std::string const &output_name) + /// Read the current velocity of the given atom + inline cvm::rvector get_atom_velocity(int index) { - std::list::iterator osi = output_files.begin(); - std::list::iterator osni = output_stream_names.begin(); - for ( ; osi != output_files.end(); osi++, osni++) { - if (*osni == output_name) { - ((std::ofstream *) (*osi))->close(); - output_files.erase(osi); - output_stream_names.erase(osni); - return COLVARS_OK; - } - } - cvm::error("Error: trying to close an output file or stream that wasn't open.\n", - BUG_ERROR); - return COLVARS_ERROR; + cvm::error("Error: reading the current velocity of an atom " + "is not yet implemented.\n", + COLVARS_NOT_IMPLEMENTED); + return cvm::rvector(0.0); } - /// \brief Rename the given file, before overwriting it - virtual int backup_file(char const *filename) + inline std::vector *modify_atom_ids() { - return COLVARS_NOT_IMPLEMENTED; + return &atoms_ids; } - - - // **************** ACCESS SYSTEM DATA **************** - - /// Pass restraint energy value for current timestep to MD engine - virtual void add_energy(cvm::real energy) = 0; - - /// Tell the proxy whether total forces are needed (may not always be available) - virtual void request_total_force(bool yesno) + inline std::vector *modify_atom_masses() { - if (yesno == true) - cvm::error("Error: total forces are currently not implemented.\n", - COLVARS_NOT_IMPLEMENTED); + return &atoms_masses; } - /// Are total forces being used? - virtual bool total_forces_enabled() const + inline std::vector *modify_atom_charges() { - return false; + return &atoms_charges; } - /// \brief Get the PBC-aware distance vector between two positions - virtual cvm::rvector position_distance(cvm::atom_pos const &pos1, - cvm::atom_pos const &pos2) = 0; - - /// \brief Get the PBC-aware square distance between two positions; - /// may need to be reimplemented independently from position_distance() for optimization purposes - virtual cvm::real position_dist2(cvm::atom_pos const &pos1, - cvm::atom_pos const &pos2) + inline std::vector *modify_atom_positions() { - return (position_distance(pos1, pos2)).norm2(); + return &atoms_positions; } - /// \brief Get the closest periodic image to a reference position - /// \param pos The position to look for the closest periodic image - /// \param ref_pos The reference position - virtual void select_closest_image(cvm::atom_pos &pos, - cvm::atom_pos const &ref_pos) + inline std::vector *modify_atom_total_forces() { - pos = position_distance(ref_pos, pos) + ref_pos; + return &atoms_total_forces; } - /// \brief Perform select_closest_image() on a set of atomic positions - /// - /// After that, distance vectors can then be calculated directly, - /// without using position_distance() - void select_closest_images(std::vector &pos, - cvm::atom_pos const &ref_pos) + inline std::vector *modify_atom_new_colvar_forces() { - for (std::vector::iterator pi = pos.begin(); - pi != pos.end(); ++pi) { - select_closest_image(*pi, ref_pos); - } + return &atoms_new_colvar_forces; } - - // **************** ACCESS ATOMIC DATA **************** protected: /// \brief Array of 0-based integers used to uniquely associate atoms /// within the host program std::vector atoms_ids; /// \brief Keep track of how many times each atom is used by a separate colvar object std::vector atoms_ncopies; /// \brief Masses of the atoms (allow redefinition during a run, as done e.g. in LAMMPS) std::vector atoms_masses; /// \brief Charges of the atoms (allow redefinition during a run, as done e.g. in LAMMPS) std::vector atoms_charges; /// \brief Current three-dimensional positions of the atoms std::vector atoms_positions; /// \brief Most recent total forces on each atom std::vector atoms_total_forces; /// \brief Forces applied from colvars, to be communicated to the MD integrator std::vector atoms_new_colvar_forces; - /// Used by all init_atom() functions: create a slot for an atom not requested yet - inline int add_atom_slot(int atom_id) - { - atoms_ids.push_back(atom_id); - atoms_ncopies.push_back(1); - atoms_masses.push_back(1.0); - atoms_charges.push_back(0.0); - 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)); - return (atoms_ids.size() - 1); - } + /// Used by all init_atom() functions: create a slot for an atom not + /// requested yet; returns the index in the arrays + int add_atom_slot(int atom_id); + +}; + + +/// \brief Container of atom group data (allow collection of aggregated atomic +/// data) +class colvarproxy_atom_groups { public: - /// Prepare this atom for collective variables calculation, selecting it by numeric index (1-based) - virtual int init_atom(int atom_number) = 0; + /// Contructor + colvarproxy_atom_groups(); - /// Check that this atom number is valid, but do not initialize the corresponding atom yet - virtual int check_atom_id(int atom_number) = 0; + /// Destructor + virtual ~colvarproxy_atom_groups(); - /// 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. - virtual int init_atom(cvm::residue_id const &residue, - std::string const &atom_name, - std::string const &segment_id) - { - cvm::error("Error: initializing an atom by name and residue number is currently not supported.\n", - COLVARS_NOT_IMPLEMENTED); - return COLVARS_NOT_IMPLEMENTED; - } + /// Clear atom group data + int reset(); - /// Check that this atom is valid, but do not initialize it yet - virtual int check_atom_id(cvm::residue_id const &residue, - std::string const &atom_name, - std::string const &segment_id) - { - cvm::error("Error: initializing an atom by name and residue number is currently not supported.\n", - COLVARS_NOT_IMPLEMENTED); - return COLVARS_NOT_IMPLEMENTED; - } + /// \brief Whether this proxy implementation has capability for scalable groups + virtual int scalable_group_coms(); - /// \brief Used by the atom class destructor: rather than deleting the array slot - /// (costly) set the corresponding atoms_ncopies to zero - virtual void clear_atom(int index) - { - if (((size_t) index) >= atoms_ids.size()) { - cvm::error("Error: trying to disable an atom that was not previously requested.\n", - INPUT_ERROR); - } - if (atoms_ncopies[index] > 0) { - atoms_ncopies[index] -= 1; - } - } + /// Prepare this group for collective variables calculation, selecting atoms by internal ids (0-based) + virtual int init_atom_group(std::vector const &atoms_ids); - /// Get the numeric ID of the given atom (for the program) - inline int get_atom_id(int index) const - { - return atoms_ids[index]; - } + /// \brief Used by the atom_group class destructor + virtual void clear_atom_group(int index); - /// Get the mass of the given atom - inline cvm::real get_atom_mass(int index) const + /// Get the numeric ID of the given atom group (for the MD program) + inline int get_atom_group_id(int index) const { - return atoms_masses[index]; + return atom_groups_ids[index]; } - /// Get the charge of the given atom - inline cvm::real get_atom_charge(int index) const + /// Get the mass of the given atom group + inline cvm::real get_atom_group_mass(int index) const { - return atoms_charges[index]; + return atom_groups_masses[index]; } - /// Read the current position of the given atom - inline cvm::rvector get_atom_position(int index) const + /// Get the charge of the given atom group + inline cvm::real get_atom_group_charge(int index) const { - return atoms_positions[index]; + return atom_groups_charges[index]; } - /// Read the current total force of the given atom - inline cvm::rvector get_atom_total_force(int index) const + /// Read the current position of the center of mass given atom group + inline cvm::rvector get_atom_group_com(int index) const { - return atoms_total_forces[index]; + return atom_groups_coms[index]; } - /// Request that this force is applied to the given atom - inline void apply_atom_force(int index, cvm::rvector const &new_force) + /// Read the current total force of the given atom group + inline cvm::rvector get_atom_group_total_force(int index) const { - atoms_new_colvar_forces[index] += new_force; + return atom_groups_total_forces[index]; } - /// Read the current velocity of the given atom - virtual cvm::rvector get_atom_velocity(int index) + /// Request that this force is applied to the given atom group + inline void apply_atom_group_force(int index, cvm::rvector const &new_force) { - cvm::error("Error: reading the current velocity of an atom is not yet implemented.\n", - COLVARS_NOT_IMPLEMENTED); - return cvm::rvector(0.0); + atom_groups_new_colvar_forces[index] += new_force; } - // useful functions for data management outside this class - inline std::vector *modify_atom_ids() { return &atoms_ids; } - inline std::vector *modify_atom_masses() { return &atoms_masses; } - inline std::vector *modify_atom_charges() { return &atoms_charges; } - inline std::vector *modify_atom_positions() { return &atoms_positions; } - inline std::vector *modify_atom_total_forces() { return &atoms_total_forces; } - inline std::vector *modify_atom_new_colvar_forces() { return &atoms_new_colvar_forces; } - - /// \brief Read atom identifiers from a file \param filename name of - /// the file (usually a PDB) \param atoms array to which atoms read - /// from "filename" will be appended \param pdb_field (optiona) if - /// "filename" is a PDB file, use this field to determine which are - /// the atoms to be set - virtual int load_atoms(char const *filename, - cvm::atom_group &atoms, - std::string const &pdb_field, - double const pdb_field_value = 0.0) + /// Read the current velocity of the given atom group + inline cvm::rvector get_atom_group_velocity(int index) { - cvm::error("Error: loading atom identifiers from a file is currently not implemented.\n", + cvm::error("Error: reading the current velocity of an atom group is not yet implemented.\n", COLVARS_NOT_IMPLEMENTED); - return COLVARS_NOT_IMPLEMENTED; - } - - /// \brief Load the coordinates for a group of atoms from a file - /// (usually a PDB); if "pos" is already allocated, the number of its - /// elements must match the number of atoms in "filename" - virtual int load_coords(char const *filename, - std::vector &pos, - const std::vector &indices, - std::string const &pdb_field, - double const pdb_field_value = 0.0) - { - cvm::error("Error: loading atomic coordinates from a file is currently not implemented.\n"); - return COLVARS_NOT_IMPLEMENTED; + return cvm::rvector(0.0); } - // **************** ACCESS GROUP DATA **************** - protected: /// \brief Array of 0-based integers used to uniquely associate atom groups /// within the host program std::vector atom_groups_ids; /// \brief Keep track of how many times each group is used by a separate cvc std::vector atom_groups_ncopies; /// \brief Total masses of the atom groups std::vector atom_groups_masses; /// \brief Total charges of the atom groups (allow redefinition during a run, as done e.g. in LAMMPS) std::vector atom_groups_charges; /// \brief Current centers of mass of the atom groups std::vector atom_groups_coms; /// \brief Most recently updated total forces on the com of each group std::vector atom_groups_total_forces; /// \brief Forces applied from colvars, to be communicated to the MD integrator std::vector atom_groups_new_colvar_forces; - /// TODO Add here containers of handles to cvc objects that are computed in parallel + /// Used by all init_atom_group() functions: create a slot for an atom group not requested yet + int add_atom_group_slot(int atom_group_id); +}; + + +/// \brief Methods for SMP parallelization +class colvarproxy_smp { public: - /// \brief Whether this proxy implementation has capability for scalable groups - virtual int scalable_group_coms() - { - return COLVARS_NOT_IMPLEMENTED; - } + /// Constructor + colvarproxy_smp(); - /// Used by all init_atom_group() functions: create a slot for an atom group not requested yet - // TODO Add a handle to cvc objects - inline int add_atom_group_slot(int atom_group_id) - { - atom_groups_ids.push_back(atom_group_id); - atom_groups_ncopies.push_back(1); - atom_groups_masses.push_back(1.0); - atom_groups_charges.push_back(0.0); - atom_groups_coms.push_back(cvm::rvector(0.0, 0.0, 0.0)); - atom_groups_total_forces.push_back(cvm::rvector(0.0, 0.0, 0.0)); - atom_groups_new_colvar_forces.push_back(cvm::rvector(0.0, 0.0, 0.0)); - return (atom_groups_ids.size() - 1); - } + /// Destructor + virtual ~colvarproxy_smp(); - /// Prepare this group for collective variables calculation, selecting atoms by internal ids (0-based) - virtual int init_atom_group(std::vector const &atoms_ids) // TODO Add a handle to cvc objects - { - cvm::error("Error: initializing a group outside of the colvars module is currently not supported.\n", - COLVARS_NOT_IMPLEMENTED); - return COLVARS_NOT_IMPLEMENTED; - } + /// Whether threaded parallelization should be used (TODO: make this a + /// cvm::deps feature) + bool b_smp_active; - /// \brief Used by the atom_group class destructor - virtual void clear_atom_group(int index) - { - if (cvm::debug()) { - log("Trying to remove/disable atom group number "+cvm::to_str(index)+"\n"); - } - - if (((size_t) index) >= atom_groups_ids.size()) { - cvm::error("Error: trying to disable an atom group that was not previously requested.\n", - INPUT_ERROR); - } - - if (atom_groups_ncopies[index] > 0) { - atom_groups_ncopies[index] -= 1; - } - } + /// Whether threaded parallelization is available (TODO: make this a cvm::deps feature) + virtual int smp_enabled(); - /// Get the numeric ID of the given atom group (for the MD program) - inline cvm::real get_atom_group_id(int index) const - { - return atom_groups_ids[index]; - } + /// Distribute calculation of colvars (and their components) across threads + virtual int smp_colvars_loop(); - /// Get the mass of the given atom group - inline cvm::real get_atom_group_mass(int index) const - { - return atom_groups_masses[index]; - } + /// Distribute calculation of biases across threads + virtual int smp_biases_loop(); - /// Get the charge of the given atom group - inline cvm::real get_atom_group_charge(int index) const + /// Distribute calculation of biases across threads 2nd through last, with all scripted biased on 1st thread + virtual int smp_biases_script_loop(); + + /// Index of this thread + virtual int smp_thread_id(); + + /// Number of threads sharing this address space + virtual int smp_num_threads(); + + /// Lock the proxy's shared data for access by a thread, if threads are implemented; if not implemented, does nothing + virtual int smp_lock(); + + /// Attempt to lock the proxy's shared data + virtual int smp_trylock(); + + /// Release the lock + virtual int smp_unlock(); +}; + + +/// \brief Methods for multiple-replica communication +class colvarproxy_replicas { + +public: + + /// Constructor + colvarproxy_replicas(); + + /// Destructor + virtual ~colvarproxy_replicas(); + + /// \brief Indicate if multi-replica support is available and active + virtual bool replica_enabled(); + + /// \brief Index of this replica + virtual int replica_index(); + + /// \brief Total number of replica + virtual int replica_num(); + + /// \brief Synchronize replica + virtual void replica_comm_barrier(); + + /// \brief Receive data from other replica + virtual int replica_comm_recv(char* msg_data, int buf_len, int src_rep); + + /// \brief Send data to other replica + virtual int replica_comm_send(char* msg_data, int msg_len, int dest_rep); + +}; + + +/// Method for scripting language interface (Tcl or Python) +class colvarproxy_script { + +public: + + /// Constructor + colvarproxy_script(); + + /// Destructor + virtual ~colvarproxy_script(); + + /// Convert a script object (Tcl or Python call argument) to a C string + virtual char *script_obj_to_str(unsigned char *obj); + + /// Pointer to the scripting interface object + /// (does not need to be allocated in a new interface) + colvarscript *script; + + /// is a user force script defined? + bool force_script_defined; + + /// Do we have a scripting interface? + bool have_scripts; + + /// Run a user-defined colvar forces script + virtual int run_force_callback(); + + virtual int run_colvar_callback( + std::string const &name, + std::vector const &cvcs, + colvarvalue &value); + + virtual int run_colvar_gradient_callback( + std::string const &name, + std::vector const &cvcs, + std::vector > &gradient); +}; + + +/// Methods for data input/output +class colvarproxy_io { + +public: + + /// Constructor + colvarproxy_io(); + + /// Destructor + virtual ~colvarproxy_io(); + + /// \brief Save the current frame number in the argument given + // Returns error code + virtual int get_frame(long int &); + + /// \brief Set the current frame number (as well as colvarmodule::it) + // Returns error code + virtual int set_frame(long int); + + /// \brief Returns a reference to the given output channel; + /// if this is not open already, then open it + virtual std::ostream *output_stream(std::string const &output_name, + std::ios_base::openmode mode = + std::ios_base::out); + + /// \brief Flushes the given output channel + virtual int flush_output_stream(std::ostream *os); + + /// \brief Closes the given output channel + virtual int close_output_stream(std::string const &output_name); + + /// \brief Rename the given file, before overwriting it + virtual int backup_file(char const *filename); + + /// \brief Rename the given file, before overwriting it + inline int backup_file(std::string const &filename) { - return atom_groups_charges[index]; + return backup_file(filename.c_str()); } - /// Read the current position of the center of mass given atom group - inline cvm::rvector get_atom_group_com(int index) const + /// \brief Prefix of the input state file + inline std::string & input_prefix() { - return atom_groups_coms[index]; + return input_prefix_str; } - /// Read the current total force of the given atom group - inline cvm::rvector get_atom_group_total_force(int index) const + /// \brief Prefix to be used for output restart files + inline std::string & restart_output_prefix() { - return atom_groups_total_forces[index]; + return restart_output_prefix_str; } - /// Request that this force is applied to the given atom group - inline void apply_atom_group_force(int index, cvm::rvector const &new_force) + /// \brief Prefix to be used for output files (final system + /// configuration) + inline std::string & output_prefix() { - atom_groups_new_colvar_forces[index] += new_force; + return output_prefix_str; } - /// Read the current velocity of the given atom group - virtual cvm::rvector get_atom_group_velocity(int index) +protected: + + /// \brief Prefix to be used for input files (restarts, not + /// configuration) + std::string input_prefix_str, output_prefix_str, restart_output_prefix_str; + + /// \brief Currently opened output files: by default, these are ofstream objects. + /// Allows redefinition to implement different output mechanisms + std::list output_files; + /// \brief Identifiers for output_stream objects: by default, these are the names of the files + std::list output_stream_names; + +}; + + + +/// \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. +/// Only pure virtual functions ("= 0") must be reimplemented to ensure baseline functionality. +class colvarproxy + : public colvarproxy_system, + public colvarproxy_atoms, + public colvarproxy_atom_groups, + public colvarproxy_smp, + public colvarproxy_replicas, + public colvarproxy_script, + public colvarproxy_io +{ + +public: + + /// Pointer to the main object + colvarmodule *colvars; + + /// Constructor + colvarproxy(); + + /// Destructor + virtual ~colvarproxy(); + + /// \brief Reset proxy state, e.g. requested atoms + virtual int reset(); + + /// (Re)initialize required member data after construction + virtual int setup(); + + /// \brief Update data required by the colvars module (e.g. cache atom positions) + /// + /// TODO Break up colvarproxy_namd and colvarproxy_lammps function into these + virtual int update_input(); + + /// \brief Update data based from the results of a module update (e.g. send forces) + virtual int update_output(); + + /// Print a message to the main log + virtual void log(std::string const &message) = 0; + + /// Print a message to the main log and let the rest of the program handle the error + virtual void error(std::string const &message) = 0; + + /// Print a message to the main log and exit with error code + virtual void fatal_error(std::string const &message) = 0; + + /// \brief Restarts will be written each time this number of steps has passed + virtual size_t restart_frequency(); + + /// Whether a simulation is running (warn against irrecovarable errors) + inline bool simulation_running() const { - cvm::error("Error: reading the current velocity of an atom group is not yet implemented.\n", - COLVARS_NOT_IMPLEMENTED); - return cvm::rvector(0.0); + return b_simulation_running; } +protected: + + /// Whether a simulation is running (warn against irrecovarable errors) + bool b_simulation_running; + }; #endif diff --git a/lib/colvars/colvars_version.h b/lib/colvars/colvars_version.h new file mode 100644 index 000000000..e54475642 --- /dev/null +++ b/lib/colvars/colvars_version.h @@ -0,0 +1,8 @@ +#define COLVARS_VERSION "2017-07-15" +// 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. + diff --git a/lib/colvars/colvarscript.cpp b/lib/colvars/colvarscript.cpp index f192dcb7c..5bb2faae2 100644 --- a/lib/colvars/colvarscript.cpp +++ b/lib/colvars/colvarscript.cpp @@ -1,582 +1,582 @@ // -*- 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 "colvarscript.h" +#include "colvarproxy.h" #include "colvardeps.h" colvarscript::colvarscript(colvarproxy *p) : proxy(p), colvars(p->colvars), proxy_error(0) { } extern "C" { // Generic hooks; NAMD and VMD have Tcl-specific versions in the respective proxies - int run_colvarscript_command(int argc, const char **argv) + int run_colvarscript_command(int objc, unsigned char *const objv[]) { colvarproxy *cvp = cvm::proxy; if (!cvp) { return -1; } if (!cvp->script) { cvm::error("Called run_colvarscript_command without a script object initialized.\n"); return -1; } - return cvp->script->run(argc, argv); + return cvp->script->run(objc, objv); } const char * get_colvarscript_result() { colvarproxy *cvp = cvm::proxy; if (!cvp->script) { cvm::error("Called run_colvarscript_command without a script object initialized.\n"); return ""; } return cvp->script->result.c_str(); } } /// Run method based on given arguments -int colvarscript::run(int argc, char const *argv[]) { - - result = ""; +int colvarscript::run(int objc, unsigned char *const objv[]) +{ + result.clear(); if (cvm::debug()) { - cvm::log("Called script run with " + cvm::to_str(argc) + " args"); - for (int i = 0; i < argc; i++) { cvm::log(argv[i]); } + cvm::log("Called script run with " + cvm::to_str(objc) + " args:"); + for (int i = 0; i < objc; i++) { + cvm::log(obj_to_str(objv[i])); + } } - if (argc < 2) { + if (objc < 2) { result = help_string(); return COLVARS_OK; } - std::string cmd = argv[1]; + std::string const cmd(obj_to_str(objv[1])); int error_code = COLVARS_OK; if (cmd == "colvar") { - return proc_colvar(argc-1, &(argv[1])); + if (objc < 3) { + result = "Missing parameters\n" + help_string(); + return COLVARSCRIPT_ERROR; + } + std::string const name(obj_to_str(objv[2])); + colvar *cv = cvm::colvar_by_name(name); + if (cv == NULL) { + result = "Colvar not found: " + name; + return COLVARSCRIPT_ERROR; + } + return proc_colvar(cv, objc-1, &(objv[1])); } if (cmd == "bias") { - return proc_bias(argc-1, &(argv[1])); + if (objc < 3) { + result = "Missing parameters\n" + help_string(); + return COLVARSCRIPT_ERROR; + } + std::string const name(obj_to_str(objv[2])); + colvarbias *b = cvm::bias_by_name(name); + if (b == NULL) { + result = "Bias not found: " + name; + return COLVARSCRIPT_ERROR; + } + return proc_bias(b, objc-1, &(objv[1])); } if (cmd == "version") { result = COLVARS_VERSION; return COLVARS_OK; } if (cmd == "reset") { /// Delete every child object colvars->reset(); return COLVARS_OK; } if (cmd == "delete") { // Note: the delete bit may be ignored by some backends // it is mostly useful in VMD colvars->set_error_bits(DELETE_COLVARS); return COLVARS_OK; } if (cmd == "update") { error_code |= proxy->update_input(); error_code |= colvars->calc(); error_code |= proxy->update_output(); if (error_code) { - result += "Error updating the colvars module.\n"; + result += "Error updating the Colvars module.\n"; } return error_code; } if (cmd == "list") { - if (argc == 2) { + if (objc == 2) { for (std::vector::iterator cvi = colvars->colvars.begin(); cvi != colvars->colvars.end(); ++cvi) { result += (cvi == colvars->colvars.begin() ? "" : " ") + (*cvi)->name; } return COLVARS_OK; - } else if (argc == 3 && !strcmp(argv[2], "biases")) { + } else if (objc == 3 && !strcmp(obj_to_str(objv[2]), "biases")) { for (std::vector::iterator bi = colvars->biases.begin(); bi != colvars->biases.end(); ++bi) { result += (bi == colvars->biases.begin() ? "" : " ") + (*bi)->name; } return COLVARS_OK; } else { result = "Wrong arguments to command \"list\"\n" + help_string(); return COLVARSCRIPT_ERROR; } } /// Parse config from file if (cmd == "configfile") { - if (argc < 3) { + if (objc < 3) { result = "Missing arguments\n" + help_string(); return COLVARSCRIPT_ERROR; } - if (colvars->read_config_file(argv[2]) == COLVARS_OK) { + if (colvars->read_config_file(obj_to_str(objv[2])) == COLVARS_OK) { return COLVARS_OK; } else { result = "Error parsing configuration file"; return COLVARSCRIPT_ERROR; } } /// Parse config from string if (cmd == "config") { - if (argc < 3) { + if (objc < 3) { result = "Missing arguments\n" + help_string(); return COLVARSCRIPT_ERROR; } - std::string conf = argv[2]; + std::string const conf(obj_to_str(objv[2])); if (colvars->read_config_string(conf) == COLVARS_OK) { return COLVARS_OK; } else { result = "Error parsing configuration string"; return COLVARSCRIPT_ERROR; } } /// Load an input state file if (cmd == "load") { - if (argc < 3) { + if (objc < 3) { result = "Missing arguments\n" + help_string(); return COLVARSCRIPT_ERROR; } - proxy->input_prefix() = argv[2]; + proxy->input_prefix() = obj_to_str(objv[2]); if (colvars->setup_input() == COLVARS_OK) { return COLVARS_OK; } else { result = "Error loading state file"; return COLVARSCRIPT_ERROR; } } /// Save to an output state file if (cmd == "save") { - if (argc < 3) { + if (objc < 3) { result = "Missing arguments"; return COLVARSCRIPT_ERROR; } - proxy->output_prefix_str = argv[2]; + proxy->output_prefix() = obj_to_str(objv[2]); int error = 0; error |= colvars->setup_output(); error |= colvars->write_output_files(); return error ? COLVARSCRIPT_ERROR : COLVARS_OK; } /// Print the values that would go on colvars.traj if (cmd == "printframelabels") { std::ostringstream os; colvars->write_traj_label(os); result = os.str(); return COLVARS_OK; } if (cmd == "printframe") { std::ostringstream os; colvars->write_traj(os); result = os.str(); return COLVARS_OK; } if (cmd == "frame") { - if (argc == 2) { + if (objc == 2) { long int f; int error = proxy->get_frame(f); if (error == COLVARS_OK) { result = cvm::to_str(f); return COLVARS_OK; } else { result = "Frame number is not available"; return COLVARSCRIPT_ERROR; } - } else if (argc == 3) { + } else if (objc == 3) { // Failure of this function does not trigger an error, but // returns nonzero, to let scripts detect available frames - int error = proxy->set_frame(strtol(argv[2], NULL, 10)); + int error = proxy->set_frame(strtol(obj_to_str(objv[2]), NULL, 10)); result = cvm::to_str(error == COLVARS_OK ? 0 : -1); return COLVARS_OK; } else { result = "Wrong arguments to command \"frame\"\n" + help_string(); return COLVARSCRIPT_ERROR; } } if (cmd == "addenergy") { - if (argc == 3) { - colvars->total_bias_energy += strtod(argv[2], NULL); + if (objc == 3) { + colvars->total_bias_energy += strtod(obj_to_str(objv[2]), NULL); return COLVARS_OK; } else { result = "Wrong arguments to command \"addenergy\"\n" + help_string(); return COLVARSCRIPT_ERROR; } } result = "Syntax error\n" + help_string(); return COLVARSCRIPT_ERROR; } -int colvarscript::proc_colvar(int argc, char const *argv[]) { - if (argc < 3) { - result = "Missing parameters\n" + help_string(); - return COLVARSCRIPT_ERROR; - } +int colvarscript::proc_colvar(colvar *cv, int objc, unsigned char *const objv[]) { - std::string name = argv[1]; - colvar *cv = cvm::colvar_by_name(name); - if (cv == NULL) { - result = "Colvar not found: " + name; - return COLVARSCRIPT_ERROR; - } - std::string subcmd = argv[2]; + std::string const subcmd(obj_to_str(objv[2])); if (subcmd == "value") { result = (cv->value()).to_simple_string(); return COLVARS_OK; } if (subcmd == "width") { result = cvm::to_str(cv->width, 0, cvm::cv_prec); return COLVARS_OK; } if (subcmd == "type") { result = cv->value().type_desc(cv->value().value_type); return COLVARS_OK; } if (subcmd == "update") { cv->calc(); cv->update_forces_energy(); result = (cv->value()).to_simple_string(); return COLVARS_OK; } if (subcmd == "delete") { size_t i; for (i = 0; i < cv->biases.size(); i++) { delete cv->biases[i]; } - cv->biases.resize(0); + cv->biases.clear(); // colvar destructor is tasked with the cleanup delete cv; // TODO this could be done by the destructors - colvars->write_traj_label(colvars->cv_traj_os); + colvars->write_traj_label(*(colvars->cv_traj_os)); return COLVARS_OK; } if (subcmd == "getconfig") { result = cv->get_config(); return COLVARS_OK; } if (subcmd == "getappliedforce") { result = (cv->applied_force()).to_simple_string(); return COLVARS_OK; } if (subcmd == "getsystemforce") { // TODO warning here result = (cv->total_force()).to_simple_string(); return COLVARS_OK; } if (subcmd == "gettotalforce") { result = (cv->total_force()).to_simple_string(); return COLVARS_OK; } if (subcmd == "addforce") { - if (argc < 4) { + if (objc < 4) { result = "addforce: missing parameter: force value\n" + help_string(); return COLVARSCRIPT_ERROR; } - std::string f_str = argv[3]; + std::string const f_str(obj_to_str(objv[3])); std::istringstream is(f_str); is.width(cvm::cv_width); is.precision(cvm::cv_prec); colvarvalue force(cv->value()); force.is_derivative(); if (force.from_simple_string(is.str()) != COLVARS_OK) { result = "addforce : error parsing force value"; return COLVARSCRIPT_ERROR; } cv->add_bias_force(force); result = force.to_simple_string(); return COLVARS_OK; } if (subcmd == "cvcflags") { - if (argc < 4) { + if (objc < 4) { result = "cvcflags: missing parameter: vector of flags"; return COLVARSCRIPT_ERROR; } - std::string flags_str = argv[3]; + std::string const flags_str(obj_to_str(objv[3])); std::istringstream is(flags_str); std::vector flags; int flag; while (is >> flag) { flags.push_back(flag != 0); } int res = cv->set_cvc_flags(flags); if (res != COLVARS_OK) { result = "Error setting CVC flags"; return COLVARSCRIPT_ERROR; } result = "0"; return COLVARS_OK; } if ((subcmd == "get") || (subcmd == "set") || (subcmd == "state")) { - return proc_features(cv, argc, argv); + return proc_features(cv, objc, objv); } result = "Syntax error\n" + help_string(); return COLVARSCRIPT_ERROR; } -int colvarscript::proc_bias(int argc, char const *argv[]) { - if (argc < 3) { - result = "Missing parameters\n" + help_string(); - return COLVARSCRIPT_ERROR; - } - - std::string name = argv[1]; - colvarbias *b = cvm::bias_by_name(name); - if (b == NULL) { - result = "Bias not found: " + name; - return COLVARSCRIPT_ERROR; - } +int colvarscript::proc_bias(colvarbias *b, int objc, unsigned char *const objv[]) { - std::string subcmd = argv[2]; + std::string const key(obj_to_str(objv[0])); + std::string const subcmd(obj_to_str(objv[2])); if (subcmd == "energy") { result = cvm::to_str(b->get_energy()); return COLVARS_OK; } if (subcmd == "update") { b->update(); result = cvm::to_str(b->get_energy()); return COLVARS_OK; } if (subcmd == "getconfig") { result = b->get_config(); return COLVARS_OK; } // Subcommands for MW ABF if (subcmd == "bin") { int r = b->current_bin(); result = cvm::to_str(r); return COLVARS_OK; } if (subcmd == "binnum") { int r = b->bin_num(); if (r < 0) { result = "Error: calling bin_num() for bias " + b->name; return COLVARSCRIPT_ERROR; } result = cvm::to_str(r); return COLVARS_OK; } if (subcmd == "share") { int r = b->replica_share(); if (r < 0) { result = "Error: calling replica_share() for bias " + b->name; return COLVARSCRIPT_ERROR; } result = cvm::to_str(r); return COLVARS_OK; } // End commands for MW ABF if (subcmd == "delete") { // the bias destructor takes care of the cleanup at cvm level delete b; // TODO this could be done by the destructors - colvars->write_traj_label(colvars->cv_traj_os); + colvars->write_traj_label(*(colvars->cv_traj_os)); return COLVARS_OK; } if ((subcmd == "get") || (subcmd == "set") || (subcmd == "state")) { - return proc_features(b, argc, argv); + return proc_features(b, objc, objv); } - if (argc >= 4) { - std::string param = argv[3]; + if (objc >= 4) { + std::string const param(obj_to_str(objv[3])); if (subcmd == "count") { int index; if (!(std::istringstream(param) >> index)) { result = "bin_count: error parsing bin index"; return COLVARSCRIPT_ERROR; } result = cvm::to_str(b->bin_count(index)); return COLVARS_OK; } result = "Syntax error\n" + help_string(); return COLVARSCRIPT_ERROR; } result = "Syntax error\n" + help_string(); return COLVARSCRIPT_ERROR; } int colvarscript::proc_features(colvardeps *obj, - int argc, char const *argv[]) { + int objc, unsigned char *const objv[]) { // size was already checked before calling - std::string subcmd = argv[2]; + std::string const subcmd(obj_to_str(objv[2])); - if (argc == 3) { + if (objc == 3) { if (subcmd == "state") { // TODO make this returned as result? obj->print_state(); return COLVARS_OK; } // get and set commands require more arguments result = "Syntax error\n" + help_string(); return COLVARSCRIPT_ERROR; } if ((subcmd == "get") || (subcmd == "set")) { std::vector &features = obj->features(); - std::string const req_feature(argv[3]); + std::string const req_feature(obj_to_str(objv[3])); colvardeps::feature *f = NULL; int fid = 0; for (fid = 0; fid < int(features.size()); fid++) { if (features[fid]->description == colvarparse::to_lower_cppstr(req_feature)) { f = features[fid]; break; } } if (f == NULL) { result = "Error: feature \""+req_feature+"\" does not exist.\n"; return COLVARSCRIPT_ERROR; } else { if (! obj->is_available(fid)) { result = "Error: feature \""+req_feature+"\" is unavailable.\n"; return COLVARSCRIPT_ERROR; } if (subcmd == "get") { result = cvm::to_str(obj->is_enabled(fid) ? 1 : 0); return COLVARS_OK; } if (subcmd == "set") { - if (argc == 5) { + if (objc == 5) { std::string const yesno = - colvarparse::to_lower_cppstr(std::string(argv[4])); + colvarparse::to_lower_cppstr(std::string(obj_to_str(objv[4]))); if ((yesno == std::string("yes")) || (yesno == std::string("on")) || (yesno == std::string("1"))) { obj->enable(fid); return COLVARS_OK; } else if ((yesno == std::string("no")) || (yesno == std::string("off")) || (yesno == std::string("0"))) { - // TODO disable() function does not exist yet, - // dependencies will not be resolved - // obj->disable(fid); - obj->set_enabled(fid, false); + obj->disable(fid); return COLVARS_OK; } } result = "Syntax error\n" + help_string(); return COLVARSCRIPT_ERROR; } } } result = "Syntax error\n" + help_string(); return COLVARSCRIPT_ERROR; } std::string colvarscript::help_string() { std::string buf; buf = "Usage: cv [args...]\n\ \n\ -Managing the colvars module:\n\ +Managing the Colvars module:\n\ configfile -- read configuration from a file\n\ config -- read configuration from the given string\n\ reset -- delete all internal configuration\n\ - delete -- delete this colvars module instance\n\ + delete -- delete this Colvars module instance\n\ version -- return version of colvars code\n\ \n\ Input and output:\n\ list -- return a list of all variables\n\ list biases -- return a list of all biases\n\ load -- load a state file (requires configuration)\n\ save -- save a state file (requires configuration)\n\ update -- recalculate colvars and biases\n\ addenergy -- add to the total bias energy\n\ printframe -- return a summary of the current frame\n\ printframelabels -- return labels to annotate printframe's output\n"; long int tmp; if (proxy->get_frame(tmp) != COLVARS_NOT_IMPLEMENTED) { buf += "\ frame -- return current frame number\n\ frame -- set frame number\n"; } buf += "\n\ Accessing collective variables:\n\ colvar value -- return the current value of colvar \n\ colvar update -- recalculate colvar \n\ colvar type -- return the type of colvar \n\ colvar delete -- delete colvar \n\ colvar addforce -- apply given force on colvar \n\ colvar getconfig -- return config string of colvar \n\ colvar cvcflags -- enable or disable cvcs according to 0/1 flags\n\ colvar get -- get the value of the colvar feature \n\ colvar set -- set the value of the colvar feature \n\ \n\ Accessing biases:\n\ bias energy -- return the current energy of bias \n\ bias update -- recalculate bias \n\ bias delete -- delete bias \n\ bias getconfig -- return config string of bias \n\ bias get -- get the value of the bias feature \n\ bias set -- set the value of the bias feature \n\ "; return buf; } diff --git a/lib/colvars/colvarscript.h b/lib/colvars/colvarscript.h index 46b1ddd20..94d451809 100644 --- a/lib/colvars/colvarscript.h +++ b/lib/colvars/colvarscript.h @@ -1,63 +1,71 @@ // -*- 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 COLVARSCRIPT_H #define COLVARSCRIPT_H #include #include "colvarmodule.h" #include "colvarvalue.h" #include "colvarbias.h" #include "colvarproxy.h" // Only these error values are part of the scripting interface #define COLVARSCRIPT_ERROR -1 #define COLVARSCRIPT_OK 0 class colvarscript { private: colvarproxy *proxy; colvarmodule *colvars; inline colvarscript() {} // no-argument construction forbidden public: friend class colvarproxy; colvarscript(colvarproxy * p); inline ~colvarscript() {} /// If an error is caught by the proxy through fatal_error(), this is set to COLVARSCRIPT_ERROR int proxy_error; /// If an error is returned by one of the methods, it should set this to the error message std::string result; - /// Run script command with given positional arguments - int run(int argc, char const *argv[]); + /// Run script command with given positional arguments (objects) + int run(int objc, unsigned char *const objv[]); private: /// Run subcommands on colvar - int proc_colvar(int argc, char const *argv[]); + int proc_colvar(colvar *cv, int argc, unsigned char *const argv[]); /// Run subcommands on bias - int proc_bias(int argc, char const *argv[]); + int proc_bias(colvarbias *b, int argc, unsigned char *const argv[]); /// Run subcommands on base colvardeps object (colvar, bias, ...) int proc_features(colvardeps *obj, - int argc, char const *argv[]); + int argc, unsigned char *const argv[]); - /// Builds and return a short help + /// Build and return a short help std::string help_string(void); + +public: + + inline char const *obj_to_str(unsigned char *const obj) + { + return cvm::proxy->script_obj_to_str(obj); + } + }; #endif diff --git a/lib/colvars/colvartypes.h b/lib/colvars/colvartypes.h index e0cebb83b..17c09a509 100644 --- a/lib/colvars/colvartypes.h +++ b/lib/colvars/colvartypes.h @@ -1,1599 +1,1604 @@ // -*- 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 COLVARTYPES_H #define COLVARTYPES_H #include #include #include "colvarmodule.h" #ifndef PI #define PI 3.14159265358979323846 #endif // ---------------------------------------------------------------------- /// Linear algebra functions and data types used in the collective /// variables implemented so far // ---------------------------------------------------------------------- /// \brief Arbitrary size array (one dimensions) suitable for linear /// algebra operations (i.e. for floating point numbers it can be used /// with library functions) template class colvarmodule::vector1d { protected: std::vector data; public: /// Default constructor inline vector1d(size_t const n = 0) { data.resize(n); reset(); } /// Constructor from C array inline vector1d(size_t const n, T const *t) { data.resize(n); reset(); size_t i; for (i = 0; i < size(); i++) { data[i] = t[i]; } } /// Return a pointer to the data location inline T * c_array() { if (data.size() > 0) { return &(data[0]); } else { return NULL; } } /// Return a reference to the data inline std::vector &data_array() { return data; } inline ~vector1d() { data.clear(); } /// Set all elements to zero inline void reset() { data.assign(data.size(), T(0.0)); } inline size_t size() const { return data.size(); } inline void resize(size_t const n) { data.resize(n); } + inline void clear() + { + data.clear(); + } + inline T & operator [] (size_t const i) { return data[i]; } inline T const & operator [] (size_t const i) const { return data[i]; } inline static void check_sizes(vector1d const &v1, vector1d const &v2) { if (v1.size() != v2.size()) { cvm::error("Error: trying to perform an operation between vectors of different sizes, "+ cvm::to_str(v1.size())+" and "+cvm::to_str(v2.size())+".\n"); } } inline void operator += (vector1d const &v) { check_sizes(*this, v); size_t i; for (i = 0; i < this->size(); i++) { (*this)[i] += v[i]; } } inline void operator -= (vector1d const &v) { check_sizes(*this, v); size_t i; for (i = 0; i < this->size(); i++) { (*this)[i] -= v[i]; } } inline void operator *= (cvm::real const &a) { size_t i; for (i = 0; i < this->size(); i++) { (*this)[i] *= a; } } inline void operator /= (cvm::real const &a) { size_t i; for (i = 0; i < this->size(); i++) { (*this)[i] /= a; } } inline friend vector1d operator + (vector1d const &v1, vector1d const &v2) { check_sizes(v1.size(), v2.size()); vector1d result(v1.size()); size_t i; for (i = 0; i < v1.size(); i++) { result[i] = v1[i] + v2[i]; } return result; } inline friend vector1d operator - (vector1d const &v1, vector1d const &v2) { check_sizes(v1.size(), v2.size()); vector1d result(v1.size()); size_t i; for (i = 0; i < v1.size(); i++) { result[i] = v1[i] - v2[i]; } return result; } inline friend vector1d operator * (vector1d const &v, cvm::real const &a) { vector1d result(v.size()); size_t i; for (i = 0; i < v.size(); i++) { result[i] = v[i] * a; } return result; } inline friend vector1d operator * (cvm::real const &a, vector1d const &v) { return v * a; } inline friend vector1d operator / (vector1d const &v, cvm::real const &a) { vector1d result(v.size()); size_t i; for (i = 0; i < v.size(); i++) { result[i] = v[i] / a; } return result; } /// Inner product inline friend T operator * (vector1d const &v1, vector1d const &v2) { check_sizes(v1.size(), v2.size()); T prod(0.0); size_t i; for (i = 0; i < v1.size(); i++) { prod += v1[i] * v2[i]; } return prod; } /// Squared norm inline cvm::real norm2() const { cvm::real result = 0.0; size_t i; for (i = 0; i < this->size(); i++) { result += (*this)[i] * (*this)[i]; } return result; } inline cvm::real norm() const { return std::sqrt(this->norm2()); } inline cvm::real sum() const { cvm::real result = 0.0; size_t i; for (i = 0; i < this->size(); i++) { result += (*this)[i]; } return result; } /// Slicing inline vector1d const slice(size_t const i1, size_t const i2) const { if ((i2 < i1) || (i2 >= this->size())) { cvm::error("Error: trying to slice a vector using incorrect boundaries.\n"); } vector1d result(i2 - i1); size_t i; for (i = 0; i < (i2 - i1); i++) { result[i] = (*this)[i1+i]; } return result; } /// Assign a vector to a slice of this vector inline void sliceassign(size_t const i1, size_t const i2, vector1d const &v) { if ((i2 < i1) || (i2 >= this->size())) { cvm::error("Error: trying to slice a vector using incorrect boundaries.\n"); } size_t i; for (i = 0; i < (i2 - i1); i++) { (*this)[i1+i] = v[i]; } } /// Formatted output inline size_t output_width(size_t const &real_width) const { return real_width*(this->size()) + 3*(this->size()-1) + 4; } inline friend std::istream & operator >> (std::istream &is, cvm::vector1d &v) { if (v.size() == 0) return is; size_t const start_pos = is.tellg(); char sep; if ( !(is >> sep) || !(sep == '(') ) { is.clear(); is.seekg(start_pos, std::ios::beg); is.setstate(std::ios::failbit); return is; } size_t count = 0; while ( (is >> v[count]) && (count < (v.size()-1) ? ((is >> sep) && (sep == ',')) : true) ) { if (++count == v.size()) break; } if (count < v.size()) { is.clear(); is.seekg(start_pos, std::ios::beg); is.setstate(std::ios::failbit); } return is; } inline friend std::ostream & operator << (std::ostream &os, cvm::vector1d const &v) { std::streamsize const w = os.width(); std::streamsize const p = os.precision(); os.width(2); os << "( "; size_t i; for (i = 0; i < v.size()-1; i++) { os.width(w); os.precision(p); os << v[i] << " , "; } os.width(w); os.precision(p); os << v[v.size()-1] << " )"; return os; } inline std::string to_simple_string() const { if (this->size() == 0) return std::string(""); std::ostringstream os; os.setf(std::ios::scientific, std::ios::floatfield); os.precision(cvm::cv_prec); os << (*this)[0]; size_t i; for (i = 1; i < this->size(); i++) { os << " " << (*this)[i]; } return os.str(); } inline int from_simple_string(std::string const &s) { std::stringstream stream(s); size_t i = 0; if (this->size()) { while ((stream >> (*this)[i]) && (i < this->size())) { i++; } if (i < this->size()) { return COLVARS_ERROR; } } else { T input; while (stream >> input) { if ((i % 100) == 0) { data.reserve(data.size()+100); } data.resize(data.size()+1); data[i] = input; i++; } } return COLVARS_OK; } }; /// \brief Arbitrary size array (two dimensions) suitable for linear /// algebra operations (i.e. for floating point numbers it can be used /// with library functions) template class colvarmodule::matrix2d { public: friend class row; size_t outer_length; size_t inner_length; protected: class row { public: T * data; size_t length; inline row(T * const row_data, size_t const inner_length) : data(row_data), length(inner_length) {} inline T & operator [] (size_t const j) { return *(data+j); } inline T const & operator [] (size_t const j) const { return *(data+j); } inline operator vector1d() const { return vector1d(length, data); } }; std::vector data; std::vector rows; std::vector pointers; public: /// Allocation routine, used by all constructors inline void resize(size_t const ol, size_t const il) { if ((ol > 0) && (il > 0)) { if (data.size() > 0) { // copy previous data size_t i, j; std::vector new_data(ol * il); for (i = 0; i < outer_length; i++) { for (j = 0; j < inner_length; j++) { new_data[il*i+j] = data[inner_length*i+j]; } } data.resize(ol * il); // copy them back data = new_data; } else { data.resize(ol * il); } outer_length = ol; inner_length = il; if (data.size() > 0) { // rebuild rows size_t i; rows.clear(); rows.reserve(outer_length); pointers.clear(); pointers.reserve(outer_length); for (i = 0; i < outer_length; i++) { rows.push_back(row(&(data[0])+inner_length*i, inner_length)); pointers.push_back(&(data[0])+inner_length*i); } } } else { // zero size data.clear(); rows.clear(); } } /// Deallocation routine inline void clear() { rows.clear(); data.clear(); } /// Set all elements to zero inline void reset() { data.assign(data.size(), T(0.0)); } inline size_t size() const { return data.size(); } /// Default constructor inline matrix2d() : outer_length(0), inner_length(0) { this->resize(0, 0); } inline matrix2d(size_t const ol, size_t const il) : outer_length(ol), inner_length(il) { this->resize(outer_length, inner_length); reset(); } /// Copy constructor inline matrix2d(matrix2d const &m) : outer_length(m.outer_length), inner_length(m.inner_length) { // reinitialize data and rows arrays this->resize(outer_length, inner_length); // copy data data = m.data; } /// Destructor inline ~matrix2d() { this->clear(); } /// Return a reference to the data inline std::vector &data_array() { return data; } inline row & operator [] (size_t const i) { return rows[i]; } inline row const & operator [] (size_t const i) const { return rows[i]; } /// Assignment inline matrix2d & operator = (matrix2d const &m) { if ((outer_length != m.outer_length) || (inner_length != m.inner_length)){ this->clear(); outer_length = m.outer_length; inner_length = m.inner_length; this->resize(outer_length, inner_length); } data = m.data; return *this; } /// Return the 2-d C array inline T ** c_array() { if (rows.size() > 0) { return &(pointers[0]); } else { return NULL; } } inline static void check_sizes(matrix2d const &m1, matrix2d const &m2) { if ((m1.outer_length != m2.outer_length) || (m1.inner_length != m2.inner_length)) { cvm::error("Error: trying to perform an operation between matrices of different sizes, "+ cvm::to_str(m1.outer_length)+"x"+cvm::to_str(m1.inner_length)+" and "+ cvm::to_str(m2.outer_length)+"x"+cvm::to_str(m2.inner_length)+".\n"); } } inline void operator += (matrix2d const &m) { check_sizes(*this, m); size_t i; for (i = 0; i < data.size(); i++) { data[i] += m.data[i]; } } inline void operator -= (matrix2d const &m) { check_sizes(*this, m); size_t i; for (i = 0; i < data.size(); i++) { data[i] -= m.data[i]; } } inline void operator *= (cvm::real const &a) { size_t i; for (i = 0; i < data.size(); i++) { data[i] *= a; } } inline void operator /= (cvm::real const &a) { size_t i; for (i = 0; i < data.size(); i++) { data[i] /= a; } } inline friend matrix2d operator + (matrix2d const &m1, matrix2d const &m2) { check_sizes(m1, m2); matrix2d result(m1.outer_length, m1.inner_length); size_t i; for (i = 0; i < m1.data.size(); i++) { result.data[i] = m1.data[i] + m2.data[i]; } return result; } inline friend matrix2d operator - (matrix2d const &m1, matrix2d const &m2) { check_sizes(m1, m2); matrix2d result(m1.outer_length, m1.inner_length); size_t i; for (i = 0; i < m1.data.size(); i++) { result.data[i] = m1.data[i] - m1.data[i]; } return result; } inline friend matrix2d operator * (matrix2d const &m, cvm::real const &a) { matrix2d result(m.outer_length, m.inner_length); size_t i; for (i = 0; i < m.data.size(); i++) { result.data[i] = m.data[i] * a; } return result; } inline friend matrix2d operator * (cvm::real const &a, matrix2d const &m) { return m * a; } inline friend matrix2d operator / (matrix2d const &m, cvm::real const &a) { matrix2d result(m.outer_length, m.inner_length); size_t i; for (i = 0; i < m.data.size(); i++) { result.data[i] = m.data[i] * a; } return result; } /// Matrix multiplication // inline friend matrix2d const & operator * (matrix2d const &m1, matrix2d const &m2) // { // matrix2d result(m1.outer_length, m2.inner_length); // if (m1.inner_length != m2.outer_length) { // cvm::error("Error: trying to multiply two matrices of incompatible sizes, "+ // cvm::to_str(m1.outer_length)+"x"+cvm::to_str(m1.inner_length)+" and "+ // cvm::to_str(m2.outer_length)+"x"+cvm::to_str(m2.inner_length)+".\n"); // } else { // size_t i, j, k; // for (i = 0; i < m1.outer_length; i++) { // for (j = 0; j < m2.inner_length; j++) { // for (k = 0; k < m1.inner_length; k++) { // result[i][j] += m1[i][k] * m2[k][j]; // } // } // } // } // return result; // } /// vector-matrix multiplication inline friend vector1d operator * (vector1d const &v, matrix2d const &m) { vector1d result(m.inner_length); if (m.outer_length != v.size()) { cvm::error("Error: trying to multiply a vector and a matrix of incompatible sizes, "+ cvm::to_str(v.size()) + " and " + cvm::to_str(m.outer_length)+"x"+cvm::to_str(m.inner_length) + ".\n"); } else { size_t i, k; for (i = 0; i < m.inner_length; i++) { for (k = 0; k < m.outer_length; k++) { result[i] += m[k][i] * v[k]; } } } return result; } // /// matrix-vector multiplication (unused for now) // inline friend vector1d const & operator * (matrix2d const &m, vector1d const &v) // { // vector1d result(m.outer_length); // if (m.inner_length != v.size()) { // cvm::error("Error: trying to multiply a matrix and a vector of incompatible sizes, "+ // cvm::to_str(m.outer_length)+"x"+cvm::to_str(m.inner_length) // + " and " + cvm::to_str(v.length) + ".\n"); // } else { // size_t i, k; // for (i = 0; i < m.outer_length; i++) { // for (k = 0; k < m.inner_length; k++) { // result[i] += m[i][k] * v[k]; // } // } // } // return result; // } /// Formatted output friend std::ostream & operator << (std::ostream &os, matrix2d const &m) { std::streamsize const w = os.width(); std::streamsize const p = os.precision(); os.width(2); os << "( "; size_t i; for (i = 0; i < m.outer_length; i++) { os << " ( "; size_t j; for (j = 0; j < m.inner_length-1; j++) { os.width(w); os.precision(p); os << m[i][j] << " , "; } os.width(w); os.precision(p); os << m[i][m.inner_length-1] << " )"; } os << " )"; return os; } inline std::string to_simple_string() const { if (this->size() == 0) return std::string(""); std::ostringstream os; os.setf(std::ios::scientific, std::ios::floatfield); os.precision(cvm::cv_prec); os << (*this)[0]; size_t i; for (i = 1; i < data.size(); i++) { os << " " << data[i]; } return os.str(); } inline int from_simple_string(std::string const &s) { std::stringstream stream(s); size_t i = 0; while ((stream >> data[i]) && (i < data.size())) { i++; } if (i < data.size()) { return COLVARS_ERROR; } return COLVARS_OK; } }; /// vector of real numbers with three components class colvarmodule::rvector { public: cvm::real x, y, z; inline rvector() : x(0.0), y(0.0), z(0.0) {} inline rvector(cvm::real const &x_i, cvm::real const &y_i, cvm::real const &z_i) : x(x_i), y(y_i), z(z_i) {} inline rvector(cvm::vector1d const &v) : x(v[0]), y(v[1]), z(v[2]) {} inline rvector(cvm::real t) : x(t), y(t), z(t) {} /// \brief Set all components to a scalar value inline void set(cvm::real const &value) { x = y = z = value; } /// \brief Assign all components inline void set(cvm::real const &x_i, cvm::real const &y_i, cvm::real const &z_i) { x = x_i; y = y_i; z = z_i; } /// \brief Set all components to zero inline void reset() { x = y = z = 0.0; } /// \brief Access cartesian components by index inline cvm::real & operator [] (int const &i) { return (i == 0) ? x : (i == 1) ? y : (i == 2) ? z : x; } /// \brief Access cartesian components by index inline cvm::real const & operator [] (int const &i) const { return (i == 0) ? x : (i == 1) ? y : (i == 2) ? z : x; } inline cvm::vector1d const as_vector() const { cvm::vector1d result(3); result[0] = this->x; result[1] = this->y; result[2] = this->z; return result; } inline cvm::rvector & operator = (cvm::real const &v) { x = v; y = v; z = v; return *this; } inline void operator += (cvm::rvector const &v) { x += v.x; y += v.y; z += v.z; } inline void operator -= (cvm::rvector const &v) { x -= v.x; y -= v.y; z -= v.z; } inline void operator *= (cvm::real const &v) { x *= v; y *= v; z *= v; } inline void operator /= (cvm::real const& v) { x /= v; y /= v; z /= v; } inline cvm::real norm2() const { return (x*x + y*y + z*z); } inline cvm::real norm() const { return std::sqrt(this->norm2()); } inline cvm::rvector unit() const { const cvm::real n = this->norm(); return (n > 0. ? cvm::rvector(x, y, z)/n : cvm::rvector(1., 0., 0.)); } static inline size_t output_width(size_t const &real_width) { return 3*real_width + 10; } static inline cvm::rvector outer(cvm::rvector const &v1, cvm::rvector const &v2) { return cvm::rvector( v1.y*v2.z - v2.y*v1.z, -v1.x*v2.z + v2.x*v1.z, v1.x*v2.y - v2.x*v1.y); } friend inline cvm::rvector operator - (cvm::rvector const &v) { return cvm::rvector(-v.x, -v.y, -v.z); } friend inline int operator == (cvm::rvector const &v1, cvm::rvector const &v2) { return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); } friend inline int operator != (cvm::rvector const &v1, cvm::rvector const &v2) { return (v1.x != v2.x) || (v1.y != v2.y) || (v1.z != v2.z); } friend inline cvm::rvector operator + (cvm::rvector const &v1, cvm::rvector const &v2) { return cvm::rvector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); } friend inline cvm::rvector operator - (cvm::rvector const &v1, cvm::rvector const &v2) { return cvm::rvector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); } friend inline cvm::real operator * (cvm::rvector const &v1, cvm::rvector const &v2) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; } friend inline cvm::rvector operator * (cvm::real const &a, cvm::rvector const &v) { return cvm::rvector(a*v.x, a*v.y, a*v.z); } friend inline cvm::rvector operator * (cvm::rvector const &v, cvm::real const &a) { return cvm::rvector(a*v.x, a*v.y, a*v.z); } friend inline cvm::rvector operator / (cvm::rvector const &v, cvm::real const &a) { return cvm::rvector(v.x/a, v.y/a, v.z/a); } std::string to_simple_string() const; int from_simple_string(std::string const &s); }; /// \brief 2-dimensional array of real numbers with three components /// along each dimension (works with colvarmodule::rvector) class colvarmodule::rmatrix : public colvarmodule::matrix2d { private: public: /// Return the xx element inline cvm::real & xx() { return (*this)[0][0]; } /// Return the xy element inline cvm::real & xy() { return (*this)[0][1]; } /// Return the xz element inline cvm::real & xz() { return (*this)[0][2]; } /// Return the yx element inline cvm::real & yx() { return (*this)[1][0]; } /// Return the yy element inline cvm::real & yy() { return (*this)[1][1]; } /// Return the yz element inline cvm::real & yz() { return (*this)[1][2]; } /// Return the zx element inline cvm::real & zx() { return (*this)[2][0]; } /// Return the zy element inline cvm::real & zy() { return (*this)[2][1]; } /// Return the zz element inline cvm::real & zz() { return (*this)[2][2]; } /// Return the xx element inline cvm::real xx() const { return (*this)[0][0]; } /// Return the xy element inline cvm::real xy() const { return (*this)[0][1]; } /// Return the xz element inline cvm::real xz() const { return (*this)[0][2]; } /// Return the yx element inline cvm::real yx() const { return (*this)[1][0]; } /// Return the yy element inline cvm::real yy() const { return (*this)[1][1]; } /// Return the yz element inline cvm::real yz() const { return (*this)[1][2]; } /// Return the zx element inline cvm::real zx() const { return (*this)[2][0]; } /// Return the zy element inline cvm::real zy() const { return (*this)[2][1]; } /// Return the zz element inline cvm::real zz() const { return (*this)[2][2]; } /// Default constructor inline rmatrix() : cvm::matrix2d(3, 3) {} /// Constructor component by component inline rmatrix(cvm::real const &xxi, cvm::real const &xyi, cvm::real const &xzi, cvm::real const &yxi, cvm::real const &yyi, cvm::real const &yzi, cvm::real const &zxi, cvm::real const &zyi, cvm::real const &zzi) : cvm::matrix2d(3, 3) { this->xx() = xxi; this->xy() = xyi; this->xz() = xzi; this->yx() = yxi; this->yy() = yyi; this->yz() = yzi; this->zx() = zxi; this->zy() = zyi; this->zz() = zzi; } /// Destructor inline ~rmatrix() {} /// Return the determinant inline cvm::real determinant() const { return ( xx() * (yy()*zz() - zy()*yz())) - (yx() * (xy()*zz() - zy()*xz())) + (zx() * (xy()*yz() - yy()*xz())); } inline cvm::rmatrix transpose() const { return cvm::rmatrix(this->xx(), this->yx(), this->zx(), this->xy(), this->yy(), this->zy(), this->xz(), this->yz(), this->zz()); } friend cvm::rvector operator * (cvm::rmatrix const &m, cvm::rvector const &r); // friend inline cvm::rmatrix const operator * (cvm::rmatrix const &m1, cvm::rmatrix const &m2) { // return cvm::rmatrix (m1.xx()*m2.xx() + m1.xy()*m2.yx() + m1.xz()*m2.yz(), // m1.xx()*m2.xy() + m1.xy()*m2.yy() + m1.xz()*m2.zy(), // m1.xx()*m2.xz() + m1.xy()*m2.yz() + m1.xz()*m2.zz(), // m1.yx()*m2.xx() + m1.yy()*m2.yx() + m1.yz()*m2.yz(), // m1.yx()*m2.xy() + m1.yy()*m2.yy() + m1.yz()*m2.yy(), // m1.yx()*m2.xz() + m1.yy()*m2.yz() + m1.yz()*m2.yz(), // m1.zx()*m2.xx() + m1.zy()*m2.yx() + m1.zz()*m2.yz(), // m1.zx()*m2.xy() + m1.zy()*m2.yy() + m1.zz()*m2.yy(), // m1.zx()*m2.xz() + m1.zy()*m2.yz() + m1.zz()*m2.yz()); // } }; inline cvm::rvector operator * (cvm::rmatrix const &m, cvm::rvector const &r) { return cvm::rvector(m.xx()*r.x + m.xy()*r.y + m.xz()*r.z, m.yx()*r.x + m.yy()*r.y + m.yz()*r.z, m.zx()*r.x + m.zy()*r.y + m.zz()*r.z); } /// Numerical recipes diagonalization void jacobi(cvm::real **a, cvm::real *d, cvm::real **v, int *nrot); /// Eigenvector sort void eigsrt(cvm::real *d, cvm::real **v); /// Transpose the matrix void transpose(cvm::real **v); /// \brief 1-dimensional vector of real numbers with four components and /// a quaternion algebra class colvarmodule::quaternion { public: cvm::real q0, q1, q2, q3; /// Constructor from a 3-d vector inline quaternion(cvm::real const &x, cvm::real const &y, cvm::real const &z) : q0(0.0), q1(x), q2(y), q3(z) {} /// Constructor component by component inline quaternion(cvm::real const qv[4]) : q0(qv[0]), q1(qv[1]), q2(qv[2]), q3(qv[3]) {} /// Constructor component by component inline quaternion(cvm::real const &q0i, cvm::real const &q1i, cvm::real const &q2i, cvm::real const &q3i) : q0(q0i), q1(q1i), q2(q2i), q3(q3i) {} inline quaternion(cvm::vector1d const &v) : q0(v[0]), q1(v[1]), q2(v[2]), q3(v[3]) {} /// "Constructor" after Euler angles (in radians) /// /// http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles inline void set_from_euler_angles(cvm::real const &phi_in, cvm::real const &theta_in, cvm::real const &psi_in) { q0 = ( (std::cos(phi_in/2.0)) * (std::cos(theta_in/2.0)) * (std::cos(psi_in/2.0)) + (std::sin(phi_in/2.0)) * (std::sin(theta_in/2.0)) * (std::sin(psi_in/2.0)) ); q1 = ( (std::sin(phi_in/2.0)) * (std::cos(theta_in/2.0)) * (std::cos(psi_in/2.0)) - (std::cos(phi_in/2.0)) * (std::sin(theta_in/2.0)) * (std::sin(psi_in/2.0)) ); q2 = ( (std::cos(phi_in/2.0)) * (std::sin(theta_in/2.0)) * (std::cos(psi_in/2.0)) + (std::sin(phi_in/2.0)) * (std::cos(theta_in/2.0)) * (std::sin(psi_in/2.0)) ); q3 = ( (std::cos(phi_in/2.0)) * (std::cos(theta_in/2.0)) * (std::sin(psi_in/2.0)) - (std::sin(phi_in/2.0)) * (std::sin(theta_in/2.0)) * (std::cos(psi_in/2.0)) ); } /// \brief Default constructor inline quaternion() { reset(); } /// \brief Set all components to a scalar inline void set(cvm::real const &value = 0.0) { q0 = q1 = q2 = q3 = value; } /// \brief Set all components to zero (null quaternion) inline void reset() { q0 = q1 = q2 = q3 = 0.0; } /// \brief Set the q0 component to 1 and the others to 0 (quaternion /// representing no rotation) inline void reset_rotation() { q0 = 1.0; q1 = q2 = q3 = 0.0; } /// Tell the number of characters required to print a quaternion, given that of a real number static inline size_t output_width(size_t const &real_width) { return 4*real_width + 13; } std::string to_simple_string() const; int from_simple_string(std::string const &s); /// \brief Formatted output operator friend std::ostream & operator << (std::ostream &os, cvm::quaternion const &q); /// \brief Formatted input operator friend std::istream & operator >> (std::istream &is, cvm::quaternion &q); /// Access the quaternion as a 4-d array (return a reference) inline cvm::real & operator [] (int const &i) { switch (i) { case 0: return this->q0; case 1: return this->q1; case 2: return this->q2; case 3: return this->q3; default: cvm::error("Error: incorrect quaternion component.\n"); return q0; } } /// Access the quaternion as a 4-d array (return a value) inline cvm::real operator [] (int const &i) const { switch (i) { case 0: return this->q0; case 1: return this->q1; case 2: return this->q2; case 3: return this->q3; default: cvm::error("Error: trying to access a quaternion " "component which is not between 0 and 3.\n"); return 0.0; } } inline cvm::vector1d const as_vector() const { cvm::vector1d result(4); result[0] = q0; result[1] = q1; result[2] = q2; result[3] = q3; return result; } /// Square norm of the quaternion inline cvm::real norm2() const { return q0*q0 + q1*q1 + q2*q2 + q3*q3; } /// Norm of the quaternion inline cvm::real norm() const { return std::sqrt(this->norm2()); } /// Return the conjugate quaternion inline cvm::quaternion conjugate() const { return cvm::quaternion(q0, -q1, -q2, -q3); } inline void operator *= (cvm::real const &a) { q0 *= a; q1 *= a; q2 *= a; q3 *= a; } inline void operator /= (cvm::real const &a) { q0 /= a; q1 /= a; q2 /= a; q3 /= a; } inline void set_positive() { if (q0 > 0.0) return; q0 = -q0; q1 = -q1; q2 = -q2; q3 = -q3; } inline void operator += (cvm::quaternion const &h) { q0+=h.q0; q1+=h.q1; q2+=h.q2; q3+=h.q3; } inline void operator -= (cvm::quaternion const &h) { q0-=h.q0; q1-=h.q1; q2-=h.q2; q3-=h.q3; } /// Promote a 3-vector to a quaternion static inline cvm::quaternion promote(cvm::rvector const &v) { return cvm::quaternion(0.0, v.x, v.y, v.z); } /// Return the vector component inline cvm::rvector get_vector() const { return cvm::rvector(q1, q2, q3); } friend inline cvm::quaternion operator + (cvm::quaternion const &h, cvm::quaternion const &q) { return cvm::quaternion(h.q0+q.q0, h.q1+q.q1, h.q2+q.q2, h.q3+q.q3); } friend inline cvm::quaternion operator - (cvm::quaternion const &h, cvm::quaternion const &q) { return cvm::quaternion(h.q0-q.q0, h.q1-q.q1, h.q2-q.q2, h.q3-q.q3); } /// \brief Provides the quaternion product. \b NOTE: for the inner /// product use: \code h.inner (q); \endcode friend inline cvm::quaternion operator * (cvm::quaternion const &h, cvm::quaternion const &q) { return cvm::quaternion(h.q0*q.q0 - h.q1*q.q1 - h.q2*q.q2 - h.q3*q.q3, h.q0*q.q1 + h.q1*q.q0 + h.q2*q.q3 - h.q3*q.q2, h.q0*q.q2 + h.q2*q.q0 + h.q3*q.q1 - h.q1*q.q3, h.q0*q.q3 + h.q3*q.q0 + h.q1*q.q2 - h.q2*q.q1); } friend inline cvm::quaternion operator * (cvm::real const &c, cvm::quaternion const &q) { return cvm::quaternion(c*q.q0, c*q.q1, c*q.q2, c*q.q3); } friend inline cvm::quaternion operator * (cvm::quaternion const &q, cvm::real const &c) { return cvm::quaternion(q.q0*c, q.q1*c, q.q2*c, q.q3*c); } friend inline cvm::quaternion operator / (cvm::quaternion const &q, cvm::real const &c) { return cvm::quaternion(q.q0/c, q.q1/c, q.q2/c, q.q3/c); } /// \brief Rotate v through this quaternion (put it in the rotated /// reference frame) inline cvm::rvector rotate(cvm::rvector const &v) const { return ((*this) * promote(v) * ((*this).conjugate())).get_vector(); } /// \brief Rotate Q2 through this quaternion (put it in the rotated /// reference frame) inline cvm::quaternion rotate(cvm::quaternion const &Q2) const { cvm::rvector const vq_rot = this->rotate(Q2.get_vector()); return cvm::quaternion(Q2.q0, vq_rot.x, vq_rot.y, vq_rot.z); } /// Return the 3x3 matrix associated to this quaternion inline cvm::rmatrix rotation_matrix() const { cvm::rmatrix R; R.xx() = q0*q0 + q1*q1 - q2*q2 - q3*q3; R.yy() = q0*q0 - q1*q1 + q2*q2 - q3*q3; R.zz() = q0*q0 - q1*q1 - q2*q2 + q3*q3; R.xy() = 2.0 * (q1*q2 - q0*q3); R.xz() = 2.0 * (q0*q2 + q1*q3); R.yx() = 2.0 * (q0*q3 + q1*q2); R.yz() = 2.0 * (q2*q3 - q0*q1); R.zx() = 2.0 * (q1*q3 - q0*q2); R.zy() = 2.0 * (q0*q1 + q2*q3); return R; } /// \brief Multiply the given vector by the derivative of the given /// (rotated) position with respect to the quaternion cvm::quaternion position_derivative_inner(cvm::rvector const &pos, cvm::rvector const &vec) const; /// \brief Return the cosine between the orientation frame /// associated to this quaternion and another inline cvm::real cosine(cvm::quaternion const &q) const { cvm::real const iprod = this->inner(q); return 2.0*iprod*iprod - 1.0; } /// \brief Square distance from another quaternion on the /// 4-dimensional unit sphere: returns the square of the angle along /// the shorter of the two geodesics inline cvm::real dist2(cvm::quaternion const &Q2) const { cvm::real const cos_omega = this->q0*Q2.q0 + this->q1*Q2.q1 + this->q2*Q2.q2 + this->q3*Q2.q3; cvm::real const omega = std::acos( (cos_omega > 1.0) ? 1.0 : ( (cos_omega < -1.0) ? -1.0 : cos_omega) ); // get the minimum distance: x and -x are the same quaternion if (cos_omega > 0.0) return omega * omega; else return (PI-omega) * (PI-omega); } /// Gradient of the square distance: returns a 4-vector equivalent /// to that provided by slerp inline cvm::quaternion dist2_grad(cvm::quaternion const &Q2) const { cvm::real const cos_omega = this->q0*Q2.q0 + this->q1*Q2.q1 + this->q2*Q2.q2 + this->q3*Q2.q3; cvm::real const omega = std::acos( (cos_omega > 1.0) ? 1.0 : ( (cos_omega < -1.0) ? -1.0 : cos_omega) ); cvm::real const sin_omega = std::sin(omega); if (std::fabs(sin_omega) < 1.0E-14) { // return a null 4d vector return cvm::quaternion(0.0, 0.0, 0.0, 0.0); } cvm::quaternion const grad1((-1.0)*sin_omega*Q2.q0 + cos_omega*(this->q0-cos_omega*Q2.q0)/sin_omega, (-1.0)*sin_omega*Q2.q1 + cos_omega*(this->q1-cos_omega*Q2.q1)/sin_omega, (-1.0)*sin_omega*Q2.q2 + cos_omega*(this->q2-cos_omega*Q2.q2)/sin_omega, (-1.0)*sin_omega*Q2.q3 + cos_omega*(this->q3-cos_omega*Q2.q3)/sin_omega); if (cos_omega > 0.0) { return 2.0*omega*grad1; } else { return -2.0*(PI-omega)*grad1; } } /// \brief Choose the closest between Q2 and -Q2 and save it back. /// Not required for dist2() and dist2_grad() inline void match(cvm::quaternion &Q2) const { cvm::real const cos_omega = this->q0*Q2.q0 + this->q1*Q2.q1 + this->q2*Q2.q2 + this->q3*Q2.q3; if (cos_omega < 0.0) Q2 *= -1.0; } /// \brief Inner product (as a 4-d vector) with Q2; requires match() /// if the largest overlap is looked for inline cvm::real inner(cvm::quaternion const &Q2) const { cvm::real const prod = this->q0*Q2.q0 + this->q1*Q2.q1 + this->q2*Q2.q2 + this->q3*Q2.q3; return prod; } }; /// \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; /// \brief Positions to superimpose: the rotation should brings pos1 /// into pos2 std::vector pos1, pos2; cvm::rmatrix C; cvm::matrix2d S; cvm::vector1d S_eigval; cvm::matrix2d S_eigvec; /// Used for debugging gradients cvm::matrix2d S_backup; /// 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; /// Allocate space for the derivatives of the rotation inline void request_group1_gradients(size_t const &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)); } /// Allocate space for the derivatives of the rotation inline void request_group2_gradients(size_t const &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)); } /// \brief Calculate the optimal rotation and store the /// corresponding eigenvalue and eigenvector in the arguments l0 and /// q0; if the gradients have been previously requested, calculate /// them as well /// /// The method to derive the optimal rotation is defined 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 calc_optimal_rotation(std::vector const &pos1, std::vector const &pos2); /// Default constructor inline rotation() : b_debug_gradients(false) {} /// Constructor after a quaternion inline rotation(cvm::quaternion const &qi) : q(qi), b_debug_gradients(false) { } /// Constructor after an axis of rotation and an angle (in radians) inline rotation(cvm::real const &angle, cvm::rvector const &axis) : b_debug_gradients(false) { cvm::rvector const axis_n = axis.unit(); cvm::real const sina = std::sin(angle/2.0); q = cvm::quaternion(std::cos(angle/2.0), sina * axis_n.x, sina * axis_n.y, sina * axis_n.z); } /// Destructor inline ~rotation() {} /// Return the rotated vector inline cvm::rvector rotate(cvm::rvector const &v) const { return q.rotate(v); } /// Return the inverse of this rotation inline cvm::rotation inverse() const { return cvm::rotation(this->q.conjugate()); } /// Return the associated 3x3 matrix inline cvm::rmatrix matrix() const { return q.rotation_matrix(); } /// \brief Return the spin angle (in degrees) with respect to the /// provided axis (which MUST be normalized) inline cvm::real spin_angle(cvm::rvector const &axis) const { cvm::rvector const q_vec = q.get_vector(); cvm::real alpha = (180.0/PI) * 2.0 * std::atan2(axis * q_vec, q.q0); while (alpha > 180.0) alpha -= 360; while (alpha < -180.0) alpha += 360; return alpha; } /// \brief Return the derivative of the spin angle with respect to /// the quaternion inline cvm::quaternion dspin_angle_dq(cvm::rvector const &axis) const { cvm::rvector const q_vec = q.get_vector(); cvm::real const iprod = axis * q_vec; if (q.q0 != 0.0) { // cvm::real const x = iprod/q.q0; cvm::real const dspindx = (180.0/PI) * 2.0 * (1.0 / (1.0 + (iprod*iprod)/(q.q0*q.q0))); return cvm::quaternion( dspindx * (iprod * (-1.0) / (q.q0*q.q0)), dspindx * ((1.0/q.q0) * axis.x), dspindx * ((1.0/q.q0) * axis.y), dspindx * ((1.0/q.q0) * axis.z)); } else { // (1/(1+x^2)) ~ (1/x)^2 return cvm::quaternion((180.0/PI) * 2.0 * ((-1.0)/iprod), 0.0, 0.0, 0.0); // XX TODO: What if iprod == 0? XX } } /// \brief Return the projection of the orientation vector onto a /// predefined axis inline cvm::real cos_theta(cvm::rvector const &axis) const { cvm::rvector const q_vec = q.get_vector(); cvm::real const alpha = (180.0/PI) * 2.0 * std::atan2(axis * q_vec, q.q0); cvm::real const cos_spin_2 = std::cos(alpha * (PI/180.0) * 0.5); cvm::real const cos_theta_2 = ( (cos_spin_2 != 0.0) ? (q.q0 / cos_spin_2) : (0.0) ); // cos(2t) = 2*cos(t)^2 - 1 return 2.0 * (cos_theta_2*cos_theta_2) - 1.0; } /// Return the derivative of the tilt wrt the quaternion inline cvm::quaternion dcos_theta_dq(cvm::rvector const &axis) const { cvm::rvector const q_vec = q.get_vector(); cvm::real const iprod = axis * q_vec; cvm::real const cos_spin_2 = std::cos(std::atan2(iprod, q.q0)); if (q.q0 != 0.0) { cvm::real const d_cos_theta_dq0 = (4.0 * q.q0 / (cos_spin_2*cos_spin_2)) * (1.0 - (iprod*iprod)/(q.q0*q.q0) / (1.0 + (iprod*iprod)/(q.q0*q.q0))); cvm::real const d_cos_theta_dqn = (4.0 * q.q0 / (cos_spin_2*cos_spin_2) * (iprod/q.q0) / (1.0 + (iprod*iprod)/(q.q0*q.q0))); return cvm::quaternion(d_cos_theta_dq0, d_cos_theta_dqn * axis.x, d_cos_theta_dqn * axis.y, d_cos_theta_dqn * axis.z); } else { cvm::real const d_cos_theta_dqn = (4.0 / (cos_spin_2*cos_spin_2 * iprod)); return cvm::quaternion(0.0, d_cos_theta_dqn * axis.x, d_cos_theta_dqn * axis.y, d_cos_theta_dqn * axis.z); } } /// \brief Whether to test for eigenvalue crossing static bool monitor_crossings; /// \brief Threshold for the eigenvalue crossing test static cvm::real crossing_threshold; protected: /// \brief Previous value of the rotation (used to warn the user /// when the structure changes too much, and there may be an /// eigenvalue crossing) cvm::quaternion q_old; /// Build the overlap matrix S (used by calc_optimal_rotation()) void build_matrix(std::vector const &pos1, std::vector const &pos2, cvm::matrix2d &S); /// Diagonalize the overlap matrix S (used by calc_optimal_rotation()) void diagonalize_matrix(cvm::matrix2d &S, cvm::vector1d &S_eigval, cvm::matrix2d &S_eigvec); }; #endif diff --git a/lib/colvars/colvarvalue.cpp b/lib/colvars/colvarvalue.cpp index deccc6b7e..7b498be6d 100644 --- a/lib/colvars/colvarvalue.cpp +++ b/lib/colvars/colvarvalue.cpp @@ -1,631 +1,906 @@ // -*- 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 "colvarmodule.h" #include "colvarvalue.h" +std::string const colvarvalue::type_desc(Type t) +{ + switch (t) { + case colvarvalue::type_scalar: + return "scalar number"; break; + case colvarvalue::type_3vector: + return "3-dimensional vector"; break; + case colvarvalue::type_unit3vector: + return "3-dimensional unit vector"; break; + case colvarvalue::type_unit3vectorderiv: + return "derivative of a 3-dimensional unit vector"; break; + case colvarvalue::type_quaternion: + return "4-dimensional unit quaternion"; break; + case colvarvalue::type_quaternionderiv: + return "4-dimensional tangent vector"; break; + case colvarvalue::type_vector: + return "n-dimensional vector"; break; + case colvarvalue::type_notset: + // fallthrough + default: + return "not set"; break; + } +} + + +std::string const colvarvalue::type_keyword(Type t) +{ + switch (t) { + case colvarvalue::type_notset: + default: + return "not_set"; break; + case colvarvalue::type_scalar: + return "scalar"; break; + case colvarvalue::type_3vector: + return "vector3"; break; + case colvarvalue::type_unit3vector: + return "unit_vector3"; break; + case colvarvalue::type_unit3vectorderiv: + return ""; break; + case colvarvalue::type_quaternion: + return "unit_quaternion"; break; + case colvarvalue::type_quaternionderiv: + return ""; break; + case colvarvalue::type_vector: + return "vector"; break; + } +} + + +size_t colvarvalue::num_df(Type t) +{ + switch (t) { + case colvarvalue::type_notset: + default: + return 0; break; + case colvarvalue::type_scalar: + return 1; break; + case colvarvalue::type_3vector: + return 3; break; + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return 2; break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return 3; break; + case colvarvalue::type_vector: + // the size of a vector is unknown without its object + return 0; break; + } +} + + +size_t colvarvalue::num_dimensions(Type t) +{ + switch (t) { + case colvarvalue::type_notset: + default: + return 0; break; + case colvarvalue::type_scalar: + return 1; break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return 3; break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return 4; break; + case colvarvalue::type_vector: + // the size of a vector is unknown without its object + return 0; break; + } +} + + +void colvarvalue::reset() +{ + switch (value_type) { + case colvarvalue::type_scalar: + real_value = 0.0; + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + rvector_value.reset(); + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + quaternion_value.reset(); + break; + case colvarvalue::type_vector: + vector1d_value.reset(); + break; + case colvarvalue::type_notset: + default: + break; + } +} + + +void colvarvalue::apply_constraints() +{ + switch (value_type) { + case colvarvalue::type_scalar: + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vectorderiv: + case colvarvalue::type_quaternionderiv: + break; + case colvarvalue::type_unit3vector: + rvector_value /= std::sqrt(rvector_value.norm2()); + break; + case colvarvalue::type_quaternion: + quaternion_value /= std::sqrt(quaternion_value.norm2()); + break; + case colvarvalue::type_vector: + if (elem_types.size() > 0) { + // if we have information about non-scalar types, use it + size_t i; + for (i = 0; i < elem_types.size(); i++) { + if (elem_sizes[i] == 1) continue; // TODO this can be optimized further + colvarvalue cvtmp(vector1d_value.slice(elem_indices[i], + elem_indices[i] + elem_sizes[i]), elem_types[i]); + cvtmp.apply_constraints(); + set_elem(i, cvtmp); + } + } + break; + case colvarvalue::type_notset: + default: + break; + } +} + + +void colvarvalue::type(Type const &vti) +{ + if (vti != value_type) { + // reset the value based on the previous type + reset(); + if ((value_type == type_vector) && (vti != type_vector)) { + vector1d_value.clear(); + } + value_type = vti; + } +} + + +void colvarvalue::type(colvarvalue const &x) +{ + if (x.type() != value_type) { + // reset the value based on the previous type + reset(); + if (value_type == type_vector) { + vector1d_value.clear(); + } + value_type = x.type(); + } + + if (x.type() == type_vector) { + vector1d_value.resize(x.vector1d_value.size()); + } +} + + +void colvarvalue::is_derivative() +{ + switch (value_type) { + case colvarvalue::type_scalar: + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vectorderiv: + case colvarvalue::type_quaternionderiv: + break; + case colvarvalue::type_unit3vector: + type(colvarvalue::type_unit3vectorderiv); + break; + case colvarvalue::type_quaternion: + type(colvarvalue::type_quaternionderiv); + break; + case colvarvalue::type_vector: + // TODO + break; + case colvarvalue::type_notset: + default: + break; + } +} + + +colvarvalue::colvarvalue(colvarvalue const &x) + : value_type(x.type()) +{ + switch (x.type()) { + case type_scalar: + real_value = x.real_value; + break; + case type_3vector: + case type_unit3vector: + case type_unit3vectorderiv: + rvector_value = x.rvector_value; + break; + case type_quaternion: + case type_quaternionderiv: + quaternion_value = x.quaternion_value; + break; + case type_vector: + vector1d_value = x.vector1d_value; + elem_types = x.elem_types; + elem_indices = x.elem_indices; + elem_sizes = x.elem_sizes; + case type_notset: + default: + break; + } +} + + +colvarvalue::colvarvalue(cvm::vector1d const &v, Type vti) +{ + if ((vti != type_vector) && (v.size() != num_dimensions(vti))) { + cvm::error("Error: trying to initialize a variable of type \""+type_desc(vti)+ + "\" using a vector of size "+cvm::to_str(v.size())+ + ".\n"); + value_type = type_notset; + } else { + value_type = vti; + switch (vti) { + case type_scalar: + real_value = v[0]; + break; + case type_3vector: + case type_unit3vector: + case type_unit3vectorderiv: + rvector_value = cvm::rvector(v); + break; + case type_quaternion: + case type_quaternionderiv: + quaternion_value = cvm::quaternion(v); + break; + case type_vector: + vector1d_value = v; + break; + case type_notset: + default: + break; + } + } +} + + void colvarvalue::add_elem(colvarvalue const &x) { if (this->value_type != type_vector) { cvm::error("Error: trying to set an element for a variable that is not set to be a vector.\n"); return; } size_t const n = vector1d_value.size(); size_t const nd = num_dimensions(x.value_type); elem_types.push_back(x.value_type); elem_indices.push_back(n); elem_sizes.push_back(nd); vector1d_value.resize(n + nd); set_elem(n, x); } colvarvalue const colvarvalue::get_elem(int const i_begin, int const i_end, Type const vt) const { if (vector1d_value.size() > 0) { cvm::vector1d const v(vector1d_value.slice(i_begin, i_end)); return colvarvalue(v, vt); } else { cvm::error("Error: trying to get an element from a variable that is not a vector.\n"); return colvarvalue(type_notset); } } void colvarvalue::set_elem(int const i_begin, int const i_end, colvarvalue const &x) { if (vector1d_value.size() > 0) { vector1d_value.sliceassign(i_begin, i_end, x.as_vector()); } else { cvm::error("Error: trying to set an element for a variable that is not a vector.\n"); } } colvarvalue const colvarvalue::get_elem(int const icv) const { if (elem_types.size() > 0) { return get_elem(elem_indices[icv], elem_indices[icv] + elem_sizes[icv], elem_types[icv]); } else { cvm::error("Error: trying to get a colvarvalue element from a vector colvarvalue that was initialized as a plain array.\n"); return colvarvalue(type_notset); } } void colvarvalue::set_elem(int const icv, colvarvalue const &x) { if (elem_types.size() > 0) { check_types_assign(elem_types[icv], x.value_type); set_elem(elem_indices[icv], elem_indices[icv] + elem_sizes[icv], x); } else { cvm::error("Error: trying to set a colvarvalue element for a colvarvalue that was initialized as a plain array.\n"); } } void colvarvalue::set_random() { size_t ic; switch (this->type()) { case colvarvalue::type_scalar: this->real_value = cvm::rand_gaussian(); break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: this->rvector_value.x = cvm::rand_gaussian(); this->rvector_value.y = cvm::rand_gaussian(); this->rvector_value.z = cvm::rand_gaussian(); break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: this->quaternion_value.q0 = cvm::rand_gaussian(); this->quaternion_value.q1 = cvm::rand_gaussian(); this->quaternion_value.q2 = cvm::rand_gaussian(); this->quaternion_value.q3 = cvm::rand_gaussian(); break; case colvarvalue::type_vector: for (ic = 0; ic < this->vector1d_value.size(); ic++) { this->vector1d_value[ic] = cvm::rand_gaussian(); } break; case colvarvalue::type_notset: default: undef_op(); break; } } +void colvarvalue::undef_op() const +{ + cvm::error("Error: Undefined operation on a colvar of type \""+ + type_desc(this->type())+"\".\n"); +} + + // binary operations between two colvarvalues colvarvalue operator + (colvarvalue const &x1, colvarvalue const &x2) { colvarvalue::check_types(x1, x2); switch (x1.value_type) { case colvarvalue::type_scalar: return colvarvalue(x1.real_value + x2.real_value); case colvarvalue::type_3vector: return colvarvalue(x1.rvector_value + x2.rvector_value); case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: return colvarvalue(x1.rvector_value + x2.rvector_value, colvarvalue::type_unit3vector); case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: return colvarvalue(x1.quaternion_value + x2.quaternion_value); case colvarvalue::type_vector: return colvarvalue(x1.vector1d_value + x2.vector1d_value, colvarvalue::type_vector); case colvarvalue::type_notset: default: x1.undef_op(); return colvarvalue(colvarvalue::type_notset); }; } colvarvalue operator - (colvarvalue const &x1, colvarvalue const &x2) { colvarvalue::check_types(x1, x2); switch (x1.value_type) { case colvarvalue::type_scalar: return colvarvalue(x1.real_value - x2.real_value); case colvarvalue::type_3vector: return colvarvalue(x1.rvector_value - x2.rvector_value); case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: return colvarvalue(x1.rvector_value - x2.rvector_value, colvarvalue::type_unit3vector); case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: return colvarvalue(x1.quaternion_value - x2.quaternion_value); case colvarvalue::type_vector: return colvarvalue(x1.vector1d_value - x2.vector1d_value, colvarvalue::type_vector); case colvarvalue::type_notset: default: x1.undef_op(); return colvarvalue(colvarvalue::type_notset); }; } // binary operations with real numbers colvarvalue operator * (cvm::real const &a, colvarvalue const &x) { switch (x.value_type) { case colvarvalue::type_scalar: return colvarvalue(a * x.real_value); case colvarvalue::type_3vector: return colvarvalue(a * x.rvector_value); case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: return colvarvalue(a * x.rvector_value, colvarvalue::type_unit3vector); case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: return colvarvalue(a * x.quaternion_value); case colvarvalue::type_vector: return colvarvalue(x.vector1d_value * a, colvarvalue::type_vector); case colvarvalue::type_notset: default: x.undef_op(); return colvarvalue(colvarvalue::type_notset); } } colvarvalue operator * (colvarvalue const &x, cvm::real const &a) { return a * x; } colvarvalue operator / (colvarvalue const &x, cvm::real const &a) { switch (x.value_type) { case colvarvalue::type_scalar: return colvarvalue(x.real_value / a); case colvarvalue::type_3vector: return colvarvalue(x.rvector_value / a); case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: return colvarvalue(x.rvector_value / a, colvarvalue::type_unit3vector); case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: return colvarvalue(x.quaternion_value / a); case colvarvalue::type_vector: return colvarvalue(x.vector1d_value / a, colvarvalue::type_vector); case colvarvalue::type_notset: default: x.undef_op(); return colvarvalue(colvarvalue::type_notset); } } // inner product between two colvarvalues cvm::real operator * (colvarvalue const &x1, colvarvalue const &x2) { colvarvalue::check_types(x1, x2); switch (x1.value_type) { case colvarvalue::type_scalar: return (x1.real_value * x2.real_value); case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: return (x1.rvector_value * x2.rvector_value); case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: // the "*" product is the quaternion product, here the inner // member function is used instead return (x1.quaternion_value.inner(x2.quaternion_value)); case colvarvalue::type_vector: return (x1.vector1d_value * x2.vector1d_value); case colvarvalue::type_notset: default: x1.undef_op(); return 0.0; }; } colvarvalue colvarvalue::dist2_grad(colvarvalue const &x2) const { colvarvalue::check_types(*this, x2); switch (this->value_type) { case colvarvalue::type_scalar: return 2.0 * (this->real_value - x2.real_value); case colvarvalue::type_3vector: return 2.0 * (this->rvector_value - x2.rvector_value); case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: { cvm::rvector const &v1 = this->rvector_value; cvm::rvector const &v2 = x2.rvector_value; cvm::real const cos_t = v1 * v2; cvm::real const sin_t = std::sqrt(1.0 - cos_t*cos_t); return colvarvalue( 2.0 * sin_t * cvm::rvector((-1.0) * sin_t * v2.x + cos_t/sin_t * (v1.x - cos_t*v2.x), (-1.0) * sin_t * v2.y + cos_t/sin_t * (v1.y - cos_t*v2.y), (-1.0) * sin_t * v2.z + cos_t/sin_t * (v1.z - cos_t*v2.z) ), colvarvalue::type_unit3vectorderiv ); } case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: return this->quaternion_value.dist2_grad(x2.quaternion_value); case colvarvalue::type_vector: return colvarvalue(2.0 * (this->vector1d_value - x2.vector1d_value), colvarvalue::type_vector); break; case colvarvalue::type_notset: default: this->undef_op(); return colvarvalue(colvarvalue::type_notset); }; } std::string colvarvalue::to_simple_string() const { switch (type()) { case colvarvalue::type_scalar: return cvm::to_str(real_value, 0, cvm::cv_prec); break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: return rvector_value.to_simple_string(); break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: return quaternion_value.to_simple_string(); break; case colvarvalue::type_vector: return vector1d_value.to_simple_string(); break; case colvarvalue::type_notset: default: undef_op(); break; } return std::string(); } int colvarvalue::from_simple_string(std::string const &s) { switch (type()) { case colvarvalue::type_scalar: return ((std::istringstream(s) >> real_value) ? COLVARS_OK : COLVARS_ERROR); break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: return rvector_value.from_simple_string(s); break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: return quaternion_value.from_simple_string(s); break; case colvarvalue::type_vector: return vector1d_value.from_simple_string(s); break; case colvarvalue::type_notset: default: undef_op(); break; } return COLVARS_ERROR; } std::ostream & operator << (std::ostream &os, colvarvalue const &x) { switch (x.type()) { case colvarvalue::type_scalar: os << x.real_value; break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: os << x.rvector_value; break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: os << x.quaternion_value; break; case colvarvalue::type_vector: os << x.vector1d_value; break; case colvarvalue::type_notset: default: os << "not set"; break; } return os; } std::ostream & operator << (std::ostream &os, std::vector const &v) { size_t i; for (i = 0; i < v.size(); i++) { os << v[i]; } return os; } std::istream & operator >> (std::istream &is, colvarvalue &x) { if (x.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()) { case colvarvalue::type_scalar: is >> x.real_value; break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vectorderiv: is >> x.rvector_value; break; case colvarvalue::type_unit3vector: is >> x.rvector_value; x.apply_constraints(); break; case colvarvalue::type_quaternion: is >> x.quaternion_value; x.apply_constraints(); break; case colvarvalue::type_quaternionderiv: is >> x.quaternion_value; break; case colvarvalue::type_vector: is >> x.vector1d_value; break; case colvarvalue::type_notset: default: x.undef_op(); } return is; } size_t colvarvalue::output_width(size_t const &real_width) const { switch (this->value_type) { case colvarvalue::type_scalar: return real_width; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: return cvm::rvector::output_width(real_width); case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: return cvm::quaternion::output_width(real_width); case colvarvalue::type_vector: // note how this depends on the vector's size return vector1d_value.output_width(real_width); case colvarvalue::type_notset: default: return 0; } } void colvarvalue::inner_opt(colvarvalue const &x, std::vector::iterator &xv, std::vector::iterator const &xv_end, std::vector::iterator &result) { // doing type check only once, here colvarvalue::check_types(x, *xv); std::vector::iterator &xvi = xv; std::vector::iterator &ii = result; switch (x.value_type) { case colvarvalue::type_scalar: while (xvi != xv_end) { *(ii++) += (xvi++)->real_value * x.real_value; } break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: while (xvi != xv_end) { *(ii++) += (xvi++)->rvector_value * x.rvector_value; } break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: while (xvi != xv_end) { *(ii++) += ((xvi++)->quaternion_value).cosine(x.quaternion_value); } break; case colvarvalue::type_vector: while (xvi != xv_end) { *(ii++) += (xvi++)->vector1d_value * x.vector1d_value; } break; default: x.undef_op(); }; } void colvarvalue::inner_opt(colvarvalue const &x, std::list::iterator &xv, std::list::iterator const &xv_end, std::vector::iterator &result) { // doing type check only once, here colvarvalue::check_types(x, *xv); std::list::iterator &xvi = xv; std::vector::iterator &ii = result; switch (x.value_type) { case colvarvalue::type_scalar: while (xvi != xv_end) { *(ii++) += (xvi++)->real_value * x.real_value; } break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: while (xvi != xv_end) { *(ii++) += (xvi++)->rvector_value * x.rvector_value; } break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: while (xvi != xv_end) { *(ii++) += ((xvi++)->quaternion_value).cosine(x.quaternion_value); } break; case colvarvalue::type_vector: while (xvi != xv_end) { *(ii++) += (xvi++)->vector1d_value * x.vector1d_value; } break; default: x.undef_op(); }; } void colvarvalue::p2leg_opt(colvarvalue const &x, std::vector::iterator &xv, std::vector::iterator const &xv_end, std::vector::iterator &result) { // doing type check only once, here colvarvalue::check_types(x, *xv); std::vector::iterator &xvi = xv; std::vector::iterator &ii = result; switch (x.value_type) { case colvarvalue::type_scalar: cvm::error("Error: cannot calculate Legendre polynomials " "for scalar variables.\n"); return; break; case colvarvalue::type_3vector: while (xvi != xv_end) { cvm::real const cosine = ((xvi)->rvector_value * x.rvector_value) / ((xvi)->rvector_value.norm() * x.rvector_value.norm()); xvi++; *(ii++) += 1.5*cosine*cosine - 0.5; } break; case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: while (xvi != xv_end) { cvm::real const cosine = (xvi++)->rvector_value * x.rvector_value; *(ii++) += 1.5*cosine*cosine - 0.5; } break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: while (xvi != xv_end) { cvm::real const cosine = (xvi++)->quaternion_value.cosine(x.quaternion_value); *(ii++) += 1.5*cosine*cosine - 0.5; } break; case colvarvalue::type_vector: while (xvi != xv_end) { cvm::real const cosine = ((xvi)->vector1d_value * x.vector1d_value) / ((xvi)->vector1d_value.norm() * x.rvector_value.norm()); xvi++; *(ii++) += 1.5*cosine*cosine - 0.5; } break; default: x.undef_op(); }; } void colvarvalue::p2leg_opt(colvarvalue const &x, std::list::iterator &xv, std::list::iterator const &xv_end, std::vector::iterator &result) { // doing type check only once, here colvarvalue::check_types(x, *xv); std::list::iterator &xvi = xv; std::vector::iterator &ii = result; switch (x.value_type) { case colvarvalue::type_scalar: cvm::error("Error: cannot calculate Legendre polynomials " "for scalar variables.\n"); break; case colvarvalue::type_3vector: while (xvi != xv_end) { cvm::real const cosine = ((xvi)->rvector_value * x.rvector_value) / ((xvi)->rvector_value.norm() * x.rvector_value.norm()); xvi++; *(ii++) += 1.5*cosine*cosine - 0.5; } break; case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: while (xvi != xv_end) { cvm::real const cosine = (xvi++)->rvector_value * x.rvector_value; *(ii++) += 1.5*cosine*cosine - 0.5; } break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: while (xvi != xv_end) { cvm::real const cosine = (xvi++)->quaternion_value.cosine(x.quaternion_value); *(ii++) += 1.5*cosine*cosine - 0.5; } break; default: x.undef_op(); }; } diff --git a/lib/colvars/colvarvalue.h b/lib/colvars/colvarvalue.h index e369feefc..fce0e1a97 100644 --- a/lib/colvars/colvarvalue.h +++ b/lib/colvars/colvarvalue.h @@ -1,948 +1,708 @@ // -*- 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 COLVARVALUE_H #define COLVARVALUE_H #include "colvarmodule.h" #include "colvartypes.h" /// \brief Value of a collective variable: this is a metatype which /// can be set at runtime. By default it is set to be a scalar /// number, and can be treated as such in all operations (this is /// done by most \link cvc \endlink implementations). /// /// \link colvarvalue \endlink allows \link colvar \endlink to be /// treat different data types. By default, a \link colvarvalue /// \endlink variable is a scalar number. To use it as /// another type, declare and initialize it as /// \code colvarvalue x(colvarvalue::type_xxx)\endcode, use \link x.type /// (colvarvalue::type_xxx) \endlink at a later stage, or if unset, /// assign the type with \code x = y; \endcode, provided y is correctly set. /// /// All operators (either unary or binary) on a \link /// colvarvalue \endlink object performs one or more checks on the /// \link Type \endlink, except when reading from a stream, when there is no way to /// detect the \link Type \endlink. To use \code is >> x; \endcode x \b MUST /// already have a type correcly set up for properly parsing the /// stream. No problem of course with the output streams: \code os << x; \endcode /// /// \em Note \em on \em performance: to avoid type checks in a long array of \link /// colvarvalue \endlink objects, use one of the existing "_opt" functions or implement a new one class colvarvalue { public: /// \brief Possible types of value /// /// These three cover most possibilities of data type one can /// devise. If you need to implement a new colvar with a very /// complex data type, it's better to put an allocatable class here enum Type { /// Undefined type type_notset, /// Scalar number, implemented as \link colvarmodule::real \endlink (default) type_scalar, /// 3-dimensional vector, implemented as \link colvarmodule::rvector \endlink type_3vector, /// 3-dimensional unit vector, implemented as \link colvarmodule::rvector \endlink type_unit3vector, /// 3-dimensional vector that is a derivative of a unitvector type_unit3vectorderiv, /// 4-dimensional unit vector representing a rotation, implemented as \link colvarmodule::quaternion \endlink type_quaternion, /// 4-dimensional vector that is a derivative of a quaternion type_quaternionderiv, /// vector (arbitrary dimension) type_vector, /// Needed to iterate through enum type_all }; /// Current type of this colvarvalue object Type value_type; /// \brief Real data member cvm::real real_value; /// \brief 3-dimensional vector data member cvm::rvector rvector_value; /// \brief Quaternion data member cvm::quaternion quaternion_value; /// \brief Generic vector data member cvm::vector1d vector1d_value; /// \brief If \link vector1d_value \endlink is a concatenation of colvarvalues, /// keep track of the individual types std::vector elem_types; /// \brief If \link vector1d_value \endlink is a concatenation of colvarvalues, /// these mark the initial components of each colvarvalue std::vector elem_indices; /// \brief If \link vector1d_value \endlink is a concatenation of colvarvalues, /// these mark how many components for each colvarvalue std::vector elem_sizes; /// \brief Whether or not the type check is enforced static inline bool type_checking() { return true; } /// Runtime description of value types static std::string const type_desc(Type t); /// User keywords for specifying value types in the configuration static std::string const type_keyword(Type t); /// Number of degrees of freedom for each supported type static size_t num_df(Type t); /// Number of dimensions for each supported type (used to allocate vector1d_value) static size_t num_dimensions(Type t); /// Number of dimensions of this variable size_t size() const; /// \brief Default constructor: this class defaults to a scalar /// number and always behaves like it unless you change its type inline colvarvalue() : value_type(type_scalar), real_value(0.0) {} /// Constructor from a type specification inline colvarvalue(Type const &vti) : value_type(vti) { reset(); } /// Copy constructor from real base type inline colvarvalue(cvm::real const &x) : value_type(type_scalar), real_value(x) {} /// \brief Copy constructor from rvector base type (Note: this sets /// by default a type \link type_3vector \endlink , if you want a /// \link type_unit3vector \endlink you must set it explicitly) inline colvarvalue(cvm::rvector const &v, Type vti = type_3vector) : value_type(vti), rvector_value(v) {} /// \brief Copy constructor from quaternion base type inline colvarvalue(cvm::quaternion const &q, Type vti = type_quaternion) : value_type(vti), quaternion_value(q) {} /// Copy constructor from vector1d base type colvarvalue(cvm::vector1d const &v, Type vti = type_vector); /// Copy constructor from another \link colvarvalue \endlink colvarvalue(colvarvalue const &x); /// Set to the null value for the data type currently defined void reset(); /// \brief If the variable has constraints (e.g. unitvector or /// quaternion), transform it to satisfy them; this function needs /// to be called only when the \link colvarvalue \endlink /// is calculated outside of \link cvc \endlink objects void apply_constraints(); /// Get the current type inline Type type() const { return value_type; } /// Set the type explicitly - inline void type(Type const &vti) - { - if (vti != value_type) { - // reset the value based on the previous type - reset(); - if ((value_type == type_vector) && (vti != type_vector)) { - vector1d_value.resize(0); - } - value_type = vti; - } - } + void type(Type const &vti); /// Set the type after another \link colvarvalue \endlink - inline void type(colvarvalue const &x) - { - if (x.type() != value_type) { - // reset the value based on the previous type - reset(); - if (value_type == type_vector) { - vector1d_value.resize(0); - } - value_type = x.type(); - } - - if (x.type() == type_vector) { - vector1d_value.resize(x.vector1d_value.size()); - } - } + void type(colvarvalue const &x); /// Make the type a derivative of the original type /// (so that its constraints do not apply) - inline void is_derivative(); + void is_derivative(); /// Square norm of this colvarvalue cvm::real norm2() const; /// Norm of this colvarvalue inline cvm::real norm() const { return std::sqrt(this->norm2()); } /// Square distance between this \link colvarvalue \endlink and another cvm::real dist2(colvarvalue const &x2) const; /// Derivative with respect to this \link colvarvalue \endlink of the square distance colvarvalue dist2_grad(colvarvalue const &x2) const; /// Assignment operator (type of x is checked) colvarvalue & operator = (colvarvalue const &x); void operator += (colvarvalue const &x); void operator -= (colvarvalue const &x); void operator *= (cvm::real const &a); void operator /= (cvm::real const &a); // Binary operators (return values) friend colvarvalue operator + (colvarvalue const &x1, colvarvalue const &x2); friend colvarvalue operator - (colvarvalue const &x1, colvarvalue const &x2); friend colvarvalue operator * (colvarvalue const &x, cvm::real const &a); friend colvarvalue operator * (cvm::real const &a, colvarvalue const &x); friend colvarvalue operator / (colvarvalue const &x, cvm::real const &a); /// Inner product friend cvm::real operator * (colvarvalue const &x1, colvarvalue const &x2); // Cast to scalar inline operator cvm::real() const { if (value_type != type_scalar) { cvm::error("Error: trying to use a variable of type \""+ type_desc(value_type)+"\" as one of type \""+ type_desc(type_scalar)+"\".\n"); } return real_value; } // Cast to 3-vector inline operator cvm::rvector() const { if ((value_type != type_3vector) && (value_type != type_unit3vector) && (value_type != type_unit3vectorderiv)) { cvm::error("Error: trying to use a variable of type \""+ type_desc(value_type)+"\" as one of type \""+ type_desc(type_3vector)+"\".\n"); } return rvector_value; } // Cast to quaternion inline operator cvm::quaternion() const { if ((value_type != type_quaternion) && (value_type != type_quaternionderiv)) { cvm::error("Error: trying to use a variable of type \""+ type_desc(value_type)+"\" as one of type \""+ type_desc(type_quaternion)+"\".\n"); } return quaternion_value; } // Create a n-dimensional vector from one of the basic types, or return the existing vector cvm::vector1d const as_vector() const; /// Whether this variable is a real number inline bool is_scalar() const { return (value_type == type_scalar); } /// Add an element to the vector (requires that type_vector is already set). /// This is only needed to use this object as a vector of "complex" colvar values. /// To use it instead as a plain n-dimensional vector, access vector1d_value directly. void add_elem(colvarvalue const &x); /// Get a single colvarvalue out of elements of the vector colvarvalue const get_elem(int const i_begin, int const i_end, Type const vt) const; /// Set elements of the vector from a single colvarvalue void set_elem(int const i_begin, int const i_end, colvarvalue const &x); /// Make each element a random number in N(0,1) void set_random(); /// Get a single colvarvalue out of elements of the vector colvarvalue const get_elem(int const icv) const; /// Set elements of the vector from a single colvarvalue void set_elem(int const icv, colvarvalue const &x); /// Get a scalar number out of an element of the vector - inline cvm::real operator [] (int const i) const - { - if (vector1d_value.size() > 0) { - return vector1d_value[i]; - } else { - cvm::error("Error: trying to use as a vector a variable that is not initialized as such.\n"); - return 0.0; - } - } + cvm::real operator [] (int const i) const; /// Use an element of the vector as a scalar number - inline cvm::real & operator [] (int const i) - { - if (vector1d_value.size() > 0) { - return vector1d_value[i]; - } else { - cvm::error("Error: trying to use as a vector a variable that is not initialized as such.\n"); - real_value = 0.0; - return real_value; - } - } - + cvm::real & operator [] (int const i); /// Ensure that the two types are the same within a binary operator int static check_types(colvarvalue const &x1, colvarvalue const &x2); /// Ensure that the two types are the same within an assignment, or that the left side is type_notset int static check_types_assign(Type const &vt1, Type const &vt2); /// Undefined operation void undef_op() const; /// \brief Formatted output operator friend std::ostream & operator << (std::ostream &os, colvarvalue const &q); /// \brief Formatted input operator friend std::istream & operator >> (std::istream &is, colvarvalue &q); /// Give the number of characters required to output this /// colvarvalue, given the current type assigned and the number of /// characters for a real number size_t output_width(size_t const &real_width) const; /// Formats value as a script-friendly string (space separated list) std::string to_simple_string() const; /// Parses value from a script-friendly string (space separated list) int from_simple_string(std::string const &s); // optimized routines for operations on arrays of colvar values; // xv and result are assumed to have the same number of elements /// \brief Optimized routine for the inner product of one collective /// variable with an array void static inner_opt(colvarvalue const &x, std::vector::iterator &xv, std::vector::iterator const &xv_end, std::vector::iterator &result); /// \brief Optimized routine for the inner product of one collective /// variable with an array void static inner_opt(colvarvalue const &x, std::list::iterator &xv, std::list::iterator const &xv_end, std::vector::iterator &result); /// \brief Optimized routine for the second order Legendre /// polynomial, (3cos^2(w)-1)/2, of one collective variable with an /// array void static p2leg_opt(colvarvalue const &x, std::vector::iterator &xv, std::vector::iterator const &xv_end, std::vector::iterator &result); /// \brief Optimized routine for the second order Legendre /// polynomial of one collective variable with an array void static p2leg_opt(colvarvalue const &x, std::list::iterator &xv, std::list::iterator const &xv_end, std::vector::iterator &result); }; - -inline std::string const colvarvalue::type_desc(Type t) -{ - switch (t) { - case colvarvalue::type_scalar: - return "scalar number"; break; - case colvarvalue::type_3vector: - return "3-dimensional vector"; break; - case colvarvalue::type_unit3vector: - return "3-dimensional unit vector"; break; - case colvarvalue::type_unit3vectorderiv: - return "derivative of a 3-dimensional unit vector"; break; - case colvarvalue::type_quaternion: - return "4-dimensional unit quaternion"; break; - case colvarvalue::type_quaternionderiv: - return "4-dimensional tangent vector"; break; - case colvarvalue::type_vector: - return "n-dimensional vector"; break; - case colvarvalue::type_notset: - // fallthrough - default: - return "not set"; break; - } -} - - -inline std::string const colvarvalue::type_keyword(Type t) -{ - switch (t) { - case colvarvalue::type_notset: - default: - return "not_set"; break; - case colvarvalue::type_scalar: - return "scalar"; break; - case colvarvalue::type_3vector: - return "vector3"; break; - case colvarvalue::type_unit3vector: - return "unit_vector3"; break; - case colvarvalue::type_unit3vectorderiv: - return ""; break; - case colvarvalue::type_quaternion: - return "unit_quaternion"; break; - case colvarvalue::type_quaternionderiv: - return ""; break; - case colvarvalue::type_vector: - return "vector"; break; - } -} - - -inline size_t colvarvalue::num_df(Type t) +inline size_t colvarvalue::size() const { - switch (t) { + switch (value_type) { case colvarvalue::type_notset: default: return 0; break; case colvarvalue::type_scalar: return 1; break; case colvarvalue::type_3vector: - return 3; break; case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: - return 2; break; + return 3; break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: - return 3; break; + return 4; break; case colvarvalue::type_vector: - // the size of a vector is unknown without its object - return 0; break; + return vector1d_value.size(); break; } } -inline size_t colvarvalue::num_dimensions(Type t) +inline cvm::real colvarvalue::operator [] (int const i) const { - switch (t) { + switch (value_type) { case colvarvalue::type_notset: default: - return 0; break; + cvm::error("Error: trying to access a colvar value " + "that is not initialized.\n", BUG_ERROR); + return 0.0; break; case colvarvalue::type_scalar: - return 1; break; + return real_value; break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: - return 3; break; + return rvector_value[i]; break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: - return 4; break; + return quaternion_value[i]; break; case colvarvalue::type_vector: - // the size of a vector is unknown without its object - return 0; break; + return vector1d_value[i]; break; } } -inline size_t colvarvalue::size() const +inline cvm::real & colvarvalue::operator [] (int const i) { switch (value_type) { case colvarvalue::type_notset: default: - return 0; break; + cvm::error("Error: trying to access a colvar value " + "that is not initialized.\n", BUG_ERROR); + return real_value; break; case colvarvalue::type_scalar: - return 1; break; + return real_value; break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: - return 3; break; + return rvector_value[i]; break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: - return 4; break; + return quaternion_value[i]; break; case colvarvalue::type_vector: - return vector1d_value.size(); break; - } -} - - -inline colvarvalue::colvarvalue(colvarvalue const &x) - : value_type(x.type()) -{ - switch (x.type()) { - case type_scalar: - real_value = x.real_value; - break; - case type_3vector: - case type_unit3vector: - case type_unit3vectorderiv: - rvector_value = x.rvector_value; - break; - case type_quaternion: - case type_quaternionderiv: - quaternion_value = x.quaternion_value; - break; - case type_vector: - vector1d_value = x.vector1d_value; - elem_types = x.elem_types; - elem_indices = x.elem_indices; - elem_sizes = x.elem_sizes; - case type_notset: - default: - break; - } -} - -inline colvarvalue::colvarvalue(cvm::vector1d const &v, Type vti) -{ - if ((vti != type_vector) && (v.size() != num_dimensions(vti))) { - cvm::error("Error: trying to initialize a variable of type \""+type_desc(vti)+ - "\" using a vector of size "+cvm::to_str(v.size())+ - ".\n"); - value_type = type_notset; - } else { - value_type = vti; - switch (vti) { - case type_scalar: - real_value = v[0]; - break; - case type_3vector: - case type_unit3vector: - case type_unit3vectorderiv: - rvector_value = cvm::rvector(v); - break; - case type_quaternion: - case type_quaternionderiv: - quaternion_value = cvm::quaternion(v); - break; - case type_vector: - vector1d_value = v; - break; - case type_notset: - default: - break; - } + return vector1d_value[i]; break; } } inline int colvarvalue::check_types(colvarvalue const &x1, colvarvalue const &x2) { if (!colvarvalue::type_checking()) { return COLVARS_OK; } if (x1.type() != x2.type()) { if (((x1.type() == type_unit3vector) && (x2.type() == type_unit3vectorderiv)) || ((x2.type() == type_unit3vector) && (x1.type() == type_unit3vectorderiv)) || ((x1.type() == type_quaternion) && (x2.type() == type_quaternionderiv)) || ((x2.type() == type_quaternion) && (x1.type() == type_quaternionderiv))) { return COLVARS_OK; } else { cvm::error("Trying to perform an operation between two colvar " "values with different types, \""+ colvarvalue::type_desc(x1.type())+ "\" and \""+ colvarvalue::type_desc(x2.type())+ "\".\n"); return COLVARS_ERROR; } } if (x1.type() == type_vector) { if (x1.vector1d_value.size() != x2.vector1d_value.size()) { cvm::error("Trying to perform an operation between two vector colvar " "values with different sizes, "+ cvm::to_str(x1.vector1d_value.size())+ " and "+ cvm::to_str(x2.vector1d_value.size())+ ".\n"); return COLVARS_ERROR; } } return COLVARS_OK; } inline int colvarvalue::check_types_assign(colvarvalue::Type const &vt1, colvarvalue::Type const &vt2) { if (!colvarvalue::type_checking()) { return COLVARS_OK; } if (vt1 != type_notset) { if (((vt1 == type_unit3vector) && (vt2 == type_unit3vectorderiv)) || ((vt2 == type_unit3vector) && (vt1 == type_unit3vectorderiv)) || ((vt1 == type_quaternion) && (vt2 == type_quaternionderiv)) || ((vt2 == type_quaternion) && (vt1 == type_quaternionderiv))) { return COLVARS_OK; } else { if (vt1 != vt2) { cvm::error("Trying to assign a colvar value with type \""+ type_desc(vt2)+"\" to one with type \""+ type_desc(vt1)+"\".\n"); return COLVARS_ERROR; } } } return COLVARS_OK; } -inline void colvarvalue::undef_op() const -{ - cvm::error("Error: Undefined operation on a colvar of type \""+ - type_desc(this->type())+"\".\n"); -} - - inline colvarvalue & colvarvalue::operator = (colvarvalue const &x) { check_types_assign(this->type(), x.type()); value_type = x.type(); switch (this->type()) { case colvarvalue::type_scalar: this->real_value = x.real_value; break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: this->rvector_value = x.rvector_value; break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: this->quaternion_value = x.quaternion_value; break; case colvarvalue::type_vector: vector1d_value = x.vector1d_value; elem_types = x.elem_types; elem_indices = x.elem_indices; elem_sizes = x.elem_sizes; break; case colvarvalue::type_notset: default: undef_op(); break; } return *this; } inline void colvarvalue::operator += (colvarvalue const &x) { colvarvalue::check_types(*this, x); switch (this->type()) { case colvarvalue::type_scalar: this->real_value += x.real_value; break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: this->rvector_value += x.rvector_value; break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: this->quaternion_value += x.quaternion_value; break; case colvarvalue::type_vector: this->vector1d_value += x.vector1d_value; break; case colvarvalue::type_notset: default: undef_op(); } } + inline void colvarvalue::operator -= (colvarvalue const &x) { colvarvalue::check_types(*this, x); switch (value_type) { case colvarvalue::type_scalar: real_value -= x.real_value; break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: rvector_value -= x.rvector_value; break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: quaternion_value -= x.quaternion_value; break; case colvarvalue::type_vector: this->vector1d_value -= x.vector1d_value; break; case colvarvalue::type_notset: default: undef_op(); } } inline void colvarvalue::operator *= (cvm::real const &a) { switch (value_type) { case colvarvalue::type_scalar: real_value *= a; break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vectorderiv: rvector_value *= a; break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: quaternion_value *= a; break; case colvarvalue::type_vector: this->vector1d_value *= a; break; case colvarvalue::type_notset: default: undef_op(); } } inline void colvarvalue::operator /= (cvm::real const &a) { switch (value_type) { case colvarvalue::type_scalar: real_value /= a; break; case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: rvector_value /= a; break; case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: quaternion_value /= a; break; case colvarvalue::type_vector: this->vector1d_value /= a; break; case colvarvalue::type_notset: default: undef_op(); } } inline cvm::vector1d const colvarvalue::as_vector() const { switch (value_type) { case colvarvalue::type_scalar: { cvm::vector1d v(1); v[0] = real_value; return v; } case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: return rvector_value.as_vector(); case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: return quaternion_value.as_vector(); case colvarvalue::type_vector: return vector1d_value; case colvarvalue::type_notset: default: return cvm::vector1d(0); } } -inline void colvarvalue::reset() -{ - switch (value_type) { - case colvarvalue::type_scalar: - real_value = 0.0; - break; - case colvarvalue::type_3vector: - case colvarvalue::type_unit3vector: - case colvarvalue::type_unit3vectorderiv: - rvector_value.reset(); - break; - case colvarvalue::type_quaternion: - case colvarvalue::type_quaternionderiv: - quaternion_value.reset(); - break; - case colvarvalue::type_vector: - vector1d_value.reset(); - break; - case colvarvalue::type_notset: - default: - break; - } -} - - -inline void colvarvalue::apply_constraints() -{ - switch (value_type) { - case colvarvalue::type_scalar: - case colvarvalue::type_3vector: - case colvarvalue::type_unit3vectorderiv: - case colvarvalue::type_quaternionderiv: - break; - case colvarvalue::type_unit3vector: - rvector_value /= std::sqrt(rvector_value.norm2()); - break; - case colvarvalue::type_quaternion: - quaternion_value /= std::sqrt(quaternion_value.norm2()); - break; - case colvarvalue::type_vector: - if (elem_types.size() > 0) { - // if we have information about non-scalar types, use it - size_t i; - for (i = 0; i < elem_types.size(); i++) { - if (elem_sizes[i] == 1) continue; // TODO this can be optimized further - colvarvalue cvtmp(vector1d_value.slice(elem_indices[i], - elem_indices[i] + elem_sizes[i]), elem_types[i]); - cvtmp.apply_constraints(); - set_elem(i, cvtmp); - } - } - break; - case colvarvalue::type_notset: - default: - break; - } -} - - -inline void colvarvalue::is_derivative() -{ - switch (value_type) { - case colvarvalue::type_scalar: - case colvarvalue::type_3vector: - case colvarvalue::type_unit3vectorderiv: - case colvarvalue::type_quaternionderiv: - break; - case colvarvalue::type_unit3vector: - type(colvarvalue::type_unit3vectorderiv); - break; - case colvarvalue::type_quaternion: - type(colvarvalue::type_quaternionderiv); - break; - case colvarvalue::type_vector: - // TODO - break; - case colvarvalue::type_notset: - default: - break; - } -} - - inline cvm::real colvarvalue::norm2() const { switch (value_type) { case colvarvalue::type_scalar: return (this->real_value)*(this->real_value); case colvarvalue::type_3vector: case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: return (this->rvector_value).norm2(); case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: return (this->quaternion_value).norm2(); case colvarvalue::type_vector: if (elem_types.size() > 0) { // if we have information about non-scalar types, use it cvm::real result = 0.0; size_t i; for (i = 0; i < elem_types.size(); i++) { result += (this->get_elem(i)).norm2(); } return result; } else { return vector1d_value.norm2(); } break; case colvarvalue::type_notset: default: return 0.0; } } inline cvm::real colvarvalue::dist2(colvarvalue const &x2) const { colvarvalue::check_types(*this, x2); switch (this->type()) { case colvarvalue::type_scalar: return (this->real_value - x2.real_value)*(this->real_value - x2.real_value); case colvarvalue::type_3vector: return (this->rvector_value - x2.rvector_value).norm2(); case colvarvalue::type_unit3vector: case colvarvalue::type_unit3vectorderiv: // angle between (*this) and x2 is the distance return std::acos(this->rvector_value * x2.rvector_value) * std::acos(this->rvector_value * x2.rvector_value); case colvarvalue::type_quaternion: case colvarvalue::type_quaternionderiv: // angle between (*this) and x2 is the distance, the quaternion // object has it implemented internally return this->quaternion_value.dist2(x2.quaternion_value); case colvarvalue::type_vector: return (this->vector1d_value - x2.vector1d_value).norm2(); case colvarvalue::type_notset: default: this->undef_op(); return 0.0; }; } #endif diff --git a/src/USER-COLVARS/colvarproxy_lammps.cpp b/src/USER-COLVARS/colvarproxy_lammps.cpp index 89453ded9..17dff3056 100644 --- a/src/USER-COLVARS/colvarproxy_lammps.cpp +++ b/src/USER-COLVARS/colvarproxy_lammps.cpp @@ -1,495 +1,491 @@ // -*- 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 "lammps.h" #include "atom.h" #include "error.h" #include "output.h" #include "random_park.h" #include "fix_colvars.h" #include "colvarmodule.h" #include "colvar.h" #include "colvarbias.h" #include "colvaratoms.h" #include "colvarproxy.h" #include "colvarproxy_lammps.h" #include #include #include #include #include #include #include #include #include #define HASH_FAIL -1 //////////////////////////////////////////////////////////////////////// // local helper functions // safely move filename to filename.extension static int my_backup_file(const char *filename, const char *extension) { struct stat sbuf; if (stat(filename, &sbuf) == 0) { if (!extension) extension = ".BAK"; char *backup = new char[strlen(filename)+strlen(extension)+1]; strcpy(backup, filename); strcat(backup, extension); #if defined(_WIN32) && !defined(__CYGWIN__) remove(backup); #endif if (rename(filename,backup)) { char *sys_err_msg = strerror(errno); if (!sys_err_msg) sys_err_msg = (char *) "(unknown error)"; fprintf(stderr,"Error renaming file %s to %s: %s\n", filename, backup, sys_err_msg); delete [] backup; return COLVARS_ERROR; } delete [] backup; } return COLVARS_OK; } //////////////////////////////////////////////////////////////////////// colvarproxy_lammps::colvarproxy_lammps(LAMMPS_NS::LAMMPS *lmp, const char *inp_name, const char *out_name, const int seed, const double temp, MPI_Comm root2root) : _lmp(lmp), inter_comm(root2root) { if (cvm::debug()) log("Initializing the colvars proxy object.\n"); _random = new LAMMPS_NS::RanPark(lmp,seed); first_timestep=true; total_force_requested=false; previous_step=-1; t_target=temp; do_exit=false; restart_every=0; // User-scripted forces are not available in LAMMPS force_script_defined = false; have_scripts = false; // set input restart name and strip the extension, if present input_prefix_str = std::string(inp_name ? inp_name : ""); 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()); // output prefix is always given output_prefix_str = std::string(out_name); // not so for restarts restart_output_prefix_str = std::string("rest"); // check if it is possible to save output configuration if ((!output_prefix_str.size()) && (!restart_output_prefix_str.size())) { fatal_error("Error: neither the final output state file or " "the output restart file could be defined, exiting.\n"); } // try to extract a restart prefix from a potential restart command. LAMMPS_NS::Output *outp = _lmp->output; if ((outp->restart_every_single > 0) && (outp->restart1 != 0)) { restart_output_prefix_str = std::string(outp->restart1); } else if ((outp->restart_every_double > 0) && (outp->restart2a != 0)) { restart_output_prefix_str = std::string(outp->restart2a); } // trim off unwanted stuff from the restart prefix if (restart_output_prefix_str.rfind(".*") != std::string::npos) restart_output_prefix_str.erase(restart_output_prefix_str.rfind(".*"),2); #if defined(_OPENMP) if (smp_thread_id() == 0) { omp_init_lock(&smp_lock_state); } #endif // initialize multi-replica support, if available if (replica_enabled()) { MPI_Comm_rank(inter_comm, &inter_me); MPI_Comm_size(inter_comm, &inter_num); } if (cvm::debug()) log("Done initializing the colvars proxy object.\n"); } void colvarproxy_lammps::init(const char *conf_file) { // create the colvarmodule instance colvars = new colvarmodule(this); cvm::log("Using LAMMPS interface, version "+ cvm::to_str(COLVARPROXY_VERSION)+".\n"); my_angstrom = _lmp->force->angstrom; my_boltzmann = _lmp->force->boltz; my_timestep = _lmp->update->dt * _lmp->force->femtosecond; // TODO move one or more of these to setup() if needed colvars->read_config_file(conf_file); colvars->setup_input(); colvars->setup_output(); if (_lmp->update->ntimestep != 0) { cvm::log("Initializing step number as firstTimestep.\n"); colvars->it = colvars->it_restart = _lmp->update->ntimestep; } if (cvm::debug()) { log("atoms_ids = "+cvm::to_str(atoms_ids)+"\n"); log("atoms_ncopies = "+cvm::to_str(atoms_ncopies)+"\n"); log("atoms_positions = "+cvm::to_str(atoms_positions)+"\n"); log(cvm::line_marker); log("Info: done initializing the colvars proxy object.\n"); } } colvarproxy_lammps::~colvarproxy_lammps() { delete _random; if (colvars != NULL) { colvars->write_output_files(); delete colvars; colvars = NULL; } } // re-initialize data where needed int colvarproxy_lammps::setup() { my_timestep = _lmp->update->dt * _lmp->force->femtosecond; return colvars->setup(); } // trigger colvars computation double colvarproxy_lammps::compute() { if (first_timestep) { first_timestep = false; } else { // Use the time step number inherited from LAMMPS if ( _lmp->update->ntimestep - previous_step == 1 ) colvars->it++; // Other cases could mean: // - run 0 // - beginning of a new run statement // then the internal counter should not be incremented } previous_step = _lmp->update->ntimestep; if (cvm::debug()) { cvm::log(std::string(cvm::line_marker)+ "colvarproxy_lammps, step no. "+cvm::to_str(colvars->it)+"\n"+ "Updating internal data.\n"); } // zero the forces on the atoms, so that they can be accumulated by the colvars for (size_t i = 0; i < atoms_new_colvar_forces.size(); i++) { atoms_new_colvar_forces[i].reset(); } bias_energy = 0.0; if (cvm::debug()) { log("atoms_ids = "+cvm::to_str(atoms_ids)+"\n"); log("atoms_ncopies = "+cvm::to_str(atoms_ncopies)+"\n"); log("atoms_positions = "+cvm::to_str(atoms_positions)+"\n"); log("atoms_new_colvar_forces = "+cvm::to_str(atoms_new_colvar_forces)+"\n"); } // call the collective variable module colvars->calc(); if (cvm::debug()) { log("atoms_ids = "+cvm::to_str(atoms_ids)+"\n"); log("atoms_ncopies = "+cvm::to_str(atoms_ncopies)+"\n"); log("atoms_positions = "+cvm::to_str(atoms_positions)+"\n"); log("atoms_new_colvar_forces = "+cvm::to_str(atoms_new_colvar_forces)+"\n"); } return bias_energy; } void colvarproxy_lammps::serialize_status(std::string &rst) { std::ostringstream os; colvars->write_restart(os); rst = os.str(); } // set status from string bool colvarproxy_lammps::deserialize_status(std::string &rst) { std::istringstream is; is.str(rst); if (!colvars->read_restart(is)) { return false; } else { return true; } } cvm::rvector colvarproxy_lammps::position_distance(cvm::atom_pos const &pos1, cvm::atom_pos const &pos2) { double xtmp = pos2.x - pos1.x; double ytmp = pos2.y - pos1.y; double ztmp = pos2.z - pos1.z; _lmp->domain->minimum_image(xtmp,ytmp,ztmp); return cvm::rvector(xtmp, ytmp, ztmp); } cvm::real colvarproxy_lammps::position_dist2(cvm::atom_pos const &pos1, cvm::atom_pos const &pos2) { double xtmp = pos2.x - pos1.x; double ytmp = pos2.y - pos1.y; double ztmp = pos2.z - pos1.z; _lmp->domain->minimum_image(xtmp,ytmp,ztmp); return cvm::real(xtmp*xtmp + ytmp*ytmp + ztmp*ztmp); } void colvarproxy_lammps::select_closest_image(cvm::atom_pos &pos, cvm::atom_pos const &ref) { double xtmp = pos.x - ref.x; double ytmp = pos.y - ref.y; double ztmp = pos.z - ref.z; _lmp->domain->minimum_image(xtmp,ytmp,ztmp); pos.x = ref.x + xtmp; pos.y = ref.y + ytmp; pos.z = ref.z + ztmp; } void colvarproxy_lammps::log(std::string const &message) { std::istringstream is(message); std::string line; while (std::getline(is, line)) { if (_lmp->screen) fprintf(_lmp->screen,"colvars: %s\n",line.c_str()); if (_lmp->logfile) fprintf(_lmp->logfile,"colvars: %s\n",line.c_str()); } } void colvarproxy_lammps::error(std::string const &message) { // In LAMMPS, all errors are fatal fatal_error(message); } void colvarproxy_lammps::fatal_error(std::string const &message) { log(message); - // if (!cvm::debug()) - // log("If this error message is unclear, try recompiling the " - // "colvars library and LAMMPS with -DCOLVARS_DEBUG.\n"); - _lmp->error->one(FLERR, "Fatal error in the collective variables module.\n"); } void colvarproxy_lammps::exit(std::string const &message) { log(message); log("Request to exit the simulation made.\n"); do_exit=true; } int colvarproxy_lammps::backup_file(char const *filename) { if (std::string(filename).rfind(std::string(".colvars.state")) != std::string::npos) { return my_backup_file(filename, ".old"); } else { return my_backup_file(filename, ".BAK"); } } #if defined(_OPENMP) // SMP support int colvarproxy_lammps::smp_enabled() { if (b_smp_active) { return COLVARS_OK; } return COLVARS_ERROR; } int colvarproxy_lammps::smp_colvars_loop() { colvarmodule *cv = this->colvars; colvarproxy_lammps *proxy = (colvarproxy_lammps *) cv->proxy; #pragma omp parallel for for (size_t i = 0; i < 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()) { cvm::log("["+cvm::to_str(proxy->smp_thread_id())+"/"+cvm::to_str(proxy->smp_num_threads())+ "]: calc_colvars_items_smp(), i = "+cvm::to_str(i)+", cv = "+ x->name+", cvc = "+cvm::to_str(x_item)+"\n"); } x->calc_cvcs(x_item, 1); } return cvm::get_error(); } int colvarproxy_lammps::smp_biases_loop() { colvarmodule *cv = this->colvars; #pragma omp parallel for for (size_t i = 0; i < cv->biases_active()->size(); i++) { colvarbias *b = (*(cv->biases_active()))[i]; if (cvm::debug()) { cvm::log("Calculating bias \""+b->name+"\" on thread "+ cvm::to_str(smp_thread_id())+"\n"); } b->update(); } return cvm::get_error(); } int colvarproxy_lammps::smp_thread_id() { return omp_get_thread_num(); } int colvarproxy_lammps::smp_num_threads() { return omp_get_max_threads(); } int colvarproxy_lammps::smp_lock() { omp_set_lock(&smp_lock_state); return COLVARS_OK; } int colvarproxy_lammps::smp_trylock() { return omp_test_lock(&smp_lock_state) ? COLVARS_OK : COLVARS_ERROR; } int colvarproxy_lammps::smp_unlock() { omp_unset_lock(&smp_lock_state); return COLVARS_OK; } #endif // multi-replica support void colvarproxy_lammps::replica_comm_barrier() { MPI_Barrier(inter_comm); } int colvarproxy_lammps::replica_comm_recv(char* msg_data, int buf_len, int src_rep) { MPI_Status status; int retval; retval = MPI_Recv(msg_data,buf_len,MPI_CHAR,src_rep,0,inter_comm,&status); if (retval == MPI_SUCCESS) { MPI_Get_count(&status, MPI_CHAR, &retval); } else retval = 0; return retval; } int colvarproxy_lammps::replica_comm_send(char* msg_data, int msg_len, int dest_rep) { int retval; retval = MPI_Send(msg_data,msg_len,MPI_CHAR,dest_rep,0,inter_comm); if (retval == MPI_SUCCESS) { retval = msg_len; } else retval = 0; return retval; } int colvarproxy_lammps::check_atom_id(int atom_number) { int const aid = atom_number; if (cvm::debug()) log("Adding atom "+cvm::to_str(atom_number)+ " for collective variables calculation.\n"); // TODO add upper boundary check? if ( (aid < 0) ) { cvm::error("Error: invalid atom number specified, "+ cvm::to_str(atom_number)+"\n", INPUT_ERROR); return INPUT_ERROR; } return aid; } int colvarproxy_lammps::init_atom(int atom_number) { int aid = atom_number; for (size_t i = 0; i < atoms_ids.size(); i++) { if (atoms_ids[i] == aid) { // this atom id was already recorded atoms_ncopies[i] += 1; return i; } } aid = check_atom_id(atom_number); if (aid < 0) { return aid; } int const index = colvarproxy::add_atom_slot(aid); // add entries for the LAMMPS-specific fields atoms_types.push_back(0); return index; } diff --git a/src/USER-COLVARS/colvarproxy_lammps.h b/src/USER-COLVARS/colvarproxy_lammps.h index ad63eb2f9..6cdf0edfe 100644 --- a/src/USER-COLVARS/colvarproxy_lammps.h +++ b/src/USER-COLVARS/colvarproxy_lammps.h @@ -1,182 +1,179 @@ // -*- 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 COLVARPROXY_LAMMPS_H #define COLVARPROXY_LAMMPS_H +#include "colvarproxy_lammps_version.h" + #include "colvarmodule.h" #include "colvarproxy.h" #include "colvarvalue.h" #include "lammps.h" #include "domain.h" #include "force.h" #include "update.h" #include #include #include #if defined(_OPENMP) #include #endif -#ifndef COLVARPROXY_VERSION -#define COLVARPROXY_VERSION "2017-01-09" -#endif - /* struct for packed data communication of coordinates and forces. */ struct commdata { int tag,type; double x,y,z,m,q; }; inline std::ostream & operator<< (std::ostream &out, const commdata &cd) { out << " (" << cd.tag << "/" << cd.type << ": " << cd.x << ", " << cd.y << ", " << cd.z << ") "; return out; } /// \brief Communication between colvars and LAMMPS /// (implementation of \link colvarproxy \endlink) class colvarproxy_lammps : public colvarproxy { // LAMMPS specific data objects and flags protected: // pointers to LAMMPS class instances class LAMMPS_NS::LAMMPS *_lmp; class LAMMPS_NS::RanPark *_random; // state of LAMMPS properties double t_target, my_timestep, my_boltzmann, my_angstrom; double bias_energy; int restart_every; int previous_step; bool first_timestep; bool total_force_requested; bool do_exit; // std::vector colvars_atoms; // std::vector colvars_atoms_ncopies; // std::vector positions; // std::vector total_forces; // std::vector applied_forces; // std::vector previous_applied_forces; std::vector atoms_types; MPI_Comm inter_comm; // MPI comm with 1 root proc from each world int inter_me, inter_num; // rank for the inter replica comm public: friend class cvm::atom; colvarproxy_lammps(LAMMPS_NS::LAMMPS *lmp, const char *, const char *, const int, const double, MPI_Comm); virtual ~colvarproxy_lammps(); void init(const char*); int setup(); // disable default and copy constructor private: colvarproxy_lammps() {}; colvarproxy_lammps(const colvarproxy_lammps &) {}; // methods for lammps to move data or trigger actions in the proxy public: void set_temperature(double t) { t_target = t; }; bool total_forces_enabled() const { return total_force_requested; }; bool want_exit() const { return do_exit; }; // perform colvars computation. returns biasing energy double compute(); // dump status to string void serialize_status(std::string &); // set status from string bool deserialize_status(std::string &); // implementation of pure methods from base class public: inline cvm::real unit_angstrom() { return my_angstrom; }; inline cvm::real boltzmann() { return my_boltzmann; }; inline cvm::real temperature() { return t_target; }; inline cvm::real dt() { return my_timestep; }; // return _lmp->update->dt * _lmp->force->femtosecond; }; inline size_t restart_frequency() { return restart_every; }; void add_energy(cvm::real energy) { bias_energy += energy; }; void request_total_force(bool yesno) { total_force_requested = yesno; }; void log(std::string const &message); void error(std::string const &message); void fatal_error(std::string const &message); void exit(std::string const &message); cvm::rvector position_distance(cvm::atom_pos const &pos1, cvm::atom_pos const &pos2); cvm::real position_dist2(cvm::atom_pos const &pos1, cvm::atom_pos const &pos2); void select_closest_image(cvm::atom_pos &pos, cvm::atom_pos const &ref_pos); int backup_file(char const *filename); cvm::real rand_gaussian(void) { return _random->gaussian(); }; int init_atom(int atom_number); int check_atom_id(int atom_number); inline std::vector *modify_atom_types() { return &atoms_types; } // implementation of optional methods from base class public: #if defined(_OPENMP) // SMP support int smp_enabled(); int smp_colvars_loop(); int smp_biases_loop(); int smp_thread_id(); int smp_num_threads(); protected: omp_lock_t smp_lock_state; public: int smp_lock(); int smp_trylock(); int smp_unlock(); #endif // Multi-replica support // Indicate if multi-replica support is available and active virtual bool replica_enabled() { return (inter_comm != MPI_COMM_NULL); } // Index of this replica virtual int replica_index() { return inter_me; } // Total number of replica virtual int replica_num() { return inter_num; } // Synchronize replica virtual void replica_comm_barrier(); // Receive data from other replica virtual int replica_comm_recv(char* msg_data, int buf_len, int src_rep); // Send data to other replica virtual int replica_comm_send(char* msg_data, int msg_len, int dest_rep); }; #endif diff --git a/src/USER-COLVARS/colvarproxy_lammps_version.h b/src/USER-COLVARS/colvarproxy_lammps_version.h new file mode 100644 index 000000000..834bd1748 --- /dev/null +++ b/src/USER-COLVARS/colvarproxy_lammps_version.h @@ -0,0 +1,10 @@ +#ifndef COLVARPROXY_VERSION +#define COLVARPROXY_VERSION "2017-07-15" +// 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. + +#endif