[docs]@dataclasses.dataclassclassDebugCheck(base.Check):"""A check that interrupts the dynamics after a fixed amount of iterations. For testing purposes. Attributes ---------- n_iterations: int number of iterations before stopping """n_iterations:int=10
[docs]definitialize(self,atoms:ase.Atoms)->None:self.counter=0self.is_initialized=Trueself.status="n_iterations not reached"
[docs]defcheck(self,atoms):ifself.counter>=self.n_iterations:self.status="n_iterations reached"self.counter=0returnTrueself.counter+=1self.status="n_iterations not reached"returnFalse
[docs]@dataclasses.dataclassclassNaNCheck(base.Check):"""Check Node to see whether positions, energies or forces become NaN during a simulation. """
[docs]defcheck(self,atoms:ase.Atoms)->bool:positions=atoms.positionsepot=atoms.get_potential_energy()forces=atoms.get_forces()positions_is_none=np.any(positionsisNone)epot_is_none=epotisNoneforces_is_none=np.any(forcesisNone)ifany([positions_is_none,epot_is_none,forces_is_none]):self.status=("NaN check failed: last iterationpositions energy or forces = NaN")returnTrueelse:self.status="No NaN occurred"returnFalse
[docs]@dataclasses.dataclassclassConnectivityCheck(base.Check):"""Check to see whether the covalent connectivity of the system changes during a simulation. The connectivity is based on ASE's natural cutoffs. The pair of atoms which triggered this check will be converted to Lithium for easy visibility """bonded_min_dist:float=0.6bonded_max_dist:float=2.0def__post_init__(self)->None:self.nl=Noneself.first_cm=None
[docs]defcheck(self,atoms:ase.Atoms)->bool:p1=atoms.positions[self.idx_i]p2=atoms.positions[self.idx_j]_,dists=conditional_find_mic(p1-p2,atoms.cell,atoms.pbc)unstable=Falseifself.bonded_min_dist:min_dist=np.min(dists)too_close=min_dist<self.bonded_min_distunstable=unstableortoo_closeiftoo_close:min_idx=np.argmin(dists)first_atom=self.idx_i[min_idx]second_atom=self.idx_j[min_idx]atoms.numbers[first_atom]=3atoms.numbers[second_atom]=3ifself.bonded_max_dist:max_dist=np.max(dists)too_far=max_dist>self.bonded_max_distunstable=unstableortoo_fariftoo_far:max_idx=np.argmax(dists)first_atom=self.idx_i[max_idx]second_atom=self.idx_j[max_idx]atoms.numbers[first_atom]=3atoms.numbers[second_atom]=3ifunstable:self.status=("Connectivity check failed: last iteration""covalent connectivity of the system changed")returnTrueelse:self.status="covalent connectivity of the system is intact"returnFalse
[docs]@dataclasses.dataclassclassEnergySpikeCheck(base.Check):"""Check to see whether the potential energy of the system has fallen below a minimum or above a maximum threshold. Attributes ---------- min_factor: Simulation stops if `E(current) > E(initial) * min_factor` max_factor: Simulation stops if `E(current) < E(initial) * max_factor` """min_factor:float=0.5max_factor:float=2.0max_energy:float|None=Nonemin_energy:float|None=None
[docs]defcheck(self,atoms:ase.Atoms)->bool:epot=atoms.get_potential_energy()# energy is negative, hence sign conventionifepot<self.max_energy:self.status=("Energy spike check failed: last iteration"f"E {epot} > E_max {self.max_energy}")returnTrueelifepot>self.min_energy:self.status=("Energy spike check failed: last iteration"f"E {epot} < E_min {self.min_energy}")returnTrueelse:self.status="No energy spike occurred"returnFalse
[docs]@dataclasses.dataclassclassTemperatureCheck(base.Check):"""Calculate and check teperature during a MD simulation Attributes ---------- max_temperature: float maximum temperature, when reaching it simulation will be stopped """max_temperature:float=10000.0
[docs]defcheck(self,atoms):self.temperature,_=get_energy(atoms)ifself.temperature>self.max_temperature:self.status=("Temperature Check failed last iteration"f"T {self.temperature} K > T_max {self.max_temperature} K")returnTrueelse:self.status=(f"Temperature Check: T {self.temperature} K <"f"T_max {self.max_temperature} K")returnFalse
[docs]@dataclasses.dataclassclassThresholdCheck(base.Check):"""Calculate and check a given threshold and std during a MD simulation Compute the standard deviation of the selected property. If the property is off by more than a selected amount from the mean, the simulation will be stopped. Furthermore, the simulation will be stopped if the property exceeds a threshold value. Attributes ---------- key: str name of the property to check max_std: float, optional Maximum number of standard deviations away from the mean to stop the simulation. Roughly the value corresponds to the following percentiles: {1: 68%, 2: 95%, 3: 99.7%} window_size: int, optional Number of steps to average over max_value: float, optional Maximum value of the property to check before the simulation is stopped minimum_window_size: int, optional Minimum number of steps to average over before checking the standard deviation. Also minimum number of steps to run, before the simulation can be stopped. larger_only: bool, optional Only check the standard deviation of points that are larger than the mean. E.g. useful for uncertainties, where a lower uncertainty is not a problem. """key:str="energy_uncertainty"max_std:float=Nonewindow_size:int=500max_value:float=Noneminimum_window_size:int=1larger_only:bool=Falsedef__post_init__(self):ifself.max_stdisNoneandself.max_valueisNone:raiseValueError("Either max_std or max_value must be set")self.values=collections.deque(maxlen=self.window_size)
[docs]definitialize(self,atoms:ase.Atoms)->None:# clear the dequeself.values.clear()self.status=None
[docs]defget_value(self,atoms):"""Get the value of the property to check. Extracted into method so it can be subclassed. """returnnp.max(atoms.calc.results[self.key])
[docs]defcheck(self,atoms)->bool:value=atoms.calc.results[self.key]self.values.append(value)mean=np.mean(self.values)std=np.std(self.values)distance=value-meanifself.larger_only:distance=np.abs(distance)iflen(self.values)<self.minimum_window_size:returnFalseifself.max_valueisnotNoneandnp.max(value)>self.max_value:self.status=(f"StandardDeviationCheck for {self.key} triggered by"f" '{np.max(self.values[-1]):.3f}' > max_value {self.max_value}")returnTrueelifself.max_stdisnotNoneandnp.max(distance)>self.max_std*std:self.status=(f"StandardDeviationCheck for '{self.key}' triggered by"f" '{np.max(self.values[-1]):.3f}' for '{mean:.3f} +-"f" {std:.3f}' and max value '{self.max_value}'")returnTrueelse:self.status=(f"StandardDeviationCheck for '{self.key}' passed with"f" '{np.max(self.values[-1]):.3f}' for '{mean:.3f} +-"f" {std:.3f}' and max value '{self.max_value}'")returnFalse
[docs]defget_value(self,atoms):"""Get the value of the density to check."""returnget_density_from_atoms(atoms)
[docs]defcheck(self,atoms:ase.Atoms)->bool:density=get_density_from_atoms(atoms)ifself.max_densityisnotNoneanddensity>self.max_density:self.status=("Density Check failed: last iteration"f" density {density} > max density {self.max_density}")returnTrueelifself.min_densityisnotNoneanddensity<self.min_density:self.status=("Density Check failed: last iteration"f" density {density} < min density {self.min_density}")returnTrueelse:self.status=(f"Density Check passed: density {density} "f"between min {self.min_density} and max {self.max_density}")returnFalse