示例#1
0
def add_reaction_coordinate(
    system: openmm.System,
    host_index: List[int],
    guest_index: List[int],
    k_z: simtk_unit.Quantity,
    z_0: simtk_unit.Quantity,
    force_group: Optional[int] = 11,
):
    """
    Applies the umbrella reaction coordinate to the guest molecule
    """

    # Simple umbrella potential string expression
    reaction = openmm.CustomCentroidBondForce(
        2, "0.5 * k_z * (r_z - z_0)^2;" "r_z = z2 - z1;"
    )
    reaction.setUsesPeriodicBoundaryConditions(False)
    reaction.setForceGroup(force_group)

    # Umbrella parameters
    reaction.addPerBondParameter("k_z", k_z)
    reaction.addPerBondParameter("z_0", z_0)

    # Add host and guest indices
    g1 = reaction.addGroup(host_index)
    g2 = reaction.addGroup(guest_index)

    # Add bond
    reaction.addBond([g1, g2], [k_z, z_0])

    # Add force to system
    system.addForce(reaction)
示例#2
0
def add_funnel_potential(
    system: openmm.System,
    host_index: List[int],
    guest_index: List[int],
    k_xy: Optional[simtk_unit.Quantity] = 10.0
    * simtk_unit.kilocalorie_per_mole
    / simtk_unit.angstrom ** 2,
    z_cc: Optional[simtk_unit.Quantity] = 11.0 * simtk_unit.angstrom,
    alpha: Optional[simtk_unit.Quantity] = 35.0 * simtk_unit.degrees,
    R_cylinder: Optional[simtk_unit.Quantity] = 1.0 * simtk_unit.angstrom,
    force_group: Optional[int] = 10,
):
    """
    Applies a funnel potential to a guest molecule
    """

    # Funnel potential string expression
    funnel = openmm.CustomCentroidBondForce(
        2,
        "U_funnel + U_cylinder;"
        "U_funnel = step(z_cc - abs(r_z))*step(r_xy - R_funnel)*Wall;"
        "U_cylinder = step(abs(r_z) - z_cc)*step(r_xy - R_cylinder)*Wall;"
        "Wall = 0.5 * k_xy * r_xy^2;"
        "R_funnel = (z_cc-abs(r_z))*tan(alpha) + R_cylinder;"
        "r_xy = sqrt((x2 - x1)^2 + (y2 - y1)^2);"
        "r_z = z2 - z1;",
    )
    funnel.setUsesPeriodicBoundaryConditions(False)
    funnel.setForceGroup(force_group)

    # Funnel parameters
    funnel.addGlobalParameter("k_xy", k_xy)
    funnel.addGlobalParameter("z_cc", z_cc)
    funnel.addGlobalParameter("alpha", alpha)
    funnel.addGlobalParameter("R_cylinder", R_cylinder)

    # Add host and guest indices
    g1 = funnel.addGroup(host_index)
    g2 = funnel.addGroup(guest_index)

    # Add bond
    funnel.addBond([g1, g2], [])

    # Add force to system
    system.addForce(funnel)
示例#3
0
    def add_interactions(self, system, topology):
        if self.active:
            rest = self.restraints[0]
            # convert indices from 1-based to 0-based
            rest_indices1 = [r - 1 for r in rest.indices1]
            rest_indices2 = [r - 1 for r in rest.indices2]

            # create the expression for the energy
            components = []
            if 'x' in rest.dims:
                components.append('(x1-x2)*(x1-x2)')
            if 'y' in rest.dims:
                components.append('(y1-y2)*(y1-y2)')
            if 'z' in rest.dims:
                components.append('(z1-z2)*(z1-z2)')
            dist_expr = 'dist = sqrt({});'.format(' + '.join(components))
            energy_expr = '0.5 * com_k * (dist - com_ref_dist)*(dist-com_ref_dist);'
            expr = '\n'.join([energy_expr, dist_expr])

            # create the force
            force = mm.CustomCentroidBondForce(2, expr)
            force.addPerBondParameter('com_k')
            force.addPerBondParameter('com_ref_dist')

            # create the restraint with parameters
            if rest.weights1:
                g1 = force.addGroup(rest_indices1, rest.weights1)
            else:
                g1 = force.addGroup(rest_indices1)
            if rest.weights2:
                g2 = force.addGroup(rest_indices2, rest.weights2)
            else:
                g2 = force.addGroup(rest_indices2)
            force_const = rest.force_const
            pos = rest.positioner(0)
            force.addBond([g1, g2], [force_const, pos])

            system.addForce(force)
            self.force = force
        return system
示例#4
0
    def add_interactions(self, system, topology):
        if self.active:
            rest = self.restraints[0]
            # convert indices from 1-based to 0-based
            indices = [r - 1 for r in rest.indices]

            # create the expression for the energy
            components = []
            if "x" in rest.dims:
                components.append("(x1-abscom_x)*(x1-abscom_x)")
            if "y" in rest.dims:
                components.append("(y1-abscom_y)*(y1-abscom_y)")
            if "z" in rest.dims:
                components.append("(z1-abscom_z)*(z1-abscom_z)")
            dist_expr = "dist2={};".format(" + ".join(components))
            energy_expr = "0.5 * com_k * dist2;"
            expr = "\n".join([energy_expr, dist_expr])

            # create the force
            force = mm.CustomCentroidBondForce(1, expr)
            force.addPerBondParameter("com_k")
            force.addPerBondParameter("abscom_x")
            force.addPerBondParameter("abscom_y")
            force.addPerBondParameter("abscom_z")

            # create the restraint with parameters
            if rest.weights:
                g1 = force.addGroup(indices, rest.weights)
            else:
                g1 = force.addGroup(indices)
            force_const = rest.force_const
            pos_x = rest.position[0]
            pos_y = rest.position[1]
            pos_z = rest.position[2]
            force.addBond([g1], [force_const, pos_x, pos_y, pos_z])

            system.addForce(force)
            self.force = force
        return system
示例#5
0
    def add_interactions(self, system, topology):
        if self.active:
            rest = self.restraints[0]
            # convert indices from 1-based to 0-based
            indices = [r - 1 for r in rest.indices]

            # create the expression for the energy
            components = []
            if 'x' in rest.dims:
                components.append('(x1-abscom_x)*(x1-abscom_x)')
            if 'y' in rest.dims:
                components.append('(y1-abscom_y)*(y1-abscom_y)')
            if 'z' in rest.dims:
                components.append('(z1-abscom_z)*(z1-abscom_z)')
            dist_expr = 'dist2={};'.format(' + '.join(components))
            energy_expr = '0.5 * com_k * dist2;'
            expr = '\n'.join([energy_expr, dist_expr])

            # create the force
            force = mm.CustomCentroidBondForce(1, expr)
            force.addPerBondParameter('com_k')
            force.addPerBondParameter('abscom_x')
            force.addPerBondParameter('abscom_y')
            force.addPerBondParameter('abscom_z')

            # create the restraint with parameters
            if rest.weights:
                g1 = force.addGroup(indices, rest.weights)
            else:
                g1 = force.addGroup(indices)
            force_const = rest.force_const
            pos_x = rest.position[0]
            pos_y = rest.position[1]
            pos_z = rest.position[2]
            force.addBond([g1], [force_const, pos_x, pos_y, pos_z])

            system.addForce(force)
            self.force = force
        return system
示例#6
0
    def add_interactions(self, state: interfaces.IState, system: mm.System,
                         topology: app.Topology) -> mm.System:
        if self.active:
            rest = self.restraints[0]

            # create the expression for the energy
            components = []
            if "x" in rest.dims:
                components.append("(x1-x2)*(x1-x2)")
            if "y" in rest.dims:
                components.append("(y1-y2)*(y1-y2)")
            if "z" in rest.dims:
                components.append("(z1-z2)*(z1-z2)")
            dist_expr = "dist = sqrt({});".format(" + ".join(components))
            energy_expr = "0.5 * com_k * (dist - com_ref_dist)*(dist-com_ref_dist);"
            expr = "\n".join([energy_expr, dist_expr])

            # create the force
            force = mm.CustomCentroidBondForce(2, expr)
            force.addPerBondParameter("com_k")
            force.addPerBondParameter("com_ref_dist")

            # create the restraint with parameters
            if rest.weights1:
                g1 = force.addGroup(rest.indices1, rest.weights1)
            else:
                g1 = force.addGroup(rest.indices1)
            if rest.weights2:
                g2 = force.addGroup(rest.indices2, rest.weights2)
            else:
                g2 = force.addGroup(rest.indices2)
            force_const = rest.force_const
            pos = rest.positioner(0)
            force.addBond([g1, g2], [force_const, pos])

            system.addForce(force)
            self.force = force
        return system
示例#7
0
def apply_dat_restraint(system,
                        restraint,
                        phase,
                        window_number,
                        flat_bottom=False,
                        force_group=None):
    """A utility function which takes in pAPRika restraints and applies the
    restraints to an OpenMM System object.

    Parameters
    ----------
    system : :class:`openmm.System`
        The system object to add the positional restraints to.
    restraint : list
        List of pAPRika defined restraints
    phase : str
        Phase of calculation ("attach", "pull" or "release")
    window_number : int
        The corresponding window number of the current phase
    flat_bottom : bool, optional
        Specify whether the restraint is a flat bottom potential
    force_group : int, optional
        The force group to add the positional restraints to.

    """

    from simtk import openmm, unit

    assert phase in {"attach", "pull", "release"}

    if flat_bottom and phase == "attach" and restraint.mask3:
        flat_bottom_force = openmm.CustomAngleForce(
            "step(-(theta - theta_0)) * k * (theta - theta_0)^2")
        # If theta is greater than theta_0, then the argument to step is negative,
        # which means the force is off.
        flat_bottom_force.addPerAngleParameter("k")
        flat_bottom_force.addPerAngleParameter("theta_0")

        theta_0 = 91.0 * unit.degrees
        k = (restraint.phase[phase]["force_constants"][window_number] *
             unit.kilocalories_per_mole / unit.radian**2)
        flat_bottom_force.addAngle(
            restraint.index1[0],
            restraint.index2[0],
            restraint.index3[0],
            [k, theta_0],
        )
        system.addForce(flat_bottom_force)
        if force_group:
            flat_bottom_force.setForceGroup(force_group)

        return

    elif flat_bottom and phase == "attach" and not restraint.mask3:
        flat_bottom_force = openmm.CustomBondForce(
            "step((r - r_0)) * k * (r - r_0)^2")
        # If x is greater than x_0, then the argument to step is positive, which means
        # the force is on.
        flat_bottom_force.addPerBondParameter("k")
        flat_bottom_force.addPerBondParameter("r_0")

        r_0 = restraint.phase[phase]["targets"][window_number] * unit.angstrom
        k = (restraint.phase[phase]["force_constants"][window_number] *
             unit.kilocalories_per_mole / unit.radian**2)
        flat_bottom_force.addBond(
            restraint.index1[0],
            restraint.index2[0],
            [k, r_0],
        )
        system.addForce(flat_bottom_force)
        if force_group:
            flat_bottom_force.setForceGroup(force_group)

        return

    elif flat_bottom and phase == "pull":
        return
    elif flat_bottom and phase == "release":
        return

    if restraint.mask2 and not restraint.mask3:
        if not restraint.group1 and not restraint.group2:
            bond_restraint = openmm.CustomBondForce("k * (r - r_0)^2")
            bond_restraint.addPerBondParameter("k")
            bond_restraint.addPerBondParameter("r_0")

            r_0 = restraint.phase[phase]["targets"][
                window_number] * unit.angstroms
            k = (restraint.phase[phase]["force_constants"][window_number] *
                 unit.kilocalories_per_mole / unit.angstrom**2)
            bond_restraint.addBond(restraint.index1[0], restraint.index2[0],
                                   [k, r_0])
            system.addForce(bond_restraint)
        else:
            bond_restraint = openmm.CustomCentroidBondForce(
                2, "k * (distance(g1, g2) - r_0)^2")
            bond_restraint.addPerBondParameter("k")
            bond_restraint.addPerBondParameter("r_0")
            r_0 = restraint.phase[phase]["targets"][
                window_number] * unit.angstroms
            k = (restraint.phase[phase]["force_constants"][window_number] *
                 unit.kilocalories_per_mole / unit.angstrom**2)
            g1 = bond_restraint.addGroup(restraint.index1)
            g2 = bond_restraint.addGroup(restraint.index2)
            bond_restraint.addBond([g1, g2], [k, r_0])
            system.addForce(bond_restraint)

        if force_group:
            bond_restraint.setForceGroup(force_group)

    elif restraint.mask3 and not restraint.mask4:
        if not restraint.group1 and not restraint.group2 and not restraint.group3:
            angle_restraint = openmm.CustomAngleForce(
                "k * (theta - theta_0)^2")
            angle_restraint.addPerAngleParameter("k")
            angle_restraint.addPerAngleParameter("theta_0")

            theta_0 = restraint.phase[phase]["targets"][
                window_number] * unit.degrees
            k = (restraint.phase[phase]["force_constants"][window_number] *
                 unit.kilocalories_per_mole / unit.radian**2)
            angle_restraint.addAngle(
                restraint.index1[0],
                restraint.index2[0],
                restraint.index3[0],
                [k, theta_0],
            )
            system.addForce(angle_restraint)
        else:
            # Probably needs openmm.CustomCentroidAngleForce (?)
            raise NotImplementedError
        if force_group:
            angle_restraint.setForceGroup(force_group)

    elif restraint.mask4:
        if (not restraint.group1 and not restraint.group2
                and not restraint.group3 and not restraint.group4):
            dihedral_restraint = openmm.CustomTorsionForce(
                f"k * min(min(abs(theta - theta_0), abs(theta - theta_0 + 2 * "
                f"{_PI_})), abs(theta - theta_0 - 2 * {_PI_}))^2")
            dihedral_restraint.addPerTorsionParameter("k")
            dihedral_restraint.addPerTorsionParameter("theta_0")

            theta_0 = restraint.phase[phase]["targets"][
                window_number] * unit.degrees
            k = (restraint.phase[phase]["force_constants"][window_number] *
                 unit.kilocalories_per_mole / unit.radian**2)
            dihedral_restraint.addTorsion(
                restraint.index1[0],
                restraint.index2[0],
                restraint.index3[0],
                restraint.index4[0],
                [k, theta_0],
            )
            system.addForce(dihedral_restraint)
        else:
            # Probably needs openmm.CustomCentroidTorsionForce (?)
            raise NotImplementedError
        if force_group:
            dihedral_restraint.setForceGroup(force_group)
#         cv.addGroup([int(index) for index in ligand_atoms])
#         cv.addGroup([int(index) for index in bottom_atoms], [1.0, 1.0, 1.0])
#         cv.addGroup([int(index) for index in top_atoms], [1.0, 1.0, 1.0])
#         cv.addBond([0,1,2], [])
#         system.addForce(cv)

md_topology = md.Topology.from_openmm(pdb.topology)
topology = pdb.topology

bottom_atoms = [1396, 5018, 3076]
top_atoms = [6241, 3325, 1626]
selection = '(resname CM7) and (mass > 3.0)'
print('Determining ligand atoms using "{}"...'.format(selection))
ligand_atoms = md_topology.select(selection)

r_parallel = openmm.CustomCentroidBondForce(3, '0')
r_parallel.addGroup([int(index) for index in ligand_atoms])
r_parallel.addGroup([int(index) for index in bottom_atoms], [1.0, 1.0, 1.0])
r_parallel.addGroup([int(index) for index in top_atoms], [1.0, 1.0, 1.0])
r_parallel.addBond([0, 1, 2], [])

