Beispiel #1
0
def test_create_openmm_topology():
    system, _ = get_system('cau13')
    with pytest.raises(AssertionError):
        create_openmm_topology(system)

    rvecs = system.cell._get_rvecs().copy()
    transform_lower_triangular(system.pos, rvecs, reorder=True)
    reduce_box_vectors(rvecs)

    system.cell.update_rvecs(rvecs)
    topology = create_openmm_topology(system)
    # verify box vectors are correct
    a, b, c = topology.getPeriodicBoxVectors()
    assert np.allclose(
        a.value_in_unit(unit.angstrom),
        rvecs[0, :] / molmod.units.angstrom,
    )
    assert np.allclose(
        b.value_in_unit(unit.angstrom),
        rvecs[1, :] / molmod.units.angstrom,
    )
    assert np.allclose(
        c.value_in_unit(unit.angstrom),
        rvecs[2, :] / molmod.units.angstrom,
    )
Beispiel #2
0
 def log_system(self):
     """Logs information about this system"""
     log_header('system information', logger)
     logger.info('')
     logger.info('')
     natom = self.system.natom
     if self.box is not None:
         system_type = 'periodic'
     else:
         system_type = 'non-periodic'
     logger.info('number of atoms:     {}'.format(natom))
     logger.info('system type:         ' + system_type)
     logger.info('')
     if self.box is not None:
         lengths, angles = compute_lengths_angles(self.box, degree=True)
         logger.info('initial box vectors (in angstrom):')
         logger.info('\ta: {}'.format(self.box[0, :]))
         logger.info('\tb: {}'.format(self.box[1, :]))
         logger.info('\tc: {}'.format(self.box[2, :]))
         logger.info('')
         logger.info('initial box lengths (in angstrom):')
         logger.info('\ta: {:.4f}'.format(lengths[0]))
         logger.info('\tb: {:.4f}'.format(lengths[1]))
         logger.info('\tc: {:.4f}'.format(lengths[2]))
         logger.info('')
         logger.info('initial box angles (in degrees):')
         logger.info('\talpha: {:.4f}'.format(angles[0]))
         logger.info('\tbeta : {:.4f}'.format(angles[1]))
         logger.info('\tgamma: {:.4f}'.format(angles[2]))
         logger.info('')
         logger.info('')
         rvecs = np.array(self.box)  # create copy
         transform_lower_triangular(np.zeros((1, 3)), rvecs, reorder=True)
         reduce_box_vectors(rvecs)
         lengths, angles = compute_lengths_angles(rvecs, degree=True)
         logger.info('REDUCED box vectors (in angstrom):')
         logger.info('\ta: {}'.format(rvecs[0, :]))
         logger.info('\tb: {}'.format(rvecs[1, :]))
         logger.info('\tc: {}'.format(rvecs[2, :]))
         logger.info('')
         logger.info('REDUCED box lengths (in angstrom):')
         logger.info('\ta: {:.4f}'.format(lengths[0]))
         logger.info('\tb: {:.4f}'.format(lengths[1]))
         logger.info('\tc: {:.4f}'.format(lengths[2]))
         logger.info('')
         logger.info('REDUCED box angles (in degrees):')
         logger.info('\talpha: {:.4f}'.format(angles[0]))
         logger.info('\tbeta : {:.4f}'.format(angles[1]))
         logger.info('\tgamma: {:.4f}'.format(angles[2]))
         logger.info('')
     logger.info('found {} prefixes:'.format(len(self.prefixes)))
     for prefix in self.prefixes:
         logger.info('\t' + prefix)
     logger.info('')
     logger.info('')
