Example #1
0
    def __init__(self,
                 system,
                 term,
                 other_terms,
                 cart_penalty=1e-3 * angstrom):
        '''
            A class deriving from the Yaff ForceField class to implement the
            strain of a molecular geometry associated with the term defined by
            term_index.

            **Arguments**

            system
                A Yaff System instance containing all system information.

            term
                a Term instance representing the term of the perturbation
                trajectory of the current strain

            other_terms
                a list of Term instances representing all other terms for ICs
                for which a strain contribution should be added

            **Keyword Arguments**

            cart_penalty
                Magnitude of an extra term added to the strain that penalises
                a deviation of the cartesian coordinates of each atom with
                respect to the equilibrium coordinates. This penalty is equal
                to norm(R-R_eq)**2/(2.0*3*Natoms*cart_penalty**2) and prevents
                global translations, global rotations as well as rotations of
                molecular fragments far from the IC under consideration.
        '''
        self.coords0 = system.pos.copy()
        self.ndof = np.prod(self.coords0.shape)
        self.cart_penalty = cart_penalty
        self.cons_ic_atindexes = term.get_atoms()
        #construct main strain
        strain = ForcePartValence(system)
        for other in other_terms:
            if other.kind == 3: continue  #no cross terms
            strain.add_term(Harmonic(1.0, None, other.ics[0]))
        #set the rest values to the equilibrium values
        strain.dlist.forward()
        strain.iclist.forward()
        for iterm in range(strain.vlist.nv):
            vterm = strain.vlist.vtab[iterm]
            ic = strain.iclist.ictab[vterm['ic0']]
            vterm['par1'] = ic['value']
        ForceField.__init__(self, system, [strain])
        #Abuse the Chebychev1 polynomial to simply get the value of q-1 and
        #implement the contraint
        constraint = ForcePartValence(system)
        constraint.add_term(Chebychev1(-2.0, term.ics[0]))
        self.constraint = ForceField(system, [constraint])
        self.constrain_target = None
        self.constrain_value = None
        self.value = None
Example #2
0
    def __init__(self, system, term, other_terms, cart_penalty=1e-3*angstrom):
        '''
            A class deriving from the Yaff ForceField class to implement the
            strain of a molecular geometry associated with the term defined by
            term_index.

            **Arguments**

            system
                A Yaff System instance containing all system information.

            term
                a Term instance representing the term of the perturbation
                trajectory of the current strain

            other_terms
                a list of Term instances representing all other terms for ICs
                for which a strain contribution should be added

            **Keyword Arguments**

            cart_penalty
                Magnitude of an extra term added to the strain that penalises
                a deviation of the cartesian coordinates of each atom with
                respect to the equilibrium coordinates. This penalty is equal
                to norm(R-R_eq)**2/(2.0*3*Natoms*cart_penalty**2) and prevents
                global translations, global rotations as well as rotations of
                molecular fragments far from the IC under consideration.
        '''
        self.coords0 = system.pos.copy()
        self.ndof = np.prod(self.coords0.shape)
        self.cart_penalty = cart_penalty
        self.cons_ic_atindexes = term.get_atoms()
        #construct main strain
        strain = ForcePartValence(system)
        for other in other_terms:
            if other.kind == 3: continue #no cross terms
            strain.add_term(Harmonic(1.0, None, other.ics[0]))
        #set the rest values to the equilibrium values
        strain.dlist.forward()
        strain.iclist.forward()
        for iterm in range(strain.vlist.nv):
            vterm = strain.vlist.vtab[iterm]
            ic = strain.iclist.ictab[vterm['ic0']]
            vterm['par1'] = ic['value']
        ForceField.__init__(self, system, [strain])
        #Abuse the Chebychev1 polynomial to simply get the value of q-1 and
        #implement the contraint
        constraint = ForcePartValence(system)
        constraint.add_term(Chebychev1(-2.0,term.ics[0]))
        self.constraint = ForceField(system, [constraint])
        self.constrain_target = None
        self.constrain_value = None
        self.value = None
