Exemplo n.º 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,
    )
Exemplo n.º 2
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)
Exemplo n.º 3
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('')
Exemplo n.º 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))
Exemplo n.º 5
0
def test_periodic():
    systems = ['uio66', 'cau13', 'mil53', 'ppycof', 'cof5', 'mof5']
    platforms = ['Reference']
    seed_kinds = ['covalent', 'dispersion', 'electrostatic']

    # systematic constant offset in dispersion energy for COFs, unclear why

    tolerance = {
        ('Reference', 'covalent'): 1e-6,  # some MM3 terms have error 1e-7
        ('Reference', 'dispersion'): 1e-2,  # some MM3 terms have error 1e-3
        ('Reference', 'electrostatic'): 1e-3,
        #('CUDA', 'covalent'): 1e-3,
        #('CUDA', 'dispersion'): 1e-3,
        #('CUDA', 'electrostatic'): 1e-3,
    }

    nstates = 5
    disp_ampl = 0.3
    box_ampl = 0.3

    for name in systems:
        for platform in platforms:
            for kind in seed_kinds:
                system, pars = get_system(name)
                configuration = Configuration(system, pars)
                tol = tolerance[(platform, kind)]

                # YAFF and OpenMM use a different switching function. If it is disabled,
                # the results between both are identical up to 6 decimals
                configuration.switch_width = 0.0  # disable switching
                configuration.rcut = 13.0  # request cutoff of 13 angstorm
                configuration.interaction_radius = 15.0
                configuration.update_properties(configuration.write())
                conversion = ExplicitConversion(pme_error_thres=5e-4)
                seed_mm = conversion.apply(configuration, seed_kind=kind)
                seed_yaff = configuration.create_seed(kind=kind)

                wrapper_mm = OpenMMForceFieldWrapper.from_seed(
                    seed_mm, platform)
                wrapper_yaff = YaffForceFieldWrapper.from_seed(seed_yaff)
                assert wrapper_yaff.periodic  # system should not be considered periodic
                assert wrapper_mm.periodic  # system should not be considered periodic

                pos = seed_yaff.system.pos.copy()
                rvecs = seed_yaff.system.cell._get_rvecs().copy()
                for i in range(nstates):
                    dpos = np.random.uniform(-disp_ampl,
                                             disp_ampl,
                                             size=pos.shape)
                    drvecs = np.random.uniform(-box_ampl,
                                               box_ampl,
                                               size=rvecs.shape)
                    drvecs[0, 1] = 0
                    drvecs[0, 2] = 0
                    drvecs[1, 2] = 0
                    tmp = rvecs + drvecs
                    reduce_box_vectors(tmp)
                    energy_mm, forces_mm = wrapper_mm.evaluate(
                        (pos + dpos) / molmod.units.angstrom,
                        rvecs=tmp / molmod.units.angstrom,
                    )
                    energy, forces = wrapper_yaff.evaluate(
                        (pos + dpos) / molmod.units.angstrom,
                        rvecs=tmp / molmod.units.angstrom,
                    )
                    assert_tol(energy, energy_mm, tol)
                    assert_tol(forces, forces_mm, 10 * tol)
Exemplo n.º 6
0
    def _internal_validate(self, configuration, conversion, platform, kind):
        """Calculates the numerical stress over a series of states"""
        assert configuration.box is not None, (
            'cannot compute numerical stress'
            ' for nonperiodic systems')

        # perform conversion, initialize arrays and wrappers
        seed_yaff = configuration.create_seed(kind)
        seed_mm = conversion.apply(configuration, seed_kind=kind)
        stress = np.zeros(
            (3, 6, self.nstates))  # stores energies and rel error
        wrapper_yaff = YaffForceFieldWrapper.from_seed(seed_yaff)
        wrapper_mm = OpenMMForceFieldWrapper.from_seed(seed_mm, platform)

        # generate states
        states = []
        positions = seed_yaff.system.pos.copy() / angstrom
        rvecs = seed_yaff.system.cell._get_rvecs().copy() / angstrom
        for i in range(self.nstates):
            delta = 2 * self.disp_ampl * np.random.uniform(
                size=positions.shape)
            state = (positions + delta, )
            delta = 2 * self.box_ampl * np.random.uniform(size=rvecs.shape)
            delta[0, 1] = 0
            delta[0, 2] = 0
            delta[1, 2] = 0
            drvecs = rvecs + delta
            reduce_box_vectors(drvecs)  # possibly no longer reduced
            state += (drvecs, )
            states.append(state)
        logger.info('')
        logger.info('')
        logger.info('\t\tPLATFORM: {} \t\t INTERACTION: {}'.format(
            platform, kind))
        logger.info('-' * 90)
        prefixes = configuration.get_prefixes(kind)
        if len(prefixes) > 0:  # ignore empty parts
            nspaces = 4
            header = ' ' * (9) + 'YAFF [kJ/angstrom**3]'
            header += nspaces * ' '
            header += '  OpenMM [kJ/angstrom**3]'
            header += nspaces * ' '
            header += '   delta [kJ/angstrom**3]'
            logger.info(header)
            for i, state in enumerate(states):
                stress_yaff = wrapper_yaff.compute_stress(
                    *state,
                    dh=self.dh,
                    use_symmetric=True,
                )
                stress_mm = wrapper_mm.compute_stress(
                    *state,
                    dh=self.dh,
                    use_symmetric=True,
                )
                # symmetrize and print six components
                stress_yaff = (stress_yaff + stress_yaff.T) / 2
                stress_mm = (stress_mm + stress_mm.T) / 2
                nspaces = 9
                start = 3
                value_yaff = np.trace(stress_yaff) / 3
                value_mm = np.trace(stress_mm) / 3
                line = 'PRESSURE:' + start * ' '
                line += '{:17.4f}'.format(value_yaff)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_mm)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_yaff - value_mm)
                logger.info(line)

                value_yaff = stress_yaff[0, 0] / 3
                value_mm = stress_mm[0, 0] / 3
                line = 'sigma_xx:' + start * ' '
                line += '{:17.4f}'.format(value_yaff)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_mm)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_yaff - value_mm)
                logger.info(line)

                value_yaff = stress_yaff[1, 1] / 3
                value_mm = stress_mm[1, 1] / 3
                line = 'sigma_yy:' + start * ' '
                line += '{:17.4f}'.format(value_yaff)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_mm)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_yaff - value_mm)
                logger.info(line)

                value_yaff = stress_yaff[2, 2] / 3
                value_mm = stress_mm[2, 2] / 3
                line = 'sigma_zz:' + start * ' '
                line += '{:17.4f}'.format(value_yaff)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_mm)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_yaff - value_mm)
                logger.info(line)

                value_yaff = stress_yaff[1, 2] / 3
                value_mm = stress_mm[1, 2] / 3
                line = 'sigma_yz:' + start * ' '
                line += '{:17.4f}'.format(value_yaff)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_mm)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_yaff - value_mm)
                logger.info(line)

                value_yaff = stress_yaff[0, 2] / 3
                value_mm = stress_mm[0, 2] / 3
                line = 'sigma_xz:' + start * ' '
                line += '{:17.4f}'.format(value_yaff)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_mm)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_yaff - value_mm)
                logger.info(line)

                value_yaff = stress_yaff[0, 1] / 3
                value_mm = stress_mm[0, 1] / 3
                line = 'sigma_xy:' + start * ' '
                line += '{:17.4f}'.format(value_yaff)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_mm)
                line += nspaces * ' '
                line += '{:17.4f}'.format(value_yaff - value_mm)
                logger.info(line)
                logger.info('')

        else:
            logger.info('\tno {} interactions present'.format(kind))