Beispiel #3
0
def test_wrap_coordinates():
    for name in ['cau13', 'uio66', 'ppycof', 'mof5', 'mil53', 'cof5']:
        ff = get_system(name, return_forcefield=True)
        positions = ff.system.pos.copy()
        rvecs = ff.system.cell._get_rvecs().copy()
        rvecs_ = ff.system.cell._get_rvecs().copy()
        ff.update_pos(positions)
        ff.update_rvecs(rvecs)
        e = ff.compute()

        # make random periodic displacements
        for i in range(100):
            coefficients = np.random.randint(0, high=3, size=(3, 1))
            atom = np.random.randint(0, high=ff.system.natom)
            positions[atom, :] += np.sum(coefficients * rvecs, axis=0)
        ff.update_pos(positions)
        ff.update_rvecs(rvecs)
        e0 = ff.compute()
        assert np.allclose(e, e0)

        wrap_coordinates(positions, rvecs, rectangular=False)
        frac = np.dot(positions,
                      np.linalg.inv(rvecs))  # fractional coordinates
        assert np.all(frac >= 0)
        assert np.all(frac <= 1)
        assert np.allclose(rvecs, rvecs_)  # rvecs should not change
        ff.update_pos(positions)
        ff.update_rvecs(rvecs)
        e1 = ff.compute()
        assert np.allclose(e0, e1)

        with pytest.raises(AssertionError):
            wrap_coordinates(positions, rvecs, rectangular=True)

        # transform rvecs
        transform_lower_triangular(positions, rvecs, reorder=False)
        reduce_box_vectors(rvecs)
        wrap_coordinates(positions, rvecs, rectangular=True)
        for i in range(positions.shape[0]):
            assert np.all(np.abs(positions[i, :]) < np.diag(rvecs))
        ff.update_pos(positions)
        ff.update_rvecs(rvecs)
        e2 = ff.compute()
        assert np.allclose(e0, e2)

        # reorder rvecs
        transform_lower_triangular(positions, rvecs, reorder=True)
        reduce_box_vectors(rvecs)
        wrap_coordinates(positions, rvecs, rectangular=True)
        for i in range(positions.shape[0]):
            assert np.all(np.abs(positions[i, :]) < np.diag(rvecs))
        ff.update_pos(positions)
        ff.update_rvecs(rvecs)
        e3 = ff.compute()
        assert np.allclose(e0, e3)
Beispiel #4
0
def test_lattice_reduction():
    system, pars = get_system('cau13')
    pos = system.pos.copy()
    rvecs = system.cell._get_rvecs().copy()

    # use reduction algorithm from Bekker, and transform to diagonal
    reduced = do_gram_schmidt_reduction(rvecs)
    reduced_LT = np.linalg.cholesky(reduced @ reduced.T)
    assert np.allclose(reduced_LT, np.diag(np.diag(reduced_LT)))  # diagonal

    # transform to lower triangular
    transform_lower_triangular(pos, rvecs, reorder=True)
    reduce_box_vectors(rvecs)

    # assert equality of diagonal elements from both methods
    np.testing.assert_almost_equal(np.diag(rvecs), np.diag(reduced_LT))
Beispiel #5
0
def test_transform_symmetric():
    system, pars = get_system('mil53')
    pos = system.pos.copy()
    rvecs = system.cell._get_rvecs().copy()

    # transform to symmetric form
    transform_symmetric(pos, rvecs)
    assert np.allclose(rvecs, rvecs.T)

    # transform to triangular, and back to symmetric
    rvecs_ = rvecs.copy()
    pos_ = pos.copy()
    transform_lower_triangular(pos_, rvecs_, reorder=False)
    transform_symmetric(pos_, rvecs_)

    # assert equality
    np.testing.assert_almost_equal(rvecs_, rvecs)
    np.testing.assert_almost_equal(pos_, pos)
Beispiel #6
0
def test_transform_lower_triangular():
    for i in range(100):
        trial = np.random.uniform(-20, 20, size=(3, 3))
        trial *= np.sign(np.linalg.det(trial))
        assert np.linalg.det(trial) > 0
        pos = np.random.uniform(-100, 100, size=(10, 3))
        transform_lower_triangular(pos, trial)  # in-place
        # comparison with cholesky made inside transform_lower_triangular

    for name in ['cau13', 'uio66']:  # FAILS ON COBDP; ewald_reci changes
        ff = get_system(name, return_forcefield=True)  # nonrectangular system
        gpos0 = np.zeros((ff.system.natom, 3))
        energy0 = ff.compute(gpos0, None)
        rvecs = ff.system.cell._get_rvecs().copy()
        transform_lower_triangular(ff.system.pos, rvecs, reorder=True)
        assert is_lower_triangular(rvecs)
        ff.update_pos(ff.system.pos)
        ff.update_rvecs(rvecs)
        gpos1 = np.zeros((ff.system.natom, 3))
        energy1 = ff.compute(gpos1, None)
        np.testing.assert_almost_equal(  # energy should remain the same
            energy0,
            energy1,
        )