Example #3
0
def get_ei_ff(name, system, charges, scales, radii=None, average=True, pbc=[0,0,0]):
    '''
        A routine to construct a Yaff force field for the electrostatics

        **Arguments**

        name
            A string for the name of the force field. This name will show in
            possible plots visualizing contributions along perturbation
            trajectories

        system
            A Yaff System instance representing the system

        charges
            A numpy array containing the charge of each separate atom

        scales
            A list of 4 floats, representing scale1, scale2, scale3, scale4,
            i.e. the electrostatic scaling factors

        **Optional Arguments**

        radii
            A numpy array containing the gaussian radii of each separate atom.
            If this argument is omitted, point charges are used.

        average
            If set to True, the charges and radii will first be averaged over
            atom types. This is True by default.
    '''
    if not (np.array(pbc)==0).all():
        raise NotImplementedError('Periodic system not implemented in get_ei_ff')
    if average:
        qs = {}
        rs = {}
        ffatypes = [system.ffatypes[i] for i in system.ffatype_ids]
        for i, atype in enumerate(ffatypes):
            if atype in qs: qs[atype].append(charges[i])
            else: qs[atype] = [charges[i]]
            if radii is not None:
                if atype in rs: rs[atype].append(radii[i])
                else: rs[atype] = [radii[i]]
        for i, atype in enumerate(ffatypes):
            charges[i] = np.array(qs[atype]).mean()
            if radii is not None:
                radii[i] = np.array(rs[atype]).mean()
    if radii is None: radii = np.zeros(len(system.ffatype_ids), float)
    pair_pot = PairPotEI(charges.astype(np.float), 0.0, 50*angstrom, None, 1.0, radii.astype(np.float))
    nlist = NeighborList(system, 0)
    scalings = Scalings(system, scale1=scales[0], scale2=scales[1], scale3=scales[2], scale4=scales[3])
    part = ForcePartPair(system, nlist, scalings, pair_pot)
    ff = ForceField(system, [part], nlist=nlist)
    return YaffForceField(name, ff)
Example #4
0
 def get_hessian_contrib(self, index, fc=None):
     '''
         Get the contribution to the covalent hessian of term with given
         index (and its slaves). If fc is given, set the fc of the master
         and its slave to the given fc.
     '''
     val = ForcePartValence(self.system)
     kind = self.vlist.vtab[index]['kind']
     masterslaves = [index] + self.terms[index].slaves
     if kind == 4:  #Cosine
         m, k, rv = self.get_params(index)
         if fc is not None: k = fc
         for jterm in masterslaves:
             ics = self.terms[jterm].ics
             args = (m, k, rv) + tuple(ics)
             val.add_term(Cosine(*args))
     elif kind == 3:  #cross
         k, rv0, rv1 = self.get_params(index)
         if fc is not None: k = fc
         for jterm in masterslaves:
             ics = self.terms[jterm].ics
             args = (k, rv0, rv1) + tuple(ics)
             val.add_term(Cross(*args))
     elif kind == 1:  #Polyfour
         a0, a1, a2, a3 = list(self.get_params(index))
         if fc is not None:
             a3 = 2.0 * fc
             a1 = -4.0 * fc * np.cos(a0)**2
         for jterm in masterslaves:
             ics = self.terms[jterm].ics
             args = ([0.0, a1, 0.0, a3], ) + tuple(ics)
             val.add_term(PolyFour(*args))
     elif kind == 0:  #Harmonic:
         k, rv = self.get_params(index)
         if fc is not None: k = fc
         for jterm in masterslaves:
             ics = self.terms[jterm].ics
             args = (k, rv) + tuple(ics)
             val.add_term(Harmonic(*args))
     else:
         raise ValueError('Term kind %i not supported' % kind)
     ff = ForceField(self.system, [val])
     hcov = estimate_cart_hessian(ff)
     return hcov
