def _add_yzcartesian_restraints(system, restraint_list, alpha, timestep, force_dict): # split restraints into confinement and others cartesian_restraints = [r for r in restraint_list if isinstance(r, YZCartesianRestraint)] noncartesian_restraints = [r for r in restraint_list if not isinstance(r, YZCartesianRestraint)] if cartesian_restraints: # create the confinement force cartesian_force = CustomExternalForce( '0.5 * cart_force_const * r_eff^2; r_eff = max(0.0, r - cart_delta);' 'r = sqrt(dy*dy + dz*dz);' 'dy = y - cart_y;' 'dz = z - cart_z;') cartesian_force.addPerParticleParameter('cart_y') cartesian_force.addPerParticleParameter('cart_z') cartesian_force.addPerParticleParameter('cart_delta') cartesian_force.addPerParticleParameter('cart_force_const') # add the atoms for r in cartesian_restraints: weight = r.force_const * r.scaler(alpha) * r.ramp(timestep) cartesian_force.addParticle(r.atom_index - 1, [r.y, r.z, r.delta, weight]) system.addForce(cartesian_force) force_dict['yzcartesian'] = cartesian_force else: force_dict['yzcartesian'] = None return noncartesian_restraints
def add_interactions(self, system, topology): if self.active: # create the confinement force if self.use_pbc: cartesian_force = CustomExternalForce( "0.5 * cart_force_const * r_eff^2;" "r_eff = max(0.0, r - cart_delta);" "r = periodicdistance(0, y, z, 0, cart_y, cart_z);" ) else: cartesian_force = CustomExternalForce( "0.5 * cart_force_const * r_eff^2;" "r_eff = max(0.0, r - cart_delta);" "r = sqrt(r2);" "r2 = dy*dy + dz*dz;" "dy = y - cart_y;" "dz = z - cart_z;" ) cartesian_force.addPerParticleParameter("cart_y") cartesian_force.addPerParticleParameter("cart_z") cartesian_force.addPerParticleParameter("cart_delta") cartesian_force.addPerParticleParameter("cart_force_const") # add the atoms for r in self.restraints: weight = r.force_const cartesian_force.addParticle( r.atom_index - 1, [r.y, r.z, r.delta, weight] ) system.addForce(cartesian_force) self.force = cartesian_force return system
def _add_backbone_restraint(self): # https://github.com/ParmEd/ParmEd/wiki/OpenMM-Tricks-and-Recipes#positional-restraints positions = self.modeller.getPositions() force = CustomExternalForce('k*((x-x0)^2+(y-y0)^2+(z-z0)^2)') force.addGlobalParameter( 'k', 5.0 * u.kilocalories_per_mole / u.angstroms**2) force.addPerParticleParameter('x0') force.addPerParticleParameter('y0') force.addPerParticleParameter('z0') for index, atom in enumerate(self.modeller.topology.atoms()): if atom.name in ('CA', 'C', 'N'): coord = positions[index] force.addParticle(index, coord.value_in_unit(u.nanometers)) self.restraint_force_id = self.system.addForce(force)
def add_interactions(self, system, topology): if self.active: # create the confinement force confinement_force = CustomExternalForce( 'step(r - radius) * force_const * (radius - r)^2;' 'r=sqrt(x*x + y*y + z*z)') confinement_force.addPerParticleParameter('radius') confinement_force.addPerParticleParameter('force_const') # add the atoms for r in self.restraints: weight = r.force_const confinement_force.addParticle(r.atom_index - 1, [r.radius, weight]) system.addForce(confinement_force) self.force = confinement_force return system
def add_interactions(self, system, topology): if self.active: # create the confinement force cartesian_force = CustomExternalForce( '0.5 * cart_force_const * r_eff2;' 'r_eff2 = max(0.0, r2 - cart_delta^2);' 'r2 = dy*dy + dz*dz;' 'dy = y - cart_y;' 'dz = z - cart_z;') cartesian_force.addPerParticleParameter('cart_y') cartesian_force.addPerParticleParameter('cart_z') cartesian_force.addPerParticleParameter('cart_delta') cartesian_force.addPerParticleParameter('cart_force_const') # add the atoms for r in self.restraints: weight = r.force_const cartesian_force.addParticle(r.atom_index - 1, [r.y, r.z, r.delta, weight]) system.addForce(cartesian_force) self.force = cartesian_force return system
def _add_confinement_restraints(system, restraint_list, alpha, timestep, force_dict): # split restraints into confinement and others confinement_restraints = [r for r in restraint_list if isinstance(r, ConfinementRestraint)] nonconfinement_restraints = [r for r in restraint_list if not isinstance(r, ConfinementRestraint)] if confinement_restraints: # create the confinement force confinement_force = CustomExternalForce( 'step(r - radius) * force_const * (radius - r)^2; r=sqrt(x*x + y*y + z*z)') confinement_force.addPerParticleParameter('radius') confinement_force.addPerParticleParameter('force_const') # add the atoms for r in confinement_restraints: weight = r.force_const * r.scaler(alpha) * r.ramp(timestep) confinement_force.addParticle(r.atom_index - 1, [r.radius, weight]) system.addForce(confinement_force) force_dict['confine'] = confinement_force else: force_dict['confine'] = None return nonconfinement_restraints
def add_interactions(self, system, topology): if self.active: # create the confinement force if self.use_pbc: confinement_force = CustomExternalForce( "step(r - radius) * force_const * (radius - r)^2;" "r = periodicdistance(x, y, z, 0, 0 ,0)") else: confinement_force = CustomExternalForce( "step(r - radius) * force_const * (radius - r)^2;" "r=sqrt(x*x + y*y + z*z)") confinement_force.addPerParticleParameter("radius") confinement_force.addPerParticleParameter("force_const") # add the atoms for r in self.restraints: weight = r.force_const confinement_force.addParticle(r.atom_index - 1, [r.radius, weight]) system.addForce(confinement_force) self.force = confinement_force return system
def add_interactions(self, system, topology): if self.active: # create the confinement force if self.use_pbc: confinement_force = CustomExternalForce( "step(r - radius) * force_const * (radius - r)^2;" "r = periodicdistance(x, y, z, 0, 0 ,0)" ) else: confinement_force = CustomExternalForce( "step(r - radius) * force_const * (radius - r)^2;" "r=sqrt(x*x + y*y + z*z)" ) confinement_force.addPerParticleParameter("radius") confinement_force.addPerParticleParameter("force_const") # add the atoms for r in self.restraints: weight = r.force_const confinement_force.addParticle(r.atom_index - 1, [r.radius, weight]) system.addForce(confinement_force) self.force = confinement_force return system
def _targeted_sim(self, coords0, coords1, tmdk=15., d_steps=100, n_max_steps=10000, ddtol=1e-3, n_conv=5): try: from simtk.openmm import CustomExternalForce from simtk.openmm.app import StateDataReporter from simtk.unit import nanometer, kelvin, angstrom, kilojoule_per_mole, MOLAR_GAS_CONSTANT_R except ImportError: raise ImportError( 'Please install PDBFixer and OpenMM in order to use ClustENM.') tmdk *= kilojoule_per_mole / angstrom**2 tmdk = tmdk.value_in_unit(kilojoule_per_mole / nanometer**2) # coords1_ca = coords1[self._idx_cg, :] pos1 = coords1 * angstrom # pos1_ca = pos1[self._idx_cg, :] force = CustomExternalForce('tmdk*((x-x0)^2+(y-y0)^2+(z-z0)^2)') force.addGlobalParameter('tmdk', 0.) force.addPerParticleParameter('x0') force.addPerParticleParameter('y0') force.addPerParticleParameter('z0') force.setForceGroup(1) # for i, atm_idx in enumerate(self._idx_cg): # pars = pos1_ca[i, :].value_in_unit(nanometer) # force.addParticle(int(atm_idx), pars) n_atoms = coords0.shape[0] atom_indices = np.arange(n_atoms) for i, atm_idx in enumerate(atom_indices): pars = pos1[i, :].value_in_unit(nanometer) force.addParticle(int(atm_idx), pars) simulation = self._prep_sim([force]) # automatic conversion into nanometer will be carried out. simulation.context.setPositions(coords0 * angstrom) dist = dist0 = calcRMSD(coords0, coords1) m_conv = 0 n_steps = 0 try: simulation.minimizeEnergy(tolerance=self._tolerance * kilojoule_per_mole, maxIterations=self._maxIterations) # update parameters while n_steps < n_max_steps: simulation.context.setParameter('tmdk', tmdk) force.updateParametersInContext(simulation.context) simulation.step(d_steps) n_steps += d_steps # evaluate distance to destination pos = simulation.context.getState( getPositions=True).getPositions( asNumpy=True).value_in_unit(angstrom) d = calcRMSD(pos, coords1) dd = np.abs(dist - d) if dd < ddtol: m_conv += 1 if m_conv >= n_conv: break dist = d LOGGER.debug('RMSD: %4.2f -> %4.2f' % (dist0, dist)) simulation.context.setParameter('tmdk', 0.0) simulation.minimizeEnergy(tolerance=self._tolerance * kilojoule_per_mole, maxIterations=self._maxIterations) pos = simulation.context.getState(getPositions=True).getPositions( asNumpy=True).value_in_unit(angstrom) pot = simulation.context.getState( getEnergy=True).getPotentialEnergy().value_in_unit( kilojoule_per_mole) return pot, pos except BaseException as be: LOGGER.warning( 'OpenMM exception: ' + be.__str__() + ' so the corresponding conformer will be discarded!') return np.nan, np.full_like(coords0, np.nan)
def main(argdict): """ Main function for entry point checking. Expects a dictionary of command line arguments. """ # load configuration from logfile: with open(argdict["log"], 'r') as f: argdict = json.load(f) # load system initial configuration: pdb = pdb_file_nonstandard_bonds(argdict["pdb"]) print("--> input topology: ", end="") print(pdb.topology) # physical parameters of simulation: sim_temperature = argdict["temperature"] * kelvin sim_andersen_coupling = 1 / picosecond sim_pressure = ( (argdict["pressure"], argdict["pressure"], argdict["pressure"]) * bar) sim_scale_x = True sim_scale_y = True sim_scale_z = True # simulation control parameters: sim_timestep = argdict["timestep"] * femtoseconds # restraints parameters: sim_restr_fc = argdict["restr_fc"] * kilojoule_per_mole / nanometer**2 # create force field object: ff = ForceField(*argdict["ff"]) # build a simulation system from topology and force field: # (note that AMOEBA is intended to be run without constraints) # (note that mutualInducedtargetEpsilon defaults to 0.01 unlike what is # specified in the documentation which claims 0.00001) system = ff.createSystem( pdb.topology, nonbondedMethod=PME, nonbondedCutoff=argdict["nonbonded_cutoff"] * nanometer, vdwCutoff=argdict["vdw_cutoff"] * nanometer, ewaldErrorTolerance=argdict["ewald_error_tolerance"], polarisation=argdict["polarisation"], mutualInducedTargetEpsilon=argdict["mutual_induced_target_epsilon"], constraints=None, rigidWater=False, removeCMMotion=True # removes centre of mass motion ) # overwrite the polarisation method set at system creation; this is # necessary as openMM always sets polarisation method to "mutual" of the # target epsilon is specified at system creation; this way, target epsilon # is ignored for all but the mutual method multipole_force = [ f for f in system.getForces() if isinstance(f, AmoebaMultipoleForce) ][0] print("--> using polarisation method " + str(argdict["polarisation"])) if argdict["polarisation"] == "mutual": multipole_force.setPolarizationType(multipole_force.Mutual) if argdict["polarisation"] == "extrapolated": multipole_force.setPolarizationType(multipole_force.Extrapolated) if argdict["polarisation"] == "direct": multipole_force.setPolarizationType(multipole_force.Direct) # will use Andersen thermostat here: # (Inhibits particle dynamics somewhat, but little or no ergodicity # issues (from Gromacs documenation). However, only alternative is full # Langevin dynamics, which is even worse wrt dynamics. Bussi/v-rescale is # not available at the moment, it seems (it is available in tinker, but # without GPU acceleration)) system.addForce(AndersenThermostat(sim_temperature, sim_andersen_coupling)) # use anisotropic barostat: # (note that this corresponds to semiisotropic pressure coupling in Gromacs # if the pressure is identical for the x- and y/axes) # (note that by default this attempts an update every 25 steps) system.addForce( MonteCarloAnisotropicBarostat(sim_pressure, sim_temperature, sim_scale_x, sim_scale_y, sim_scale_z)) # prepare harmonic restraining potential: # (note that periodic distance is absolutely necessary here to prevent # system from blowing up, as otherwise periodic image position may be used # resulting in arbitrarily large forces) force = CustomExternalForce("k*periodicdistance(x, y, z, x0, y0, z0)^2") force.addGlobalParameter("k", sim_restr_fc) force.addPerParticleParameter("x0") force.addPerParticleParameter("y0") force.addPerParticleParameter("z0") # apply harmonic restraints to C-alphas: if argdict["restr"] == "capr": print("--> applying harmonic positional restraints to CA atoms") for atm in pdb.topology.atoms(): if atm.name == "CA": force.addParticle(atm.index, pdb.positions[atm.index]) elif argdict["restr"] == "hapr": sys.exit("Restraints mode " + str(argdict["restr"]) + "is not implemented.") elif argdict["restr"] == "none": print("--> applying no harmonic positional restraints to any atom") else: sys.exit("Restraints mode " + str(argdict["restr"]) + "is not implemented.") # add restraining force to system: system.addForce(force) # make special group for nonbonded forces: for f in system.getForces(): if (isinstance(f, AmoebaMultipoleForce) or isinstance(f, AmoebaVdwForce) or isinstance(f, AmoebaGeneralizedKirkwoodForce) or isinstance(f, AmoebaWcaDispersionForce)): f.setForceGroup(1) # select integrator: if argdict["integrator"] == "mts": # use multiple timestep RESPA integrator: print("--> using RESPA/MTS integrator") integrator = MTSIntegrator(sim_timestep, [(0, argdict["inner_ts_frac"]), (1, 1)]) if argdict["integrator"] == "verlet": # use Leapfrog Verlet integrator here: print("--> using Verlet integrator") integrator = VerletIntegrator(sim_timestep) # select a platform (should be CUDA, otherwise VERY slow): platform = Platform.getPlatformByName(argdict["platform"]) properties = { "CudaPrecision": argdict["precision"], "CudaDeviceIndex": "0" } # create simulation system: sim = Simulation(pdb.topology, system, integrator, platform, properties) # unit conversion factors: ang2nm = 0.1 # create MDA universe: u = mda.Universe(args.s, args.f) # selection for overall system will be needed to set OpenMM positions # accordingt to trajectory: allsystem = u.select_atoms("all") # get parameters to define cylinder around protein center of geometry: # (the cylinder spans the entire box in the z-direction) protein = u.select_atoms("protein") radius = str(args.r) z_margin = args.z_margin z_min = str(protein.bbox()[0, 2] - protein.center_of_geometry()[2] - z_margin) z_max = str(protein.bbox()[1, 2] - protein.center_of_geometry()[2] + z_margin) # select all solvent atoms, note that AMOEBA residue name is HOH: # (these must be updating, as water may move in and out of pore!) solvent = u.select_atoms("byres (resname HOH SOL) and cyzone " + radius + " " + z_max + " " + z_min + " protein", updating=True) solvent_ow = solvent.select_atoms("name O OW", updating=True) # lambda function for converting atomic dipoles to molecular dipoles: # (this only works on 1D arrays, hence use apply_along_axis if quantity is # vector-valued, e.g. positions and dipoles) def atomic2molecular_sum(arr): return np.bincount(allsystem.resindices, arr) def atomic2molecular_avg(arr): return np.bincount(allsystem.resindices, arr) / np.bincount( allsystem.resindices) # create lambda function for obtaining charges in vectorisable way: # (units are elementary_charge) get_atomic_charges = np.vectorize( lambda index: multipole_force.getMultipoleParameters(int(index))[ 0].value_in_unit(elementary_charge)) # obtain atomic charges: # (charges are static, so need this only once; units are elementary charge) atomic_charges = get_atomic_charges(allsystem.ix) # obtain start and end time as will as time step: dt = float(args.dt) t_start = float(args.b) t_end = float(args.e) # prepare results dictionary: res = { "t": [], "x": [], "y": [], "z": [], "indu_rho": [], "indu_costheta": [], "indu_cosphi": [], "perm_rho": [], "perm_costheta": [], "perm_cosphi": [], "mono_rho": [], "mono_costheta": [], "mono_cosphi": [], "total_rho": [], "total_costheta": [], "total_cosphi": [] } # loop over trajectory: for ts in u.trajectory: # skip all frames before starting frame: if ts.time < t_start: continue # only analyse relevant time frames: if round(ts.time, 4) % dt == 0: # inform user: print("analysing frame: " + str(ts.frame) + " at time: " + str(ts.time)) print("number of selected solvent molecules in this frame: " + str(solvent.n_residues)) # convert mda positions to OpenMM positions and set context: omm_positions = Quantity( [tuple(pos) for pos in list(allsystem.positions)], unit=angstrom) sim.context.setPositions(omm_positions) # calculate molecular positions (or molecular centre of geometry) by # averaging over all atomic positions within a residue: # (units are Angstrom in MDAnalysis!) molecular_positions = np.apply_along_axis( atomic2molecular_avg, 0, allsystem.positions) * ang2nm # calculate charge-weighted positions by multiplying the relative # atomic positions with the atomic charges (relative positions are # necessary to account for charged residues/molecules, where the # dipole moment is calculated relative to the center of geometry of # the residue): # (units are elementary charge * nanometer) atomic_charge_weighted_positions = ( allsystem.positions - molecular_positions[allsystem.resindices]) atomic_charge_weighted_positions *= (atomic_charges[np.newaxis].T * ang2nm) # obtain induced and permanent atomic dipoles from OpenMM: # (units are elementary charge * nm) atomic_dipoles_indu = np.array( multipole_force.getInducedDipoles(sim.context)) atomic_dipoles_perm = np.array( multipole_force.getLabFramePermanentDipoles(sim.context)) # convert atomic to molecular quantities and calculate total dipole: molecular_dipoles_indu = np.apply_along_axis( atomic2molecular_sum, 0, atomic_dipoles_indu) molecular_dipoles_perm = np.apply_along_axis( atomic2molecular_sum, 0, atomic_dipoles_perm) molecular_dipoles_mono = np.apply_along_axis( atomic2molecular_sum, 0, atomic_charge_weighted_positions) molecular_dipoles_total = (molecular_dipoles_indu + molecular_dipoles_perm + molecular_dipoles_mono) # convert to spherical coordinates: molecular_dipoles_indu = cartesian2spherical( molecular_dipoles_indu) molecular_dipoles_perm = cartesian2spherical( molecular_dipoles_perm) molecular_dipoles_mono = cartesian2spherical( molecular_dipoles_mono) molecular_dipoles_total = cartesian2spherical( molecular_dipoles_total) # insert into results dictionary: res["t"].append(np.repeat(ts.time, solvent.n_residues)) res["x"].append(molecular_positions[solvent_ow.resindices, 0]) res["y"].append(molecular_positions[solvent_ow.resindices, 1]) res["z"].append(molecular_positions[solvent_ow.resindices, 2]) res["indu_rho"].append( molecular_dipoles_indu[solvent_ow.resindices, 0]) res["indu_costheta"].append( molecular_dipoles_indu[solvent_ow.resindices, 1]) res["indu_cosphi"].append( molecular_dipoles_indu[solvent_ow.resindices, 2]) res["perm_rho"].append( molecular_dipoles_perm[solvent_ow.resindices, 0]) res["perm_costheta"].append( molecular_dipoles_perm[solvent_ow.resindices, 1]) res["perm_cosphi"].append( molecular_dipoles_perm[solvent_ow.resindices, 2]) res["mono_rho"].append( molecular_dipoles_mono[solvent_ow.resindices, 0]) res["mono_costheta"].append( molecular_dipoles_mono[solvent_ow.resindices, 1]) res["mono_cosphi"].append( molecular_dipoles_mono[solvent_ow.resindices, 2]) res["total_rho"].append( molecular_dipoles_total[solvent_ow.resindices, 0]) res["total_costheta"].append( molecular_dipoles_total[solvent_ow.resindices, 1]) res["total_cosphi"].append( molecular_dipoles_total[solvent_ow.resindices, 2]) # stop iterating through trajectory after end time: if ts.time > t_end: break # convert lists of arrays to arrays: for k in res.keys(): res[k] = np.concatenate(res[k]) # convert units of dipole magnitude to Debye: eNm2debye = 48.03205 res["indu_rho"] = eNm2debye * res["indu_rho"] res["perm_rho"] = eNm2debye * res["perm_rho"] res["mono_rho"] = eNm2debye * res["mono_rho"] res["total_rho"] = eNm2debye * res["total_rho"] # load spline curve data: with open(args.j, "r") as f: chap_data = json.load(f) # create spline curve from CHAP data: spline_curve = BSplineCurve(chap_data) # calculate s-coordinate from z-coordinate: res["s"] = spline_curve.z2s(res["z"]) # convert results to data frame: df_res = pd.DataFrame(res) # loop over various numbers of bins: df = [] for nbins in args.nbins: # create a temporary data frame: tmp = df_res # drop positional coordinates: tmp = tmp.drop(["x", "y", "z", "t"], axis=1) # bin by value of s-coordinate: tmp = tmp.groupby(pd.cut(tmp.s, nbins)) # aggregate variables: tmp = tmp.agg([np.mean, np.std, sem, np.size, np.median, qlo, qhi]).reset_index() # rename columns (combines variable name with aggregation method): tmp.columns = ["_".join(x) for x in tmp.columns.ravel()] # remove grouping key: tmp = tmp.drop("s_", axis=1) # add column wit number of bins: tmp["nbins"] = nbins # append to list of data frames: df.append(tmp) # combine list of data frames into single data frame: df = pd.concat(df) # write to JSON file: df.to_json(args.o, orient="records") # need to add newline for POSIX compliance: with open(args.o, "a") as f: f.write("\n")
def main(argdict): """ Main function for entry point checking. Expects a dictionary of command line arguments. """ # are we continuing from logfile or starting from fresh PDB? if argdict["log"] is None: # keep track of restart number: argdict["restart_number"] = int(0) # write arguments to a file to keep a record: with open(argdict["outname"] + "_parameters.log", 'w') as f: print(json.dumps(argdict, sort_keys=True, indent=4), file=f) else: # load configuration from logfile: logfile = argdict["log"] with open(argdict["log"], 'r') as f: argdict = json.load(f) # make sure log file has appropriate entry: argdict["log"] = logfile # increment restart number: argdict["restart_number"] += 1 # write unnumbered parameters file (for easy restarts): with open(argdict["outname"] + "_parameters.log", 'w') as f: print(json.dumps(argdict, sort_keys=True, indent=4), file=f) # write numbered parameters file (for record keeping): with open( argdict["outname"] + "_" + str(argdict["restart_number"]) + "_parameters.log", 'w') as f: print(json.dumps(argdict, sort_keys=True, indent=4), file=f) # load system initial configuration: pdb = pdb_file_nonstandard_bonds(argdict["pdb"]) print("--> input topology: ", end="") print(pdb.topology) # get XML file from absolute path: xmlfile = os.path.abspath(argdict["xml"]) # set box size in topology to values from XML file: box_vectors = periodic_box_vectors_from_xml(xmlfile) pdb.topology.setPeriodicBoxVectors(box_vectors) # physical parameters of simulation: sim_temperature = argdict["temperature"] * kelvin sim_andersen_coupling = 1 / picosecond sim_pressure = ( (argdict["pressure"], argdict["pressure"], argdict["pressure"]) * bar) sim_scale_x = True sim_scale_y = True sim_scale_z = True # simulation control parameters: sim_timestep = argdict["timestep"] * femtoseconds sim_num_steps = argdict["num_steps"] sim_traj_rep_steps = argdict["report_freq"] sim_state_rep_steps = argdict["report_freq"] sim_checkpoint_steps = argdict["report_freq"] # restraints parameters: sim_restr_fc = argdict["restr_fc"] * kilojoule_per_mole / nanometer**2 # create force field object: ff = ForceField(*argdict["ff"]) # build a simulation system from topology and force field: # (note that AMOEBA is intended to be run without constraints) # (note that mutualInducedtargetEpsilon defaults to 0.01 unlike what is # specified in the documentation which claims 0.00001) sys = ff.createSystem( pdb.topology, nonbondedMethod=PME, nonbondedCutoff=argdict["nonbonded_cutoff"] * nanometer, vdwCutoff=argdict["vdw_cutoff"] * nanometer, ewaldErrorTolerance=argdict["ewald_error_tolerance"], polarisation=argdict["polarisation"], mutualInducedTargetEpsilon=argdict["mutual_induced_target_epsilon"], constraints=None, rigidWater=False, removeCMMotion=True # removes centre of mass motion ) # overwrite the polarisation method set at system creation; this is # necessary as openMM always sets polarisation method to "mutual" of the # target epsilon is specified at system creation; this way, target epsilon # is ignored for all but the mutual method multipole_force = sys.getForce(9) print("--> using polarisation method " + str(argdict["polarisation"])) if argdict["polarisation"] == "mutual": multipole_force.setPolarizationType(multipole_force.Mutual) elif argdict["polarisation"] == "extrapolated": multipole_force.setPolarizationType(multipole_force.Extrapolated) elif argdict["polarisation"] == "direct": multipole_force.setPolarizationType(multipole_force.Direct) else: raise Exception("Polarisation method " + str(argdict["polarisation"]) + " not supported!") # will use Andersen thermostat here: # (Inhibits particle dynamics somewhat, but little or no ergodicity # issues (from Gromacs documenation). However, only alternative is full # Langevin dynamics, which is even worse wrt dynamics. Bussi/v-rescale is # not available at the moment, it seems (it is available in tinker, but # without GPU acceleration)) sys.addForce(AndersenThermostat(sim_temperature, sim_andersen_coupling)) # use anisotropic barostat: # (note that this corresponds to semiisotropic pressure coupling in Gromacs # if the pressure is identical for the x- and y/axes) # (note that by default this attempts an update every 25 steps) sys.addForce( MonteCarloAnisotropicBarostat(sim_pressure, sim_temperature, sim_scale_x, sim_scale_y, sim_scale_z)) # prepare harmonic restraining potential: # (note that periodic distance is absolutely necessary here to prevent # system from blowing up, as otherwise periodic image position may be used # resulting in arbitrarily large forces) force = CustomExternalForce("k*periodicdistance(x, y, z, x0, y0, z0)^2") force.addGlobalParameter("k", sim_restr_fc) force.addPerParticleParameter("x0") force.addPerParticleParameter("y0") force.addPerParticleParameter("z0") # apply harmonic restraints to C-alphas: if argdict["restr"] == "capr": print("--> applying harmonic positional restraints to CA atoms") for atm in pdb.topology.atoms(): if atm.name == "CA": force.addParticle(atm.index, pdb.positions[atm.index]) elif argdict["restr"] == "hapr": sys.exit("Restraints mode " + str(argdict["restr"]) + "is not implemented.") elif argdict["restr"] == "none": print("--> applying no harmonic positional restraints to any atom") else: sys.exit("Restraints mode " + str(argdict["restr"]) + "is not implemented.") # add restraining force to system: sys.addForce(force) # make special group for nonbonded forces: for f in sys.getForces(): if (isinstance(f, AmoebaMultipoleForce) or isinstance(f, AmoebaVdwForce) or isinstance(f, AmoebaGeneralizedKirkwoodForce) or isinstance(f, AmoebaWcaDispersionForce)): f.setForceGroup(1) # read umbrella parameters from file: with open(argdict["umbrella_target"], "r") as f: umbrella_target = json.load(f) # obtain index from atom to be restrained: umbrella_index = umbrella_target["target_particle"]["index"] umbrella_fc = (umbrella_target["umbrella_params"]["fc"] * kilojoule_per_mole / nanometer**2) umbrella_cv = umbrella_target["umbrella_params"]["cv"] * nanometer # inform user: print("--> applying umbrella potential to " + str(list(pdb.topology.atoms())[umbrella_index]) + " at position " + str(pdb.positions[umbrella_index])) # additional restraining force applied to ion under umbrella restraints: umbrella_force = CustomExternalForce( "k*periodicdistance(0.0, 0.0, z, 0.0, 0.0, z0)^2") umbrella_force.addGlobalParameter("k", umbrella_fc) # z0 is set to value in JSON file rather than initial particle coordinate to # allow for a few steps of energy minimisation to avoid clashes between the # restrained umbrella target and surrounding atoms: umbrella_force.addGlobalParameter("z0", umbrella_cv) # select integrator: if argdict["integrator"] == "mts": # use multiple timestep RESPA integrator: print("--> using RESPA/MTS integrator") integrator = MTSIntegrator(sim_timestep, [(0, argdict["inner_ts_frac"]), (1, 1)]) elif argdict["integrator"] == "verlet": # use Leapfrog Verlet integrator here: print("--> using Verlet integrator") integrator = VerletIntegrator(sim_timestep) else: # no other integrators supported: raise Exception("Integrator " + str(argdict["integrator"]) + " is not supported.") # select a platform: platform = Platform.getPlatformByName(argdict["platform"]) properties = { "CudaPrecision": argdict["precision"], "CudaDeviceIndex": "0" } # prepare a simulation from topology, system, and integrator and set initial # positions as in PDB file: sim = Simulation(pdb.topology, sys, integrator, platform, properties) # is this initial simulation or restart from checkpoint? if argdict["log"] is None: # load positions and velocities from XML file: print("--> loading simulation state from XML file...") sim.loadState(xmlfile) # find all particles bonded to ion (i.e. Drude particles): idx_bonded = atom_idx_from_bonds(sim.topology, umbrella_index) idx_shift = idx_bonded + [umbrella_index] # shift target particle into position: pos = (sim.context.getState(getPositions=True).getPositions( asNumpy=True)) pos[idx_shift, :] = (umbrella_target["target_particle"]["init_pos"] * nanometer) print("--> target particle now placed at " + str(pos[idx_shift, :])) # set new particle positions in context: sim.context.setPositions(pos) e_pot = sim.context.getState(getEnergy=True).getPotentialEnergy() print("--> potential energy after target placement is: " + str(e_pot)) # minimise energy to remove clashes: # (too many steps might ruin everythin!) print("--> running energy minimisation...") sim.minimizeEnergy(maxIterations=argdict["minimisation_steps"]) e_pot = sim.context.getState(getEnergy=True).getPotentialEnergy() print( "--> " + str(argdict["minimisation_steps"]) + " steps of energy minimisation reduced the potential energy to " + str(e_pot)) # reduce time step for equilibration period: print("--> running equilibration at reduced time step...") sim.integrator.setStepSize(0.1 * sim.integrator.getStepSize()) sim.context.setTime(0.0 * picosecond) # will write report about equilibration phase: sim.reporters.append( StateDataReporter(stdout, int(argdict["equilibration_steps"] / 10), step=True, time=True, speed=True, progress=True, remainingTime=True, totalSteps=argdict["equilibration_steps"], separator="\t")) # run equilibration steps: sim.step(argdict["equilibration_steps"]) # reset step size to proper value: sim.integrator.setStepSize(10.0 * sim.integrator.getStepSize()) sim.context.setTime(0.0 * picosecond) sim.reporters.clear() else: # load checkpoint file: checkpoint_file = (str(argdict["outname"]) + "_" + str(argdict["restart_number"] - 1) + ".chk") print("--> loading checkpoint file " + checkpoint_file) sim.loadCheckpoint(checkpoint_file) # write collective variable value to file: sample_outname = (argdict["outname"] + "_" + str(argdict["restart_number"]) + str(".dat")) sim.reporters.append( CollectiveVariableReporter(sample_outname, argdict["umbrella_freq"], umbrella_index)) # write simulation trajectory to DCD file: dcd_outname = (argdict["outname"] + "_" + str(argdict["restart_number"]) + str(".dcd")) sim.reporters.append(DCDReporter(dcd_outname, sim_traj_rep_steps)) # write state data to tab-separated CSV file: state_outname = (argdict["outname"] + "_" + str(argdict["restart_number"]) + str(".csv")) sim.reporters.append( StateDataReporter(state_outname, sim_state_rep_steps, step=True, time=True, progress=False, remainingTime=True, potentialEnergy=True, kineticEnergy=True, totalEnergy=True, temperature=True, volume=True, density=False, speed=True, totalSteps=sim_num_steps, separator="\t")) # write limited state information to standard out: sim.reporters.append( StateDataReporter(stdout, sim_state_rep_steps, step=True, time=True, speed=True, progress=True, remainingTime=True, totalSteps=sim_num_steps, separator="\t")) # write checkpoint files regularly: checkpoint_outname = (argdict["outname"] + "_" + str(argdict["restart_number"]) + ".chk") sim.reporters.append( CheckpointReporter(checkpoint_outname, sim_checkpoint_steps)) # run simulation: sim.step(argdict["num_steps"]) # save final simulation state: sim.saveState(argdict["outname"] + "_" + str(argdict["restart_number"]) + ".xml") positions = sim.context.getState(getPositions=True).getPositions() PDBFile.writeFile( sim.topology, positions, open( argdict["outname"] + "_" + str(argdict["restart_number"]) + ".pdb", "w"))