Beispiel #1
0
 def _update_rvecs(self, rvecs):
     self.cell = Cell(rvecs)
     if self.cell.nvec != 3:
         raise ValueError(
             'RDF can only be computed for 3D periodic systems.')
     if (2 * self.rcut > self.cell.rspacings * (1 + 2 * self.nimage)).any():
         raise ValueError(
             'The 2*rcut argument should not exceed any of the cell spacings.'
         )
Beispiel #2
0
 def _generate_ffs(self, nguests):
     for iguest in range(len(self._ffs),nguests):
         if len(self._ffs)==0:
             # The very first force field, no guests
             system = System.create_empty()
             system.cell = Cell(self.guest.cell.rvecs)
         elif len(self._ffs)==1:
             # The first real force field, a single guest
             system = self.guest
         else:
             # Take the system of the lastly generated force field (N-1) guests
             # and add an additional guest
             system = self._ffs[-1].system.merge(self.guest)
         self._ffs.append(self.ff_generator(system, self.guest))
Beispiel #3
0
    def __init__(self,
                 system,
                 pppm_accuracy=1e-5,
                 fn_log='lammps.log',
                 scalings=np.zeros(6),
                 fn_system='lammps.data',
                 fn_table='lammps.table',
                 triclinic=True,
                 comm=None):
        '''
           **Arguments:**

           system
                An instance of the ``System`` class.

           **Optional Arguments:**

           scalings
                Numpy array [6x1] containing the scaling factors for 1-2, 1-3,
                1-4 Lennard-Jones and 1-2, 1-3, 1-4 electrostatic interactions.
                Default: [0.0,0.0,0.0,0.0,0.0,0.0]

           pppm_accuracy
                Desired relative error in electrostatic forces
                Default: 1e-5

           fn_log
                Filename where LAMMPS output is stored.
                Default: 'lammps.log'

           fn_system
                Filename of file containing system information, can be written
                using the ```write_lammps_data``` method.
                Default: lammps.data

           fn_table
                Filename of file containing tabulated non-bonded potential
                without charges, can be written using the
                ```write_lammps_table``` method.
                Default: lammps.table

           triclinic
                Boolean, specify whether a triclinic cell will be used during
                the simulation. If the cell is orthogonal, set it to False
                as LAMMPS should run slightly faster.
                Default: True

           comm
                MPI communicator, required if LAMMPS should run in parallel
        '''
        if system.cell.nvec != 3:
            raise ValueError(
                'The system must be 3d periodic for Lammps calculations.')
        if not os.path.isfile(fn_system):
            raise ValueError('Could not read file %s' % fn_system)
        if not os.path.isfile(fn_table):
            raise ValueError('Could not read file %s' % fn_table)
        ForcePart.__init__(self, 'lammps', system)
        self.system = system
        self.comm = comm
        self.triclinic = triclinic
        self.setup_lammps(fn_system, fn_table, pppm_accuracy, fn_log, scalings)
        # LAMMPS needs cell vectors (ax,0,0), (bx,by,0) and (cx,cy,cz)
        # This means we need to perform a rotation to switch between Yaff and
        # LAMMPS coordinates. All information about this rotation is stored
        # in the variables defined below
        self.rvecs = np.eye(3)
        self.cell = Cell(self.rvecs)
        self.rot = np.zeros((3, 3))
Beispiel #4
0
    def align_cell(self, lcs=None, swap=True):
        """Align the unit cell with respect to the Cartesian Axes frame

           **Optional Arguments:**

           lcs
                The linear combinations of the unit cell that must get aligned.
                This is a 2x3 array, where each row represents a linear
                combination of cell vectors. The first row is for alignment with
                the x-axis, second for the z-axis. The default value is::

                    np.array([
                        [1, 0, 0],
                        [0, 0, 1],
                    ])

           swap
                By default, the first alignment is done with the z-axis, then
                with the x-axis. The order is reversed when swap is set to
                False.

           The alignment of the first linear combination is always perfect. The
           alignment of the second linear combination is restricted to a plane.
           The cell is always made right-handed. The coordinates are also
           rotated with respect to the origin, but never inverted.

           The attributes of the system are modified in-place. Note that this
           method only works on 3D periodic systems.
        """
        from molmod import Rotation, deg
        # define the target
        target = np.array([
            [1, 0, 0],
            [0, 0, 1],
        ])

        # default value for linear combination
        if lcs is None:
            lcs = target.copy()

        # The starting values
        pos = self.pos
        rvecs = self.cell.rvecs.copy()
        if rvecs.shape != (3, 3):
            raise TypeError(
                'The align_cell method only supports 3D periodic systems.')

        # Optionally swap a cell vector if the cell is not right-handed.
        if np.linalg.det(rvecs) < 0:
            # Find a reasonable vector to swap...
            index = rvecs.sum(axis=1).argmin()
            rvecs[index] *= -1

        # Define the source
        source = np.dot(lcs, rvecs)

        # Do the swapping
        if swap:
            target = target[::-1]
            source = source[::-1]

        # auxiliary function
        def get_angle_axis(t, s):
            cos = np.dot(s, t) / np.linalg.norm(s) / np.linalg.norm(t)
            angle = np.arccos(np.clip(cos, -1, 1))
            axis = np.cross(s, t)
            return angle, axis

        # first alignment
        angle, axis = get_angle_axis(target[0], source[0])
        if np.linalg.norm(axis) > 0:
            r1 = Rotation.from_properties(angle, axis, False)
            pos = r1 * pos
            rvecs = r1 * rvecs
            source = r1 * source

        # second alignment
        # Make sure the source is orthogonal to target[0]
        s1p = source[1] - target[0] * np.dot(target[0], source[1])
        angle, axis = get_angle_axis(target[1], s1p)
        r2 = Rotation.from_properties(angle, axis, False)
        pos = r2 * pos
        rvecs = r2 * rvecs

        # assign
        self.pos = pos
        self.cell = Cell(rvecs)