Example #5
0
File: cost.py Project: boegel/yaff
 def __call__(self, parameters):
     # prepare force field
     ff = ForceField.generate(self.system, parameters, **self.kwargs)
     # run actual simulation
     return self.run(ff)
Example #6
0
    def __init__(self,
                 system,
                 cons_ic,
                 cons_ic_atindexes,
                 ics,
                 cart_penalty=1e-3 * angstrom):
        '''
            A class deriving from the Yaff ForceField class to implement the
            strain of a molecular geometry associated with the term defined by
            term_index.

            **Arguments**

            system
                A Yaff System instance containing all system information.

            cons_ic
                An instance of Yaff Internal Coordinate representing the
                constrained term in the strain.

            cons_ic_atindexes
                A list of the atoms involved in the constrained IC. This is
                required for the implementation of the cartesian penalty. In
                principle this could be extracted from the information stored
                in cons_ic, but this is the lazy solution.

            ics
                A list of Yaff Internal Coordinate instances for which the
                strain needs to be minimized.

            cart_penalty
                Magnitude of an extra term added to the strain that penalises
                a deviation of the cartesian coordinates of each atom with
                respect to the equilibrium coordinates. This penalty is equal
                to norm(R-R_eq)**2/(2.0*3*Natoms*cart_penalty**2) and prevents
                global translations, global rotations as well as rotations of
                molecular fragments far from the IC under consideration.
        '''
        self.coords0 = system.pos.copy()
        self.ndof = np.prod(self.coords0.shape)
        self.cart_penalty = cart_penalty
        self.cons_ic_atindexes = cons_ic_atindexes
        part = ForcePartValence(system)
        for ic in ics:
            part.add_term(Harmonic(1.0, None, ic))
        #set the rest values to the equilibrium values
        part.dlist.forward()
        part.iclist.forward()
        for iterm in xrange(part.vlist.nv):
            term = part.vlist.vtab[iterm]
            ic = part.iclist.ictab[term['ic0']]
            term['par1'] = ic['value']
        ForceField.__init__(self, system, [part])
        #Abuse the Chebychev1 polynomial to simply get the value of q-1 and
        #implement the contraint
        part = ForcePartValence(system)
        part.add_term(Chebychev1(-2.0, cons_ic))
        self.constraint = ForceField(system, [part])
        self.constrain_target = None
        self.constrain_value = None
        self.value = None
