"""Track changes of an atom"""fromfunctoolsimportwrapsfromtypingimportUnionimportwarningsfromaseimportAtomsfromase.buildimportmake_supercellfromase.buildimportsortasase_sortimportnumpyasnpfrompackagingimportversionfromaiidaimport__version__asAIIDA_VERSIONfromaiidaimportormfromaiida.engineimportcalcfunction
[docs]defdummy_function(*args,**kwargs):""" A dummy function with ``*args`` and ``**kwargs`` Need to trigger dynamic namespace in aiida-core >= 2.3.0 """_=args_=kwargs
[docs]defwraps_ase_out_of_place(func):"""Wraps an ASE out of place operation"""@wraps(func)definner(tracker,*args,**kwargs):"""Inner function wrapped"""atoms=tracker.node.get_ase()aiida_kwargs={key:to_aiida_rep(value)forkey,valueinkwargs.items()}fori,arginenumerate(args):aiida_kwargs[f"arg_{i:02d}"]=to_aiida_rep(arg)# Create a dummy connection between the input the output using @calcfunctionnew_atoms=func(atoms,*args,**kwargs)@wraps(func)def_transform(node,**dummy_args):# pylint:disable=unused-argumentreturnorm.StructureData(ase=new_atoms)ifversion.parse(AIIDA_VERSION)>=version.parse("2.3.0"):_transform.__wrapped__=dummy_functiontransform=calcfunction(_transform)iftracker.track_provenance:node=transform(tracker.node,**aiida_kwargs)else:node=_transform(tracker.node,**aiida_kwargs)returnAtomsTracker(obj=node,atoms=new_atoms)returninner
wop=wraps_ase_out_of_place
[docs]defwraps_ase_inplace(func):"""Wraps an ASE in place operation"""@wraps(func)definner(tracker,*args,**kwargs):"""Inner function wrapped"""atoms=tracker.atomsaiida_kwargs={key:to_aiida_rep(value)forkey,valueinkwargs.items()}fori,arginenumerate(args):aiida_kwargs[f"arg_{i:02d}"]=to_aiida_rep(arg)retobj=[]@wraps(func)def_transform(node,**dummy_args):# pylint:disable=unused-argument# func is an inplace operationretobj.append(func(atoms,*args,**kwargs))returnorm.StructureData(ase=atoms)ifversion.parse(AIIDA_VERSION)>=version.parse("2.3.0"):_transform.__wrapped__=dummy_functiontransform=calcfunction(_transform)iftracker.track_provenance:# Call the wrapped function if we indeed tracking the provenancenode=transform(tracker.node,**aiida_kwargs)else:node=_transform(tracker.node,**aiida_kwargs)# Update the current nodetracker.node=nodereturnretobj[0]returninner
[docs]defto_aiida_rep(pobj):""" Convert to AiiDA representation and serialization. The return object is not guaranteed to fully deserialize back to the input. A string representation is used as the fallback. """ifisinstance(pobj,dict):returnorm.Dict(dict=pobj)ifisinstance(pobj,list):returnorm.List(list=pobj)ifisinstance(pobj,tuple):returnorm.List(list=list(pobj))ifisinstance(pobj,Atoms):returnorm.StructureData(ase=pobj)ifisinstance(pobj,float):returnorm.Float(pobj)ifisinstance(pobj,int):returnorm.Int(pobj)ifisinstance(pobj,str):returnorm.Str(pobj)ifisinstance(pobj,np.ndarray):data=orm.ArrayData()data.set_array("array",pobj)returndatawarnings.warn(f"Cannot serialise {pobj} - falling back to string representation.")returnorm.Str(pobj)
[docs]classAtomsTracker:# pylint: disable=too-few-public-methods"""Tracking changes of an atom"""def__init__(self,obj,atoms:Union[Atoms,None]=None,track=True,):"""Instantiate"""ifisinstance(obj,Atoms):self.atoms=objself.node=orm.StructureData(ase=obj)elifisinstance(obj,AtomsTracker):self.node=obj.nodeself.atoms=self.node.get_ase()else:self.node=objself.atoms=self.node.get_ase()ifatomsisNoneelseatomsself.track_provenance=track
sort=wraps_ase_out_of_place(ase_sort)make_supercell=wraps_ase_out_of_place(make_supercell)@propertydeflabel(self):"""Label of the underlying node."""returnself.node.label@label.setterdeflabel(self,value):"""Set the label of the underlying node."""self.node.label=value@propertydefdescription(self):"""Description of the underlying node."""returnself.node.description@propertydefid(self):# pylint: disable=invalid-name"""ID of the underlying node"""returnself.node.id@propertydefuuid(self):"""UUID of the underlying node"""returnself.node.uuid@description.setterdefdescription(self,value):"""Set the description of the underlying node."""self.node.description=value@propertydefbase(self):"""The `base` accessor for the underlying node."""returnself.node.base
[docs]defstore_node(self,*args,**kwargs):"""Store the underlying node"""self.node.store(*args,**kwargs)
[docs]def_populate_methods():"""Populate the methods for the `AtomsTracker` class"""methods_in_place=["set_cell","set_positions","set_pbc","set_atomic_numbers","set_chemical_symbols","set_masses","pop","translate","center","set_center_of_mass","rotate","euler_rotate","set_dihedral","rotate_dihedral","set_angle","rattle","set_distance","set_scaled_positions","wrap","__delitem__","__imul__",]methods_out_of_place=["repeat","__getitem__","__mul__"]fornameinmethods_in_place:setattr(AtomsTracker,name,wraps_ase_inplace(getattr(Atoms,name)))fornameinmethods_out_of_place:setattr(AtomsTracker,name,wraps_ase_out_of_place(getattr(Atoms,name)))