bv = mtd.BiasVariable(r_parallel_bias, -2.0, 3.0, 0.1, False)
integrator = openmm.LangevinIntegrator(temperature, collision_rate, timestep)
meta = mtd.Metadynamics(
    openmm_system, [bv],
    r_parallel,
    temperature,
    10,
    5 * unit.kilojoules_per_mole,
    1000,
    saveFrequency=20000,
示例#9
0
def runProductionSimulation(equilibrationFiles,
                            workerNumber,
                            outputDir,
                            seed,
                            parameters,
                            reportFileName,
                            checkpoint,
                            ligandName,
                            replica_id,
                            trajsPerReplica,
                            restart=False):
    """
    Functions that runs the production run at NVT conditions.
    If a boxRadius is defined in the parameters section, a Flat-bottom harmonic restrains will be applied between
    the protein and the ligand

    :param equilibrationFiles: Tuple with the paths for the Amber topology file (prmtop) and the pdb for the system
    :type equilibrationFiles: Tuple
    :param workerNumber: Number of the subprocess
    :type workerNumber: int
    :param outputDir: path to the directory where the output will be written
    :type outputDir: str
    :param seed: Seed to use to generate the random numbers
    :type seed: int
    :param parameters: Object with the parameters for the simulation
    :type parameters: :py:class:`/simulationrunner/SimulationParameters` -- SimulationParameters object
    :param reportFileName: Name for the file where the energy report will be written
    :type reportFileName: str
    :param checkpoint: Path to the checkpoint from where the production run will be restarted (Optional)
    :type checkpoint: str
    :param ligandName: Code Name for the ligand
    :type ligandName: str
    :param replica_id: Id of the replica running
    :type replica_id: int
    :param trajsPerReplica: Number of trajectories per replica
    :type trajsPerReplica: int
    :param restart: Whether the simulation run has to be restarted or not
    :type restart: bool

    """
    deviceIndex = workerNumber
    workerNumber += replica_id * trajsPerReplica + 1
    prmtop, pdb = equilibrationFiles
    prmtop = app.AmberPrmtopFile(prmtop)
    trajName = os.path.join(
        outputDir, constants.AmberTemplates.trajectoryTemplate %
        (workerNumber, parameters.format))
    stateReporter = os.path.join(outputDir,
                                 "%s_%s" % (reportFileName, workerNumber))
    checkpointReporter = os.path.join(
        outputDir,
        constants.AmberTemplates.CheckPointReporterTemplate % workerNumber)
    lastStep = getLastStep(stateReporter)
    simulation_length = parameters.productionLength - lastStep
    # if the string is unicode the PDBReaders fails to read the file (this is
    # probably due to the fact that openmm was built with python2 in my
    # computer, will need to test thoroughly with python3)
    pdb = app.PDBFile(str(pdb))
    PLATFORM = mm.Platform_getPlatformByName(str(parameters.runningPlatform))
    if parameters.runningPlatform == "CUDA":
        platformProperties = {
            "Precision":
            "mixed",
            "DeviceIndex":
            getDeviceIndexStr(
                deviceIndex,
                parameters.devicesPerTrajectory,
                devicesPerReplica=parameters.maxDevicesPerReplica),
            "UseCpuPme":
            "false"
        }
    else:
        platformProperties = {}
    system = prmtop.createSystem(nonbondedMethod=app.PME,
                                 nonbondedCutoff=parameters.nonBondedCutoff *
                                 unit.angstroms,
                                 constraints=app.HBonds,
                                 removeCMMotion=True)
    system.addForce(
        mm.AndersenThermostat(parameters.Temperature * unit.kelvin,
                              1 / unit.picosecond))
    integrator = mm.VerletIntegrator(parameters.timeStep * unit.femtoseconds)
    system.addForce(
        mm.MonteCarloBarostat(1 * unit.bar,
                              parameters.Temperature * unit.kelvin))
    if parameters.boxRadius:
        group_ligand = []
        group_protein = []
        for atom in prmtop.topology.atoms():
            if atom.residue.name == ligandName:
                group_ligand.append(atom.index)
            elif atom.residue.name not in ("HOH", "Cl-", "Na+"):
                group_protein.append(atom.index)
        # Harmonic flat-bottom restrain for the ligand
        group_ligand = np.array(group_ligand)
        group_protein = np.array(group_protein)
        force = mm.CustomCentroidBondForce(
            2, 'step(distance(g1,g2)-r) * (k/2) * (distance(g1,g2)-r)^2')
        force.addGlobalParameter(
            "k", 5.0 * unit.kilocalories_per_mole / unit.angstroms**2)
        force.addGlobalParameter("r", parameters.boxRadius * unit.angstroms)
        force.addGroup(group_protein)
        force.addGroup(group_ligand)
        force.addBond(
            [0, 1], []
        )  # the first parameter is the list of indexes of the groups, the second is the list of perbondparameters
        system.addForce(force)
    simulation = app.Simulation(prmtop.topology,
                                system,
                                integrator,
                                PLATFORM,
                                platformProperties=platformProperties)
    simulation.context.setPositions(pdb.positions)

    if restart:
        with open(str(checkpoint), 'rb') as check:
            simulation.context.loadCheckpoint(check.read())
            stateData = open(str(stateReporter), "a")
    else:
        simulation.context.setVelocitiesToTemperature(
            parameters.Temperature * unit.kelvin, seed)
        stateData = open(str(stateReporter), "w")

    if parameters.format == "xtc":
        simulation.reporters.append(
            XTCReporter(str(trajName), parameters.reporterFreq,
                        append=restart))
    elif parameters.format == "dcd":
        simulation.reporters.append(
            app.DCDReporter(str(trajName),
                            parameters.reporterFreq,
                            append=restart,
                            enforcePeriodicBox=True))

    simulation.reporters.append(
        app.CheckpointReporter(str(checkpointReporter),
                               parameters.reporterFreq))
    simulation.reporters.append(
        CustomStateDataReporter(stateData,
                                parameters.reporterFreq,
                                step=True,
                                potentialEnergy=True,
                                temperature=True,
                                time_sim=True,
                                volume=True,
                                remainingTime=True,
                                speed=True,
                                totalSteps=parameters.productionLength,
                                separator="\t",
                                append=restart,
                                initialStep=lastStep))
    if workerNumber == 1:
        frequency = min(10 * parameters.reporterFreq,
                        parameters.productionLength)
        simulation.reporters.append(
            app.StateDataReporter(sys.stdout, frequency, step=True))
    simulation.step(simulation_length)
    stateData.close()
示例#10
0
    def _create_thermodynamic_states(self,
                                     reference_thermodynamic_state,
                                     spacing=0.25 * unit.angstroms):
        """
        Create thermodynamic states for sampling along pore axis.

        Here, the porin is restrained in the (x,z) plane and the pore axis is oriented along the y-axis.

        Parameters
        ----------
        reference_thermodynamic_state : openmmtools.states.ThermodynamicState
            Reference ThermodynamicState containing system, temperature, and pressure
        spacing : simtk.unit.Quantity with units compatible with angstroms
            Spacing between umbrellas spanning pore axis
        """
        # Determine relevant coordinates
        # TODO: Allow selections to be specified as class methods
        axis_center_atom = self.mdtraj_topology.select(
            'residue 128 and resname ALA and name CA')[0]
        axis_center = self._get_reference_coordinates(axis_center_atom)
        print('axis center (x,z) : {} : {}'.format(axis_center_atom,
                                                   axis_center))
        pore_top_atom = self.mdtraj_topology.select(
            'residue 226 and resname SER and name CA')[0]
        pore_top = self._get_reference_coordinates(pore_top_atom)
        print('pore top (y-max): {} : {}'.format(pore_top_atom, pore_top))
        pore_bottom_atom = self.mdtraj_topology.select(
            'residue 88 and resname VAL and name CA')[0]
        pore_bottom = self._get_reference_coordinates(pore_bottom_atom)
        print('pore bottom (y-min): {} : {}'.format(pore_bottom_atom,
                                                    pore_bottom))

        # Determine ligand atoms
        selection = '(residue {}) and (mass > 1.5)'.format(self.ligand_resseq)
        print('Determining ligand atoms using "{}"...'.format(selection))
        ligand_atoms = self.mdtraj_topology.select(selection)
        if len(ligand_atoms) == 0:
            raise ValueError('Ligand residue name {} not found'.format(
                self.ligand_resseq))
        print('ligand heavy atoms: {}'.format(ligand_atoms))

        # Determine protein atoms
        bottom_selection = '((residue 342 and resname GLY) or (residue 97 and resname ASP) or (residue 184 and resname SER)) and (name CA)'
        bottom_protein_atoms = self.mdtraj_topology.select(bottom_selection)
        top_selection = '((residue 226 and resname SER) or (residue 421 and resname LEU) or (residue 147 and resname GLU)) and (name CA)'
        top_protein_atoms = self.mdtraj_topology.select(top_selection)

        # Compute pore bottom (lambda=0) and top (lambda=1)
        axis_bottom = (axis_center[0], pore_bottom[1], axis_center[2])
        axis_top = (axis_center[0], pore_top[1], axis_center[2])
        axis_distance = pore_top[1] - pore_bottom[1]
        print('axis_distance: {}'.format(axis_distance))

        # Compute spacing and spring constant
        expansion_factor = 1.3
        nstates = int(expansion_factor * axis_distance / spacing) + 1
        print('nstates: {}'.format(nstates))
        sigma_y = axis_distance / float(
            nstates)  # stddev of force-free fluctuations in y-axis
        K_y = self.kT / (sigma_y**2)  # spring constant
        print('vertical sigma_y = {:.3f} A'.format(sigma_y / unit.angstroms))

        # Compute restraint width
        # TODO: Come up with a better way to define pore width?
        scale_factor = 0.25
        sigma_xz = scale_factor * unit.sqrt(
            (axis_center[0] - pore_bottom[0])**2 +
            (axis_center[2] - pore_bottom[2])**
            2)  # stddev of force-free fluctuations in xz-plane
        K_xz = self.kT / (sigma_xz**2)  # spring constant
        print('in-plane sigma_xz = {:.3f} A'.format(sigma_xz / unit.angstroms))

        dr = axis_distance * (expansion_factor - 1.0) / 1.0
        rmax = axis_distance + dr
        rmin = -1.0 * axis_distance

        # Create restraint state that encodes this axis
        # TODO: Rework this as CustomCVForce with in-plane and along-axis deviations as separate forces so we can store them each iteration
        print('Creating restraint...')
        from yank.restraints import RestraintState
        energy_expression = '(K_parallel/2)*(r_parallel-r0)^2 + (K_orthogonal/2)*r_orthogonal^2;'
        energy_expression += 'r_parallel = r*cos(theta);'
        energy_expression += 'r_orthogonal = r*sin(theta);'
        energy_expression += 'r = distance(g1,g2);'
        energy_expression += 'theta = angle(g1,g2,g3);'
        energy_expression += 'r0 = lambda_restraints * (rmax - rmin) + rmin;'
        force = openmm.CustomCentroidBondForce(3, energy_expression)
        force.addGlobalParameter('lambda_restraints', 1.0)
        force.addGlobalParameter('K_parallel', K_y)
        force.addGlobalParameter('K_orthogonal', K_xz)
        force.addGlobalParameter('rmax', rmax)
        force.addGlobalParameter('rmin', rmin)
        force.addGroup([int(index) for index in ligand_atoms])
        force.addGroup([int(index) for index in bottom_protein_atoms])
        force.addGroup([int(index) for index in top_protein_atoms])
        force.addBond([0, 1, 2], [])
        self.system.addForce(force)
        # Update reference thermodynamic state
        print('Updating system in reference thermodynamic state...')
        self.reference_thermodynamic_state.set_system(self.system,
                                                      fix_state=True)

        # Create alchemical state
        #from openmmtools.alchemy import AlchemicalState
        #alchemical_state = AlchemicalState.from_system(self.reference_thermodynamic_state.system)

        # Create restraint state
        restraint_state = RestraintState(lambda_restraints=1.0)

        # Create thermodynamic states to be sampled
        # TODO: Should we include an unbiased state?
        initial_time = time.time()
        thermodynamic_states = list()
        #compound_state = states.CompoundThermodynamicState(self.reference_thermodynamic_state, composable_states=[alchemical_state, restraint_state])
        compound_state = states.CompoundThermodynamicState(
            self.reference_thermodynamic_state,
            composable_states=[restraint_state])
        for lambda_restraints in np.linspace(0, 1, nstates):
            thermodynamic_state = copy.deepcopy(compound_state)
            thermodynamic_state.lambda_restraints = lambda_restraints
            #thermodynamic_state.lambda_sterics = 1.0
            #thermodynamic_state.lambda_electrostatics = 1.0
            thermodynamic_states.append(thermodynamic_state)
        elapsed_time = time.time() - initial_time
        print('Creating thermodynamic states took %.3f s' % elapsed_time)

        return thermodynamic_states
示例#11
0
#=============================
external = {
    "U": extparams['Uext'] * epsilon,
    "NPeriod": extparams['Nperiod'],
    "axis": extparams['axis'],
    "planeLoc": extparams['planeLoc']
}
direction = ['x', 'y', 'z']
ax = external["axis"]
#atomsInExtField = [elementMap[atomname]]
if external["U"] > 0.0 * unit.kilojoules_per_mole:
    print('\n=== Creating sinusoidal external potential in the {} direction'.
          format(direction[axis]))
    energy_function = 'U*sin(2*pi*NPeriod*({axis}1-r0)/L)'.format(
        axis=direction[ax])
    fExt = openmm.CustomCentroidBondForce(1, energy_function)
    fExt.addGlobalParameter("U", external["U"])
    fExt.addGlobalParameter("NPeriod", external["NPeriod"])
    fExt.addGlobalParameter("pi", np.pi)
    fExt.addGlobalParameter("r0", external["planeLoc"])
    fExt.addGlobalParameter("L", box_edge[ax])
    atomThusFar = 0
    for i in range(int(n_p * DOP / mapping)):  #looping through CG beads
        fExt.addGroup(
            range(atomThusFar, atomThusFar + mapping)
        )  #assuming the first n_p*DOP atoms are polymer atoms and atom index increases along chain
        fExt.addBond([i], [])
        atomThusFar += mapping
    system.addForce(fExt)

#==============================
示例#12
0
def main():
    print("Reading the PSF file")

    # Read the PSF file
    #psf = app.CharmmPsfFile('g1_25mm.psf');
    psf = app.CharmmPsfFile('holo_neutr.psf')

    boxsize = 4.895883  # Boxsize in nm
    psf.setBox(boxsize * nanometer, boxsize * nanometer, boxsize * nanometer)

    print("Reading the pdb file")
    #pdb = app.PDBFile('g1_25mm.pdb');
    #pdb  = app.PDBFile('md_298k_100ns.pdb')
    pdb = app.PDBFile('holo_neutr.pdb')

    # Load the parameter set
    lib = 'toppar/'
    #params = app.CharmmParameterSet('toppar/top_all36_cgenff.rtf','toppar/par_all36_cgenff.prm','toppar/cgenff3.0.1/top_all36_cgenff.rtf','toppar/cgenff3.0.1/par_all36_cgenff.prm','g1_new.str','toppar_water_ions.str')
    #params = app.CharmmParameterSet('toppar/cgenff3.0.1/top_all36_cgenff.rtf','toppar/cgenff3.0.1/par_all36_cgenff.prm','g1_new.str','toppar_water_ions.str')
    params = app.CharmmParameterSet('toppar/cgenff3.0.1/top_all36_cgenff.rtf',
                                    'toppar/cgenff3.0.1/par_all36_cgenff.prm',
                                    'g1_new.str', 'oah_groups.str',
                                    'toppar_water_ions.str')

    platform = openmm.Platform.getPlatformByName('CUDA')

    isShortSim = False
    #isShortSim = True
    isPeriodic = True
    #isPeriodic = False
    #if not isPeriodic :
    #  isShortSim = True;

    # Creating the system
    if (isPeriodic):
        print("PME is being used")
        system = psf.createSystem(params,
                                  nonbondedMethod=app.PME,
                                  nonbondedCutoff=1.2 * nanometer,
                                  switchDistance=1.0 * nanometer,
                                  ewaldErrorTolerance=0.0001,
                                  constraints=app.HBonds)
    else:
        print("PME is not being used")
        system = psf.createSystem(
            params,
            #                          nonbondedMethod=app.PME,
            nonbondedCutoff=1.2 * nanometer,
            switchDistance=1.0 * nanometer,
            ewaldErrorTolerance=0.0001,
            constraints=app.HBonds)

    #for force in system.getForces():
    #  print(force, force.usesPeriodicBoundaryConditions())
    # Thermostat @ 298 K
    #system.addForce(openmm.AndersenThermostat(298*kelvin, 1/picosecond))
    # adding the barostat for now
    #system.addForce(openmm.MonteCarloBarostat(1*bar, 298*kelvin));

    # adding positional restriants
    if isPeriodic:
        force = openmm.CustomExternalForce(
            "k*periodicdistance(x, y, z, x0, y0, z0)^2")
    else:
        force = openmm.CustomExternalForce("k*((x-x0)^2+(y-y0)^2+(z-z0)^2)")
        #force = openmm.CustomExternalForce("k*periodicdistance(x, y, z, x0, y0, z0)^2");

    force.addGlobalParameter("k", 0.1 * kilocalories_per_mole / angstroms**2)
    force.addPerParticleParameter("x0")
    force.addPerParticleParameter("y0")
    force.addPerParticleParameter("z0")

    topology = pdb.getTopology()
    positions = pdb.getPositions()
    #for atom in  topology.atoms():
    #  print(atom)

    host = []
    guest = []
    for res in topology.residues():
        if res.name == 'OAH':
            for atom in res.atoms():
                host.append(atom.index)
                force.addParticle(atom.index, positions[atom.index])
                #force.addParticle(atom.index, (0, 0, 0)*nanometers)
        if res.name == 'GOA':
            for atom in res.atoms():
                guest.append(atom.index)
    system.addForce(force)

    print("Does customExternalForce use periodic boundary condition : ",
          force.usesPeriodicBoundaryConditions())
    # adding restraint between the host and guest
    # this will be inside the umbrella sampling loop
    host_guest_centroid_force = openmm.CustomCentroidBondForce(
        2, "0.5*k*(distance(g1,g2)-d0)^2")
    host_guest_centroid_force.addGlobalParameter(
        "k", 10.0 * kilocalories_per_mole / angstroms**2)
    #d0_global_parameter_index = force.addGlobalParameter("d0", 2.0*angstroms);
    #d0_perBond_parameter_index = force.addPerBondParameter("d0", 2.0*angstroms);
    d0_perBond_parameter_index = host_guest_centroid_force.addPerBondParameter(
        "d0")
    group1 = host_guest_centroid_force.addGroup(host)
    group2 = host_guest_centroid_force.addGroup(guest)
    host_guest_bond = host_guest_centroid_force.addBond([group1, group2])
    host_guest_centroid_force.setBondParameters(host_guest_bond,
                                                [group1, group2],
                                                [20 * angstroms])
    system.addForce(host_guest_centroid_force)
    #sys.exit(0)
    """
  # Restrain along z axis
  # adding positional restriants
  if isPeriodic :
    z_force = openmm.CustomExternalForce("k*periodicdistance(x, x0)^2");
  else :
    z_force = openmm.CustomExternalForce("k*((x-x0)^2+(y-y0)^2)");
    #force = openmm.CustomExternalForce("k*periodicdistance(x, y, z, x0, y0, z0)^2");

  z_force.addGlobalParameter("k", 0.1*kilocalories_per_mole/angstroms**2);
  z_force.addPerParticleParameter("x0");
  z_force.addPerParticleParameter("y0");

  for res in topology.residues():
    if res.name == 'GOA' :
      for atom in res.atoms():
        pos = list(positions[atom.index])
        print(pos[2])
        z_force.addParticle(atom.index, [pos[0], pos[1]])
  system.addForce(z_force)
  """

    # langevin integrator
    integrator = openmm.LangevinIntegrator(
        298 * kelvin,  # Temperature 
        1.0 / picoseconds,  # Friction coefficient
        0.001 * picoseconds  # Time step
    )
    #integrator = openmm.VerletIntegrator(0.001*picoseconds);
    simulation = app.Simulation(psf.topology, system, integrator, platform)
    simulation.context.setPositions(pdb.getPositions())

    #currentState = simulation.context.getState(getPositions=True)
    #pdbr = app.PDBReporter("pdbreport.pdb",1);
    #pdbr.report(simulation, currentState)

    print("Minimizing...")
    simulation.minimizeEnergy(maxIterations=2000)
    print("Equilibrating...")
    simulation.context.setVelocitiesToTemperature(298 * kelvin)
    #currentState = simulation.context.getState(getPositions=True)
    #pdbr = app.PDBReporter("pdbreport_after_min.pdb",1);
    #pdbr.report(simulation, currentState)
    #pdbr = app.PDBReporter("pdbreport_after_step1.pdb",1);

    nsavcrd = 10000  # save coordinates every 10 ps
    nprint = 2000  # report every 2 ps
    if isShortSim:
        nstep = 20000  # run the simulation for 0.2ns
    else:
        nstep = 2000000  # run the simulation for 2ns
    firstDcdStep = nsavcrd

    # Reporters
    dcdReportInterval = 10000  # save coordinates every 10 ps
    dcd = app.DCDReporter('umb_3.dcd', dcdReportInterval)
    dcd._dcd = app.DCDFile(dcd._out, simulation.topology,
                           simulation.integrator.getStepSize(), firstDcdStep,
                           nsavcrd)

    stateReporter = app.StateDataReporter('umb_3.out',
                                          nprint,
                                          step=True,
                                          kineticEnergy=True,
                                          potentialEnergy=True,
                                          totalEnergy=True,
                                          temperature=True,
                                          volume=True,
                                          speed=True)

    simulation.reporters.append(dcd)
    simulation.reporters.append(stateReporter)
    #simulation.reporters.append(pdbr);
    #simulation.reporters.append(app.StateDataReporter('umb.out', nprint, step=True, kineticEnergy=True, potentialEnergy=True, totalEnergy=True, temperature=True, volume=True, speed=True))

    # Run the simulation
    print("Simulation begins ...")
    #simulation.step(1)
    #sys.exit(0)
    for i in range(15):
        print("Simulation for umbrella : ", i)
        host_guest_centroid_force.setBondParameters(host_guest_bond,
                                                    [group1, group2],
                                                    [(5 + 3 * i) * angstroms])
        #force.setGlobalParameterDefaultValue(d0_global_parameter_index, (2+i)*angstroms)
        host_guest_centroid_force.updateParametersInContext(simulation.context)
        print("host guest bond parameters",
              host_guest_centroid_force.getBondParameters(host_guest_bond))
        #serialized = openmm.XmlSerializer.serialize(simulation.system)
        #of = open("serial_sim_"+str(i)+".xml",'w');
        #of.write(serialized);
        #of.close();
        #simulation.saveState("state_"+str(i)+".xml");
        simulation.step(nstep)

    simulation.reporters.pop()
    simulation.reporters.pop()
    dcd._out.close()
示例#13
0
def setup_openmm_restraints(system, restraint, phase, window):
    """
    Add particle restraints with OpenMM.
    """

    # http://docs.openmm.org/7.1.0/api-c++/generated/OpenMM.CustomExternalForce.html
    # It's possible we might need to use `periodicdistance`.

    if (restraint.mask1 is not None and restraint.mask2 is not None
            and restraint.mask3 is None and restraint.mask4 is None):

        if restraint.group1 is False and restraint.group2 is False:
            bond_restraint = mm.CustomBondForce("k * (r - r_0)^2")
            bond_restraint.addPerBondParameter("k")
            bond_restraint.addPerBondParameter("r_0")

            r_0 = restraint.phase[phase]["targets"][window] * unit.angstrom
            k = (restraint.phase[phase]["force_constants"][window] *
                 unit.kilocalorie_per_mole / unit.angstrom**2)
            bond_restraint.addBond(restraint.index1[0], restraint.index2[0],
                                   [k, r_0])
            bond_restraint.setForceGroup(1)
            system.addForce(bond_restraint)
            log.debug(
                "Added bond restraint between {} and {} with target value = "
                "{} and force constant = {}".format(restraint.mask1,
                                                    restraint.mask2, r_0, k))
        elif restraint.group1 is True or restraint.group2 is True:
            # http://docs.openmm.org/7.0.0/api-python/generated/simtk.openmm.openmm.CustomManyParticleForce.html
            # http://getyank.org/development/_modules/yank/restraints.html
            bond_restraint = mm.CustomCentroidBondForce(
                2, "k * (distance(g1, g2) - r_0)^2")
            bond_restraint.addPerBondParameter("k")
            bond_restraint.addPerBondParameter("r_0")

            r_0 = restraint.phase[phase]["targets"][window] * unit.angstrom
            k = (restraint.phase[phase]["force_constants"][window] *
                 unit.kilocalorie_per_mole / unit.angstrom**2)
            g1 = bond_restraint.addGroup(restraint.index1)
            g2 = bond_restraint.addGroup(restraint.index2)
            bond_restraint.addBond([g1, g2], [k, r_0])
            bond_restraint.setForceGroup(1)
            system.addForce(bond_restraint)
            log.debug(
                "Added bond restraint between {} and {} with target value = "
                "{} and force constant = {}".format(restraint.mask1,
                                                    restraint.mask2, r_0, k))
    else:
        log.error("Unable to add bond restraint...")
        log.debug("restraint.index1 = {}".format(restraint.index1))
        log.debug("restraint.index2 = {}".format(restraint.index2))
        raise Exception("Unable to add bond restraint...")

    if (restraint.mask1 is not None and restraint.mask2 is not None
            and restraint.mask3 is not None and restraint.mask4 is None):
        if (restraint.group1 is not False and restraint.group2 is not False
                and restraint.group3 is not False):
            log.error("Unable to add a group angle restraint...")
            log.debug("restraint.index1 = {}".format(restraint.index1))
            log.debug("restraint.index2 = {}".format(restraint.index2))
            log.debug("restraint.index3 = {}".format(restraint.index3))
            raise Exception("Unable to add a group angle restraint...")

        angle_restraint = mm.CustomAngleForce("k * (theta - theta_0)^2")
        angle_restraint.addPerAngleParameter("k")
        angle_restraint.addPerAngleParameter("theta_0")

        log.debug("Setting an angle restraint in degrees using a "
                  "force constant in kcal per mol rad**2...")
        theta_0 = restraint.phase[phase]["targets"][window] * unit.degrees
        k = (restraint.phase[phase]["force_constants"][window] *
             unit.kilocalorie_per_mole / unit.radian**2)
        angle_restraint.addAngle(restraint.index1[0], restraint.index2[0],
                                 restraint.index3[0], [k, theta_0])
        system.addForce(angle_restraint)

    if (restraint.mask1 is not None and restraint.mask2 is not None
            and restraint.mask3 is not None and restraint.mask4 is not None):
        if (restraint.group1 is not False and restraint.group2 is not False
                and restraint.group3 is not False
                and restraint.group4 is not False):
            log.error("Unable to add a group dihedral restraint...")
            log.debug("restraint.index1 = {}".format(restraint.index1))
            log.debug("restraint.index2 = {}".format(restraint.index2))
            log.debug("restraint.index3 = {}".format(restraint.index3))
            log.debug("restraint.index4 = {}".format(restraint.index4))
            raise Exception("Unable to add a group dihedral restraint...")

        dihedral_restraint = mm.CustomTorsionForce("k * (theta - theta_0)^2")
        dihedral_restraint.addPerTorsionParameter("k")
        dihedral_restraint.addPerTorsionParameter("theta_0")

        log.debug("Setting a torsion restraint in degrees using a "
                  "force constant in kcal per mol rad**2...")
        theta_0 = restraint.phase[phase]["targets"][window] * unit.degrees
        k = (restraint.phase[phase]["force_constants"][window] *
             unit.kilocalorie_per_mole / unit.radian**2)
        dihedral_restraint.addTorsion(
            restraint.index1[0],
            restraint.index2[0],
            restraint.index3[0],
            restraint.index4[0],
            [k, theta_0],
        )
        system.addForce(dihedral_restraint)

    return system
def main(args):
    #Get the structure and topology files from the command line
    #ParmEd accepts a wide range of file types (Amber, GROMACS, CHARMM, OpenMM... but not LAMMPS)
    try:
        topFile = args[0]
        strucFile = args[1]
    except IndexError:
        print("Specify topology and structure files from the command line.")
        sys.exit(2)

    print("Using topology file: %s" % topFile)
    print("Using structure file: %s" % strucFile)

    print("\nSetting up system...")

    #Load in the files for initial simulations
    top = pmd.load_file(topFile)
    struc = pmd.load_file(strucFile)

    #Transfer unit cell information to topology object
    top.box = struc.box[:]

    #Set up some global features to use in all simulations
    temperature = 298.15 * u.kelvin

    #Define the platform (i.e. hardware and drivers) to use for running the simulation
    #This can be CUDA, OpenCL, CPU, or Reference
    #CUDA is for NVIDIA GPUs
    #OpenCL is for CPUs or GPUs, but must be used for old CPUs (not SSE4.1 compatible)
    #CPU only allows single precision (CUDA and OpenCL allow single, mixed, or double)
    #Reference is a clear, stable reference for other code development and is very slow, using double precision by default
    platform = mm.Platform.getPlatformByName('CUDA')
    prop = {  #'Threads': '2', #number of threads for CPU - all definitions must be strings (I think)
        'Precision':
        'mixed',  #for CUDA or OpenCL, select the precision (single, mixed, or double)
        'DeviceIndex':
        '0',  #selects which GPUs to use - set this to zero if using CUDA_VISIBLE_DEVICES
        'DeterministicForces':
        'True'  #Makes sure forces with CUDA and PME are deterministic
    }

    #Create the OpenMM system that can be used as a reference
    systemRef = top.createSystem(
        nonbondedMethod=app.
        PME,  #Uses PME for long-range electrostatics, simple cut-off for LJ
        nonbondedCutoff=12.0 *
        u.angstroms,  #Defines cut-off for non-bonded interactions
        rigidWater=True,  #Use rigid water molecules
        constraints=app.HBonds,  #Constrains all bonds involving hydrogens
        flexibleConstraints=
        False,  #Whether to include energies for constrained DOFs
        removeCMMotion=
        False,  #Whether or not to remove COM motion (don't want to if part of system frozen)
    )

    #Set up the integrator to use as a reference
    integratorRef = mm.LangevinIntegrator(
        temperature,  #Temperature for Langevin
        1.0 / u.picoseconds,  #Friction coefficient
        2.0 * u.femtoseconds,  #Integration timestep
    )
    integratorRef.setConstraintTolerance(1.0E-08)

    #To freeze atoms, set mass to zero (does not apply to virtual sites, termed "extra particles" in OpenMM)
    #Here assume (correctly, I think) that the topology indices for atoms correspond to those in the system
    for i, atom in enumerate(top.atoms):
        if atom.type in ('SU'):  #, 'CU', 'CUO'):
            systemRef.setParticleMass(i, 0 * u.dalton)

    #Track non-bonded force, mainly to turn off dispersion correction
    NBForce = None
    for frc in systemRef.getForces():
        if (isinstance(frc, mm.NonbondedForce)):
            NBForce = frc

    #Turn off dispersion correction since have interface
    NBForce.setUseDispersionCorrection(False)

    #Get solute atoms and solute heavy atoms separately
    soluteIndices = []
    heavyIndices = []
    for res in top.residues:
        if res.name not in ['OTM', 'CTM', 'STM', 'NTM', 'SOL']:
            for atom in res.atoms:
                soluteIndices.append(atom.idx)
                if 'H' not in atom.name[0]:
                    heavyIndices.append(atom.idx)

    #JUST for boric acid, add a custom bonded force
    #Couldn't find a nice, compatible force field, but did find A forcefield, so using it
    #But has no angle terms on O-B-O and instead a weird bond repulsion term
    #This term also prevents out of plane bending
    #Simple in our case because boric acid is symmetric, so only need one parameter
    #Parameters come from Otkidach and Pletnev, 2001
    #Here, Ad = (A^2) / (d^6) since Ai and Aj and di and dj are all the same
    #In the original paper, B-OH bond had A = 1.72 and d = 0.354
    #Note that d is dimensionless and A should have units of (Angstrom^3)*(kcal/mol)^(1/2)
    #These units are inferred just to make things work out with kcal/mol and the given distance dependence
    bondRepulsionFunction = 'Ad*(1.0/r)^6'
    BondRepulsionForce = mm.CustomBondForce(bondRepulsionFunction)
    BondRepulsionForce.addPerBondParameter(
        'Ad')  #Units are technically kJ/mol * nm^6
    baOxInds = []
    for aind in soluteIndices:
        if top.atoms[aind].type == 'oh':
            baOxInds.append(aind)
    for i in range(len(baOxInds)):
        for j in range(i + 1, len(baOxInds)):
            BondRepulsionForce.addBond(baOxInds[i], baOxInds[j], [0.006289686])

    systemRef.addForce(BondRepulsionForce)

    #Also get surface SU atoms
    surfIndices = []
    for atom in top.atoms:
        if atom.type == 'SU':
            surfIndices.append(atom.idx)

    startPos = np.array(struc.positions.value_in_unit(u.nanometer))

    print("\nSolute indices: %s" % str(soluteIndices))
    print("Solute heavy atom indices: %s" % str(heavyIndices))
    print("Surface SU atom indices: %s" % str(surfIndices))

    #Solute should already be placed far from the surface
    #If this is not done right, or it is too close to half the periodic box distance, will have issues
    #Either way, set this as the starting reference z distance
    initRefZ = np.average(startPos[heavyIndices, 2]) - np.average(
        startPos[surfIndices, 2])

    print(initRefZ)

    #Will now add a custom bonded force between solute heavy atoms and surface SU atoms
    #Should be in units of kJ/mol*nm^2, but should check this
    #For expanded ensemble, fine to assume solute is less than half the box distance from the surface
    #But for umbrella sampling, want to apply pulling towards surface regardless of whether pull from above or below
    #Also allows us to get further from the surface with our umbrellas without worrying about PBCs
    restraintExpression = '0.5*k*(abs(z2 - z1) - refZ)^2'
    restraintForce = mm.CustomCentroidBondForce(2, restraintExpression)
    restraintForce.addPerBondParameter('k')
    restraintForce.addGlobalParameter(
        'refZ', initRefZ)  #Make global so can modify during simulation
    restraintForce.addGroup(surfIndices, np.ones(
        len(surfIndices)))  #Don't weight with masses
    restraintForce.addGroup(heavyIndices, np.ones(len(heavyIndices)))
    restraintForce.addBond([0, 1], [1000.0])
    restraintForce.setUsesPeriodicBoundaryConditions(
        True)  #Only when doing umbrella sampling
    systemRef.addForce(restraintForce)

    forceLabelsRef = getForceLabels(systemRef)

    decompEnergy(systemRef,
                 struc.positions,
                 labels=forceLabelsRef,
                 verbose=False)

    #Do NVT simulation
    stateFileNVT1, stateNVT1 = doSimNVT(top,
                                        systemRef,
                                        integratorRef,
                                        platform,
                                        prop,
                                        temperature,
                                        pos=struc.positions)

    #Do pulling simulation - really I'm just slowly changing the equilibrium bond distance between the surface and the solute
    stateFilePull, statePull = doSimPull(top,
                                         systemRef,
                                         integratorRef,
                                         platform,
                                         prop,
                                         temperature,
                                         state=stateFileNVT1)

    decompEnergy(systemRef, statePull, labels=forceLabelsRef, verbose=False)

    #Load in pulling restraint data and pulling trajectory to identify starting structures for each umbrella
    pullData = np.loadtxt('pull_restraint.txt')
    trajtop = copy.deepcopy(top)
    trajtop.rb_torsions = pmd.TrackedList([])
    trajtop = pt.load_parmed(trajtop, traj=False)
    pulltraj = pt.iterload('pull.nc', trajtop)
    frameTimes = np.array([frame.time for frame in pulltraj])

    #Define some umbrella distances based on a fixed spacing between initial and final pulling coordinate
    #Actually for final pulling coordinate, close to the surface, using average of actual coordinate and reference
    zSpace = 0.1  #nm
    zUmbs = np.arange(0.5 * (pullData[-1, 1] + pullData[-1, 2]),
                      pullData[0, 2], zSpace)

    print("\nUsing following umbrellas:")
    print(zUmbs)

    #Then loop over umbrellas and run simulation for each
    for i, zRefDist in enumerate(zUmbs):

        os.mkdir("umbrella%i" % i)
        os.chdir("umbrella%i" % i)

        #Find where in the pulling trajectory the solute came closest to this umbrella
        pullDatInd = np.argmin(abs(pullData[:, 2] - zRefDist))
        frameInd = np.argmin(abs(frameTimes - pullData[pullDatInd, 0]))

        #Get starting coordinates
        #Making sure to assign the units that will be returned by pytraj
        thisCoords = np.array(pulltraj[frameInd].xyz) * u.angstrom

        #Set reference value for harmonic force
        restraintForce.setGlobalParameterDefaultValue(0, zRefDist)

        print("\nUmbrella %i:" % i)
        print("\tReference distance: %f" % zRefDist)
        print("\tFrame chosen from trajectory: %i (%f ps)" %
              (frameInd, frameTimes[frameInd]))

        #And run simulations, first equilibrating in NVT, then NPT, then production in NPT
        stateFileNVT, stateNVT = doSimNVT(top,
                                          systemRef,
                                          integratorRef,
                                          platform,
                                          prop,
                                          temperature,
                                          pos=thisCoords)

        stateFileNPT, stateNPT = doSimNPT(top,
                                          systemRef,
                                          integratorRef,
                                          platform,
                                          prop,
                                          temperature,
                                          state=stateFileNVT)

        stateFileProd, stateProd = doSimUmbrella(top,
                                                 systemRef,
                                                 integratorRef,
                                                 platform,
                                                 prop,
                                                 temperature,
                                                 state=stateFileNPT)

        os.chdir("../")
示例#15
0
文件: iapetus.py 项目: LaYeqa/iapetus
    def _auto_create_thermodynamic_states(self,
                                          structure,
                                          topology,
                                          reference_thermodynamic_state,
                                          spacing=0.25 * unit.angstroms):
        """
        Create thermodynamic states for sampling along pore axis.
        """
        topo = md.Topology.from_openmm(topology)
        data = DataPoints(structure, topo)
        data.writeCoordinates(open('cylinder.xyz', 'w'))
        cylinder = CylinderFitting(data.coordinates)
        print(cylinder.vmdCommands(), file=open('vmd_commands.txt', 'w'))
        b_index, t_index = cylinder.atomsInExtremes(data.coordinates, n=5)
        cylinder.writeExtremesCoords(data.coordinates, b_index, t_index,
                                     open('extremes.xyz', 'w'))
        bottom_atoms = []
        top_atoms = []
        for idx in b_index:
            bottom_atoms.append(data.index[idx])
        for idx in t_index:
            top_atoms.append(data.index[idx])

        axis_distance = cylinder.height * unit.angstroms

        selection = '(residue {}) and (mass > 1.5)'.format(self.ligand_resseq)
        print('Determining ligand atoms using "{}"...'.format(selection))
        ligand_atoms = self.mdtraj_topology.select(selection)

        expansion_factor = 1.3
        nstates = int(expansion_factor * axis_distance / spacing) + 1
        print('nstates: {}'.format(nstates))
        sigma_y = axis_distance / float(
            nstates)  # stddev of force-free fluctuations in y-axis
        K_y = self.kT / (sigma_y**2)  # spring constant

        print('vertical sigma_y = {:.3f} A'.format(sigma_y / unit.angstroms))

        # Compute restraint width
        scale_factor = 0.25
        sigma_xz = scale_factor * cylinder.r * unit.angstroms  # stddev of force-free fluctuations in xz-plane
        K_xz = self.kT / (sigma_xz**2)  # spring constant

        Kmax = K_y
        Kmin = K_xz
        dr = axis_distance * (expansion_factor - 1.0) / 2.0
        rmax = axis_distance + dr
        rmin = -dr

        # Create restraint state that encodes this axis
        print('Creating restraint...')
        from yank.restraints import RestraintState

        # Collective variable definitions
        common = 'r = distance(g1,g2);'
        common += 'theta = angle(g1,g2,g3);'
        r_parallel = openmm.CustomCentroidBondForce(3,
                                                    'r*cos(theta);' + common)
        r_orthogonal = openmm.CustomCentroidBondForce(3,
                                                      'r*sin(theta);' + common)

        for cv in [r_parallel, r_orthogonal]:
            cv.addGroup([int(index) for index in ligand_atoms])
            cv.addGroup([int(index) for index in bottom_atoms])
            cv.addGroup([int(index) for index in top_atoms])
            cv.addBond([0, 1, 2], [])

        energy_parallel = '(K_parallel/2)*(r_parallel-r0)^2;'
        energy_parallel += 'r0 = lambda_restraints * (rmax - rmin) + rmin;'
        self.cvforce_parallel = openmm.CustomCVForce(energy_parallel)
        self.cvforce_parallel.addCollectiveVariable('r_parallel', r_parallel)

        energy_orthogonal = '(1/2)*(Kmin + S*(Kmax - Kmin))*(r_orthogonal^2);'
        energy_orthogonal += 'S = 1 - step(z_ext-z)*(1 + u^3*(15*u - 6*u^2 - 10));'
        energy_orthogonal += 'u = step(z-z_int)*(z - z_int)/(z_ext - z_int);'
        energy_orthogonal += 'z = abs(r0-z_c);'
        energy_orthogonal += 'r0 = lambda_restraints * (rmax - rmin) + rmin;'
        self.cvforce_orthogonal = openmm.CustomCVForce(energy_orthogonal)
        self.cvforce_orthogonal.addCollectiveVariable('r_orthogonal',
                                                      r_orthogonal)
        for force in [self.cvforce_parallel, self.cvforce_orthogonal]:
            force.addGlobalParameter('rmax', rmax)
            force.addGlobalParameter('rmin', rmin)
            force.addGlobalParameter('lambda_restraints', 1.0)

        self.cvforce_parallel.addGlobalParameter('K_parallel', K_y)

        self.cvforce_orthogonal.addGlobalParameter('Kmax', Kmax)
        self.cvforce_orthogonal.addGlobalParameter('Kmin', Kmin)
        self.cvforce_orthogonal.addGlobalParameter('z_c', axis_distance / 2.0)
        self.cvforce_orthogonal.addGlobalParameter('z_int',
                                                   0.8 * (axis_distance / 2.0))
        self.cvforce_orthogonal.addGlobalParameter('z_ext',
                                                   1.3 * (axis_distance / 2.0))

        self.system.addForce(self.cvforce_parallel)
        self.system.addForce(self.cvforce_orthogonal)
        # Update reference thermodynamic state
        print('Updating system in reference thermodynamic state...')
        self.reference_thermodynamic_state.set_system(self.system,
                                                      fix_state=True)

        ## Create alchemical state
        ##from openmmtools.alchemy import AlchemicalState
        ##alchemical_state = AlchemicalState.from_system(self.reference_thermodynamic_state.system)

        # Create restraint state
        restraint_state = RestraintState(lambda_restraints=1.0)

        # Create thermodynamic states to be sampled
        # TODO: Should we include an unbiased state?
        initial_time = time.time()
        thermodynamic_states = list()
        ##compound_state = states.CompoundThermodynamicState(self.reference_thermodynamic_state, composable_states=[alchemical_state, restraint_state])
        compound_state = states.CompoundThermodynamicState(
            self.reference_thermodynamic_state,
            composable_states=[restraint_state])
        for lambda_restraints in np.linspace(0, 1, nstates):
            thermodynamic_state = copy.deepcopy(compound_state)
            thermodynamic_state.lambda_restraints = lambda_restraints
            #    #thermodynamic_state.lambda_sterics = 1.0
            #    #thermodynamic_state.lambda_electrostatics = 1.0
            thermodynamic_states.append(thermodynamic_state)
        elapsed_time = time.time() - initial_time
        print('Creating thermodynamic states took %.3f s' % elapsed_time)

        return thermodynamic_states
示例#16
0
def apply_openmm_restraints(system,
                            restraint,
                            window,
                            flat_bottom=False,
                            ForceGroup=None):
    if window[0] == "a":
        phase = "attach"
    elif window[0] == "p":
        phase = "pull"
    elif window[0] == "r":
        phase = "release"
    window_number = int(window[1:])

    if flat_bottom and phase == "attach" and restraint.mask3:
        flat_bottom_force = openmm.CustomAngleForce(
            'step(-(theta - theta_0)) * k * (theta - theta_0)^2')
        # If theta is greater than theta_0, then the argument to step is negative, which means the force is off.
        flat_bottom_force.addPerAngleParameter("k")
        flat_bottom_force.addPerAngleParameter("theta_0")

        theta_0 = 91.0 * unit.degrees
        k = (restraint.phase[phase]["force_constants"][window_number] *
             unit.kilocalories_per_mole / unit.radian**2)
        flat_bottom_force.addAngle(
            restraint.index1[0],
            restraint.index2[0],
            restraint.index3[0],
            [k, theta_0],
        )
        system.addForce(flat_bottom_force)
        if ForceGroup:
            flat_bottom_force.setForceGroup(ForceGroup)

        return system
    elif flat_bottom and phase == "attach" and not restraint.mask3:
        flat_bottom_force = openmm.CustomBondForce(
            'step((r - r_0)) * k * (r - r_0)^2')
        # If x is greater than x_0, then the argument to step is positive, which means the force is on.
        flat_bottom_force.addPerBondParameter("k")
        flat_bottom_force.addPerBondParameter("r_0")

        r_0 = restraint.phase[phase]["targets"][window_number] * unit.angstrom
        k = (restraint.phase[phase]["force_constants"][window_number] *
             unit.kilocalories_per_mole / unit.radian**2)
        flat_bottom_force.addBond(
            restraint.index1[0],
            restraint.index2[0],
            [k, r_0],
        )
        system.addForce(flat_bottom_force)
        if ForceGroup:
            flat_bottom_force.setForceGroup(ForceGroup)

        return system

    elif flat_bottom and phase == "pull":
        return system
    elif flat_bottom and phase == "release":
        return system

    if restraint.mask2 and not restraint.mask3:
        if not restraint.group1 and not restraint.group2:
            bond_restraint = openmm.CustomBondForce("k * (r - r_0)^2")
            bond_restraint.addPerBondParameter("k")
            bond_restraint.addPerBondParameter("r_0")

            r_0 = restraint.phase[phase]["targets"][
                window_number] * unit.angstroms
            k = (restraint.phase[phase]["force_constants"][window_number] *
                 unit.kilocalories_per_mole / unit.angstrom**2)
            bond_restraint.addBond(restraint.index1[0], restraint.index2[0],
                                   [k, r_0])
            system.addForce(bond_restraint)
        else:
            bond_restraint = openmm.CustomCentroidBondForce(
                2, "k * (distance(g1, g2) - r_0)^2")
            bond_restraint.addPerBondParameter("k")
            bond_restraint.addPerBondParameter("r_0")
            r_0 = restraint.phase[phase]["targets"][
                window_number] * unit.angstroms
            k = (restraint.phase[phase]["force_constants"][window_number] *
                 unit.kilocalories_per_mole / unit.angstrom**2)
            g1 = bond_restraint.addGroup(restraint.index1)
            g2 = bond_restraint.addGroup(restraint.index2)
            bond_restraint.addBond([g1, g2], [k, r_0])
            system.addForce(bond_restraint)

        if ForceGroup:
            bond_restraint.setForceGroup(ForceGroup)

    elif restraint.mask3 and not restraint.mask4:
        if not restraint.group1 and not restraint.group2 and not restraint.group3:
            angle_restraint = openmm.CustomAngleForce(
                "k * (theta - theta_0)^2")
            angle_restraint.addPerAngleParameter("k")
            angle_restraint.addPerAngleParameter("theta_0")

            theta_0 = restraint.phase[phase]["targets"][
                window_number] * unit.degrees
            k = (restraint.phase[phase]["force_constants"][window_number] *
                 unit.kilocalories_per_mole / unit.radian**2)
            angle_restraint.addAngle(
                restraint.index1[0],
                restraint.index2[0],
                restraint.index3[0],
                [k, theta_0],
            )
            system.addForce(angle_restraint)
        else:
            # Probably needs openmm.CustomCentroidAngleForce (?)
            raise NotImplementedError
        if ForceGroup:
            angle_restraint.setForceGroup(ForceGroup)

    elif restraint.mask4:
        if (not restraint.group1 and not restraint.group2
                and not restraint.group3 and not restraint.group4):
            dihedral_restraint = openmm.CustomTorsionForce(
                f"k * min(min(abs(theta - theta_0), abs(theta - theta_0 + 2 * {_PI_})), abs(theta - theta_0 - 2 * {_PI_}))^2"
            )
            dihedral_restraint.addPerTorsionParameter("k")
            dihedral_restraint.addPerTorsionParameter("theta_0")

            theta_0 = restraint.phase[phase]["targets"][
                window_number] * unit.degrees
            k = (restraint.phase[phase]["force_constants"][window_number] *
                 unit.kilocalories_per_mole / unit.radian**2)
            dihedral_restraint.addTorsion(
                restraint.index1[0],
                restraint.index2[0],
                restraint.index3[0],
                restraint.index4[0],
                [k, theta_0],
            )
            system.addForce(dihedral_restraint)
        else:
            # Probably needs openmm.CustomCentroidTorsionForce (?)
            raise NotImplementedError
        if ForceGroup:
            dihedral_restraint.setForceGroup(ForceGroup)
    return system
def main(args):
    #Get the structure and topology files from the command line
    #ParmEd accepts a wide range of file types (Amber, GROMACS, CHARMM, OpenMM... but not LAMMPS)
    try:
        topFile = args[0]
        strucFile = args[1]
    except IndexError:
        print("Specify topology and structure files from the command line.")
        sys.exit(2)

    print("Using topology file: %s" % topFile)
    print("Using structure file: %s" % strucFile)

    print("\nSetting up system...")

    #Load in the files for initial simulations
    top = pmd.load_file(topFile)
    struc = pmd.load_file(strucFile)

    #Transfer unit cell information to topology object
    top.box = struc.box[:]

    #Set up some global features to use in all simulations
    temperature = 298.15 * u.kelvin

    #Define the platform (i.e. hardware and drivers) to use for running the simulation
    #This can be CUDA, OpenCL, CPU, or Reference
    #CUDA is for NVIDIA GPUs
    #OpenCL is for CPUs or GPUs, but must be used for old CPUs (not SSE4.1 compatible)
    #CPU only allows single precision (CUDA and OpenCL allow single, mixed, or double)
    #Reference is a clear, stable reference for other code development and is very slow, using double precision by default
    platform = mm.Platform.getPlatformByName('CUDA')
    prop = {  #'Threads': '2', #number of threads for CPU - all definitions must be strings (I think)
        'Precision':
        'mixed',  #for CUDA or OpenCL, select the precision (single, mixed, or double)
        'DeviceIndex':
        '0',  #selects which GPUs to use - set this to zero if using CUDA_VISIBLE_DEVICES
        'DeterministicForces':
        'True'  #Makes sure forces with CUDA and PME are deterministic
    }

    #Create the OpenMM system that can be used as a reference
    systemRef = top.createSystem(
        nonbondedMethod=app.
        PME,  #Uses PME for long-range electrostatics, simple cut-off for LJ
        nonbondedCutoff=12.0 *
        u.angstroms,  #Defines cut-off for non-bonded interactions
        rigidWater=True,  #Use rigid water molecules
        constraints=app.HBonds,  #Constrains all bonds involving hydrogens
        flexibleConstraints=
        False,  #Whether to include energies for constrained DOFs
        removeCMMotion=
        False,  #Whether or not to remove COM motion (don't want to if part of system frozen)
    )

    #Set up the integrator to use as a reference
    integratorRef = mm.LangevinIntegrator(
        temperature,  #Temperature for Langevin
        1.0 / u.picoseconds,  #Friction coefficient
        2.0 * u.femtoseconds,  #Integration timestep
    )
    integratorRef.setConstraintTolerance(1.0E-08)

    #To freeze atoms, set mass to zero (does not apply to virtual sites, termed "extra particles" in OpenMM)
    #Here assume (correctly, I think) that the topology indices for atoms correspond to those in the system
    for i, atom in enumerate(top.atoms):
        if atom.type in ('SU'):  #, 'CU', 'CUO'):
            systemRef.setParticleMass(i, 0 * u.dalton)

    #Get solute atoms and solute heavy atoms separately
    #Do this for as many solutes as we have so get list for each
    soluteIndices = []
    heavyIndices = []
    for res in top.residues:
        if res.name not in ['OTM', 'CTM', 'STM', 'NTM', 'SOL']:
            thissolinds = []
            thisheavyinds = []
            for atom in res.atoms:
                thissolinds.append(atom.idx)
                if 'H' not in atom.name[0]:
                    thisheavyinds.append(atom.idx)
            soluteIndices.append(thissolinds)
            heavyIndices.append(thisheavyinds)

    #For convenience, also create flattened version of soluteIndices
    allSoluteIndices = []
    for inds in soluteIndices:
        allSoluteIndices += inds

    #Also get surface SU atoms and surface CU atoms at top and bottom of surface
    surfIndices = []
    for atom in top.atoms:
        if atom.type == 'SU':
            surfIndices.append(atom.idx)

    print("\nSolute indices: %s" % str(soluteIndices))
    print("(all together - %s)" % str(allSoluteIndices))
    print("Solute heavy atom indices: %s" % str(heavyIndices))
    print("Surface SU atom indices: %s" % str(surfIndices))

    #Will now add a custom bonded force between heavy atoms of each solute and surface SU atoms
    #Should be in units of kJ/mol*nm^2, but should check this
    #Also, note that here we are using a flat-bottom restraint to keep close to surface
    #AND to keep from penetrating into surface when it's in the decoupled state
    refZlo = 1.4 * u.nanometer  #in nm, the distance between the SU atoms and the solute centroid
    refZhi = 1.7 * u.nanometer
    restraintExpression = '0.5*k*step(refZlo - (z2 - z1))*(((z2 - z1) - refZlo)^2)'
    restraintExpression += '+ 0.5*k*step((z2 - z1) - refZhi)*(((z2 - z1) - refZhi)^2)'
    restraintForce = mm.CustomCentroidBondForce(2, restraintExpression)
    restraintForce.addPerBondParameter('k')
    restraintForce.addPerBondParameter('refZlo')
    restraintForce.addPerBondParameter('refZhi')
    restraintForce.addGroup(surfIndices, np.ones(
        len(surfIndices)))  #Don't weight with masses
    #To assign flat-bottom restraint correctly, need to know if each solute is above or below interface
    #Will need surface z-positions for this
    suZpos = np.average(struc.coordinates[surfIndices, 2])
    for i in range(len(heavyIndices)):
        restraintForce.addGroup(heavyIndices[i], np.ones(len(heavyIndices[i])))
        solZpos = np.average(struc.coordinates[heavyIndices[i], 2])
        if (solZpos - suZpos) > 0:
            restraintForce.addBond([0, i + 1], [10000.0, refZlo, refZhi])
        else:
            #A little confusing, but have to negate and switch for when z2-z1 is always going to be negative
            restraintForce.addBond([0, i + 1], [10000.0, -refZhi, -refZlo])
    systemRef.addForce(restraintForce)

    #Setting up the alchemical system so we can repeat the calculation with a decoupled particle
    #We need to add a custom non-bonded force for the solute being alchemically changed
    #Will be helpful to have handle on non-bonded force handling LJ and coulombic interactions
    NBForce = None
    for frc in systemRef.getForces():
        if (isinstance(frc, mm.NonbondedForce)):
            NBForce = frc

    #Turn off dispersion correction since have interface
    NBForce.setUseDispersionCorrection(False)

    forceLabelsRef = getForceLabels(systemRef)

    decompEnergy(systemRef, struc.positions, labels=forceLabelsRef)

    #Separate out alchemical and regular particles using set objects
    alchemicalParticles = set(allSoluteIndices)
    chemicalParticles = set(range(
        systemRef.getNumParticles())) - alchemicalParticles

    #Define the soft-core function for turning on/off LJ interactions
    #In energy expressions for CustomNonbondedForce, r is a special variable and refers to the distance between particles
    #All other variables must be defined somewhere in the function.
    #The exception are variables like sigma1 and sigma2.
    #It is understood that a parameter will be added called 'sigma' and that the '1' and '2' are to specify the combining rule.
    softCoreFunction = '4.0*lambdaLJ*epsilon*x*(x-1.0); x = (1.0/reff_sterics);'
    softCoreFunction += 'reff_sterics = (0.5*(1.0-lambdaLJ) + ((r/sigma)^6));'
    softCoreFunction += 'sigma=0.5*(sigma1+sigma2); epsilon = sqrt(epsilon1*epsilon2)'
    #Define the system force for this function and its parameters
    SoftCoreForce = mm.CustomNonbondedForce(softCoreFunction)
    SoftCoreForce.addGlobalParameter(
        'lambdaLJ', 1.0
    )  #Throughout, should follow convention that lambdaLJ=1.0 is fully-interacting state
    SoftCoreForce.addPerParticleParameter('sigma')
    SoftCoreForce.addPerParticleParameter('epsilon')

    #Will turn off electrostatics completely in the original non-bonded force
    #In the end-state, only want electrostatics inside the alchemical molecule
    #To do this, just turn ON a custom force as we turn OFF electrostatics in the original force
    ONE_4PI_EPS0 = 138.935456  #in kJ/mol nm/e^2
    soluteCoulFunction = '(1.0-(lambdaQ^2))*ONE_4PI_EPS0*charge/r;'
    soluteCoulFunction += 'ONE_4PI_EPS0 = %.16e;' % (ONE_4PI_EPS0)
    soluteCoulFunction += 'charge = charge1*charge2'
    SoluteCoulForce = mm.CustomNonbondedForce(soluteCoulFunction)
    #Note this lambdaQ will be different than for soft core (it's also named differently, which is CRITICAL)
    #This lambdaQ corresponds to the lambda that scales the charges to zero
    #To turn on this custom force at the same rate, need to multiply by (1.0-lambdaQ**2), which we do
    SoluteCoulForce.addGlobalParameter('lambdaQ', 1.0)
    SoluteCoulForce.addPerParticleParameter('charge')

    #Also create custom force for intramolecular alchemical LJ interactions
    #Could include with electrostatics, but nice to break up
    #We could also do this with a separate NonbondedForce object, but it would be a little more work, actually
    soluteLJFunction = '4.0*epsilon*x*(x-1.0); x = (sigma/r)^6;'
    soluteLJFunction += 'sigma=0.5*(sigma1+sigma2); epsilon=sqrt(epsilon1*epsilon2)'
    SoluteLJForce = mm.CustomNonbondedForce(soluteLJFunction)
    SoluteLJForce.addPerParticleParameter('sigma')
    SoluteLJForce.addPerParticleParameter('epsilon')

    #Loop over all particles and add to custom forces
    #As we go, will also collect full charges on the solute particles
    #AND we will set up the solute-solute interaction forces
    alchemicalCharges = [[0]] * len(allSoluteIndices)
    for ind in range(systemRef.getNumParticles()):
        #Get current parameters in non-bonded force
        [charge, sigma, epsilon] = NBForce.getParticleParameters(ind)
        #Make sure that sigma is not set to zero! Fine for some ways of writing LJ energy, but NOT OK for soft-core!
        if sigma / u.nanometer == 0.0:
            newsigma = 0.3 * u.nanometer  #This 0.3 is what's used by GROMACS as a default value for sc-sigma
        else:
            newsigma = sigma
        #Add the particle to the soft-core force (do for ALL particles)
        SoftCoreForce.addParticle([newsigma, epsilon])
        #Also add the particle to the solute only forces
        SoluteCoulForce.addParticle([charge])
        SoluteLJForce.addParticle([sigma, epsilon])
        #If the particle is in the alchemical molecule, need to set it's LJ interactions to zero in original force
        if ind in allSoluteIndices:
            NBForce.setParticleParameters(ind, charge, sigma, epsilon * 0.0)
            #And keep track of full charge so we can scale it right by lambda
            alchemicalCharges[allSoluteIndices.index(ind)] = charge

    #Now we need to handle exceptions carefully
    for ind in range(NBForce.getNumExceptions()):
        [p1, p2, excCharge, excSig,
         excEps] = NBForce.getExceptionParameters(ind)
        #For consistency, must add exclusions where we have exceptions for custom forces
        SoftCoreForce.addExclusion(p1, p2)
        SoluteCoulForce.addExclusion(p1, p2)
        SoluteLJForce.addExclusion(p1, p2)

    #Only compute interactions between the alchemical and other particles for the soft-core force
    SoftCoreForce.addInteractionGroup(alchemicalParticles, chemicalParticles)

    #And only compute alchemical/alchemical interactions for other custom forces
    SoluteCoulForce.addInteractionGroup(alchemicalParticles,
                                        alchemicalParticles)
    SoluteLJForce.addInteractionGroup(alchemicalParticles, alchemicalParticles)

    #Set other soft-core parameters as needed
    SoftCoreForce.setCutoffDistance(12.0 * u.angstroms)
    SoftCoreForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoftCoreForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoftCoreForce)

    #Set other parameters as needed - note that for the solute force would like to set no cutoff
    #However, OpenMM won't allow a bunch of potentials with cutoffs then one without...
    #So as long as the solute is smaller than the cut-off, won't have any problems!
    SoluteCoulForce.setCutoffDistance(12.0 * u.angstroms)
    SoluteCoulForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoluteCoulForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoluteCoulForce)

    SoluteLJForce.setCutoffDistance(12.0 * u.angstroms)
    SoluteLJForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoluteLJForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoluteLJForce)

    #First do simulation with fully coupled state
    SoftCoreForce.setGlobalParameterDefaultValue(0, 1.0)
    SoluteCoulForce.setGlobalParameterDefaultValue(0, 1.0)

    for k, ind in enumerate(allSoluteIndices):
        [charge, sig, eps] = NBForce.getParticleParameters(ind)
        NBForce.setParticleParameters(ind, alchemicalCharges[k] * 1.0, sig,
                                      eps)

    forceLabelsRef = getForceLabels(systemRef)
    decompEnergy(systemRef, struc.positions, labels=forceLabelsRef)

    os.mkdir('coupled')
    os.chdir('coupled')

    #Do NVT simulation
    stateFileNVT, stateNVT = doSimNVT(top,
                                      systemRef,
                                      integratorRef,
                                      platform,
                                      prop,
                                      temperature,
                                      pos=struc.positions)

    #And do NPT simulation using state information from NVT
    stateFileNPT, stateNPT = doSimNPT(top,
                                      systemRef,
                                      integratorRef,
                                      platform,
                                      prop,
                                      temperature,
                                      scalexy=False,
                                      state=stateFileNVT)

    #Now perform dynamics simulation to get dynamics - this is defined here, NOT in openmm_surface_affinities_lib.py
    numShellWaters, dipoleCosAng, timePoints = doSimDynamics(
        top,
        systemRef,
        integratorRef,
        platform,
        prop,
        temperature,
        scalexy=False,
        coupled=True,
        state=stateFileNPT)

    #Finally, want to now save the water residency over time and then also fit to exponential decay
    np.savetxt(
        "shell_watCounts_coupled.txt",
        np.hstack((np.array([timePoints]).T, numShellWaters)),
        header="Time (ps)  Number waters in the 1st and 2nd solvation shells")

    opt1, pcov1 = optimize.curve_fit(
        normalExponential, timePoints,
        numShellWaters[:, 0] / numShellWaters[0, 0])
    decayTime1 = opt1[1]
    opt2, pcov2 = optimize.curve_fit(
        normalExponential, timePoints,
        numShellWaters[:, 1] / numShellWaters[0, 1])
    decayTime2 = opt2[1]

    print("\nIn the fully coupled ensemble:")
    print("\tWater residency correlation time for 1st shell waters: %f" %
          decayTime1)
    print("\tWater residency correlation time for 2nd shell waters: %f" %
          decayTime2)

    #Finally, want to now save the dipoles over time and then also fit to stretched exponential
    np.savetxt(
        "rotational_timeCorr_coupled.txt",
        np.hstack((np.array([timePoints]).T, dipoleCosAng)),
        header=
        "Time (ps)  Cos(angle) between starting dipole and dipole for 1st and 2nd solvation shells"
    )

    opt1, pcov1 = optimize.curve_fit(stretchedExponential, timePoints,
                                     dipoleCosAng[:, 0])
    decayTime1 = opt1[1]
    opt2, pcov2 = optimize.curve_fit(stretchedExponential, timePoints,
                                     dipoleCosAng[:, 1])
    decayTime2 = opt2[1]

    print("\tRotational correlation time for 1st shell waters: %f" %
          decayTime1)
    print("\tRotational correlation time for 2nd shell waters: %f" %
          decayTime2)

    os.chdir('../')

    #Next simulate with decoupled state, but do same analysis
    #At least this way the volumes considered will be similar
    SoftCoreForce.setGlobalParameterDefaultValue(0, 0.0)
    SoluteCoulForce.setGlobalParameterDefaultValue(0, 0.0)

    for k, ind in enumerate(allSoluteIndices):
        [charge, sig, eps] = NBForce.getParticleParameters(ind)
        NBForce.setParticleParameters(ind, alchemicalCharges[k] * 0.0, sig,
                                      eps)

    forceLabelsRef = getForceLabels(systemRef)
    decompEnergy(systemRef, struc.positions, labels=forceLabelsRef)

    os.mkdir('decoupled')
    os.chdir('decoupled')

    #Do NVT simulation
    stateFileNVT, stateNVT = doSimNVT(top,
                                      systemRef,
                                      integratorRef,
                                      platform,
                                      prop,
                                      temperature,
                                      pos=struc.positions)

    #And do NPT simulation using state information from NVT
    stateFileNPT, stateNPT = doSimNPT(top,
                                      systemRef,
                                      integratorRef,
                                      platform,
                                      prop,
                                      temperature,
                                      scalexy=False,
                                      state=stateFileNVT)

    #Now perform dynamics simulation to get dynamics - this is defined here, NOT in openmm_surface_affinities_lib.py
    numShellWaters, dipoleCosAng, timePoints = doSimDynamics(
        top,
        systemRef,
        integratorRef,
        platform,
        prop,
        temperature,
        scalexy=False,
        coupled=False,
        state=stateFileNPT)

    #Finally, want to now save the water residency over time and then also fit to exponential decay
    np.savetxt(
        "shell_watCounts_decoupled.txt",
        np.hstack((np.array([timePoints]).T, numShellWaters)),
        header="Time (ps)  Number waters in the 1st and 2nd solvation shells")

    opt1, pcov1 = optimize.curve_fit(
        normalExponential, timePoints,
        numShellWaters[:, 0] / numShellWaters[0, 0])
    decayTime1 = opt1[1]
    opt2, pcov2 = optimize.curve_fit(
        normalExponential, timePoints,
        numShellWaters[:, 1] / numShellWaters[0, 1])
    decayTime2 = opt2[1]

    print("\nIn the perfectly decoupled ensemble:")
    print("\tWater residency correlation time for 1st shell waters: %f" %
          decayTime1)
    print("\tWater residency correlation time for 2nd shell waters: %f" %
          decayTime2)

    #Finally, want to now save the dipoles over time and then also fit to stretched exponential
    np.savetxt(
        "rotational_timeCorr_decoupled.txt",
        np.hstack((np.array([timePoints]).T, dipoleCosAng)),
        header=
        "Time (ps)  Cos(angle) between starting dipole and dipole for 1st and 2nd solvation shells"
    )

    opt1, pcov1 = optimize.curve_fit(stretchedExponential, timePoints,
                                     dipoleCosAng[:, 0])
    decayTime1 = opt1[1]
    opt2, pcov2 = optimize.curve_fit(stretchedExponential, timePoints,
                                     dipoleCosAng[:, 1])
    decayTime2 = opt2[1]

    print("\tRotational correlation time for 1st shell waters: %f" %
          decayTime1)
    print("\tRotational correlation time for 2nd shell waters: %f" %
          decayTime2)

    os.chdir('../')