Example #7
0
class Strain(ForceField):
    def __init__(self,
                 system,
                 cons_ic,
                 cons_ic_atindexes,
                 ics,
                 cart_penalty=1e-3 * angstrom):
        '''
            A class deriving from the Yaff ForceField class to implement the
            strain of a molecular geometry associated with the term defined by
            term_index.

            **Arguments**

            system
                A Yaff System instance containing all system information.

            cons_ic
                An instance of Yaff Internal Coordinate representing the
                constrained term in the strain.

            cons_ic_atindexes
                A list of the atoms involved in the constrained IC. This is
                required for the implementation of the cartesian penalty. In
                principle this could be extracted from the information stored
                in cons_ic, but this is the lazy solution.

            ics
                A list of Yaff Internal Coordinate instances for which the
                strain needs to be minimized.

            cart_penalty
                Magnitude of an extra term added to the strain that penalises
                a deviation of the cartesian coordinates of each atom with
                respect to the equilibrium coordinates. This penalty is equal
                to norm(R-R_eq)**2/(2.0*3*Natoms*cart_penalty**2) and prevents
                global translations, global rotations as well as rotations of
                molecular fragments far from the IC under consideration.
        '''
        self.coords0 = system.pos.copy()
        self.ndof = np.prod(self.coords0.shape)
        self.cart_penalty = cart_penalty
        self.cons_ic_atindexes = cons_ic_atindexes
        part = ForcePartValence(system)
        for ic in ics:
            part.add_term(Harmonic(1.0, None, ic))
        #set the rest values to the equilibrium values
        part.dlist.forward()
        part.iclist.forward()
        for iterm in xrange(part.vlist.nv):
            term = part.vlist.vtab[iterm]
            ic = part.iclist.ictab[term['ic0']]
            term['par1'] = ic['value']
        ForceField.__init__(self, system, [part])
        #Abuse the Chebychev1 polynomial to simply get the value of q-1 and
        #implement the contraint
        part = ForcePartValence(system)
        part.add_term(Chebychev1(-2.0, cons_ic))
        self.constraint = ForceField(system, [part])
        self.constrain_target = None
        self.constrain_value = None
        self.value = None

    def gradient(self, X):
        '''
            Compute the gradient of the strain wrt Cartesian coordinates of the
            system. For every ic that needs to be constrained, a Lagrange multiplier
            is included.
        '''
        #small check
        #assert X.shape[0] ==  self.ndof + 1
        #initialize return value
        grad = np.zeros((len(X), ))
        #compute strain gradient
        gstrain = np.zeros(self.coords0.shape)
        self.update_pos(self.coords0 + X[:self.ndof].reshape((-1, 3)))
        self.value = self.compute(gpos=gstrain)
        #compute constraint gradient
        gconstraint = np.zeros(self.coords0.shape)
        self.constraint.update_pos(self.coords0 +
                                   X[:self.ndof].reshape((-1, 3)))
        self.constrain_value = self.constraint.compute(gpos=gconstraint) + 1.0
        #construct gradient
        grad[:self.ndof] = gstrain.reshape(
            (-1, )) + X[self.ndof] * gconstraint.reshape((-1, ))
        grad[self.ndof] = self.constrain_value - self.constrain_target
        #cartesian penalty, i.e. extra penalty for deviation w.r.t. cartesian equilibrium coords
        indices = np.array([[3 * i, 3 * i + 1, 3 * i + 2]
                            for i in xrange(self.ndof / 3)
                            if i not in self.cons_ic_atindexes]).ravel()
        if len(indices) > 0:
            grad[indices] += X[indices] / (self.ndof * self.cart_penalty**2)
        with log.section('PTGEN', 4, timer='PT Generate'):
            log.dump('      Gradient:  rms = %.3e  max = %.3e  cnstr = %.3e' %
                     (np.sqrt(
                         (grad[:self.ndof]**2).mean()), max(
                             grad[:self.ndof]), grad[self.ndof]))
        return grad