Beispiel #5
0
    def __init__(self,
                 numbers,
                 pos,
                 scopes=None,
                 scope_ids=None,
                 ffatypes=None,
                 ffatype_ids=None,
                 bonds=None,
                 rvecs=None,
                 charges=None,
                 radii=None,
                 valence_charges=None,
                 dipoles=None,
                 radii2=None,
                 masses=None):
        r'''Initialize a System object.

           **Arguments:**

           numbers
                A numpy array with atomic numbers

           pos
                A numpy array (N,3) with atomic coordinates in Bohr.

           **Optional arguments:**

           scopes
                A list with scope names

           scope_ids
                A list of scope indexes that links each atom with an element of
                the scopes list. If this argument is not present, while scopes
                is given, it is assumed that scopes contains a scope name for
                every atom, i.e. that it is a list with length natom. In that
                case, it will be converted automatically to a scopes list
                with only unique name together with a corresponding scope_ids
                array.

           ffatypes
                A list of labels of the force field atom types.

           ffatype_ids
                A list of atom type indexes that links each atom with an element
                of the list ffatypes. If this argument is not present, while
                ffatypes is given, it is assumed that ffatypes contains an
                atom type for every element, i.e. that it is a list with length
                natom. In that case, it will be converted automatically to
                a short ffatypes list with only unique elements (within each
                scope) together with a corresponding ffatype_ids array.

           bonds
                a numpy array (B,2) with atom indexes (counting starts from
                zero) to define the chemical bonds.

           rvecs
                An array whose rows are the unit cell vectors. At most three
                rows are allowed, each containing three Cartesian coordinates.

           charges
                An array of atomic charges

           radii
                An array of atomic radii, :math:`R_{A,c}`, that determine shape of the atomic
                charge distribution:

                .. math::

                    \rho_{A,c}(\mathbf{r}) = \frac{q_A}{\pi^{3/2}R_{A,c}^3} \exp\left(
                    -\frac{|r - \mathbf{R}_A|^2}{R_{A,c}^2}
                    \right)

           valence_charges
                In case a point-core + distribute valence charge is used, this
                vector contains the valence charges. The core charges can be
                computed by subtracting the valence charges from the net
                charges.

           dipoles
                An array of atomic dipoles

           radii2
                An array of atomic radii, :math:`R_{A,d}`, that determine shape of the
                atomic dipole distribution:

                .. math::

                   \rho_{A,d}(\mathbf{r}) = -2\frac{\mathbf{d}_A \cdot (\mathbf{r} - \mathbf{R}_A)}{
                   \sqrt{\pi} R_{A,d}^5
                   }\exp\left(
                    -\frac{|r - \mathbf{R}_A|^2}{R_{A,d}^2}
                    \right)

           masses
                The atomic masses (in atomic units, i.e. m_e)


           Several attributes are derived from the (optional) arguments:

           * ``cell`` contains the rvecs attribute and is an instance of the
             ``Cell`` class.

           * ``neighs1``, ``neighs2`` and ``neighs3`` are dictionaries derived
             from ``bonds`` that contain atoms that are separated 1, 2 and 3
             bonds from a given atom, respectively. This means that i in
             system.neighs3[j] is ``True`` if there are three bonds between
             atoms i and j.
        '''
        if len(numbers.shape) != 1:
            raise ValueError(
                'Argument numbers must be a one-dimensional array.')
        if pos.shape != (len(numbers), 3):
            raise ValueError(
                'The pos array must have Nx3 rows. Mismatch with numbers argument with shape (N,).'
            )
        self.numbers = numbers
        self.pos = pos
        self.ffatypes = ffatypes
        self.ffatype_ids = ffatype_ids
        self.scopes = scopes
        self.scope_ids = scope_ids
        self.bonds = bonds
        self.cell = Cell(rvecs)
        self.charges = charges
        self.radii = radii
        self.valence_charges = valence_charges
        self.dipoles = dipoles
        self.radii2 = radii2
        self.masses = masses
        with log.section('SYS'):
            # report some stuff
            self._init_log()
            # compute some derived attributes
            self._init_derived()