示例#18
0
    def add_interactions(self, state: interfaces.IState, system: mm.System,
                         topology: app.Topology) -> mm.System:
        # The approach we use is based on
        # Habeck, Nilges, Rieping, J. Biomol. NMR., 2007, 135-144.
        #
        # Rather than solving for the exact alignment tensor
        # every step, we sample from a distribution of alignment
        # tensors.
        #
        # We encode the five components of the alignment tensor in
        # the positions of two dummy atoms relative to the center
        # of mass. The value of kappa should be scaled so that the
        # components of the alignment tensor are approximately unity.
        #
        # There is a restraint on the z-component of the seocnd dummy
        # particle to ensure that it does not diffuse off to ininity,
        # which could cause precision issues.
        if self.active:
            rdc_force = mm.CustomCentroidBondForce(
                5,
                "Erest + z_scaler*Ez;"
                "Erest = (1 - step(dev - quadcut)) * quad + step(dev - quadcut) * linear;"
                "linear = 0.5 * k_rdc * quadcut^2 + k_rdc * quadcut * (dev - quadcut);"
                "quad = 0.5 * k_rdc * dev^2;"
                "dev = max(0, abs(d_obs - dcalc) - flat);"
                "dcalc=2/3 * kappa_rdc/r^5 * (s1*(rx^2-ry^2) + s2*(3*rz^2-r^2) + s3*2*rx*ry + s4*2*rx*rz + s5*2*ry*rz);"
                "r=distance(g4, g5);"
                "rx=x4-x5;"
                "ry=y4-y5;"
                "rz=z4-z5;"
                "s1=x2-x1;"
                "s2=y2-y1;"
                "s3=z2-z1;"
                "s4=x3-x1;"
                "s5=y3-y1;"
                "Ez=(z3-z1)^2;",
            )
            rdc_force.addPerBondParameter("d_obs")
            rdc_force.addPerBondParameter("kappa_rdc")
            rdc_force.addPerBondParameter("k_rdc")
            rdc_force.addPerBondParameter("flat")
            rdc_force.addPerBondParameter("quadcut")
            rdc_force.addPerBondParameter("z_scaler")

            for experiment in self.expt_dict:
                # find the set of all atoms involved in this experiment
                com_ind = set()
                for r in self.expt_dict[experiment]:
                    com_ind.add(r.atom_index_1)
                    com_ind.add(r.atom_index_2)

                # add groups for the COM and dummy particles
                s1 = self.expt_dict[experiment][0].s1_index
                s2 = self.expt_dict[experiment][0].s2_index
                g1 = rdc_force.addGroup(list(com_ind))
                g2 = rdc_force.addGroup([s1])
                g3 = rdc_force.addGroup([s2])

                # add non-bonded exclusions between dummy particles and all other atoms
                nb_forces = [
                    f for f in system.getForces()
                    if isinstance(f, mm.NonbondedForce)
                    or isinstance(f, mm.CustomNonbondedForce)
                ]
                for nb_force in nb_forces:
                    n_parts = nb_force.getNumParticles()
                    for i in range(n_parts):
                        if isinstance(nb_force, mm.NonbondedForce):
                            if i != s1:
                                nb_force.addException(i,
                                                      s1,
                                                      0.0,
                                                      0.0,
                                                      0.0,
                                                      replace=True)
                            if i != s2:
                                nb_force.addException(i,
                                                      s2,
                                                      0.0,
                                                      0.0,
                                                      0.0,
                                                      replace=True)
                        else:
                            if i != s1:
                                nb_force.addExclusion(i, s1)
                            if i != s2:
                                nb_force.addExclusion(i, s2)

                for r in self.expt_dict[experiment]:
                    # add groups for the atoms involved in the RDC
                    g4 = rdc_force.addGroup([r.atom_index_1])
                    g5 = rdc_force.addGroup([r.atom_index_2])
                    rdc_force.addBond(
                        [g1, g2, g3, g4, g5],
                        [
                            r.d_obs,
                            r.kappa,
                            0.0,
                            r.tolerance,
                            r.quadratic_cut,
                            0,
                        ],  # z_scaler initial value shouldn't matter
                    )

            system.addForce(rdc_force)
            self.force = rdc_force
        return system
