예제 #1
0
    def _create_damping_param(self) -> DampingParam:
        """Create a new API damping parameter object"""

        try:
            dpar = DampingParam(
                method=self.parameters.get("method"),
                **self.parameters.get("params_tweaks", {}),
            )

        except RuntimeError:
            raise InputError("Cannot construct damping parameter for dftd4")

        return dpar
예제 #2
0
def _format_basis_set(basis, basisfile, basis_set):
    '''Format either: the basis set filename (basisfile), the basis set file
    contents (from reading basisfile), or the basis_set text as a list of
    strings to be added to the gaussian input file.'''
    out = []
    # if basis='gen', set basisfile. Either give a path to a basisfile, or
    # read in the provided file and paste it verbatim
    if basisfile is not None:
        if basisfile[0] == '@':
            out.append(basisfile)
        else:
            with open(basisfile, 'r') as f:
                out.append(f.read())
    elif basis_set is not None:
        out.append(basis_set)
    else:
        if basis is not None and basis.lower() == 'gen':
            raise InputError('Please set basisfile or basis_set')
    return out
예제 #3
0
    def _create_api_calculator(self) -> DispersionModel:
        """Create a new API calculator object"""

        try:
            _cell = self.atoms.cell
            _periodic = self.atoms.pbc
            _charge = self.atoms.get_initial_charges().sum()

            disp = DispersionModel(
                self.atoms.numbers,
                self.atoms.positions / Bohr,
                _charge,
                _cell / Bohr,
                _periodic,
            )

        except RuntimeError:
            raise InputError("Cannot construct dispersion model for dftd4")

        return disp
