"""Utilities related to using ASE"""fromcontextlibimportcontextmanagerfromtypingimportIteratorimportloggingimportaseimportnumpyasnpfromase.calculators.calculatorimportCalculatorlogger=logging.getLogger(__name__)
[docs]@contextmanagerdefmake_ephemeral_calculator(calc:Calculator|dict)->Iterator[Calculator]:"""Make a calculator then tear it down after completion Args: calc: Already-defined calculator or a dict defining it. The dict must contain the key "name" to define the name of the code and could contain the keys "args" and "kwargs" to define the arguments and keyword arguments for creating a new one, respectively. Yields: An Calculator that is town down as the context manager exits """# Special case: just yield the calculatorifisinstance(calc,Calculator):yieldcalcreturn# Get the arguments for the calculatorargs=calc.get('args',[])kwargs=calc.get('kwargs',{})# Otherwise, create onename=calc['name'].lower()ifname=='cp2k':fromase.calculators.cp2kimportCP2Kcalc=CP2K(*calc.get('args',[]),**calc.get('kwargs',{}))yieldcalc# Kill the calculator by deleting the object to stop the underlying# shell and then set the `_shell` parameter of the object so that the# calculator object's destructor will skip the shell shutdown process# when the object is finally garbage collectedtry:calc.__del__()exceptAssertionErrorase:# pragma: no-coveragelogger.info(f'CP2K was noisy on failure: {e}')passcalc._shell=Noneelifname=='gaussian':fromase.calculators.gaussianimportGaussianyieldGaussian(*args,**kwargs)elifname=='xtb':fromxtb.ase.calculatorimportXTByieldXTB(*args,**kwargs)elifname=='mopac':fromase.calculators.mopacimportMOPACyieldMOPAC(*args,**kwargs)else:raiseValueError('No such calculator')
[docs]definitialize_charges(atoms:ase.Atoms,charge:int):"""Set initial charges to sum up to a certain value Args: atoms: Atoms object to be manipulated charge: Total charge for the system """charges=np.ones((len(atoms),))*(charge/len(atoms))atoms.set_initial_charges(charges)
[docs]defadd_vacuum_buffer(atoms:ase.Atoms,buffer_size:float,cubic:bool=False):"""Add a vacuum buffer around a molecule Args: atoms: Atoms object to be edited buffer_size: Length of vacuum on each side of the molecule (unit: Angstrom) cubic: Whether the resultant box should be cubic """ifcubic:atoms.positions-=atoms.positions.min(axis=0)atoms.cell=[atoms.positions.max()+buffer_size*2]*3atoms.positions+=atoms.cell.max(axis=0)/2-atoms.positions.mean(axis=0)else:atoms.center(vacuum=buffer_size)