Beispiel #6
0
    def from_files(cls, guest, parameters, **kwargs):
        """Automated setup of GCMC simulation

           **Arguments:**

           guest
                Two types are accepted: (i) the filename of a system file
                describing one guest molecule, (ii) a System instance of
                one guest molecule

           parameters
                Force-field parameters describing guest-guest and optionally
                host-guest interaction.
                Three types are accepted: (i) the filename of the parameter
                file, which is a text file that adheres to YAFF parameter
                format, (ii) a list of such filenames, or (iii) an instance of
                the Parameters class.

           **Optional arguments:**

           hooks
                A list of MCHooks

           host
                Two types are accepted: (i) the filename of a system file
                describing the host system, (ii) a System instance of the host

           All other keyword arguments are passed to the ForceField constructor
           See the constructor of the :class:`yaff.pes.generator.FFArgs` class
           for the available optional arguments.

        """
        # Load the guest System
        if isinstance(guest, str):
            guest = System.from_file(guest)
        assert isinstance(guest, System)
        # We want to control nlow and nhigh here ourselves, so remove it from the
        # optional arguments if the user provided it.
        kwargs.pop('nlow', None)
        kwargs.pop('nhigh', None)
        # Rough guess for number of adsorbed guests
        nguests = kwargs.pop('nguests', 10)
        # Load the host if it is present as a keyword
        host = kwargs.pop('host', None)
        # Extract the hooks
        hooks = kwargs.pop('hooks', [])
        # Efficient treatment of reciprocal ewald contribution
        if not 'reci_ei' in kwargs.keys():
            kwargs['reci_ei'] = 'ewald_interaction'
        if host is not None:
            if isinstance(host, str):
                host = System.from_file(host)
            assert isinstance(host, System)
            # If the guest molecule is currently an isolated molecule, than put
            # it in the same periodic box as the host
            if guest.cell is None or guest.cell.nvec==0:
                guest.cell = Cell(host.cell.rvecs)
            # Construct a complex of host and one guest and the corresponding
            # force field excluding host-host interactions
            hostguest = host.merge(guest)
            external_potential = ForceField.generate(hostguest, parameters,
                 nlow=host.natom, nhigh=host.natom, **kwargs)
        else:
            external_potential = None
#        # Compare the energy of the guest, once isolated, once in a periodic box
#        guest_isolated = guest.subsystem(np.arange(guest.natom))
#        guest_isolated.cell = Cell(np.zeros((0,3)))
#        optional_arguments = {}
#        for key in kwargs.keys():
#            if key=='reci_ei': continue
#            optional_arguments[key] = kwargs[key]
#        ff_guest_isolated = ForceField.generate(guest_isolated, parameters, **optional_arguments)
#        e_isolated = ff_guest_isolated.compute()
#        guest_periodic = guest.subsystem(np.arange(guest.natom))
#        ff_guest_periodic = ForceField.generate(guest_periodic, parameters, **optional_arguments)
#        e_periodic = ff_guest_periodic.compute()
#        if np.abs(e_isolated-e_periodic)>1e-4:
#            if log.do_warning:
#                log.warn("An interaction energy of %s of the guest with its periodic "
#                         "images was detected. The interaction of a guest with its periodic "
#                         "images will however NOT be taken into account in this simulation. "
#                         "If the energy difference is large compared to k_bT, you should "
#                         "consider using a supercell." % (log.energy(e_isolated-e_periodic)))
        # By making use of nlow=nhigh, we automatically discard intramolecular energies
        eguest = 0.0
        # Generator of guest-guest force fields, excluding interactions
        # between the first N-1 guests
        def ff_generator(system, guest):
            return ForceField.generate(system, parameters, nlow=max(0,system.natom-guest.natom), nhigh=max(0,system.natom-guest.natom), **kwargs)
        return cls(guest, ff_generator, external_potential=external_potential,
             eguest=eguest, hooks=hooks, nguests=nguests)