def main(args):
    #Get the structure, topology, and trajectory files from the command line
    #ParmEd accepts a wide range of file types (Amber, GROMACS, CHARMM, OpenMM... but not LAMMPS)
    try:
        topFile = args[0]
        strucFile = args[1]
        trajFile = args[2]
    except IndexError:
        print(
            "Specify topology, structure, and trajectory files from the command line."
        )
        print(Usage)
        sys.exit(2)

    #And also allow user to specify start frame, but default to zero if no input
    try:
        startFrame = int(args[3])
    except IndexError:
        startFrame = 0

    #And get information on whether or not to use a restraint
    try:
        boolstr = args[4]
        if boolstr.lower() == 'true' or boolstr.lower() == 'yes':
            restraintBool = True
        else:
            restraintBool = False
    except IndexError:
        restraintBool = False

    print("Using topology file: %s" % topFile)
    print("Using structure file: %s" % strucFile)
    print("Using trajectory file: %s" % trajFile)

    print("\nSetting up system...")

    #Load in the files for initial simulations
    top = pmd.load_file(topFile)
    struc = pmd.load_file(strucFile)

    #Transfer unit cell information to topology object
    top.box = struc.box[:]

    #Set up some global features to use in all simulations
    temperature = 298.15 * u.kelvin

    #Define the platform (i.e. hardware and drivers) to use for running the simulation
    #This can be CUDA, OpenCL, CPU, or Reference
    #CUDA is for NVIDIA GPUs
    #OpenCL is for CPUs or GPUs, but must be used for old CPUs (not SSE4.1 compatible)
    #CPU only allows single precision (CUDA and OpenCL allow single, mixed, or double)
    #Reference is a clear, stable reference for other code development and is very slow, using double precision by default
    platform = mm.Platform.getPlatformByName('CUDA')
    prop = {  #'Threads': '1', #number of threads for CPU - all definitions must be strings (I think)
        'Precision':
        'mixed',  #for CUDA or OpenCL, select the precision (single, mixed, or double)
        'DeviceIndex':
        '0',  #selects which GPUs to use - set this to zero if using CUDA_VISIBLE_DEVICES
        'DeterministicForces':
        'True'  #Makes sure forces with CUDA and PME are deterministic
    }

    #Create the OpenMM system that can be used as a reference
    systemRef = top.createSystem(
        nonbondedMethod=app.
        PME,  #Uses PME for long-range electrostatics, simple cut-off for LJ
        nonbondedCutoff=12.0 *
        u.angstroms,  #Defines cut-off for non-bonded interactions
        rigidWater=True,  #Use rigid water molecules
        constraints=app.HBonds,  #Constrains all bonds involving hydrogens
        flexibleConstraints=
        False,  #Whether to include energies for constrained DOFs
        removeCMMotion=
        True,  #Whether or not to remove COM motion (don't want to if part of system frozen)
    )

    #Set up the integrator to use as a reference
    integratorRef = mm.LangevinIntegrator(
        temperature,  #Temperature for Langevin
        1.0 / u.picoseconds,  #Friction coefficient
        2.0 * u.femtoseconds,  #Integration timestep
    )
    integratorRef.setConstraintTolerance(1.0E-08)

    #Get solute atoms and solute heavy atoms separately
    soluteIndices = []
    heavyIndices = []
    for res in top.residues:
        if res.name not in ['OTM', 'CTM', 'STM', 'NTM', 'SOL']:
            for atom in res.atoms:
                soluteIndices.append(atom.idx)
                if 'H' not in atom.name[0]:
                    heavyIndices.append(atom.idx)

    #If working with expanded ensemble simulation of solute near the interface, need to include restraint to keep close
    if restraintBool:
        #Also get surface SU atoms and surface CU atoms at top and bottom of surface
        surfIndices = []
        for atom in top.atoms:
            if atom.type == 'SU':
                surfIndices.append(atom.idx)

        print("\nSolute indices: %s" % str(soluteIndices))
        print("Solute heavy atom indices: %s" % str(heavyIndices))
        print("Surface SU atom indices: %s" % str(surfIndices))

        #Will now add a custom bonded force between heavy atoms of each solute and surface SU atoms
        #Should be in units of kJ/mol*nm^2, but should check this
        #Also, note that here we are using a flat-bottom restraint to keep close to surface
        #AND to keep from penetrating into surface when it's in the decoupled state
        refZlo = 1.4 * u.nanometer  #in nm, the distance between the SU atoms and the solute centroid
        refZhi = 1.7 * u.nanometer
        restraintExpression = '0.5*k*step(refZlo - (z2 - z1))*(((z2 - z1) - refZlo)^2)'
        restraintExpression += '+ 0.5*k*step((z2 - z1) - refZhi)*(((z2 - z1) - refZhi)^2)'
        restraintForce = mm.CustomCentroidBondForce(2, restraintExpression)
        restraintForce.addPerBondParameter('k')
        restraintForce.addPerBondParameter('refZlo')
        restraintForce.addPerBondParameter('refZhi')
        restraintForce.addGroup(surfIndices, np.ones(
            len(surfIndices)))  #Don't weight with masses
        #To assign flat-bottom restraint correctly, need to know if each solute is above or below interface
        #Will need surface z-positions for this
        suZpos = np.average(struc.coordinates[surfIndices, 2])
        restraintForce.addGroup(heavyIndices, np.ones(len(heavyIndices)))
        solZpos = np.average(struc.coordinates[heavyIndices, 2])
        if (solZpos - suZpos) > 0:
            restraintForce.addBond([0, 1], [10000.0, refZlo, refZhi])
        else:
            #A little confusing, but have to negate and switch for when z2-z1 is always going to be negative
            restraintForce.addBond([0, 1], [10000.0, -refZhi, -refZlo])
        systemRef.addForce(restraintForce)

    #And define lambda states of interest
    lambdaVec = np.array(  #electrostatic lambda - 1.0 is fully interacting, 0.0 is non-interacting
        [
            [
                1.00, 0.75, 0.50, 0.25, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
                0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00
            ],
            #LJ lambdas - 1.0 is fully interacting, 0.0 is non-interacting
            [
                1.00, 1.00, 1.00, 1.00, 1.00, 0.90, 0.80, 0.70, 0.60, 0.50,
                0.40, 0.35, 0.30, 0.25, 0.20, 0.15, 0.10, 0.05, 0.00
            ]
        ])

    #We need to add a custom non-bonded force for the solute being alchemically changed
    #Will be helpful to have handle on non-bonded force handling LJ and coulombic interactions
    NBForce = None
    for frc in systemRef.getForces():
        if (isinstance(frc, mm.NonbondedForce)):
            NBForce = frc

    #Turn off dispersion correction since have interface
    NBForce.setUseDispersionCorrection(False)

    #Separate out alchemical and regular particles using set objects
    alchemicalParticles = set(soluteIndices)
    chemicalParticles = set(range(
        systemRef.getNumParticles())) - alchemicalParticles

    #Define the soft-core function for turning on/off LJ interactions
    #In energy expressions for CustomNonbondedForce, r is a special variable and refers to the distance between particles
    #All other variables must be defined somewhere in the function.
    #The exception are variables like sigma1 and sigma2.
    #It is understood that a parameter will be added called 'sigma' and that the '1' and '2' are to specify the combining rule.
    #Have also added parameter to switch the soft-core interaction to a WCA potential
    softCoreFunctionWCA = '(step(x-0.5))*(4.0*lambdaLJ*epsilon*x*(x-1.0) + (1.0-lambdaWCA)*lambdaLJ*epsilon) '
    softCoreFunctionWCA += '+ (1.0 - step(x-0.5))*lambdaWCA*(4.0*lambdaLJ*epsilon*x*(x-1.0));'
    softCoreFunctionWCA += 'x = (1.0/reff_sterics);'
    softCoreFunctionWCA += 'reff_sterics = (0.5*(1.0-lambdaLJ) + ((r/sigma)^6));'
    softCoreFunctionWCA += 'sigma=0.5*(sigma1+sigma2); epsilon = sqrt(epsilon1*epsilon2)'
    #Define the system force for this function and its parameters
    SoftCoreForceWCA = mm.CustomNonbondedForce(softCoreFunctionWCA)
    SoftCoreForceWCA.addGlobalParameter(
        'lambdaLJ', 1.0
    )  #Throughout, should follow convention that lambdaLJ=1.0 is fully-interacting state
    SoftCoreForceWCA.addGlobalParameter(
        'lambdaWCA',
        1.0)  #When 1, attractions included; setting to 0 turns off attractions
    SoftCoreForceWCA.addPerParticleParameter('sigma')
    SoftCoreForceWCA.addPerParticleParameter('epsilon')

    #Will turn off electrostatics completely in the original non-bonded force
    #In the end-state, only want electrostatics inside the alchemical molecule
    #To do this, just turn ON a custom force as we turn OFF electrostatics in the original force
    ONE_4PI_EPS0 = 138.935456  #in kJ/mol nm/e^2
    soluteCoulFunction = '(1.0-(lambdaQ^2))*ONE_4PI_EPS0*charge/r;'
    soluteCoulFunction += 'ONE_4PI_EPS0 = %.16e;' % (ONE_4PI_EPS0)
    soluteCoulFunction += 'charge = charge1*charge2'
    SoluteCoulForce = mm.CustomNonbondedForce(soluteCoulFunction)
    #Note this lambdaQ will be different than for soft core (it's also named differently, which is CRITICAL)
    #This lambdaQ corresponds to the lambda that scales the charges to zero
    #To turn on this custom force at the same rate, need to multiply by (1.0-lambdaQ**2), which we do
    SoluteCoulForce.addGlobalParameter('lambdaQ', 1.0)
    SoluteCoulForce.addPerParticleParameter('charge')

    #Also create custom force for intramolecular alchemical LJ interactions
    #Could include with electrostatics, but nice to break up
    #We could also do this with a separate NonbondedForce object, but it would be a little more work, actually
    soluteLJFunction = '4.0*epsilon*x*(x-1.0); x = (sigma/r)^6;'
    soluteLJFunction += 'sigma=0.5*(sigma1+sigma2); epsilon=sqrt(epsilon1*epsilon2)'
    SoluteLJForce = mm.CustomNonbondedForce(soluteLJFunction)
    SoluteLJForce.addPerParticleParameter('sigma')
    SoluteLJForce.addPerParticleParameter('epsilon')

    #Loop over all particles and add to custom forces
    #As we go, will also collect full charges on the solute particles
    #AND we will set up the solute-solute interaction forces
    alchemicalCharges = [[0]] * len(soluteIndices)
    for ind in range(systemRef.getNumParticles()):
        #Get current parameters in non-bonded force
        [charge, sigma, epsilon] = NBForce.getParticleParameters(ind)
        #Make sure that sigma is not set to zero! Fine for some ways of writing LJ energy, but NOT OK for soft-core!
        if sigma / u.nanometer == 0.0:
            newsigma = 0.3 * u.nanometer  #This 0.3 is what's used by GROMACS as a default value for sc-sigma
        else:
            newsigma = sigma
        #Add the particle to the soft-core force (do for ALL particles)
        SoftCoreForceWCA.addParticle([newsigma, epsilon])
        #Also add the particle to the solute only forces
        SoluteCoulForce.addParticle([charge])
        SoluteLJForce.addParticle([sigma, epsilon])
        #If the particle is in the alchemical molecule, need to set it's LJ interactions to zero in original force
        if ind in soluteIndices:
            NBForce.setParticleParameters(ind, charge, sigma, epsilon * 0.0)
            #And keep track of full charge so we can scale it right by lambda
            alchemicalCharges[soluteIndices.index(ind)] = charge

    #Now we need to handle exceptions carefully
    for ind in range(NBForce.getNumExceptions()):
        [p1, p2, excCharge, excSig,
         excEps] = NBForce.getExceptionParameters(ind)
        #For consistency, must add exclusions where we have exceptions for custom forces
        SoftCoreForceWCA.addExclusion(p1, p2)
        SoluteCoulForce.addExclusion(p1, p2)
        SoluteLJForce.addExclusion(p1, p2)

    #Only compute interactions between the alchemical and other particles for the soft-core force
    SoftCoreForceWCA.addInteractionGroup(alchemicalParticles,
                                         chemicalParticles)

    #And only compute alchemical/alchemical interactions for other custom forces
    SoluteCoulForce.addInteractionGroup(alchemicalParticles,
                                        alchemicalParticles)
    SoluteLJForce.addInteractionGroup(alchemicalParticles, alchemicalParticles)

    #Set other soft-core parameters as needed
    SoftCoreForceWCA.setCutoffDistance(12.0 * u.angstroms)
    SoftCoreForceWCA.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoftCoreForceWCA.setUseLongRangeCorrection(False)
    systemRef.addForce(SoftCoreForceWCA)

    #Set other parameters as needed - note that for the solute force would like to set no cutoff
    #However, OpenMM won't allow a bunch of potentials with cutoffs then one without...
    #So as long as the solute is smaller than the cut-off, won't have any problems!
    SoluteCoulForce.setCutoffDistance(12.0 * u.angstroms)
    SoluteCoulForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoluteCoulForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoluteCoulForce)

    SoluteLJForce.setCutoffDistance(12.0 * u.angstroms)
    SoluteLJForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoluteLJForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoluteLJForce)

    #Need to add integrator and context in order to evaluate potential energies
    #Integrator is arbitrary because won't use it
    integrator = mm.VerletIntegrator(1.0 * u.femtoseconds)
    context = mm.Context(systemRef, integrator, platform, prop)

    ##########################################################################

    print("\nStarting analysis...")

    kBT = u.AVOGADRO_CONSTANT_NA * u.BOLTZMANN_CONSTANT_kB * temperature

    #Set up arrays to hold potential energies
    #First row will be coupled, then no electrostatics, then no electrostatics with WCA, then decoupled
    allU = np.array([[]] * 4).T

    #We've now set everything up like we're going to run a simulation
    #But now we will use pytraj to load a trajectory to get coordinates
    #With those coordinates, we will evaluate the energies we want
    #Just need to figure out if we have a surface or a bulk system
    trajFiles = glob.glob('Quad*/%s' % trajFile)
    if len(trajFiles) == 0:
        trajFiles = [trajFile]

    print("Using following trajectory files: %s" % str(trajFiles))

    for aFile in trajFiles:

        trajtop = copy.deepcopy(top)
        trajtop.rb_torsions = pmd.TrackedList(
            [])  #Necessary for SAM systems so doesn't break pytraj
        trajtop = pt.load_parmed(trajtop, traj=False)
        traj = pt.iterload(aFile, top=trajtop, frame_slice=(startFrame, -1))

        thisAllU = np.zeros((len(traj), 4))

        #Loop over the lambda states of interest, looping over whole trajectory each time
        for i, lstate in enumerate([
            [1.0, 1.0, 1.0],  #Fully coupled 
            [1.0, 1.0, 0.0],  #Charge turned off
            [1.0, 0.0,
             0.0],  #Charged turned off, attractions turned off (so WCA)
            [0.0, 1.0, 0.0]
        ]):  #Decoupled (WCA still included, though doesn't matter)

            #Set the lambda state
            context.setParameter('lambdaLJ', lstate[0])
            context.setParameter('lambdaWCA', lstate[1])
            context.setParameter('lambdaQ', lstate[2])
            for k, ind in enumerate(soluteIndices):
                [charge, sig, eps] = NBForce.getParticleParameters(ind)
                NBForce.setParticleParameters(ind,
                                              alchemicalCharges[k] * lstate[2],
                                              sig, eps)
            NBForce.updateParametersInContext(context)

            #And loop over trajectory
            for t, frame in enumerate(traj):

                thisbox = np.array(frame.box.values[:3])
                context.setPeriodicBoxVectors(
                    np.array([thisbox[0], 0.0, 0.0]) * u.angstrom,
                    np.array([0.0, thisbox[1], 0.0]) * u.angstrom,
                    np.array([0.0, 0.0, thisbox[2]]) * u.angstrom)

                thispos = np.array(frame.xyz) * u.angstrom
                context.setPositions(thispos)

                thisAllU[t, i] = context.getState(
                    getEnergy=True).getPotentialEnergy() / kBT

        #Add this trajectory information
        allU = np.vstack((allU, thisAllU))

    #And that should be it, just need to save files and print information
    #avgUq = np.average(allU[0,:] - allU[1,:])
    #stdUq = np.std(allU[0,:] - allU[1,:], ddof=1)
    #avgUlj = np.average(allU[1,:] - allU[2,:])
    #stdUlj = np.std(allU[1,:] - allU[2,:], ddof=1)

    #print("\nAverage solute-water electrostatic potential energy: %f +/- %f"%(avgUq, stdUq))
    #print("Average solute-water LJ potential energy: %f +/- %f"%(avgUlj, stdUlj))

    np.savetxt(
        'pot_energy_decomp.txt',
        allU,
        header=
        'U_coupled (kBT)    U_noQ (kBT)    U_noQ_WCA (kBT)    U_decoupled (kBT) '
    )

    #Print some meaningful information
    #Just make sure we do so as accurately as possible using alchemical information
    alchFile = glob.glob('alchemical_U*.txt')[0]
    print('Using alchemical information file: %s' % alchFile)
    printInfo(allU, mbarFile='mbar_object.pkl', alchFile=alchFile)
