def _create_table(directory, intcor="average.ic", parmfile="fluctmatch.dist.prm", tbltype="Kb", verbose=False): if path.isdir(directory): if verbose: print("Reading directory {}".format(directory)) with reader(path.join(directory, intcor)) as ic_file: if verbose: print(" Processing {}...".format( path.join(directory, intcor))) ic_table = ic_file.read() ic_table.set_index(_header, inplace=True) with reader(path.join(directory, parmfile)) as prm_file: if verbose: print(" Processing {}...".format( path.join(directory, parmfile))) prm_table = prm_file.read()["BONDS"].set_index(_header) table = pd.concat([ic_table, prm_table], axis=1) table.reset_index(inplace=True) table = table.set_index(_index["general"])[tbltype].to_frame() table.columns = [ path.basename(directory), ] return table
def trim_get_new_bondlist_write_prm(self, atomfluc_diff, **kwargs): # READ fixed and dynamic parameter files and initial ic files params = dict() with reader(self.filenames["fixed_prm"]) as fixed: params.update(fixed.read()) dyn_params = dict() with reader(self.filenames["dynamic_prm"]) as dynamic: dyn_params.update(dynamic.read()) with reader(self.filenames["init_fluct_ic"]) as icfile: std_bonds = icfile.read().set_index(self.pair_idx) with reader(self.filenames["init_avg_ic"]) as avgfile: avg_bonds = avgfile.read().set_index(self.pair_idx) # Get the bonds to be trimmed from fixed parameter file params["BONDS"].set_index(self.pair_idx, inplace=True) logger.info(f"Before trimming NBONDS: {params['BONDS'].shape}") count = 0 bonds2trim = [] for i in params["BONDS"].index.values: if atomfluc_diff.loc[ i[0]] > self.rmsf_cutoff and atomfluc_diff.loc[ i[1]] > self.rmsf_cutoff: if params["BONDS"].loc[i, 'Kb'] > self.kb_cutoff and params[ "BONDS"].loc[i, 'b0'] > self.dist_cutoff: resI = self.universe.atoms.select_atoms( f"type {i[0]}").resids[0] resJ = self.universe.atoms.select_atoms( f"type {i[1]}").resids[0] if resI > resJ + 2 or resJ > resI + 2: bonds2trim.append(i) count += 1 logger.info(f"Totally {count} bonds found") params["BONDS"].drop(bonds2trim, inplace=True) logger.info(f"After trimming NBONDS: {params['BONDS'].shape}") params["BONDS"].reset_index(inplace=True) # Write both fixed and dynamics with mda.Writer(self.write_filenames["fixed_prm"], **kwargs) as fixed_param: fixed_param.write(params) new_bondlist = params["BONDS"].set_index(self.pair_idx).index.values # Make NBONDS in dynamic parameters to match fixed parameter NBONDS # as the # of bonds selected from parameters and dynamic_parameter # could be diffrent due to its update average bond details in every iteration of FM dyn_params["BONDS"].set_index(self.pair_idx, inplace=True) dyn_params["BONDS"] = dyn_params["BONDS"].loc[new_bondlist] dyn_params["BONDS"].reset_index(inplace=True) assert dyn_params['BONDS'].shape[0] == params['BONDS'].shape[ 0], "Shape Mismatch." with mda.Writer(self.write_filenames["dynamic_prm"], **kwargs) as dynamic_param: dynamic_param.write(dyn_params) """ for matching the shape of initial and current parameter files for force constant calculation, the atom pairs trimmed in parameter files should also be trimmed in ic files """ cols = np.asarray([ "segidI", "resI", "I", "segidJ", "resJ", "J", "segidK", "resK", "K", "segidL", "resL", "L", "r_IJ", "T_IJK", "P_IJKL", "T_JKL", "r_KL" ]) std_bonds = std_bonds.loc[new_bondlist] std_bonds.reset_index(inplace=True) std_bonds = std_bonds[cols] avg_bonds = avg_bonds.loc[new_bondlist] avg_bonds.reset_index(inplace=True) avg_bonds = avg_bonds[cols] with mda.Writer(self.write_filenames["init_fluct_ic"], **kwargs) as fluct_init: fluct_init.write(std_bonds) with mda.Writer(self.write_filenames["init_avg_ic"], **kwargs) as avg_init: avg_init.write(avg_bonds) return new_bondlist, bonds2trim
def run(self, nma_exec=None, tol=1.e-3, n_cycles=300, low_bound=0.): """Perform a self-consistent fluctuation matching. Parameters ---------- nma_exec : str executable file for normal mode analysis tol : float, optional fluct difference tolerance n_cycles : int, optional number of fluctuation matching cycles low_bound : float, optional lowest Kb values to reduce noise """ # Find CHARMM executable charmm_exec = (os.environ.get("CHARMMEXEC", util.which("charmm")) if nma_exec is None else nma_exec) if charmm_exec is None: logger.exception( "Please set CHARMMEXEC with the location of your CHARMM " "executable file or add the charmm path to your PATH " "environment.") raise_with_traceback( OSError( "Please set CHARMMEXEC with the location of your CHARMM " "executable file or add the charmm path to your PATH " "environment.")) # Read the parameters if not self.parameters: try: self.initialize(nma_exec, restart=True) except IOError: raise_with_traceback( (IOError("Some files are missing. Unable to restart."))) # Write CHARMM input file. if not path.exists(self.filenames["charmm_input"]): version = self.kwargs.get("charmm_version", 41) dimension = ("dimension chsize 1000000" if version >= 36 else "") with open(self.filenames["charmm_input"], mode="wb") as charmm_file: logger.info("Writing CHARMM input file.") charmm_inp = charmm_nma.nma.format( temperature=self.temperature, flex="flex" if version else "", version=version, dimension=dimension, **self.filenames) charmm_inp = textwrap.dedent(charmm_inp[1:]) charmm_file.write(charmm_inp.encode()) # Set the indices for the parameter tables. self.target["BONDS"].set_index(self.bond_def, inplace=True) bond_values = self.target["BONDS"].columns # Check for restart. try: if os.stat(self.filenames["error_data"]).st_size > 0: with open(self.filenames["error_data"], "rb") as data: error_info = pd.read_csv(data, header=0, skipinitialspace=True, delim_whitespace=True) if not error_info.empty: self.error["step"] = error_info["step"].values[-1] else: raise FileNotFoundError except (FileNotFoundError, OSError): with open(self.filenames["error_data"], "wb") as data: np.savetxt( data, [ self.error_hdr, ], fmt=native_str("%15s"), # Nix delimiter=native_str("")) self.error["step"] += 1 # Initiate an all true index data, for preserving bond convergence if not self.restart: temp = ~self.target["BONDS"]["Kb"].isna() temp = temp.reset_index() self.converge_bnd_list = temp.iloc[:, 2] # Start self-consistent iteration for Fluctuation Matching # Run simulation logger.info( f"Starting fluctuation matching--{n_cycles} iterations to run") if low_bound != 0.: logger.info( f"Lower bound after 75% iteration is set to {low_bound}") st = time.time() fdiff = [] for i in range(n_cycles): ct = time.time() self.error["step"] = i + 1 with open(self.filenames["charmm_log"], "w") as log_file: subprocess.check_call( [charmm_exec, "-i", self.filenames["charmm_input"]], stdout=log_file, stderr=subprocess.STDOUT, ) self.dynamic_params["BONDS"].set_index(self.bond_def, inplace=True) self.parameters["BONDS"].set_index(self.bond_def, inplace=True) # Read the average bond distance. with reader(self.filenames["avg_ic"]) as icavg: avg_ic = icavg.read().set_index(self.bond_def)["r_IJ"] # Read the bond fluctuations. with reader(self.filenames["fluct_ic"]) as icfluct: fluct_ic = icfluct.read().set_index(self.bond_def)["r_IJ"] vib_ic = pd.concat([fluct_ic, avg_ic], axis=1) vib_ic.columns = bond_values logger.info(f"Checking for bondlist convergence") fluct_diff = np.abs(vib_ic[bond_values[0]] - self.target["BONDS"][bond_values[0]]) fdiff.append(fluct_diff) fluct_diff = fluct_diff.reset_index() tmp = self.parameters["BONDS"][bond_values[0]].reset_index() if not self.restart: self.converge_bnd_list &= ((fluct_diff.iloc[:, 2] > tol) & (tmp.iloc[:, 2] > 0)) else: if i == 0: self.converge_bnd_list = ((fluct_diff.iloc[:, 2] > tol) & (tmp.iloc[:, 2] > 0)) else: self.converge_bnd_list &= ((fluct_diff.iloc[:, 2] > tol) & (tmp.iloc[:, 2] > 0)) # Calculate the r.m.s.d. between fluctuation and distances # compared with the target values. vib_error = self.target["BONDS"] - vib_ic vib_error = vib_error.apply(np.square).mean(axis=0) vib_error = np.sqrt(vib_error) self.error[self.error.columns[-2:]] = vib_error.T.values # Calculate the new force constant. optimized = vib_ic.apply(np.reciprocal).apply(np.square) target = self.target["BONDS"].apply(np.reciprocal).apply(np.square) optimized -= target optimized *= self.BOLTZ * self.KFACTOR # update bond list vib_ic[bond_values[0]] = ( self.parameters["BONDS"][bond_values[0]] - optimized[bond_values[0]]) vib_ic[bond_values[0]] = (vib_ic[bond_values[0]].where( vib_ic[bond_values[0]] >= 0., 0.)) # set negative to zero if low_bound > 0. and i > int(n_cycles * 0.75): logger.info( f"Fluctuation matching cycle {i}: low bound is {low_bound}" ) vib_ic[bond_values[0]] = (vib_ic[bond_values[0]].where( vib_ic[bond_values[0]] >= low_bound, 0.)) # r.m.s.d. between previous and current force constant diff = self.dynamic_params["BONDS"] - vib_ic diff = diff.apply(np.square).mean(axis=0) diff = np.sqrt(diff) self.error[self.error.columns[1]] = diff.values[0] # Update the parameters and write to file. self.parameters["BONDS"][bond_values[0]] = vib_ic[bond_values[0]] self.dynamic_params["BONDS"][bond_values[0]] = vib_ic[ bond_values[0]] self.dynamic_params["BONDS"][bond_values[1]] = vib_ic[ bond_values[1]] self.parameters["BONDS"].reset_index(inplace=True) self.dynamic_params["BONDS"].reset_index(inplace=True) with mda.Writer(self.filenames["fixed_prm"], **self.kwargs) as prm: prm.write(self.parameters) with mda.Writer(self.filenames["dynamic_prm"], **self.kwargs) as prm: prm.write(self.dynamic_params) # Update the error values. with open(self.filenames["error_data"], "ab") as error_file: np.savetxt( error_file, self.error, fmt=native_str("%15d%15.6f%15.6f%15.6f", ), # Nix delimiter=native_str(""), ) logger.info( "Fluctuation matching cycle {} completed in {:.6f}".format( i, time.time() - ct)) logger.info( f"{self.converge_bnd_list.sum()} not converged out of {len(self.converge_bnd_list)}" ) if self.converge_bnd_list.sum() <= len( self.converge_bnd_list.values.tolist()) * 0.003: # if bonds to converge is less than 0.3% of total bonds, use relative difference as criteria # as it takes more than 100 iterations for these 0.3% bonds to converge. relative_diff = (fluct_diff.iloc[:, 2] - tol) / tol ### To know the late converged bonds uncomment the below 5 lines ### # late_converged = pd.DataFrame() # indx = self.converge_bnd_list[self.converge_bnd_list].index.values # late_converged = pd.concat([fluct_diff.loc[indx], relative_diff.loc[indx]], axis=1) # late_converged.columns = ["I", "J", "fluct_diff_Kb", "relative_diff_kb"] # print(late_converged) self.converge_bnd_list = self.converge_bnd_list & ( relative_diff > 5) if self.converge_bnd_list.sum() == 0: logger.info( "Checking relative difference: All bonds converged, exiting" ) break fluct_conv = pd.concat(fdiff, axis=1).round(6) fluct_conv.columns = [j for j in range(1, i + 2)] fluct_conv.to_csv(self.filenames["bond_convergence"]) logger.info( "Fluctuation matching completed in {:.6f}".format(time.time() - st)) self.target["BONDS"].reset_index(inplace=True)
def initialize(self, nma_exec=None, restart=False): """Create an elastic network model from a basic coarse-grain model. Parameters ---------- nma_exec : str executable file for normal mode analysis restart : bool, optional Reinitialize the object by reading files instead of doing initial calculations. """ self.restart = restart if not self.restart: # Write CHARMM input file. if not path.exists(self.filenames["init_input"]): version = self.kwargs.get("charmm_version", 41) dimension = ("dimension chsize 1000000" if version >= 36 else "") with open(self.filenames["init_input"], mode="wb") as charmm_file: logger.info("Writing CHARMM input file.") charmm_inp = charmm_init.init.format( flex="flex" if version else "", version=version, dimension=dimension, **self.filenames) charmm_inp = textwrap.dedent(charmm_inp[1:]) charmm_file.write(charmm_inp.encode()) charmm_exec = (os.environ.get("CHARMMEXEC", util.which("charmm")) if nma_exec is None else nma_exec) with open(self.filenames["init_log"], "w") as log_file: subprocess.check_call( [charmm_exec, "-i", self.filenames["init_input"]], stdout=log_file, stderr=subprocess.STDOUT, ) # Write the parameter files. with reader(self.filenames["init_fluct_ic"]) as icfile: std_bonds = icfile.read().set_index(self.bond_def) with reader(self.filenames["init_avg_ic"]) as icfile: avg_bonds = icfile.read().set_index(self.bond_def) target = pd.concat([std_bonds["r_IJ"], avg_bonds["r_IJ"]], axis=1) target.reset_index(inplace=True) logger.info("Calculating the initial CHARMM parameters...") universe = mda.Universe(self.filenames["xplor_psf_file"], self.filenames["crd_file"]) self.target = prmutils.create_empty_parameters( universe, **self.kwargs) target.columns = self.target["BONDS"].columns self.target["BONDS"] = target.copy(deep=True) self.parameters = copy.deepcopy(self.target) self.parameters["BONDS"]["Kb"] = ( self.BOLTZ / self.parameters["BONDS"]["Kb"].apply(np.square)) self.dynamic_params = copy.deepcopy(self.parameters) with mda.Writer(self.filenames["fixed_prm"], **self.kwargs) as prm: logger.info("Writing {}...".format( self.filenames["fixed_prm"])) prm.write(self.parameters) with mda.Writer(self.filenames["dynamic_prm"], **self.kwargs) as prm: logger.info("Writing {}...".format( self.filenames["dynamic_prm"])) prm.write(self.dynamic_params) else: print("FM Restarted") if not path.exists(self.filenames["fixed_prm"]): self.initialize(nma_exec, restart=False) try: # Read the parameter files. logger.info("Loading parameter and internal coordinate files.") with reader(self.filenames["fixed_prm"]) as fixed: self.parameters.update(fixed.read()) with reader(self.filenames["dynamic_prm"]) as dynamic: self.dynamic_params.update(dynamic.read()) # Read the initial internal coordinate files. with reader(self.filenames["init_avg_ic"]) as init_avg: avg_table = init_avg.read().set_index( self.bond_def)["r_IJ"] with reader(self.filenames["init_fluct_ic"]) as init_fluct: fluct_table = (init_fluct.read().set_index( self.bond_def)["r_IJ"]) table = pd.concat([fluct_table, avg_table], axis=1) # Set the target fluctuation values. logger.info("Files loaded successfully...") self.target = copy.deepcopy(self.parameters) self.target["BONDS"].set_index(self.bond_def, inplace=True) cols = self.target["BONDS"].columns table.columns = cols self.target["BONDS"] = table.copy(deep=True).reset_index() except (FileNotFoundError, IOError): raise_with_traceback( (IOError("Some files are missing. Unable to restart.")))
def run(self, nma_exec=None, tol=1.e-4, n_cycles=250): """Perform a self-consistent fluctuation matching. Parameters ---------- nma_exec : str executable file for normal mode analysis tol : float, optional error tolerance n_cycles : int, optional number of fluctuation matching cycles """ # Find CHARMM executable charmm_exec = (os.environ.get("CHARMMEXEC", util.which("charmm")) if nma_exec is None else nma_exec) if charmm_exec is None: logger.exception( "Please set CHARMMEXEC with the location of your CHARMM " "executable file or add the charmm path to your PATH " "environment.") raise_with_traceback( OSError( "Please set CHARMMEXEC with the location of your CHARMM " "executable file or add the charmm path to your PATH " "environment.")) # Read the parameters if not self.parameters: try: self.initialize(nma_exec, restart=True) except IOError: raise_with_traceback( (IOError("Some files are missing. Unable to restart."))) # Write CHARMM input file. if not path.exists(self.filenames["charmm_input"]): version = self.kwargs.get("charmm_version", 41) dimension = ("dimension chsize 1000000" if version >= 36 else "") with open( self.filenames["charmm_input"], mode="wb") as charmm_file: logger.info("Writing CHARMM input file.") charmm_inp = charmm_nma.nma.format( temperature=self.temperature, flex="flex" if version else "", version=version, dimension=dimension, **self.filenames) charmm_inp = textwrap.dedent(charmm_inp[1:]) charmm_file.write(charmm_inp.encode()) # Set the indices for the parameter tables. self.target["BONDS"].set_index(self.bond_def, inplace=True) bond_values = self.target["BONDS"].columns # Check for restart. try: if os.stat(self.filenames["error_data"]).st_size > 0: with open(self.filenames["error_data"], "rb") as data: error_info = pd.read_csv( data, header=0, skipinitialspace=True, delim_whitespace=True) if not error_info.empty: self.error["step"] = error_info["step"].values[-1] else: raise FileNotFoundError except (FileNotFoundError, OSError): with open(self.filenames["error_data"], "wb") as data: np.savetxt( data, [ self.error_hdr, ], fmt=native_str("%10s"), delimiter=native_str("")) self.error["step"] += 1 # Run simulation logger.info("Starting fluctuation matching") st = time.time() for i in range(n_cycles): self.error["step"] = i + 1 with open(self.filenames["charmm_log"], "w") as log_file: subprocess.check_call( [charmm_exec, "-i", self.filenames["charmm_input"]], stdout=log_file, stderr=subprocess.STDOUT, ) self.dynamic_params["BONDS"].set_index(self.bond_def, inplace=True) self.parameters["BONDS"].set_index(self.bond_def, inplace=True) # Read the average bond distance. with reader(self.filenames["avg_ic"]) as intcor: avg_ic = intcor.read().set_index(self.bond_def)["r_IJ"] # Read the bond fluctuations. with reader(self.filenames["fluct_ic"]) as intcor: fluct_ic = intcor.read().set_index(self.bond_def)["r_IJ"] vib_ic = pd.concat([fluct_ic, avg_ic], axis=1) vib_ic.columns = bond_values # Calculate the r.m.s.d. between fluctuation and distances # compared with the target values. vib_error = self.target["BONDS"] - vib_ic vib_error = vib_error.apply(np.square).mean(axis=0) vib_error = np.sqrt(vib_error) self.error[self.error.columns[-2:]] = vib_error.T.values # Calculate the new force constant. optimized = vib_ic.apply(np.reciprocal).apply(np.square) target = self.target["BONDS"].apply(np.reciprocal).apply(np.square) optimized -= target optimized *= self.BOLTZ * self.KFACTOR vib_ic[bond_values[0]] = (self.parameters["BONDS"][bond_values[0]] - optimized[bond_values[0]]) vib_ic[bond_values[0]] = (vib_ic[bond_values[0]].where( vib_ic[bond_values[0]] >= 0., 0.)) # r.m.s.d. between previous and current force constant diff = self.dynamic_params["BONDS"] - vib_ic diff = diff.apply(np.square).mean(axis=0) diff = np.sqrt(diff) self.error[self.error.columns[1]] = diff.values[0] # Update the parameters and write to file. self.parameters["BONDS"][bond_values[0]] = ( vib_ic[bond_values[0]].copy(deep=True)) self.dynamic_params["BONDS"] = vib_ic.copy(deep=True) self.parameters["BONDS"].reset_index(inplace=True) self.dynamic_params["BONDS"].reset_index(inplace=True) with mda.Writer(self.filenames["fixed_prm"], **self.kwargs) as prm: prm.write(self.parameters) with mda.Writer(self.filenames["dynamic_prm"], **self.kwargs) as prm: prm.write(self.dynamic_params) # Update the error values. with open(self.filenames["error_data"], "ab") as error_file: np.savetxt( error_file, self.error, fmt=native_str("%10d%10.6f%10.6f%10.6f", ), delimiter=native_str(""), ) if (self.error[self.error.columns[1]] < tol).bool(): break logger.info("Fluctuation matching completed in {:.6f}".format( time.time() - st)) self.target["BONDS"].reset_index(inplace=True)