Example #8
0
class Strain(ForceField):
    def __init__(self, system, term, other_terms, cart_penalty=1e-3*angstrom):
        '''
            A class deriving from the Yaff ForceField class to implement the
            strain of a molecular geometry associated with the term defined by
            term_index.

            **Arguments**

            system
                A Yaff System instance containing all system information.

            term
                a Term instance representing the term of the perturbation
                trajectory of the current strain

            other_terms
                a list of Term instances representing all other terms for ICs
                for which a strain contribution should be added

            **Keyword Arguments**

            cart_penalty
                Magnitude of an extra term added to the strain that penalises
                a deviation of the cartesian coordinates of each atom with
                respect to the equilibrium coordinates. This penalty is equal
                to norm(R-R_eq)**2/(2.0*3*Natoms*cart_penalty**2) and prevents
                global translations, global rotations as well as rotations of
                molecular fragments far from the IC under consideration.
        '''
        self.coords0 = system.pos.copy()
        self.ndof = np.prod(self.coords0.shape)
        self.cart_penalty = cart_penalty
        self.cons_ic_atindexes = term.get_atoms()
        #construct main strain
        strain = ForcePartValence(system)
        for other in other_terms:
            if other.kind == 3: continue #no cross terms
            strain.add_term(Harmonic(1.0, None, other.ics[0]))
        #set the rest values to the equilibrium values
        strain.dlist.forward()
        strain.iclist.forward()
        for iterm in range(strain.vlist.nv):
            vterm = strain.vlist.vtab[iterm]
            ic = strain.iclist.ictab[vterm['ic0']]
            vterm['par1'] = ic['value']
        ForceField.__init__(self, system, [strain])
        #Abuse the Chebychev1 polynomial to simply get the value of q-1 and
        #implement the contraint
        constraint = ForcePartValence(system)
        constraint.add_term(Chebychev1(-2.0,term.ics[0]))
        self.constraint = ForceField(system, [constraint])
        self.constrain_target = None
        self.constrain_value = None
        self.value = None

    def gradient(self, X):
        '''
            Compute the gradient of the strain w.r.t. Cartesian coordinates of
            the system. For the ic that needs to be constrained, a Lagrange
            multiplier is included.
        '''
        #initialize return value
        grad = np.zeros((len(X),))
        #compute strain gradient
        gstrain = np.zeros(self.coords0.shape)
        self.update_pos(self.coords0 + X[:self.ndof].reshape((-1,3)))
        self.value = self.compute(gpos=gstrain)
        #compute constraint gradient
        gconstraint = np.zeros(self.coords0.shape)
        self.constraint.update_pos(self.coords0 + X[:self.ndof].reshape((-1,3)))
        self.constrain_value = self.constraint.compute(gpos=gconstraint) + 1.0
        #construct gradient
        grad[:self.ndof] = gstrain.reshape((-1,)) + X[self.ndof]*gconstraint.reshape((-1,))
        grad[self.ndof] = self.constrain_value - self.constrain_target
        #cartesian penalty, i.e. extra penalty for deviation w.r.t. cartesian equilibrium coords
        indices = np.array([[3*i,3*i+1,3*i+2] for i in range(self.ndof//3) if i not in self.cons_ic_atindexes]).ravel()
        if len(indices)>0:
            grad[indices] += X[indices]/(self.ndof*self.cart_penalty**2)
        with log.section('PTGEN', 4, timer='PT Generate'):
            log.dump('      Gradient:  rms = %.3e  max = %.3e  cnstr = %.3e' %(np.sqrt((grad[:self.ndof]**2).mean()), max(grad[:self.ndof]), grad[self.ndof]))
        return grad
Example #9
0
class Strain(ForceField):
    def __init__(self, system, term, other_terms, cart_penalty=1e-3*angstrom):
        '''
            A class deriving from the Yaff ForceField class to implement the
            strain of a molecular geometry associated with the term defined by
            term_index.

            **Arguments**

            system
                A Yaff System instance containing all system information.

            term
                a Term instance representing the term of the perturbation
                trajectory of the current strain

            other_terms
                a list of Term instances representing all other terms for ICs
                for which a strain contribution should be added

            **Keyword Arguments**

            cart_penalty
                Magnitude of an extra term added to the strain that penalises
                a deviation of the cartesian coordinates of each atom with
                respect to the equilibrium coordinates. This penalty is equal
                to norm(R-R_eq)**2/(2.0*3*Natoms*cart_penalty**2) and prevents
                global translations, global rotations as well as rotations of
                molecular fragments far from the IC under consideration.
        '''
        self.coords0 = system.pos.copy()
        self.ndof = np.prod(self.coords0.shape)
        self.cart_penalty = cart_penalty
        self.cons_ic_atindexes = term.get_atoms()
        #construct main strain
        strain = ForcePartValence(system)
        for other in other_terms:
            if other.kind == 3: continue #no cross terms
            strain.add_term(Harmonic(1.0, None, other.ics[0]))
        #set the rest values to the equilibrium values
        strain.dlist.forward()
        strain.iclist.forward()
        for iterm in range(strain.vlist.nv):
            vterm = strain.vlist.vtab[iterm]
            ic = strain.iclist.ictab[vterm['ic0']]
            vterm['par1'] = ic['value']
        ForceField.__init__(self, system, [strain])
        #Abuse the Chebychev1 polynomial to simply get the value of q-1 and
        #implement the contraint
        constraint = ForcePartValence(system)
        constraint.add_term(Chebychev1(-2.0,term.ics[0]))
        self.constraint = ForceField(system, [constraint])
        self.constrain_target = None
        self.constrain_value = None
        self.value = None

    def gradient(self, X):
        '''
            Compute the gradient of the strain w.r.t. Cartesian coordinates of
            the system. For the ic that needs to be constrained, a Lagrange
            multiplier is included.
        '''
        #initialize return value
        grad = np.zeros((len(X),))
        #compute strain gradient
        gstrain = np.zeros(self.coords0.shape)
        self.update_pos(self.coords0 + X[:self.ndof].reshape((-1,3)))
        self.value = self.compute(gpos=gstrain)
        #compute constraint gradient
        gconstraint = np.zeros(self.coords0.shape)
        self.constraint.update_pos(self.coords0 + X[:self.ndof].reshape((-1,3)))
        self.constrain_value = self.constraint.compute(gpos=gconstraint) + 1.0
        #construct gradient
        grad[:self.ndof] = gstrain.reshape((-1,)) + X[self.ndof]*gconstraint.reshape((-1,))
        grad[self.ndof] = self.constrain_value - self.constrain_target
        #cartesian penalty, i.e. extra penalty for deviation w.r.t. cartesian equilibrium coords
        indices = np.array([[3*i,3*i+1,3*i+2] for i in range(self.ndof//3) if i not in self.cons_ic_atindexes]).ravel()
        if len(indices)>0:
            grad[indices] += X[indices]/(self.ndof*self.cart_penalty**2)
        with log.section('PTGEN', 4, timer='PT Generate'):
            log.dump('      Gradient:  rms = %.3e  max = %.3e  cnstr = %.3e' %(np.sqrt((grad[:self.ndof]**2).mean()), max(grad[:self.ndof]), grad[self.ndof]))
        return grad
Example #10
0
def write_raspa_input(guests,
                      parameters,
                      host=None,
                      workdir='.',
                      guestdata=None,
                      hostname='host'):
    """
       Prepare input files that can be used to run RASPA simulations. Only a
       small subset of the full capabilities of RASPA can be explored.

       **Arguments:**

       guests
            A list specifying the guest molecules. Each entry can be one of
            two types: (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:**

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

       workdir
            The directory where output files will be placed.

       guestdata
            List with the same length as guests. Each entry contains a tuple
            either looking as (name, Tc, Pc, omega) or (name). In the latter
            case, the parameters Tc, Pc, and omega will be loaded from a data
            file based on the name. Otherwise, these will be left blank in the
            input file.
    """
    # Load the guest Systems
    guests = [
        System.from_file(guest) if isinstance(guest, str) else guest
        for guest in guests
    ]
    for guest in guests:
        assert isinstance(guest, System)
    if guestdata is None:
        guestdata = [("guest%03d" % iguest, ) for iguest in range(len(guests))]
    assert len(guests) == len(guestdata)
    # Load the host System
    if host is not None:
        if isinstance(host, str):
            host = System.from_file(host)
        assert isinstance(host, System)
        complex = host
    # Merge all systems
    for guest in guests:
        complex = complex.merge(guest)
    # Generate the ForceField, we don't really care about settings such as
    # cutoffs etc. We only need the parameters in the end
    ff = ForceField.generate(complex, parameters)
    # Write the force-field parameters
    write_raspa_forcefield(ff, workdir)
    # Write masses and charges of all atoms
    ffatypes, ffatype_ids = write_pseudo_atoms(ff, workdir)
    # Write the guest information
    if host == None: counter = 0
    else: counter = host.natom
    for iguest, (guest, data) in enumerate(zip(guests, guestdata)):
        write_guest(guest, data, workdir, [
            ffatypes[iffa]
            for iffa in ffatype_ids[counter:counter + guest.natom]
        ])
        counter += guest.natom
    # Write the host coordinates to a cif file
    if host is not None:
        dump_cif(
            host,
            os.path.join(workdir, '%s.cif' % hostname),
            ffatypes=[ffatypes[iffa] for iffa in ffatype_ids[:host.natom]])
    # A fairly standard input file for GCMC simulations
    dump_input(workdir, hostname, [data[0] for data in guestdata])
Example #11
0
 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)
Example #12
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)