def main(args):
    #Get the structure and topology files from the command line
    #ParmEd accepts a wide range of file types (Amber, GROMACS, CHARMM, OpenMM... but not LAMMPS)
    try:
        topFile = args[0]
        strucFile = args[1]
    except IndexError:
        print("Specify topology and structure files from the command line.")
        sys.exit(2)

    print("Using topology file: %s" % topFile)
    print("Using structure file: %s" % strucFile)

    print("\nSetting up system...")

    #Load in the files for initial simulations
    top = pmd.load_file(topFile)
    struc = pmd.load_file(strucFile)

    #Transfer unit cell information to topology object
    top.box = struc.box[:]

    #Set up some global features to use in all simulations
    temperature = 298.15 * u.kelvin

    #Define the platform (i.e. hardware and drivers) to use for running the simulation
    #This can be CUDA, OpenCL, CPU, or Reference
    #CUDA is for NVIDIA GPUs
    #OpenCL is for CPUs or GPUs, but must be used for old CPUs (not SSE4.1 compatible)
    #CPU only allows single precision (CUDA and OpenCL allow single, mixed, or double)
    #Reference is a clear, stable reference for other code development and is very slow, using double precision by default
    platform = mm.Platform.getPlatformByName('CUDA')
    prop = {  #'Threads': '2', #number of threads for CPU - all definitions must be strings (I think)
        'Precision':
        'mixed',  #for CUDA or OpenCL, select the precision (single, mixed, or double)
        'DeviceIndex':
        '0',  #selects which GPUs to use - set this to zero if using CUDA_VISIBLE_DEVICES
        'DeterministicForces':
        'True'  #Makes sure forces with CUDA and PME are deterministic
    }

    #Create the OpenMM system that can be used as a reference
    systemRef = top.createSystem(
        nonbondedMethod=app.
        PME,  #Uses PME for long-range electrostatics, simple cut-off for LJ
        nonbondedCutoff=12.0 *
        u.angstroms,  #Defines cut-off for non-bonded interactions
        rigidWater=True,  #Use rigid water molecules
        constraints=app.HBonds,  #Constrains all bonds involving hydrogens
        flexibleConstraints=
        False,  #Whether to include energies for constrained DOFs
        removeCMMotion=
        False,  #Whether or not to remove COM motion (don't want to if part of system frozen)
    )

    #Set up the integrator to use as a reference
    integratorRef = mm.LangevinIntegrator(
        temperature,  #Temperature for Langevin
        1.0 / u.picoseconds,  #Friction coefficient
        2.0 * u.femtoseconds,  #Integration timestep
    )
    integratorRef.setConstraintTolerance(1.0E-08)

    #To freeze atoms, set mass to zero (does not apply to virtual sites, termed "extra particles" in OpenMM)
    #Here assume (correctly, I think) that the topology indices for atoms correspond to those in the system
    for i, atom in enumerate(top.atoms):
        if atom.type in ('SU'):  #, 'CU', 'CUO'):
            systemRef.setParticleMass(i, 0 * u.dalton)

    #Get solute atoms and solute heavy atoms separately
    #Do this for as many solutes as we have so get list for each
    soluteIndices = []
    heavyIndices = []
    for res in top.residues:
        if res.name not in ['OTM', 'CTM', 'STM', 'NTM', 'SOL']:
            thissolinds = []
            thisheavyinds = []
            for atom in res.atoms:
                thissolinds.append(atom.idx)
                if 'H' not in atom.name[0]:
                    thisheavyinds.append(atom.idx)
            soluteIndices.append(thissolinds)
            heavyIndices.append(thisheavyinds)

    #For convenience, also create flattened version of soluteIndices
    allSoluteIndices = []
    for inds in soluteIndices:
        allSoluteIndices += inds

    #Also get surface SU atoms and surface CU atoms at top and bottom of surface
    surfIndices = []
    for atom in top.atoms:
        if atom.type == 'SU':
            surfIndices.append(atom.idx)

    print("\nSolute indices: %s" % str(soluteIndices))
    print("(all together - %s)" % str(allSoluteIndices))
    print("Solute heavy atom indices: %s" % str(heavyIndices))
    print("Surface SU atom indices: %s" % str(surfIndices))

    #Will now add a custom bonded force between heavy atoms of each solute and surface SU atoms
    #Should be in units of kJ/mol*nm^2, but should check this
    #Also, note that here we are using a flat-bottom restraint to keep close to surface
    #AND to keep from penetrating into surface when it's in the decoupled state
    refZlo = 1.4 * u.nanometer  #in nm, the distance between the SU atoms and the solute centroid
    refZhi = 1.7 * u.nanometer
    restraintExpression = '0.5*k*step(refZlo - (z2 - z1))*(((z2 - z1) - refZlo)^2)'
    restraintExpression += '+ 0.5*k*step((z2 - z1) - refZhi)*(((z2 - z1) - refZhi)^2)'
    restraintForce = mm.CustomCentroidBondForce(2, restraintExpression)
    restraintForce.addPerBondParameter('k')
    restraintForce.addPerBondParameter('refZlo')
    restraintForce.addPerBondParameter('refZhi')
    restraintForce.addGroup(surfIndices, np.ones(
        len(surfIndices)))  #Don't weight with masses
    #To assign flat-bottom restraint correctly, need to know if each solute is above or below interface
    #Will need surface z-positions for this
    suZpos = np.average(struc.coordinates[surfIndices, 2])
    for i in range(len(heavyIndices)):
        restraintForce.addGroup(heavyIndices[i], np.ones(len(heavyIndices[i])))
        solZpos = np.average(struc.coordinates[heavyIndices[i], 2])
        if (solZpos - suZpos) > 0:
            restraintForce.addBond([0, i + 1], [10000.0, refZlo, refZhi])
        else:
            #A little confusing, but have to negate and switch for when z2-z1 is always going to be negative
            restraintForce.addBond([0, i + 1], [10000.0, -refZhi, -refZlo])
    systemRef.addForce(restraintForce)

    #We also will need to force the solute to sample the entire surface
    #We will do this with flat-bottom restraints in x and y
    #But NOT on the centroid, because this is hard with CustomExternalForce
    #Instead, just apply it to the first heavy atom of each solute
    #Won't worry about keeping each solute in the same region of the surface on both faces...
    #Actually better if define regions on both faces differently
    initRefX = 0.25 * top.box[
        0] * u.angstrom  #Used parmed to load, and that program uses angstroms
    initRefY = 0.25 * top.box[1] * u.angstrom
    refDistX = 0.25 * top.box[0] * u.angstrom
    refDistY = 0.25 * top.box[1] * u.angstrom
    print('Default X reference distance: %s' % str(initRefX))
    print('Default Y reference distance: %s' % str(initRefY))
    restraintExpressionXY = '0.5*k*step(periodicdistance(x,0,0,refX,0,0) - distX)*((periodicdistance(x,0,0,refX,0,0) - distX)^2)'
    restraintExpressionXY += '+ 0.5*k*step(periodicdistance(0,y,0,0,refY,0) - distY)*((periodicdistance(0,y,0,0,refY,0) - distY)^2)'
    restraintXYForce = mm.CustomExternalForce(restraintExpressionXY)
    restraintXYForce.addPerParticleParameter('k')
    restraintXYForce.addPerParticleParameter('distX')
    restraintXYForce.addPerParticleParameter('distY')
    restraintXYForce.addGlobalParameter('refX', initRefX)
    restraintXYForce.addGlobalParameter('refY', initRefY)
    for i in range(len(heavyIndices)):
        restraintXYForce.addParticle(
            heavyIndices[i][0],
            [1000.0, refDistX, refDistY])  #k in units kJ/mol*nm^2
    systemRef.addForce(restraintXYForce)

    #JUST for acetic acid, will also need to add lambda states in order to soften torsions around the C-OH bond
    #Will base this on the work of Paluch, et al. 2011
    #First soften the torsions in the coupled state, then turn them on in the decoupled state
    #Defining lambda states here
    #Below will define a custom torsion force so can globally modify dihedrals of interest alchemically
    lambdaVec = np.array(  #electrostatic lambda - 1.0 is fully interacting, 0.0 is non-interacting
        [
            [
                1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 0.75, 0.50, 0.25, 0.00,
                0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
                0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00
            ],
            #LJ lambdas - 1.0 is fully interacting, 0.0 is non-interacting
            [
                1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00,
                0.90, 0.80, 0.70, 0.60, 0.50, 0.40, 0.35, 0.30, 0.25, 0.20,
                0.15, 0.10, 0.05, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00
            ],
            #Torsion lambdas - 1.0 is fully on, softening from there
            [
                1.00, 0.90, 0.70, 0.30, 0.10, 0.01, 0.01, 0.01, 0.01, 0.01,
                0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01,
                0.01, 0.01, 0.01, 0.01, 0.10, 0.30, 0.70, 0.90, 1.00
            ]
        ])

    #We need to add a custom non-bonded force for the solute being alchemically changed
    #Will be helpful to have handle on non-bonded force handling LJ and coulombic interactions
    NBForce = None
    for frc in systemRef.getForces():
        if (isinstance(frc, mm.NonbondedForce)):
            NBForce = frc

    #Turn off dispersion correction since have interface
    NBForce.setUseDispersionCorrection(False)

    forceLabelsRef = getForceLabels(systemRef)

    decompEnergy(systemRef,
                 struc.positions,
                 labels=forceLabelsRef,
                 verbose=True)

    #JUST do the below for acetic acid!
    #Otherwise will modify all torsions involving hydroxyls
    ########################### Starting some torsion modifications ###########################
    TorsionForce = None
    for frc in systemRef.getForces():
        if (isinstance(frc, mm.PeriodicTorsionForce)):
            TorsionForce = frc

    #Create custom torsion for those we want to soften
    alchTorsionFunction = "lambdaTorsion*k*(1+cos(per*theta-theta0))"
    AlchTorsionForce = mm.CustomTorsionForce(alchTorsionFunction)
    AlchTorsionForce.addGlobalParameter('lambdaTorsion', 1.0)
    AlchTorsionForce.addPerTorsionParameter('per')
    AlchTorsionForce.addPerTorsionParameter('theta0')
    AlchTorsionForce.addPerTorsionParameter('k')

    #Now need to find specific dihedrals we want to modify
    for ind in range(TorsionForce.getNumTorsions()):
        p1, p2, p3, p4, periodicity, phase, kval = TorsionForce.getTorsionParameters(
            ind)
        thisAtomNames = [top.atoms[x].name for x in [p1, p2, p3, p4]]
        if ((set([p1, p2, p3, p4]).issubset(allSoluteIndices))
                and (set(['H', 'O']).issubset(thisAtomNames))):
            AlchTorsionForce.addTorsion(p1, p2, p3, p4,
                                        [periodicity, phase, kval])
            #Don't want to duplicate
            TorsionForce.setTorsionParameters(ind, p1, p2, p3, p4, periodicity,
                                              phase, kval * 0.0)

    #Add custom torsion force to our system
    systemRef.addForce(AlchTorsionForce)
    ########################### Done with torsion modification bit ###########################

    #Separate out alchemical and regular particles using set objects
    alchemicalParticles = set(allSoluteIndices)
    chemicalParticles = set(range(
        systemRef.getNumParticles())) - alchemicalParticles

    #Define the soft-core function for turning on/off LJ interactions
    #In energy expressions for CustomNonbondedForce, r is a special variable and refers to the distance between particles
    #All other variables must be defined somewhere in the function.
    #The exception are variables like sigma1 and sigma2.
    #It is understood that a parameter will be added called 'sigma' and that the '1' and '2' are to specify the combining rule.
    softCoreFunction = '4.0*lambdaLJ*epsilon*x*(x-1.0); x = (1.0/reff_sterics);'
    softCoreFunction += 'reff_sterics = (0.5*(1.0-lambdaLJ) + ((r/sigma)^6));'
    softCoreFunction += 'sigma=0.5*(sigma1+sigma2); epsilon = sqrt(epsilon1*epsilon2)'
    #Define the system force for this function and its parameters
    SoftCoreForce = mm.CustomNonbondedForce(softCoreFunction)
    SoftCoreForce.addGlobalParameter(
        'lambdaLJ', 1.0
    )  #Throughout, should follow convention that lambdaLJ=1.0 is fully-interacting state
    SoftCoreForce.addPerParticleParameter('sigma')
    SoftCoreForce.addPerParticleParameter('epsilon')

    #Will turn off electrostatics completely in the original non-bonded force
    #In the end-state, only want electrostatics inside the alchemical molecule
    #To do this, just turn ON a custom force as we turn OFF electrostatics in the original force
    ONE_4PI_EPS0 = 138.935456  #in kJ/mol nm/e^2
    soluteCoulFunction = '(1.0-(lambdaQ^2))*ONE_4PI_EPS0*charge/r;'
    soluteCoulFunction += 'ONE_4PI_EPS0 = %.16e;' % (ONE_4PI_EPS0)
    soluteCoulFunction += 'charge = charge1*charge2'
    SoluteCoulForce = mm.CustomNonbondedForce(soluteCoulFunction)
    #Note this lambdaQ will be different than for soft core (it's also named differently, which is CRITICAL)
    #This lambdaQ corresponds to the lambda that scales the charges to zero
    #To turn on this custom force at the same rate, need to multiply by (1.0-lambdaQ**2), which we do
    SoluteCoulForce.addGlobalParameter('lambdaQ', 1.0)
    SoluteCoulForce.addPerParticleParameter('charge')

    #Also create custom force for intramolecular alchemical LJ interactions
    #Could include with electrostatics, but nice to break up
    #We could also do this with a separate NonbondedForce object, but it would be a little more work, actually
    soluteLJFunction = '4.0*epsilon*x*(x-1.0); x = (sigma/r)^6;'
    soluteLJFunction += 'sigma=0.5*(sigma1+sigma2); epsilon=sqrt(epsilon1*epsilon2)'
    SoluteLJForce = mm.CustomNonbondedForce(soluteLJFunction)
    SoluteLJForce.addPerParticleParameter('sigma')
    SoluteLJForce.addPerParticleParameter('epsilon')

    #Loop over all particles and add to custom forces
    #As we go, will also collect full charges on the solute particles
    #AND we will set up the solute-solute interaction forces
    alchemicalCharges = [[0]] * len(allSoluteIndices)
    for ind in range(systemRef.getNumParticles()):
        #Get current parameters in non-bonded force
        [charge, sigma, epsilon] = NBForce.getParticleParameters(ind)
        #Make sure that sigma is not set to zero! Fine for some ways of writing LJ energy, but NOT OK for soft-core!
        if sigma / u.nanometer == 0.0:
            newsigma = 0.3 * u.nanometer  #This 0.3 is what's used by GROMACS as a default value for sc-sigma
        else:
            newsigma = sigma
        #Add the particle to the soft-core force (do for ALL particles)
        SoftCoreForce.addParticle([newsigma, epsilon])
        #Also add the particle to the solute only forces
        SoluteCoulForce.addParticle([charge])
        SoluteLJForce.addParticle([sigma, epsilon])
        #If the particle is in the alchemical molecule, need to set it's LJ interactions to zero in original force
        if ind in allSoluteIndices:
            NBForce.setParticleParameters(ind, charge, sigma, epsilon * 0.0)
            #And keep track of full charge so we can scale it right by lambda
            alchemicalCharges[allSoluteIndices.index(ind)] = charge

    #Now we need to handle exceptions carefully
    for ind in range(NBForce.getNumExceptions()):
        [p1, p2, excCharge, excSig,
         excEps] = NBForce.getExceptionParameters(ind)
        #For consistency, must add exclusions where we have exceptions for custom forces
        SoftCoreForce.addExclusion(p1, p2)
        SoluteCoulForce.addExclusion(p1, p2)
        SoluteLJForce.addExclusion(p1, p2)

    #Only compute interactions between the alchemical and other particles for the soft-core force
    SoftCoreForce.addInteractionGroup(alchemicalParticles, chemicalParticles)

    #And only compute alchemical/alchemical interactions for other custom forces
    SoluteCoulForce.addInteractionGroup(alchemicalParticles,
                                        alchemicalParticles)
    SoluteLJForce.addInteractionGroup(alchemicalParticles, alchemicalParticles)

    #Set other soft-core parameters as needed
    SoftCoreForce.setCutoffDistance(12.0 * u.angstroms)
    SoftCoreForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoftCoreForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoftCoreForce)

    #Set other parameters as needed - note that for the solute force would like to set no cutoff
    #However, OpenMM won't allow a bunch of potentials with cutoffs then one without...
    #So as long as the solute is smaller than the cut-off, won't have any problems!
    SoluteCoulForce.setCutoffDistance(12.0 * u.angstroms)
    SoluteCoulForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoluteCoulForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoluteCoulForce)

    SoluteLJForce.setCutoffDistance(12.0 * u.angstroms)
    SoluteLJForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoluteLJForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoluteLJForce)

    forceLabelsRef = getForceLabels(systemRef)

    decompEnergy(systemRef, struc.positions, labels=forceLabelsRef)

    #Run set of simulations for particle restrained in x and y to each quadrant of the surface
    #For best results, the configuration read in should have the solute right in the center of the surface
    #Will store the lambda biasing weights as we got to help speed convergence (hopefully)
    for xfrac in [0.25, 0.75]:
        for yfrac in [0.25, 0.75]:

            os.mkdir('Quad_%1.2fX_%1.2fY' % (xfrac, yfrac))
            os.chdir('Quad_%1.2fX_%1.2fY' % (xfrac, yfrac))

            restraintXYForce.setGlobalParameterDefaultValue(
                0, xfrac * top.box[0] * u.angstrom)
            restraintXYForce.setGlobalParameterDefaultValue(
                1, yfrac * top.box[1] * u.angstrom)

            decompEnergy(systemRef, struc.positions, labels=forceLabelsRef)

            #Do NVT simulation
            stateFileNVT, stateNVT = doSimNVT(top,
                                              systemRef,
                                              integratorRef,
                                              platform,
                                              prop,
                                              temperature,
                                              pos=struc.positions)

            #And do NPT simulation using state information from NVT
            stateFileNPT, stateNPT = doSimNPT(top,
                                              systemRef,
                                              integratorRef,
                                              platform,
                                              prop,
                                              temperature,
                                              state=stateFileNVT)

            #And do production run in expanded ensemble!
            stateFileProd, stateProd, weightsVec = doSimExpanded(
                top,
                systemRef,
                integratorRef,
                platform,
                prop,
                temperature,
                0,
                lambdaVec,
                allSoluteIndices,
                alchemicalCharges,
                state=stateFileNPT,
                nSteps=20000000,
                equilSteps=7500000)

            #Save final simulation configuration in .gro format (mainly for genetic algorithm)
            finalStruc = copy.deepcopy(struc)
            boxVecs = np.array(stateProd.getPeriodicBoxVectors().value_in_unit(
                u.angstrom))  # parmed works with angstroms
            finalStruc.box = np.array([
                boxVecs[0, 0], boxVecs[1, 1], boxVecs[2, 2], 90.0, 90.0, 90.0
            ])
            finalStruc.coordinates = np.array(
                stateProd.getPositions().value_in_unit(u.angstrom))
            finalStruc.save('prod.gro')

            os.chdir('../')