Exemplo n.º 7
0
    def _internal_validate(self, configuration, conversion, platform, kind):
        """Performs single point validations"""
        # perform conversion, initialize arrays and wrappers
        seed_yaff = configuration.create_seed(kind)
        seed_mm = conversion.apply(configuration, seed_kind=kind)
        energy = np.zeros((4, self.nstates))  # stores energies and rel error
        forces = np.zeros((3, self.nstates, seed_yaff.system.natom, 3))
        wrapper_yaff = YaffForceFieldWrapper.from_seed(seed_yaff)
        wrapper_mm = OpenMMForceFieldWrapper.from_seed(seed_mm, platform)

        # generate states
        states = []
        positions = seed_yaff.system.pos.copy() / angstrom
        if configuration.box is not None:
            rvecs = seed_yaff.system.cell._get_rvecs().copy() / angstrom
        for i in range(self.nstates):
            delta = 2 * self.disp_ampl * np.random.uniform(
                size=positions.shape)
            state = (positions + delta, )
            if configuration.box is not None:
                delta = 2 * self.box_ampl * np.random.uniform(size=rvecs.shape)
                delta[0, 1] = 0
                delta[0, 2] = 0
                delta[1, 2] = 0
                drvecs = rvecs + delta
                reduce_box_vectors(drvecs)  # possibly no longer reduced
                state += (drvecs, )
            states.append(state)
        logger.info('')
        logger.info('\t\tPLATFORM: {} \t\t INTERACTION: {}'.format(
            platform, kind))
        logger.info('-' * 91)
        prefixes = configuration.get_prefixes(kind)
        if len(prefixes) > 0:  # ignore empty parts
            nspaces = 10
            last = 5
            header = '     YAFF [kJ/mol]'
            header += nspaces * ' '
            header += '  OpenMM [kJ/mol]'
            header += nspaces * ' '
            header += '   delta [kJ/mol]'
            header += last * ' '
            header += 'relative error'
            logger.info(header)

            for i, state in enumerate(states):
                energy[0, i], forces[0, i] = wrapper_yaff.evaluate(
                    *state,
                    do_forces=True,
                )
                energy[1, i], forces[1, i] = wrapper_mm.evaluate(
                    *state,
                    do_forces=True,
                )
                energy[2, i] = energy[1, i] - energy[0, i]
                energy[3, i] = np.abs(energy[2, i]) / np.abs(energy[0, i])
                line = ' {:17.4f}'.format(energy[0, i])
                line += nspaces * ' '
                line += '{:17.4f}'.format(energy[1, i])
                line += nspaces * ' '
                line += '{:17.4f}'.format(energy[2, i])
                line += last * ' '
                line += '{:14.4e}'.format(energy[3, i])
                logger.info(line)

            df = np.abs(forces[1, :] - forces[0, :])
            error = np.linalg.norm(df, axis=2)
            norm = np.mean(np.linalg.norm(forces[0, :], axis=2))
            logger.info('')
            nspaces = 4
            line = '\tFORCES RELATIVE ERROR: \t'
            line += 'mean={:.1e}'.format(np.mean(error) / norm)
            line += nspaces * ' '
            line += 'median={:.1e}'.format(np.median(error) / norm)
            line += nspaces * ' '
            line += 'min={:.1e}'.format(np.min(error) / norm)
            line += nspaces * ' '
            line += 'max={:.1e}'.format(np.max(error) / norm)
            logger.info(line)
            logger.info('-' * 91)
            logger.info('')
            logger.info('')
        else:
            logger.info('\tno {} interactions present'.format(kind))
Exemplo n.º 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
Exemplo n.º 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)