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
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
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
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))
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))
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)