Beispiel #7
0
def test_estimate_virial_stress():
    def energy_func(positions, rvecs):
        ff.update_pos(positions)
        ff.update_rvecs(rvecs)
        return ff.compute()

    # verify numerical pressure computation for number of benchmark systems
    # include anisotropic systems and LJ
    dh = 1e-5
    for name in ['cau13', 'uio66', 'ppycof', 'lennardjones']:
        ff = get_system(name, return_forcefield=True)
        positions = ff.system.pos.copy()
        rvecs = ff.system.cell._get_rvecs().copy()
        vtens = np.zeros((3, 3))

        ff.compute(None, vtens)
        unit = molmod.units.pascal * 1e6
        pressure = np.trace(vtens) / np.linalg.det(rvecs) / unit
        dUdh = estimate_cell_derivative(positions,
                                        rvecs,
                                        energy_func,
                                        dh=dh,
                                        use_triangular_perturbation=False)
        vtens_numerical = rvecs.T @ dUdh
        pressure_ = np.trace(vtens_numerical) / np.linalg.det(rvecs) / unit
        assert abs(pressure -
                   pressure_) < 1e-3  # require at least kPa accuracy
        assert np.allclose(vtens_numerical, vtens, atol=1e-5)

        transform_lower_triangular(positions, rvecs, reorder=True)
        ff.update_pos(positions)
        ff.update_rvecs(rvecs)
        vtens = np.zeros((3, 3))
        ff.compute(None, vtens)
        pressure_LT = np.trace(vtens) / np.linalg.det(rvecs) / unit
        dUdh = estimate_cell_derivative(positions,
                                        rvecs,
                                        energy_func,
                                        dh=dh,
                                        use_triangular_perturbation=True)
        vtens_numerical = rvecs.T @ dUdh
        pressure_LT_ = np.trace(vtens_numerical) / np.linalg.det(rvecs) / unit
        assert abs(pressure_LT - pressure) < 1e-8  # should be identical
        assert abs(pressure_LT_ - pressure_) < 1e-3  # require kPa accuracy
        #assert np.allclose(vtens_numerical, vtens, atol=1e-5)
        # VTENS != VTENS_NUMERICAL HERE!

        transform_symmetric(positions, rvecs)
        ff.update_pos(positions)
        ff.update_rvecs(rvecs)
        vtens = np.zeros((3, 3))
        ff.compute(None, vtens)
        pressure_S = np.trace(vtens) / np.linalg.det(rvecs) / unit
        dUdh = estimate_cell_derivative(positions, rvecs, energy_func, dh=dh)
        vtens_numerical = rvecs.T @ dUdh
        assert np.allclose(vtens_numerical, vtens_numerical.T, atol=1e5)
        pressure_S_ = np.trace(vtens_numerical) / np.linalg.det(rvecs) / unit
        assert abs(pressure_S - pressure) < 1e-8  # should be identical
        assert abs(pressure_S_ - pressure_) < 1e-3  # require kPa accuracy
        assert np.allclose(vtens_numerical, vtens, atol=1e-5)

        # check evaluate_using_reduced=True gives same results
        dUdh_r = estimate_cell_derivative(positions,
                                          rvecs,
                                          energy_func,
                                          dh=dh,
                                          evaluate_using_reduced=True)
        vtens_numerical_r = rvecs.T @ dUdh_r
        assert np.allclose(vtens_numerical_r, vtens_numerical_r.T, atol=1e-5)
        assert np.allclose(vtens_numerical_r, vtens_numerical, atol=1e-5)