예제 #4
0
def write_gaussian_in(fd, atoms, properties=None, **params):
    params = deepcopy(params)

    if properties is None:
        properties = ['energy']

    # pop method and basis
    method = params.pop('method', None)
    basis = params.pop('basis', None)

    # basisfile, only used if basis=gen
    basisfile = params.pop('basisfile', None)

    # basis can be omitted if basisfile is provided
    if basisfile is not None and basis is None:
        basis = 'gen'

    # determine method from xc if it is provided
    if method is None:
        xc = params.pop('xc', None)
        if xc is None:
            # Default to HF
            method = 'hf'
        else:
            method = _xc_to_method.get(xc.lower(), xc)

    # If the user requests a problematic method, rather than raising an error
    # or proceeding blindly, give the user a warning that the results parsed
    # by ASE may not be meaningful.
    if method.lower() in _problem_methods:
        warnings.warn(
            'The requested method, {}, is a composite method. Composite '
            'methods do not have well-defined potential energy surfaces, '
            'so the energies, forces, and other properties returned by '
            'ASE may not be meaningful, or they may correspond to a '
            'different geometry than the one provided. '
            'Please use these methods with caution.'.format(method))

    # determine charge from initial charges if not passed explicitly
    charge = params.pop('charge', None)
    if charge is None:
        charge = atoms.get_initial_charges().sum()

    # determine multiplicity from initial magnetic moments
    # if not passed explicitly
    mult = params.pop('mult', None)
    if mult is None:
        mult = atoms.get_initial_magnetic_moments().sum() + 1

    # pull out raw list of explicit keywords for backwards compatibility
    extra = params.pop('extra', None)

    # pull out any explicit IOPS
    ioplist = params.pop('ioplist', None)

    # also pull out 'addsec', which e.g. contains modredundant info
    addsec = params.pop('addsec', None)

    # set up link0 arguments
    out = []
    for key in _link0_keys:
        if key not in params:
            continue
        val = params.pop(key)
        if not val or (isinstance(val, str) and key.lower() == val.lower()):
            out.append('%{}'.format(key))
        else:
            out.append('%{}={}'.format(key, val))

    # These link0 keywords have a slightly different syntax
    for key in _link0_special:
        if key not in params:
            continue
        val = params.pop(key)
        if not isinstance(val, str) and isinstance(val, Iterable):
            val = ' '.join(val)
        out.append('%{} L{}'.format(key, val))

    # begin route line
    # note: unlike in old calculator, each route keyword is put on its own
    # line.
    if basis is None:
        out.append('#P {}'.format(method))
    else:
        out.append('#P {}/{}'.format(method, basis))

    for key, val in params.items():
        # assume bare keyword if val is falsey, i.e. '', None, False, etc.
        # also, for backwards compatibility: assume bare keyword if key and
        # val are the same
        if not val or (isinstance(val, str) and key.lower() == val.lower()):
            out.append(key)
        elif isinstance(val, str) and ',' in val:
            out.append('{}({})'.format(key, val))
        elif not isinstance(val, str) and isinstance(val, Iterable):
            out.append('{}({})'.format(key, ','.join(val)))
        else:
            out.append('{}={}'.format(key, val))

    if ioplist is not None:
        out.append('IOP(' + ', '.join(ioplist) + ')')

    if extra is not None:
        out.append(extra)

    # Add 'force' iff the user requested forces, since Gaussian crashes when
    # 'force' is combined with certain other keywords such as opt and irc.
    if 'forces' in properties and 'force' not in params:
        out.append('force')

    # header, charge, and mult
    out += [
        '', 'Gaussian input prepared by ASE', '',
        '{:.0f} {:.0f}'.format(charge, mult)
    ]

    # atomic positions
    for atom in atoms:
        # this formatting was chosen for backwards compatibility reasons, but
        # it would probably be better to
        # 1) Ensure proper spacing between entries with explicit spaces
        # 2) Use fewer columns for the element
        # 3) Use 'e' (scientific notation) instead of 'f' for positions
        out.append('{:<10s}{:20.10f}{:20.10f}{:20.10f}'.format(
            atom.symbol, *atom.position))

    # unit cell vectors, in case of periodic boundary conditions
    for ipbc, tv in zip(atoms.pbc, atoms.cell):
        if ipbc:
            out.append('TV {:20.10f}{:20.10f}{:20.10f}'.format(*tv))

    out.append('')

    # if basis='gen', set basisfile. Either give a path to a basisfile, or
    # read in the provided file and paste it verbatim
    if basisfile is not None:
        if basisfile[0] == '@':
            out.append(basisfile)
        else:
            with open(basisfile, 'r') as f:
                out.append(f.read())
    else:
        if basis is not None and basis.lower() == 'gen':
            raise InputError('Please set basisfile')

    if addsec is not None:
        out.append('')
        if isinstance(addsec, str):
            out.append(addsec)
        elif isinstance(addsec, Iterable):
            out += list(addsec)

    out += ['', '']
    fd.write('\n'.join(out))
예제 #5
0
    def set_psi4(self, atoms=None):
        """
        This function sets the imported psi4 module to the settings the user
        defines
        """

        # Set the scrath directory for electronic structure files.
        # The default is /tmp
        if 'PSI_SCRATCH' in os.environ:
            pass
        elif self.parameters.get('PSI_SCRATCH'):
            os.environ['PSI_SCRATCH'] = self.parameters['PSI_SCRATCH']

        # Input spin settings
        if self.parameters.get('reference') is not None:
            self.psi4.set_options({'reference': self.parameters['reference']})
        # Memory
        if self.parameters.get('memory') is not None:
            self.psi4.set_memory(self.parameters['memory'])

        # Threads
        nthreads = self.parameters.get('num_threads', 1)
        if nthreads == 'max':
            nthreads = multiprocessing.cpu_count()
        self.psi4.set_num_threads(nthreads)

        # deal with some ASE specific inputs
        if 'kpts' in self.parameters:
            raise InputError('psi4 is a non-periodic code, and thus does not'
                             ' require k-points. Please remove this '
                             'argument.')

        if self.parameters['method'] == 'LDA':
            # svwn is equivalent to LDA
            self.parameters['method'] = 'svwn'

        if 'nbands' in self.parameters:
            raise InputError('psi4 does not support the keyword "nbands"')

        if 'smearing' in self.parameters:
            raise InputError('Finite temperature DFT is not implemented in'
                             ' psi4 currently, thus a smearing argument '
                             'cannot be utilized. please remove this '
                             'argument')

        if 'xc' in self.parameters:
            raise InputError('psi4 does not accept the `xc` argument please'
                             ' use the `method` argument instead')

        if atoms is None:
            if self.atoms is None:
                return None
            else:
                atoms = self.atoms
        if self.atoms is None:
            self.atoms = atoms
        # Generate the atomic input
        geomline = '{}\t{:.15f}\t{:.15f}\t{:.15f}'
        geom = [geomline.format(atom.symbol, *atom.position) for atom in atoms]
        geom.append('symmetry {}'.format(self.parameters['symmetry']))
        geom.append('units angstrom')

        charge = self.parameters.get('charge')
        mult = self.parameters.get('multiplicity')
        if mult is None:
            mult = 1
            if charge is not None:
                warnings.warn('A charge was provided without a spin '
                              'multiplicity. A multiplicity of 1 is assumed')
        if charge is None:
            charge = 0

        geom.append('{} {}'.format(charge, mult))
        geom.append('no_reorient')

        if not os.path.isdir(self.directory):
            os.mkdir(self.directory)
        self.molecule = self.psi4.geometry('\n'.join(geom))