quintent_vol.addPerBondParameter('alpha3')
quintent_vol.addPerBondParameter('alpha4')
quintent_vol.addPerBondParameter('alpha5')
quintent_vol.addGlobalParameter('height', height)

for q in quintents:
    quintent_vol.addBond(
        [q[0], q[1], q[2], q[3], q[4]],
        [alpha[q[0]], alpha[q[1]], alpha[q[2]], alpha[q[3]], alpha[q[4]]])

vol0 = 0.5301799 * unit.nanometer**3
K = 50000 * unit.kilojoules_per_mole / unit.nanometer**6
energy = '(K/2)*((p + t + q + qui) - vol0)^2;'
cvforce = openmm.CustomCVForce(energy)
cvforce.addCollectiveVariable('p', pairs_vol)
cvforce.addCollectiveVariable('t', triplets_vol)
cvforce.addCollectiveVariable('q', quad_vol)
cvforce.addCollectiveVariable('qui', quintent_vol)
cvforce.addGlobalParameter('K', K)
cvforce.addGlobalParameter('vol0', vol0)
cvforce.setForceGroup(29)
system.addForce(cvforce)

K_c = 200 * unit.kilojoules_per_mole / unit.angstroms**2
force = openmm.CustomCentroidBondForce(2, '(K_c/2)*distance(g1, g2)^2')
force.addGlobalParameter('K_c', K_c)
force.addGroup([int(index) for index in lig1_heavy_atoms])
force.addGroup([int(index) for index in lig2_heavy_atoms])
force.addBond([0, 1], [])
system.addForce(force)