Beispiel #8
0
    def create_topology(self):
        """Creates the topology for the current configuration"""
        if self.topology is not None:
            positions = self.system.pos / molmod.units.angstrom
            if self.box is not None:
                assert tuple(self.determine_supercell()) == (1, 1, 1)
                box = self.box.copy()
                transform_lower_triangular(positions, box, reorder=True)
                reduce_box_vectors(box)
                #wrap_coordinates(positions, box)
                # copy topology
                topology = deepcopy(self.topology)
                topology.setPeriodicBoxVectors(box * unit.angstrom)
            return self.topology, positions
        topology = mm.app.Topology()
        chain = topology.addChain()

        natoms = self.system.natom
        count = 0
        if self.box is not None:
            supercell = self.determine_supercell()

            # compute box vectors and allocate positions array
            box = np.array(supercell)[:, np.newaxis] * self.box
            positions = np.zeros((np.prod(supercell) * natoms, 3))

            # construct map of atom indices to residues
            atom_index_mapping = {}
            for template, residues in self.residues.items():
                for i, residue in enumerate(residues):
                    for j, atom in enumerate(residue):
                        atom_index_mapping[atom] = (template, i, j)
            included_atoms = list(atom_index_mapping.keys())
            assert tuple(sorted(included_atoms)) == tuple(range(natoms))

            def name_residue(image, template, residue_index):
                """Defines the name of a specific residue"""
                return str(image) + '_' + str(template) + '_' + str(i)

            atoms_list = []  # necessary for adding bonds to topology
            for image, index in enumerate(np.ndindex(tuple(supercell))):

                # initialize residues and track them in a dict
                current_residues = {}
                for template, residues in self.residues.items():
                    for i, residue in enumerate(residues):
                        name = name_residue(image, template, i)
                        residue = topology.addResidue(
                            name=name,
                            chain=chain,
                            id=name,
                        )
                        current_residues[(template, i)] = residue

                # add atoms to corresponding residue in topology (in their
                # original order). Atoms are named with their element symbol
                # as well as an index that starts counting at one
                count = np.ones(118, dtype=np.int32)  # counts elements
                for j in range(natoms):
                    key = atom_index_mapping[j]
                    template = key[0]
                    residue_index = key[1]
                    atom_index = key[2]
                    number = self.system.numbers[j]
                    e = mm.app.Element.getByAtomicNumber(number)
                    atom_name = e.symbol + str(count[number])
                    atom = topology.addAtom(
                        name=atom_name,
                        element=e,
                        residue=current_residues[(template, residue_index)])
                    atoms_list.append(atom)
                    count[number] += 1

                # generate positions for this image
                image_pos = self.system.pos / molmod.units.angstrom
                translate = np.dot(np.array(index), self.box).reshape(1, 3)
                image_pos += translate
                start = (image) * natoms
                stop = (image + 1) * natoms
                positions[start:stop, :] = image_pos.copy()

            # apply cell reduction and wrap coordinates (similar to create_seed)
            transform_lower_triangular(positions, box, reorder=True)
            reduce_box_vectors(box)
            #wrap_coordinates(positions, box)
            topology.setPeriodicBoxVectors(box * unit.angstrom)

            # add bonds from supercell system object
            system = self.system.supercell(*supercell)
            for bond in system.bonds:
                topology.addBond(
                    atoms_list[bond[0]],
                    atoms_list[bond[1]],
                )

        else:  # similar workflow, but without the supercell generation
            positions = self.system.pos / molmod.units.angstrom

            # construct map of atom indices to residues
            atom_index_mapping = {}
            for template, residues in self.residues.items():
                for i, residue in enumerate(residues):
                    for j, atom in enumerate(residue):
                        atom_index_mapping[atom] = (template, i, j)
            included_atoms = list(atom_index_mapping.keys())
            assert tuple(sorted(included_atoms)) == tuple(range(natoms))

            def name_residue(template, residue_index):
                """Defines the name of a specific residue"""
                return str(template) + '_' + str(residue_index)

            atoms_list = []  # necessary for adding bonds to topology

            # initialize residues and track them in a dict
            current_residues = {}
            for template, residues in self.residues.items():
                for i, residue in enumerate(residues):
                    name = name_residue(template, i)
                    residue = topology.addResidue(
                        name='r' + str(i),
                        chain=chain,
                        #id='r' + str(i),
                    )
                    current_residues[(template, i)] = residue

            # add atoms to corresponding residue in topology (in their
            # original order)
            for j in range(natoms):
                key = atom_index_mapping[j]
                template = key[0]
                residue_index = key[1]
                atom_index = key[2]
                e = mm.app.Element.getByAtomicNumber(self.system.numbers[j])
                atom_name = 'a' + str(j)
                atom = topology.addAtom(
                    name=atom_name,
                    element=e,
                    residue=current_residues[(template, residue_index)])
                atoms_list.append(atom)

            # add bonds from system object
            for bond in self.system.bonds:
                topology.addBond(
                    atoms_list[bond[0]],
                    atoms_list[bond[1]],
                )
        return topology, positions