예제 #6
0
    def calculate(self,
                  atoms=None,
                  properties=['energy'],
                  system_changes=all_changes,
                  symmetry='c1'):
        """Do the calculation.

        properties: list of str
            List of what needs to be calculated.  Can be any combination
            of 'energy', 'forces', 'stress', 'dipole', 'charges', 'magmom'
            and 'magmoms'.
        system_changes: list of str
            List of what has changed since last calculation.  Can be
            any combination of these six: 'positions', 'numbers', 'cell',
            'pbc', 'initial_charges' and 'initial_magmoms'.

        Subclasses need to implement this, but can ignore properties
        and system_changes if they want.  Calculated properties should
        be inserted into results dictionary like shown in this dummy
        example::

            self.results = {'energy': 0.0,
                            'forces': np.zeros((len(atoms), 3)),
                            'stress': np.zeros(6),
                            'dipole': np.zeros(3),
                            'charges': np.zeros(len(atoms)),
                            'magmom': 0.0,
                            'magmoms': np.zeros(len(atoms))}

        """
        Calculator.calculate(self, atoms=atoms)
        if atoms is None:
            if self.atoms is None:
                raise InputError('An atoms object must be provided to perform'
                                 ' a calculation')
            else:
                atoms = self.atoms
        elif self.atoms is None:
            self.atoms = atoms
        if atoms.get_initial_magnetic_moments().any() != 0:
            self.parameters['reference'] = 'uhf'
            self.parameters['multiplicity'] = None
        # this inputs all the settings into psi4
        self.set_psi4(atoms=atoms)

        # Set up the method
        method = self.parameters['xc']
        basis = self.parameters['basis']

        # Do the calculations
        for item in properties:
            if item == 'energy':
                energy = self.psi4.energy('{}/{}'.format(method, basis),
                                          molecule=self.molecule)
                # convert to eV
                self.results['energy'] = energy * Hartree
            if item == 'forces':
                grad, wf = self.psi4.driver.gradient('{}/{}'.format(
                    method, basis),
                                                     return_wfn=True)
                # energy comes for free
                energy = wf.energy()
                self.results['energy'] = energy * Hartree
                # convert to eV/A
                # also note that the gradient is -1 * forces
                self.results['forces'] = -1 * np.array(grad) * Hartree / Bohr
        # dump the calculator info to the psi4 file
        save_atoms = self.atoms.copy()
        del save_atoms.calc
        save_dict = {
            'parameters': self.parameters,
            'results': self.results,
            'atoms': save_atoms
        }
        pickled = codecs.encode(pickle.dumps(save_dict), "base64").decode()
        self.psi4.core.print_out('!ASE Information\n')
        self.psi4.core.print_out(pickled)