Beispiel #9
0
    def create_seed(self, kind='all'):
        """Creates a seed for constructing a yaff.ForceField object

        The returned seed contains all objects that are required in order to
        generate the force field object unambiguously. Specifically, this
        involves a yaff.System object, a yaff.FFArgs object with the correct
        parameter settings, and a yaff.Parameters object that contains the
        actual force field parameters.
        Because tests are typically performed on isolated parts of a force
        field, it is possible to generate different seeds corresponding
        to different parts -- this is done using the kind parameter. Allowed
        values are:

                - all:
                    generates the entire force field

                - covalent
                    generates only the covalent part of the force field.

                - nonbonded
                    generates only the nonbonded part of the force field,
                    including both dispersion and electrostatics.

                - dispersion
                    generates only the dispersion part of the force field,
                    which is basically the nonbonded part minus the
                    electrostatics.

                - electrostatic
                    generates only the electrostatic part

        Parameters
        ----------

        kind : str, optional
            specifies the kind of seed to be created. Allowed values are
            'all', 'covalent', 'nonbonded', 'dispersion', 'electrostatic'

        """
        assert kind in [
            'all', 'covalent', 'nonbonded', 'dispersion', 'electrostatic'
        ]
        parameters = self.parameters.copy()
        if kind == 'all':
            pass  # do nothing, all prefixes should be retained
        elif kind == 'covalent':
            # pop all dispersion and electrostatic prefixes:
            for key in (DISPERSION_PREFIXES + ELECTROSTATIC_PREFIXES):
                parameters.sections.pop(key,
                                        None)  # returns None if not present
        elif kind == 'nonbonded':
            # retain only dispersion and electrostatic
            sections = {}
            for key in (DISPERSION_PREFIXES + ELECTROSTATIC_PREFIXES):
                section = parameters.sections.get(key, None)
                if section is not None:  # only add if present
                    sections[key] = section
            parameters = yaff.Parameters(sections)
        elif kind == 'dispersion':
            # retain only dispersion
            sections = {}
            for key in DISPERSION_PREFIXES:
                section = parameters.sections.get(key, None)
                if section is not None:  # only add if present
                    sections[key] = section
            parameters = yaff.Parameters(sections)
        elif kind == 'electrostatic':
            # retain only electrostatic
            sections = {}
            for key in ELECTROSTATIC_PREFIXES:
                section = parameters.sections.get(key, None)
                if section is not None:  # only add if present
                    sections[key] = section
            parameters = yaff.Parameters(sections)
        else:
            raise NotImplementedError(kind + ' not known.')

        # construct FFArgs instance and set properties
        ff_args = yaff.FFArgs()
        if self.box is not None:
            supercell = self.determine_supercell()
            system = self.system.supercell(*supercell)
            # apply reduction
            rvecs = system.cell._get_rvecs().copy()
            transform_lower_triangular(system.pos, rvecs, reorder=True)
            reduce_box_vectors(rvecs)
            #wrap_coordinates(system.pos, rvecs)
            system.cell.update_rvecs(rvecs)
        else:
            system = self.system

        if self.rcut is not None:
            ff_args.rcut = self.rcut * molmod.units.angstrom
        else:
            ff_args.rcut = 1e10  # humongous value; for nonperiodic systems

        if self.switch_width is not None and (self.switch_width != 0.0):
            ff_args.tr = yaff.Switch3(self.switch_width *
                                      molmod.units.angstrom)
        else:
            ff_args.tr = None

        if self.tailcorrections is not None:
            ff_args.tailcorrections = self.tailcorrections

        if self.ewald_alphascale is not None:
            ff_args.alpha_scale = self.ewald_alphascale

        if self.ewald_gcutscale is not None:
            ff_args.gcut_scale = self.ewald_gcutscale
        return YaffSeed(system, parameters, ff_args)